hir_def/
import_map.rs

1//! A map of all publicly exported items in a crate.
2
3use 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/// Item import details stored in the `ImportMap`.
25#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
26pub struct ImportInfo {
27    /// A name that can be used to import the item, relative to the container.
28    pub name: Name,
29    /// The module containing this item.
30    pub container: ModuleId,
31    /// Whether this item is annotated with `#[doc(hidden)]`.
32    pub is_doc_hidden: bool,
33    /// Whether this item is annotated with `#[unstable(..)]`.
34    pub is_unstable: bool,
35    /// The value of `#[rust_analyzer::completions(...)]`, if exists.
36    pub complete: Complete,
37}
38
39/// A map from publicly exported items to its name.
40///
41/// Reexports of items are taken into account.
42#[derive(Default)]
43pub struct ImportMap {
44    /// Maps from `ItemInNs` to information of imports that bring the item into scope.
45    item_to_info_map: ImportMapIndex,
46    /// List of keys stored in [`Self::item_to_info_map`], sorted lexicographically by their
47    /// [`Name`]. Indexed by the values returned by running `fst`.
48    ///
49    /// Since a name can refer to multiple items due to namespacing and import aliases, we store all
50    /// items with the same name right after each other. This allows us to find all items after the
51    /// fst gives us the index of the first one.
52    ///
53    /// The [`u32`] is the index into the smallvec in the value of [`Self::item_to_info_map`].
54    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            // We've only collected items, whose name cannot be tuple field so unwrapping is fine.
87            .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        // Build the FST, taking care not to insert duplicate values.
101        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        // We look only into modules that are public(ly reexported), starting with the crate root.
137        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                // The crate might reexport a module defined in another crate.
149                &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 we've just added a module, descend into it.
220                    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                // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
243                // qualifier, ergo no need to store it for imports in import_map
244                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        // `fst` and `importables` are built from `map`, so we don't need to compare them.
280        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/// A way to match import map contents against the search query.
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum SearchMode {
307    /// Import map entry should strictly match the query string.
308    Exact,
309    /// Import map entry should contain all letters from the query string,
310    /// in the same order, but not necessary adjacent.
311    Fuzzy,
312    /// Import map entry should match the query string by prefix.
313    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/// Three possible ways to search for the name in associated and/or other items.
353#[derive(Debug, Clone, Copy)]
354pub enum AssocSearchMode {
355    /// Search for the name in both associated and other items.
356    Include,
357    /// Search for the name in other items only.
358    Exclude,
359    /// Search for the name in the associated items only.
360    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    /// Fuzzy finds items instead of exact matching.
385    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    /// Specifies whether we want to include associated items in the result.
398    pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
399        Self { assoc_mode, ..self }
400    }
401
402    /// Respect casing of the query string when matching.
403    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
416/// Searches dependencies of `krate` for an importable name matching `query`.
417///
418/// This returns a list of items that could be imported from dependencies of `krate`.
419pub 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            // HashSet iteration order isn't defined - it's different on
573            // x86_64 and i686 at the very least
574            .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        // FIXME: This should check all import infos, not just the first
603        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        // Reexports need to be visible from a crate, even if the original crate exports the item
717        // at a shorter path.
718        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        // Reexporting modules from a dependency adds all contents to the import map.
766        // XXX: The rendered paths are relative to the defining crate.
767        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        // A cyclic reexport does not hang.
792        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}