1use std::fmt;
4
5use base_db::Crate;
6use fst::{Automaton, Streamer, raw::IndexedValue};
7use hir_expand::name::Name;
8use itertools::Itertools;
9use rustc_hash::FxHashSet;
10use smallvec::SmallVec;
11use span::Edition;
12use stdx::format_to;
13use triomphe::Arc;
14
15use crate::{
16 AssocItemId, AttrDefId, Complete, FxIndexMap, ModuleDefId, ModuleId, TraitId,
17 attrs::AttrFlags,
18 db::DefDatabase,
19 item_scope::{ImportOrExternCrate, ItemInNs},
20 nameres::{assoc::TraitItems, crate_def_map},
21 visibility::Visibility,
22};
23
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
26pub struct ImportInfo {
27 pub name: Name,
29 pub container: ModuleId,
31 pub is_doc_hidden: bool,
33 pub is_unstable: bool,
35 pub complete: Complete,
37}
38
39#[derive(Default)]
43pub struct ImportMap {
44 item_to_info_map: ImportMapIndex,
46 importables: Vec<(ItemInNs, u32)>,
55 fst: fst::Map<Vec<u8>>,
56}
57
58#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
59enum IsTraitAssocItem {
60 Yes,
61 No,
62}
63
64type ImportMapIndex = FxIndexMap<ItemInNs, (SmallVec<[ImportInfo; 1]>, IsTraitAssocItem)>;
65
66impl ImportMap {
67 pub fn dump(&self, db: &dyn DefDatabase) -> String {
68 let mut out = String::new();
69 for (k, v) in self.item_to_info_map.iter() {
70 format_to!(out, "{:?} ({:?}) -> ", k, v.1);
71 for v in &v.0 {
72 format_to!(out, "{}:{:?}, ", v.name.display(db, Edition::CURRENT), v.container);
73 }
74 format_to!(out, "\n");
75 }
76 out
77 }
78
79 pub(crate) fn import_map_query(db: &dyn DefDatabase, krate: Crate) -> Arc<Self> {
80 let _p = tracing::info_span!("import_map_query").entered();
81
82 let map = Self::collect_import_map(db, krate);
83
84 let mut importables: Vec<_> = map
85 .iter()
86 .flat_map(|(&item, (info, _))| {
88 info.iter()
89 .enumerate()
90 .map(move |(idx, info)| (item, info.name.as_str(), idx as u32))
91 })
92 .collect();
93 importables.sort_by(|(_, l_info, _), (_, r_info, _)| {
94 let lhs_chars = l_info.chars().map(|c| c.to_ascii_lowercase());
95 let rhs_chars = r_info.chars().map(|c| c.to_ascii_lowercase());
96 lhs_chars.cmp(rhs_chars)
97 });
98 importables.dedup();
99
100 let mut builder = fst::MapBuilder::memory();
102 let mut iter = importables
103 .iter()
104 .enumerate()
105 .dedup_by(|&(_, (_, lhs, _)), &(_, (_, rhs, _))| lhs.eq_ignore_ascii_case(rhs));
106
107 let mut insert = |name: &str, start, end| {
108 builder.insert(name.to_ascii_lowercase(), ((start as u64) << 32) | end as u64).unwrap()
109 };
110
111 if let Some((mut last, (_, name, _))) = iter.next() {
112 debug_assert_eq!(last, 0);
113 let mut last_name = name;
114 for (next, (_, next_name, _)) in iter {
115 insert(last_name, last, next);
116 last = next;
117 last_name = next_name;
118 }
119 insert(last_name, last, importables.len());
120 }
121
122 let importables = importables.into_iter().map(|(item, _, idx)| (item, idx)).collect();
123 Arc::new(ImportMap { item_to_info_map: map, fst: builder.into_map(), importables })
124 }
125
126 pub fn import_info_for(&self, item: ItemInNs) -> Option<&[ImportInfo]> {
127 self.item_to_info_map.get(&item).map(|(info, _)| &**info)
128 }
129
130 fn collect_import_map(db: &dyn DefDatabase, krate: Crate) -> ImportMapIndex {
131 let _p = tracing::info_span!("collect_import_map").entered();
132
133 let def_map = crate_def_map(db, krate);
134 let mut map = FxIndexMap::default();
135
136 let root = def_map.root_module_id();
138 let mut worklist = vec![root];
139 let mut visited = FxHashSet::default();
140
141 while let Some(module) = worklist.pop() {
142 if !visited.insert(module) {
143 continue;
144 }
145 let mod_data = if module.krate(db) == krate {
146 &def_map[module]
147 } else {
148 &module.def_map(db)[module]
150 };
151
152 let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| {
153 let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public);
154 if per_ns.is_none() { None } else { Some((name, per_ns)) }
155 });
156
157 for (name, per_ns) in visible_items {
158 for (item, import) in per_ns.iter_items() {
159 let attr_id = if let Some(import) = import {
160 match import {
161 ImportOrExternCrate::ExternCrate(id) => Some(id.into()),
162 ImportOrExternCrate::Import(id) => Some(id.use_.into()),
163 ImportOrExternCrate::Glob(id) => Some(id.use_.into()),
164 }
165 } else {
166 match item {
167 ItemInNs::Types(id) | ItemInNs::Values(id) => match id {
168 ModuleDefId::ModuleId(it) => Some(AttrDefId::ModuleId(it)),
169 ModuleDefId::FunctionId(it) => Some(it.into()),
170 ModuleDefId::AdtId(it) => Some(it.into()),
171 ModuleDefId::EnumVariantId(it) => Some(it.into()),
172 ModuleDefId::ConstId(it) => Some(it.into()),
173 ModuleDefId::StaticId(it) => Some(it.into()),
174 ModuleDefId::TraitId(it) => Some(it.into()),
175 ModuleDefId::TypeAliasId(it) => Some(it.into()),
176 ModuleDefId::MacroId(it) => Some(it.into()),
177 ModuleDefId::BuiltinType(_) => None,
178 },
179 ItemInNs::Macros(id) => Some(id.into()),
180 }
181 };
182 let (is_doc_hidden, is_unstable, do_not_complete) = match attr_id {
183 None => (false, false, Complete::Yes),
184 Some(attr_id) => {
185 let attrs = AttrFlags::query(db, attr_id);
186 let do_not_complete =
187 Complete::extract(matches!(attr_id, AttrDefId::TraitId(_)), attrs);
188 (
189 attrs.contains(AttrFlags::IS_DOC_HIDDEN),
190 attrs.contains(AttrFlags::IS_UNSTABLE),
191 do_not_complete,
192 )
193 }
194 };
195
196 let import_info = ImportInfo {
197 name: name.clone(),
198 container: module,
199 is_doc_hidden,
200 is_unstable,
201 complete: do_not_complete,
202 };
203
204 if let Some(ModuleDefId::TraitId(tr)) = item.as_module_def_id() {
205 Self::collect_trait_assoc_items(
206 db,
207 &mut map,
208 tr,
209 matches!(item, ItemInNs::Types(_)),
210 &import_info,
211 );
212 }
213
214 let (infos, _) =
215 map.entry(item).or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::No));
216 infos.reserve_exact(1);
217 infos.push(import_info);
218
219 if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() {
221 worklist.push(mod_id);
222 }
223 }
224 }
225 }
226 map.shrink_to_fit();
227 map
228 }
229
230 fn collect_trait_assoc_items(
231 db: &dyn DefDatabase,
232 map: &mut ImportMapIndex,
233 tr: TraitId,
234 is_type_in_ns: bool,
235 trait_import_info: &ImportInfo,
236 ) {
237 let _p = tracing::info_span!("collect_trait_assoc_items").entered();
238 for &(ref assoc_item_name, item) in &TraitItems::query(db, tr).items {
239 let module_def_id = match item {
240 AssocItemId::FunctionId(f) => ModuleDefId::from(f),
241 AssocItemId::ConstId(c) => ModuleDefId::from(c),
242 AssocItemId::TypeAliasId(_) => {
245 cov_mark::hit!(type_aliases_ignored);
246 continue;
247 }
248 };
249 let assoc_item = if is_type_in_ns {
250 ItemInNs::Types(module_def_id)
251 } else {
252 ItemInNs::Values(module_def_id)
253 };
254
255 let attr_id = item.into();
256 let attrs = AttrFlags::query(db, attr_id);
257 let item_do_not_complete = Complete::extract(false, attrs);
258 let do_not_complete =
259 Complete::for_trait_item(trait_import_info.complete, item_do_not_complete);
260 let assoc_item_info = ImportInfo {
261 container: trait_import_info.container,
262 name: assoc_item_name.clone(),
263 is_doc_hidden: attrs.contains(AttrFlags::IS_DOC_HIDDEN),
264 is_unstable: attrs.contains(AttrFlags::IS_UNSTABLE),
265 complete: do_not_complete,
266 };
267
268 let (infos, _) =
269 map.entry(assoc_item).or_insert_with(|| (SmallVec::new(), IsTraitAssocItem::Yes));
270 infos.reserve_exact(1);
271 infos.push(assoc_item_info);
272 }
273 }
274}
275
276impl Eq for ImportMap {}
277impl PartialEq for ImportMap {
278 fn eq(&self, other: &Self) -> bool {
279 self.item_to_info_map == other.item_to_info_map
281 }
282}
283
284impl fmt::Debug for ImportMap {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 let mut importable_names: Vec<_> = self
287 .item_to_info_map
288 .iter()
289 .map(|(item, (infos, _))| {
290 let l = infos.len();
291 match item {
292 ItemInNs::Types(it) => format!("- {it:?} (t) [{l}]",),
293 ItemInNs::Values(it) => format!("- {it:?} (v) [{l}]",),
294 ItemInNs::Macros(it) => format!("- {it:?} (m) [{l}]",),
295 }
296 })
297 .collect();
298
299 importable_names.sort();
300 f.write_str(&importable_names.join("\n"))
301 }
302}
303
304#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum SearchMode {
307 Exact,
309 Fuzzy,
312 Prefix,
314}
315
316impl SearchMode {
317 pub fn check(self, query: &str, case_sensitive: bool, candidate: &str) -> bool {
318 match self {
319 SearchMode::Exact if case_sensitive => candidate == query,
320 SearchMode::Exact => candidate.eq_ignore_ascii_case(query),
321 SearchMode::Prefix => {
322 query.len() <= candidate.len() && {
323 let prefix = &candidate[..query.len()];
324 if case_sensitive {
325 prefix == query
326 } else {
327 prefix.eq_ignore_ascii_case(query)
328 }
329 }
330 }
331 SearchMode::Fuzzy => {
332 let mut name = candidate;
333 query.chars().all(|query_char| {
334 let m = if case_sensitive {
335 name.match_indices(query_char).next()
336 } else {
337 name.match_indices([query_char, query_char.to_ascii_uppercase()]).next()
338 };
339 match m {
340 Some((index, _)) => {
341 name = name[index..].strip_prefix(|_: char| true).unwrap_or_default();
342 true
343 }
344 None => false,
345 }
346 })
347 }
348 }
349 }
350}
351
352#[derive(Debug, Clone, Copy)]
354pub enum AssocSearchMode {
355 Include,
357 Exclude,
359 AssocItemsOnly,
361}
362
363#[derive(Debug)]
364pub struct Query {
365 query: String,
366 lowercased: String,
367 search_mode: SearchMode,
368 assoc_mode: AssocSearchMode,
369 case_sensitive: bool,
370}
371
372impl Query {
373 pub fn new(query: String) -> Self {
374 let lowercased = query.to_lowercase();
375 Self {
376 query,
377 lowercased,
378 search_mode: SearchMode::Exact,
379 assoc_mode: AssocSearchMode::Include,
380 case_sensitive: false,
381 }
382 }
383
384 pub fn fuzzy(self) -> Self {
386 Self { search_mode: SearchMode::Fuzzy, ..self }
387 }
388
389 pub fn prefix(self) -> Self {
390 Self { search_mode: SearchMode::Prefix, ..self }
391 }
392
393 pub fn exact(self) -> Self {
394 Self { search_mode: SearchMode::Exact, ..self }
395 }
396
397 pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
399 Self { assoc_mode, ..self }
400 }
401
402 pub fn case_sensitive(self) -> Self {
404 Self { case_sensitive: true, ..self }
405 }
406
407 fn matches_assoc_mode(&self, is_trait_assoc_item: IsTraitAssocItem) -> bool {
408 !matches!(
409 (is_trait_assoc_item, self.assoc_mode),
410 (IsTraitAssocItem::Yes, AssocSearchMode::Exclude)
411 | (IsTraitAssocItem::No, AssocSearchMode::AssocItemsOnly)
412 )
413 }
414}
415
416pub fn search_dependencies(
420 db: &dyn DefDatabase,
421 krate: Crate,
422 query: &Query,
423) -> FxHashSet<(ItemInNs, Complete)> {
424 let _p = tracing::info_span!("search_dependencies", ?query).entered();
425
426 let import_maps: Vec<_> =
427 krate.data(db).dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect();
428
429 let mut op = fst::map::OpBuilder::new();
430
431 match query.search_mode {
432 SearchMode::Exact => {
433 let automaton = fst::automaton::Str::new(&query.lowercased);
434
435 for map in &import_maps {
436 op = op.add(map.fst.search(&automaton));
437 }
438 search_maps(db, &import_maps, op.union(), query)
439 }
440 SearchMode::Fuzzy => {
441 let automaton = fst::automaton::Subsequence::new(&query.lowercased);
442
443 for map in &import_maps {
444 op = op.add(map.fst.search(&automaton));
445 }
446 search_maps(db, &import_maps, op.union(), query)
447 }
448 SearchMode::Prefix => {
449 let automaton = fst::automaton::Str::new(&query.lowercased).starts_with();
450
451 for map in &import_maps {
452 op = op.add(map.fst.search(&automaton));
453 }
454 search_maps(db, &import_maps, op.union(), query)
455 }
456 }
457}
458
459fn search_maps(
460 _db: &dyn DefDatabase,
461 import_maps: &[Arc<ImportMap>],
462 mut stream: fst::map::Union<'_>,
463 query: &Query,
464) -> FxHashSet<(ItemInNs, Complete)> {
465 let mut res = FxHashSet::default();
466 while let Some((_, indexed_values)) = stream.next() {
467 for &IndexedValue { index: import_map_idx, value } in indexed_values {
468 let end = (value & 0xFFFF_FFFF) as usize;
469 let start = (value >> 32) as usize;
470 let ImportMap { item_to_info_map, importables, .. } = &*import_maps[import_map_idx];
471 let importables = &importables[start..end];
472
473 let iter = importables
474 .iter()
475 .copied()
476 .filter_map(|(item, info_idx)| {
477 let (import_infos, assoc_mode) = &item_to_info_map[&item];
478 query
479 .matches_assoc_mode(*assoc_mode)
480 .then(|| (item, &import_infos[info_idx as usize]))
481 })
482 .filter(|&(_, info)| {
483 query.search_mode.check(&query.query, query.case_sensitive, info.name.as_str())
484 })
485 .map(|(item, import_info)| (item, import_info.complete));
486 res.extend(iter);
487 }
488 }
489
490 res
491}
492
493#[cfg(test)]
494mod tests {
495 use base_db::RootQueryDb;
496 use expect_test::{Expect, expect};
497 use test_fixture::WithFixture;
498
499 use crate::{ItemContainerId, Lookup, nameres::assoc::TraitItems, test_db::TestDB};
500
501 use super::*;
502
503 impl ImportMap {
504 fn fmt_for_test(&self, db: &dyn DefDatabase) -> String {
505 let mut importable_paths: Vec<_> = self
506 .item_to_info_map
507 .iter()
508 .flat_map(|(item, (info, _))| info.iter().map(move |info| (item, info)))
509 .map(|(item, info)| {
510 let path = render_path(db, info);
511 let ns = match item {
512 ItemInNs::Types(_) => "t",
513 ItemInNs::Values(_) => "v",
514 ItemInNs::Macros(_) => "m",
515 };
516 format!("- {path} ({ns})")
517 })
518 .collect();
519
520 importable_paths.sort();
521 importable_paths.join("\n")
522 }
523 }
524
525 fn check_search(
526 #[rust_analyzer::rust_fixture] ra_fixture: &str,
527 crate_name: &str,
528 query: Query,
529 expect: Expect,
530 ) {
531 let db = TestDB::with_files(ra_fixture);
532 let all_crates = db.all_crates();
533 let krate = all_crates
534 .iter()
535 .copied()
536 .find(|&krate| {
537 krate
538 .extra_data(&db)
539 .display_name
540 .as_ref()
541 .is_some_and(|it| it.crate_name().as_str() == crate_name)
542 })
543 .expect("could not find crate");
544
545 let actual = search_dependencies(&db, krate, &query)
546 .into_iter()
547 .filter_map(|(dependency, _)| {
548 let dependency_krate = dependency.krate(&db)?;
549 let dependency_imports = db.import_map(dependency_krate);
550
551 let (path, mark) = match assoc_item_path(&db, &dependency_imports, dependency) {
552 Some(assoc_item_path) => (assoc_item_path, "a"),
553 None => (
554 render_path(&db, &dependency_imports.import_info_for(dependency)?[0]),
555 match dependency {
556 ItemInNs::Types(ModuleDefId::FunctionId(_))
557 | ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
558 ItemInNs::Types(_) => "t",
559 ItemInNs::Values(_) => "v",
560 ItemInNs::Macros(_) => "m",
561 },
562 ),
563 };
564
565 Some(format!(
566 "{}::{} ({})\n",
567 dependency_krate.extra_data(&db).display_name.as_ref()?,
568 path,
569 mark
570 ))
571 })
572 .sorted()
575 .collect::<String>();
576 expect.assert_eq(&actual)
577 }
578
579 fn assoc_item_path(
580 db: &dyn DefDatabase,
581 dependency_imports: &ImportMap,
582 dependency: ItemInNs,
583 ) -> Option<String> {
584 let (dependency_assoc_item_id, container) = match dependency.as_module_def_id()? {
585 ModuleDefId::FunctionId(id) => (AssocItemId::from(id), id.lookup(db).container),
586 ModuleDefId::ConstId(id) => (AssocItemId::from(id), id.lookup(db).container),
587 ModuleDefId::TypeAliasId(id) => (AssocItemId::from(id), id.lookup(db).container),
588 _ => return None,
589 };
590
591 let ItemContainerId::TraitId(trait_id) = container else {
592 return None;
593 };
594
595 let trait_info = dependency_imports.import_info_for(ItemInNs::Types(trait_id.into()))?;
596
597 let trait_items = TraitItems::query(db, trait_id);
598 let (assoc_item_name, _) = trait_items
599 .items
600 .iter()
601 .find(|(_, assoc_item_id)| &dependency_assoc_item_id == assoc_item_id)?;
602 Some(format!(
604 "{}::{}",
605 render_path(db, &trait_info[0]),
606 assoc_item_name.display(db, Edition::CURRENT)
607 ))
608 }
609
610 fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
611 let db = TestDB::with_files(ra_fixture);
612 let all_crates = db.all_crates();
613
614 let actual = all_crates
615 .iter()
616 .copied()
617 .filter_map(|krate| {
618 let cdata = &krate.extra_data(&db);
619 let name = cdata.display_name.as_ref()?;
620
621 let map = db.import_map(krate);
622
623 Some(format!("{name}:\n{}\n", map.fmt_for_test(&db)))
624 })
625 .sorted()
626 .collect::<String>();
627
628 expect.assert_eq(&actual)
629 }
630
631 fn render_path(db: &dyn DefDatabase, info: &ImportInfo) -> String {
632 let mut module = info.container;
633 let mut segments = vec![&info.name];
634
635 let def_map = module.def_map(db);
636 assert!(def_map.block_id().is_none(), "block local items should not be in `ImportMap`");
637
638 while let Some(parent) = module.containing_module(db) {
639 let parent_data = &def_map[parent];
640 let (name, _) = parent_data.children.iter().find(|(_, id)| **id == module).unwrap();
641 segments.push(name);
642 module = parent;
643 }
644
645 segments.iter().rev().map(|it| it.display(db, Edition::CURRENT)).join("::")
646 }
647
648 #[test]
649 fn smoke() {
650 check(
651 r"
652 //- /main.rs crate:main deps:lib
653
654 mod private {
655 pub use lib::Pub;
656 pub struct InPrivateModule;
657 }
658
659 pub mod publ1 {
660 use lib::Pub;
661 }
662
663 pub mod real_pub {
664 pub use lib::Pub;
665 }
666 pub mod real_pu2 { // same path length as above
667 pub use lib::Pub;
668 }
669
670 //- /lib.rs crate:lib
671 pub struct Pub {}
672 pub struct Pub2; // t + v
673 struct Priv;
674 ",
675 expect![[r#"
676 lib:
677 - Pub (t)
678 - Pub2 (t)
679 - Pub2 (v)
680 main:
681 - publ1 (t)
682 - real_pu2 (t)
683 - real_pu2::Pub (t)
684 - real_pub (t)
685 - real_pub::Pub (t)
686 "#]],
687 );
688 }
689
690 #[test]
691 fn prefers_shortest_path() {
692 check(
693 r"
694 //- /main.rs crate:main
695
696 pub mod sub {
697 pub mod subsub {
698 pub struct Def {}
699 }
700
701 pub use super::sub::subsub::Def;
702 }
703 ",
704 expect![[r#"
705 main:
706 - sub (t)
707 - sub::Def (t)
708 - sub::subsub (t)
709 - sub::subsub::Def (t)
710 "#]],
711 );
712 }
713
714 #[test]
715 fn type_reexport_cross_crate() {
716 check(
719 r"
720 //- /main.rs crate:main deps:lib
721 pub mod m {
722 pub use lib::S;
723 }
724 //- /lib.rs crate:lib
725 pub struct S;
726 ",
727 expect![[r#"
728 lib:
729 - S (t)
730 - S (v)
731 main:
732 - m (t)
733 - m::S (t)
734 - m::S (v)
735 "#]],
736 );
737 }
738
739 #[test]
740 fn macro_reexport() {
741 check(
742 r"
743 //- /main.rs crate:main deps:lib
744 pub mod m {
745 pub use lib::pub_macro;
746 }
747 //- /lib.rs crate:lib
748 #[macro_export]
749 macro_rules! pub_macro {
750 () => {};
751 }
752 ",
753 expect![[r#"
754 lib:
755 - pub_macro (m)
756 main:
757 - m (t)
758 - m::pub_macro (m)
759 "#]],
760 );
761 }
762
763 #[test]
764 fn module_reexport() {
765 check(
768 r"
769 //- /main.rs crate:main deps:lib
770 pub use lib::module as reexported_module;
771 //- /lib.rs crate:lib
772 pub mod module {
773 pub struct S;
774 }
775 ",
776 expect![[r#"
777 lib:
778 - module (t)
779 - module::S (t)
780 - module::S (v)
781 main:
782 - module::S (t)
783 - module::S (v)
784 - reexported_module (t)
785 "#]],
786 );
787 }
788
789 #[test]
790 fn cyclic_module_reexport() {
791 check(
793 r"
794 //- /lib.rs crate:lib
795 pub mod module {
796 pub struct S;
797 pub use super::sub::*;
798 }
799
800 pub mod sub {
801 pub use super::module;
802 }
803 ",
804 expect![[r#"
805 lib:
806 - module (t)
807 - module::S (t)
808 - module::S (v)
809 - module::module (t)
810 - sub (t)
811 - sub::module (t)
812 "#]],
813 );
814 }
815
816 #[test]
817 fn private_macro() {
818 check(
819 r"
820 //- /lib.rs crate:lib
821 macro_rules! private_macro {
822 () => {};
823 }
824 ",
825 expect![[r#"
826 lib:
827
828 "#]],
829 );
830 }
831
832 #[test]
833 fn namespacing() {
834 check(
835 r"
836 //- /lib.rs crate:lib
837 pub struct Thing; // t + v
838 #[macro_export]
839 macro_rules! Thing { // m
840 () => {};
841 }
842 ",
843 expect![[r#"
844 lib:
845 - Thing (m)
846 - Thing (t)
847 - Thing (v)
848 "#]],
849 );
850
851 check(
852 r"
853 //- /lib.rs crate:lib
854 pub mod Thing {} // t
855 #[macro_export]
856 macro_rules! Thing { // m
857 () => {};
858 }
859 ",
860 expect![[r#"
861 lib:
862 - Thing (m)
863 - Thing (t)
864 "#]],
865 );
866 }
867
868 #[test]
869 fn fuzzy_import_trait_and_assoc_items() {
870 cov_mark::check!(type_aliases_ignored);
871 let ra_fixture = r#"
872 //- /main.rs crate:main deps:dep
873 //- /dep.rs crate:dep
874 pub mod fmt {
875 pub trait Display {
876 type FmtTypeAlias;
877 const FMT_CONST: bool;
878
879 fn format_function();
880 fn format_method(&self);
881 }
882 }
883 "#;
884
885 check_search(
886 ra_fixture,
887 "main",
888 Query::new("fmt".to_owned()).fuzzy(),
889 expect![[r#"
890 dep::fmt (t)
891 dep::fmt::Display::FMT_CONST (a)
892 dep::fmt::Display::format_function (a)
893 dep::fmt::Display::format_method (a)
894 "#]],
895 );
896 }
897
898 #[test]
899 fn assoc_items_filtering() {
900 let ra_fixture = r#"
901 //- /main.rs crate:main deps:dep
902 //- /dep.rs crate:dep
903 pub mod fmt {
904 pub trait Display {
905 type FmtTypeAlias;
906 const FMT_CONST: bool;
907
908 fn format_function();
909 fn format_method(&self);
910 }
911 }
912 "#;
913
914 check_search(
915 ra_fixture,
916 "main",
917 Query::new("fmt".to_owned()).fuzzy().assoc_search_mode(AssocSearchMode::AssocItemsOnly),
918 expect![[r#"
919 dep::fmt::Display::FMT_CONST (a)
920 dep::fmt::Display::format_function (a)
921 dep::fmt::Display::format_method (a)
922 "#]],
923 );
924
925 check_search(
926 ra_fixture,
927 "main",
928 Query::new("fmt".to_owned()).fuzzy().assoc_search_mode(AssocSearchMode::Exclude),
929 expect![[r#"
930 dep::fmt (t)
931 "#]],
932 );
933 }
934
935 #[test]
936 fn search_mode() {
937 let ra_fixture = r#"
938//- /main.rs crate:main deps:dep
939//- /dep.rs crate:dep deps:tdep
940use tdep::fmt as fmt_dep;
941pub mod fmt {
942 pub trait Display {
943 fn fmt();
944 }
945}
946#[macro_export]
947macro_rules! Fmt {
948 () => {};
949}
950pub struct Fmt;
951
952pub fn format() {}
953pub fn no() {}
954
955//- /tdep.rs crate:tdep
956pub mod fmt {
957 pub struct NotImportableFromMain;
958}
959"#;
960
961 check_search(
962 ra_fixture,
963 "main",
964 Query::new("fmt".to_owned()).fuzzy(),
965 expect![[r#"
966 dep::Fmt (m)
967 dep::Fmt (t)
968 dep::Fmt (v)
969 dep::fmt (t)
970 dep::fmt::Display::fmt (a)
971 dep::format (f)
972 "#]],
973 );
974
975 check_search(
976 ra_fixture,
977 "main",
978 Query::new("fmt".to_owned()),
979 expect![[r#"
980 dep::Fmt (m)
981 dep::Fmt (t)
982 dep::Fmt (v)
983 dep::fmt (t)
984 dep::fmt::Display::fmt (a)
985 "#]],
986 );
987 }
988
989 #[test]
990 fn name_only() {
991 let ra_fixture = r#"
992 //- /main.rs crate:main deps:dep
993 //- /dep.rs crate:dep deps:tdep
994 use tdep::fmt as fmt_dep;
995 pub mod fmt {
996 pub trait Display {
997 fn fmt();
998 }
999 }
1000 #[macro_export]
1001 macro_rules! Fmt {
1002 () => {};
1003 }
1004 pub struct Fmt;
1005
1006 pub fn format() {}
1007 pub fn no() {}
1008
1009 //- /tdep.rs crate:tdep
1010 pub mod fmt {
1011 pub struct NotImportableFromMain;
1012 }
1013 "#;
1014
1015 check_search(
1016 ra_fixture,
1017 "main",
1018 Query::new("fmt".to_owned()),
1019 expect![[r#"
1020 dep::Fmt (m)
1021 dep::Fmt (t)
1022 dep::Fmt (v)
1023 dep::fmt (t)
1024 dep::fmt::Display::fmt (a)
1025 "#]],
1026 );
1027 }
1028
1029 #[test]
1030 fn search_casing() {
1031 let ra_fixture = r#"
1032 //- /main.rs crate:main deps:dep
1033 //- /dep.rs crate:dep
1034
1035 pub struct fmt;
1036 pub struct FMT;
1037 "#;
1038
1039 check_search(
1040 ra_fixture,
1041 "main",
1042 Query::new("FMT".to_owned()),
1043 expect![[r#"
1044 dep::FMT (t)
1045 dep::FMT (v)
1046 dep::fmt (t)
1047 dep::fmt (v)
1048 "#]],
1049 );
1050
1051 check_search(
1052 ra_fixture,
1053 "main",
1054 Query::new("FMT".to_owned()).case_sensitive(),
1055 expect![[r#"
1056 dep::FMT (t)
1057 dep::FMT (v)
1058 "#]],
1059 );
1060 }
1061
1062 #[test]
1063 fn unicode_fn_name() {
1064 let ra_fixture = r#"
1065 //- /main.rs crate:main deps:dep
1066 //- /dep.rs crate:dep
1067 pub fn あい() {}
1068 "#;
1069
1070 check_search(
1071 ra_fixture,
1072 "main",
1073 Query::new("あ".to_owned()).fuzzy(),
1074 expect![[r#"
1075 dep::あい (f)
1076 "#]],
1077 );
1078 }
1079}