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