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