Skip to main content

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, 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/// Item import details stored in the `ImportMap`.
23#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
24pub struct ImportInfo {
25    /// A name that can be used to import the item, relative to the container.
26    pub name: Name,
27    /// The module containing this item.
28    pub container: ModuleId,
29    /// Whether this item is annotated with `#[doc(hidden)]`.
30    pub is_doc_hidden: bool,
31    /// Whether this item is annotated with `#[unstable(..)]`.
32    pub is_unstable: bool,
33    /// The value of `#[rust_analyzer::completions(...)]`, if exists.
34    pub complete: Complete,
35}
36
37/// A map from publicly exported items to its name.
38///
39/// Reexports of items are taken into account.
40#[derive(Default)]
41pub struct ImportMap {
42    /// Maps from `ItemInNs` to information of imports that bring the item into scope.
43    item_to_info_map: ImportMapIndex,
44    /// List of keys stored in [`Self::item_to_info_map`], sorted lexicographically by their
45    /// [`Name`]. Indexed by the values returned by running `fst`.
46    ///
47    /// Since a name can refer to multiple items due to namespacing and import aliases, we store all
48    /// items with the same name right after each other. This allows us to find all items after the
49    /// fst gives us the index of the first one.
50    ///
51    /// The [`u32`] is the index into the smallvec in the value of [`Self::item_to_info_map`].
52    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            // We've only collected items, whose name cannot be tuple field so unwrapping is fine.
93            .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        // Build the FST, taking care not to insert duplicate values.
107        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        // We look only into modules that are public(ly reexported), starting with the crate root.
143        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                // The crate might reexport a module defined in another crate.
155                &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 we've just added a module, descend into it.
229                    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                // cannot use associated type aliases directly: need a `<Struct as Trait>::TypeAlias`
279                // qualifier, ergo no need to store it for imports in import_map
280                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        // `fst` and `importables` are built from `map`, so we don't need to compare them.
316        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/// A way to match import map contents against the search query.
341#[derive(Debug, Copy, Clone, PartialEq, Eq)]
342pub enum SearchMode {
343    /// Import map entry should strictly match the query string.
344    Exact,
345    /// Import map entry should contain all letters from the query string,
346    /// in the same order, but not necessary adjacent.
347    Fuzzy,
348    /// Import map entry should match the query string by prefix.
349    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/// Three possible ways to search for the name in associated and/or other items.
389#[derive(Debug, Clone, Copy)]
390pub enum AssocSearchMode {
391    /// Search for the name in both associated and other items.
392    Include,
393    /// Search for the name in other items only.
394    Exclude,
395    /// Search for the name in the associated items only.
396    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    /// Fuzzy finds items instead of exact matching.
421    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    /// Specifies whether we want to include associated items in the result.
434    pub fn assoc_search_mode(self, assoc_mode: AssocSearchMode) -> Self {
435        Self { assoc_mode, ..self }
436    }
437
438    /// Respect casing of the query string when matching.
439    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
452/// Searches dependencies of `krate` for an importable name matching `query`.
453///
454/// This returns a list of items that could be imported from dependencies of `krate`.
455pub 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            // HashSet iteration order isn't defined - it's different on
609            // x86_64 and i686 at the very least
610            .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        // FIXME: This should check all import infos, not just the first
639        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        // Reexports need to be visible from a crate, even if the original crate exports the item
753        // at a shorter path.
754        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        // Reexporting modules from a dependency adds all contents to the import map.
802        // XXX: The rendered paths are relative to the defining crate.
803        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        // A cyclic reexport does not hang.
828        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}