hir/
symbols.rs

1//! File symbol extraction.
2
3use std::marker::PhantomData;
4
5use base_db::FxIndexSet;
6use either::Either;
7use hir_def::{
8    AdtId, AssocItemId, AstIdLoc, Complete, DefWithBodyId, ExternCrateId, HasModule, ImplId,
9    Lookup, MacroId, ModuleDefId, ModuleId, TraitId,
10    db::DefDatabase,
11    expr_store::Body,
12    item_scope::{ImportId, ImportOrExternCrate, ImportOrGlob},
13    nameres::crate_def_map,
14    per_ns::Item,
15    signatures::{EnumSignature, ImplSignature, TraitSignature},
16    src::{HasChildSource, HasSource},
17    visibility::{Visibility, VisibilityExplicitness},
18};
19use hir_expand::{HirFileId, name::Name};
20use hir_ty::{
21    db::HirDatabase,
22    display::{HirDisplay, hir_display_with_store},
23};
24use intern::Symbol;
25use rustc_hash::FxHashMap;
26use syntax::{AstNode, AstPtr, SyntaxNode, SyntaxNodePtr, ToSmolStr, ast::HasName};
27
28use crate::{Crate, HasCrate, Module, ModuleDef, Semantics};
29
30/// The actual data that is stored in the index. It should be as compact as
31/// possible.
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub struct FileSymbol<'db> {
34    pub name: Symbol,
35    pub def: ModuleDef,
36    pub loc: DeclarationLocation,
37    pub container_name: Option<Symbol>,
38    /// Whether this symbol is a doc alias for the original symbol.
39    pub is_alias: bool,
40    pub is_assoc: bool,
41    pub is_import: bool,
42    pub do_not_complete: Complete,
43    _marker: PhantomData<&'db ()>,
44}
45
46#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
47pub struct DeclarationLocation {
48    /// The file id for both the `ptr` and `name_ptr`.
49    pub hir_file_id: HirFileId,
50    /// This points to the whole syntax node of the declaration.
51    pub ptr: SyntaxNodePtr,
52    /// This points to the [`syntax::ast::Name`] identifier of the declaration.
53    pub name_ptr: Option<AstPtr<Either<syntax::ast::Name, syntax::ast::NameRef>>>,
54}
55
56impl DeclarationLocation {
57    pub fn syntax<DB: HirDatabase>(&self, sema: &Semantics<'_, DB>) -> SyntaxNode {
58        let root = sema.parse_or_expand(self.hir_file_id);
59        self.ptr.to_node(&root)
60    }
61}
62
63/// Represents an outstanding module that the symbol collector must collect symbols from.
64#[derive(Debug)]
65struct SymbolCollectorWork {
66    module_id: ModuleId,
67    parent: Option<Name>,
68}
69
70pub struct SymbolCollector<'db> {
71    db: &'db dyn HirDatabase,
72    symbols: FxIndexSet<FileSymbol<'db>>,
73    work: Vec<SymbolCollectorWork>,
74    current_container_name: Option<Symbol>,
75    collect_pub_only: bool,
76}
77
78/// Given a [`ModuleId`] and a [`HirDatabase`], use the DefMap for the module's crate to collect
79/// all symbols that should be indexed for the given module.
80impl<'a> SymbolCollector<'a> {
81    pub fn new(db: &'a dyn HirDatabase, collect_pub_only: bool) -> Self {
82        SymbolCollector {
83            db,
84            symbols: Default::default(),
85            work: Default::default(),
86            current_container_name: None,
87            collect_pub_only,
88        }
89    }
90
91    pub fn new_module(
92        db: &'a dyn HirDatabase,
93        module: Module,
94        collect_pub_only: bool,
95    ) -> Box<[FileSymbol<'a>]> {
96        let mut symbol_collector = SymbolCollector::new(db, collect_pub_only);
97        symbol_collector.collect(module);
98        symbol_collector.finish()
99    }
100
101    pub fn collect(&mut self, module: Module) {
102        let _p = tracing::info_span!("SymbolCollector::collect", ?module).entered();
103        tracing::info!(?module, "SymbolCollector::collect");
104
105        // The initial work is the root module we're collecting, additional work will
106        // be populated as we traverse the module's definitions.
107        self.work.push(SymbolCollectorWork { module_id: module.into(), parent: None });
108
109        while let Some(work) = self.work.pop() {
110            self.do_work(work);
111        }
112    }
113
114    /// Push a symbol for a crate's root module.
115    /// This allows crate roots to appear in the symbol index for queries like `::` or `::foo`.
116    pub fn push_crate_root(&mut self, krate: Crate) {
117        let Some(display_name) = krate.display_name(self.db) else { return };
118        let crate_name = display_name.crate_name();
119        let canonical_name = display_name.canonical_name();
120
121        let def_map = crate_def_map(self.db, krate.into());
122        let module_data = &def_map[def_map.crate_root(self.db)];
123
124        let definition = module_data.origin.definition_source(self.db);
125        let hir_file_id = definition.file_id;
126        let syntax_node = definition.value.node();
127        let ptr = SyntaxNodePtr::new(&syntax_node);
128
129        let loc = DeclarationLocation { hir_file_id, ptr, name_ptr: None };
130        let root_module = krate.root_module(self.db);
131
132        self.symbols.insert(FileSymbol {
133            name: crate_name.symbol().clone(),
134            def: ModuleDef::Module(root_module),
135            loc,
136            container_name: None,
137            is_alias: false,
138            is_assoc: false,
139            is_import: false,
140            do_not_complete: Complete::Yes,
141            _marker: PhantomData,
142        });
143
144        if canonical_name != crate_name.symbol() {
145            self.symbols.insert(FileSymbol {
146                name: canonical_name.clone(),
147                def: ModuleDef::Module(root_module),
148                loc,
149                container_name: None,
150                is_alias: false,
151                is_assoc: false,
152                is_import: false,
153                do_not_complete: Complete::Yes,
154                _marker: PhantomData,
155            });
156        }
157    }
158
159    pub fn finish(self) -> Box<[FileSymbol<'a>]> {
160        self.symbols.into_iter().collect()
161    }
162
163    fn do_work(&mut self, work: SymbolCollectorWork) {
164        let _p = tracing::info_span!("SymbolCollector::do_work", ?work).entered();
165        tracing::info!(?work, "SymbolCollector::do_work");
166        self.db.unwind_if_revision_cancelled();
167
168        let parent_name = work.parent.map(|name| Symbol::intern(name.as_str()));
169        self.with_container_name(parent_name, |s| s.collect_from_module(work.module_id));
170    }
171
172    fn collect_from_module(&mut self, module_id: ModuleId) {
173        let collect_pub_only = self.collect_pub_only;
174        let is_block_module = module_id.is_block_module(self.db);
175        let push_decl = |this: &mut Self, def: ModuleDefId, name, vis| {
176            if collect_pub_only && vis != Visibility::Public {
177                return;
178            }
179            match def {
180                ModuleDefId::ModuleId(id) => this.push_module(id, name),
181                ModuleDefId::FunctionId(id) => {
182                    this.push_decl(id, name, false, None);
183                    this.collect_from_body(id, Some(name.clone()));
184                }
185                ModuleDefId::AdtId(AdtId::StructId(id)) => {
186                    this.push_decl(id, name, false, None);
187                }
188                ModuleDefId::AdtId(AdtId::EnumId(id)) => {
189                    this.push_decl(id, name, false, None);
190                    let enum_name = Symbol::intern(EnumSignature::of(this.db, id).name.as_str());
191                    this.with_container_name(Some(enum_name), |this| {
192                        let variants = id.enum_variants(this.db);
193                        for (variant_id, variant_name, _) in &variants.variants {
194                            this.push_decl(*variant_id, variant_name, true, None);
195                        }
196                    });
197                }
198                ModuleDefId::AdtId(AdtId::UnionId(id)) => {
199                    this.push_decl(id, name, false, None);
200                }
201                ModuleDefId::ConstId(id) => {
202                    this.push_decl(id, name, false, None);
203                    this.collect_from_body(id, Some(name.clone()));
204                }
205                ModuleDefId::StaticId(id) => {
206                    this.push_decl(id, name, false, None);
207                    this.collect_from_body(id, Some(name.clone()));
208                }
209                ModuleDefId::TraitId(id) => {
210                    let trait_do_not_complete = this.push_decl(id, name, false, None);
211                    this.collect_from_trait(id, trait_do_not_complete);
212                }
213                ModuleDefId::TypeAliasId(id) => {
214                    this.push_decl(id, name, false, None);
215                }
216                ModuleDefId::MacroId(id) => {
217                    match id {
218                        MacroId::Macro2Id(id) => this.push_decl(id, name, false, None),
219                        MacroId::MacroRulesId(id) => this.push_decl(id, name, false, None),
220                        MacroId::ProcMacroId(id) => this.push_decl(id, name, false, None),
221                    };
222                }
223                // Don't index these.
224                ModuleDefId::BuiltinType(_) => {}
225                ModuleDefId::EnumVariantId(_) => {}
226            }
227        };
228
229        // Nested trees are very common, so a cache here will hit a lot.
230        let import_child_source_cache = &mut FxHashMap::default();
231
232        let is_explicit_import = |vis| match vis {
233            Visibility::Public => true,
234            Visibility::PubCrate(_) => true,
235            Visibility::Module(_, VisibilityExplicitness::Explicit) => true,
236            Visibility::Module(_, VisibilityExplicitness::Implicit) => false,
237        };
238
239        let mut push_import = |this: &mut Self, i: ImportId, name: &Name, def: ModuleDefId, vis| {
240            if collect_pub_only && vis != Visibility::Public {
241                return;
242            }
243            let source = import_child_source_cache
244                .entry(i.use_)
245                .or_insert_with(|| i.use_.child_source(this.db));
246            if is_block_module && source.file_id.is_macro() {
247                // Macros tend to generate a lot of imports, the user really won't care about them
248                return;
249            }
250            let Some(use_tree_src) = source.value.get(i.idx) else { return };
251            let rename = use_tree_src.rename().and_then(|rename| rename.name());
252            let name_syntax = match rename {
253                Some(name) => Some(Either::Left(name)),
254                None if is_explicit_import(vis) => {
255                    (|| use_tree_src.path()?.segment()?.name_ref().map(Either::Right))()
256                }
257                None => None,
258            };
259            let Some(name_syntax) = name_syntax else {
260                return;
261            };
262            let dec_loc = DeclarationLocation {
263                hir_file_id: source.file_id,
264                ptr: SyntaxNodePtr::new(use_tree_src.syntax()),
265                name_ptr: Some(AstPtr::new(&name_syntax)),
266            };
267            this.symbols.insert(FileSymbol {
268                name: name.symbol().clone(),
269                def: def.into(),
270                container_name: this.current_container_name.clone(),
271                loc: dec_loc,
272                is_alias: false,
273                is_assoc: false,
274                is_import: true,
275                do_not_complete: Complete::Yes,
276                _marker: PhantomData,
277            });
278        };
279
280        let push_extern_crate =
281            |this: &mut Self, i: ExternCrateId, name: &Name, def: ModuleDefId, vis| {
282                if collect_pub_only && vis != Visibility::Public {
283                    return;
284                }
285                let loc = i.lookup(this.db);
286                if is_block_module && loc.ast_id().file_id.is_macro() {
287                    // Macros (especially derivves) tend to generate renamed extern crate items,
288                    // the user really won't care about them
289                    return;
290                }
291
292                let source = loc.source(this.db);
293                let rename = source.value.rename().and_then(|rename| rename.name());
294
295                let name_syntax = match rename {
296                    Some(name) => Some(Either::Left(name)),
297                    None if is_explicit_import(vis) => None,
298                    None => source.value.name_ref().map(Either::Right),
299                };
300                let Some(name_syntax) = name_syntax else {
301                    return;
302                };
303                let dec_loc = DeclarationLocation {
304                    hir_file_id: source.file_id,
305                    ptr: SyntaxNodePtr::new(source.value.syntax()),
306                    name_ptr: Some(AstPtr::new(&name_syntax)),
307                };
308                this.symbols.insert(FileSymbol {
309                    name: name.symbol().clone(),
310                    def: def.into(),
311                    container_name: this.current_container_name.clone(),
312                    loc: dec_loc,
313                    is_alias: false,
314                    is_assoc: false,
315                    is_import: false,
316                    do_not_complete: Complete::Yes,
317                    _marker: PhantomData,
318                });
319            };
320
321        let def_map = module_id.def_map(self.db);
322        let scope = &def_map[module_id].scope;
323
324        for impl_id in scope.impls() {
325            self.collect_from_impl(impl_id);
326        }
327
328        for (name, Item { def, vis, import }) in scope.types() {
329            if let Some(i) = import {
330                match i {
331                    ImportOrExternCrate::Import(i) => push_import(self, i, name, def, vis),
332                    ImportOrExternCrate::Glob(_) => (),
333                    ImportOrExternCrate::ExternCrate(i) => {
334                        push_extern_crate(self, i, name, def, vis)
335                    }
336                }
337
338                continue;
339            }
340            // self is a declaration
341            push_decl(self, def, name, vis)
342        }
343
344        for (name, Item { def, vis, import }) in scope.macros() {
345            if let Some(i) = import {
346                match i {
347                    ImportOrExternCrate::Import(i) => push_import(self, i, name, def.into(), vis),
348                    ImportOrExternCrate::Glob(_) => (),
349                    ImportOrExternCrate::ExternCrate(_) => (),
350                }
351                continue;
352            }
353            // self is a declaration
354            push_decl(self, ModuleDefId::MacroId(def), name, vis)
355        }
356
357        for (name, Item { def, vis, import }) in scope.values() {
358            if let Some(i) = import {
359                match i {
360                    ImportOrGlob::Import(i) => push_import(self, i, name, def, vis),
361                    ImportOrGlob::Glob(_) => (),
362                }
363                continue;
364            }
365            // self is a declaration
366            push_decl(self, def, name, vis)
367        }
368
369        for const_id in scope.unnamed_consts() {
370            self.collect_from_body(const_id, None);
371        }
372
373        for (name, id) in scope.legacy_macros() {
374            for &id in id {
375                if id.module(self.db) == module_id {
376                    match id {
377                        MacroId::Macro2Id(id) => self.push_decl(id, name, false, None),
378                        MacroId::MacroRulesId(id) => self.push_decl(id, name, false, None),
379                        MacroId::ProcMacroId(id) => self.push_decl(id, name, false, None),
380                    };
381                }
382            }
383        }
384    }
385
386    fn collect_from_body(&mut self, body_id: impl Into<DefWithBodyId>, name: Option<Name>) {
387        if self.collect_pub_only {
388            return;
389        }
390        let body_id = body_id.into();
391        let body = Body::of(self.db, body_id);
392
393        // Descend into the blocks and enqueue collection of all modules within.
394        for (_, def_map) in body.blocks(self.db) {
395            for (id, _) in def_map.modules() {
396                self.work.push(SymbolCollectorWork { module_id: id, parent: name.clone() });
397            }
398        }
399    }
400
401    fn collect_from_impl(&mut self, impl_id: ImplId) {
402        let impl_data = ImplSignature::of(self.db, impl_id);
403        let impl_name = Some(
404            hir_display_with_store(impl_data.self_ty, &impl_data.store)
405                .display(
406                    self.db,
407                    crate::Impl::from(impl_id).krate(self.db).to_display_target(self.db),
408                )
409                .to_smolstr(),
410        );
411        self.with_container_name(impl_name.as_deref().map(Symbol::intern), |s| {
412            for &(ref name, assoc_item_id) in &impl_id.impl_items(self.db).items {
413                if s.collect_pub_only && s.db.assoc_visibility(assoc_item_id) != Visibility::Public
414                {
415                    continue;
416                }
417
418                s.push_assoc_item(assoc_item_id, name, None)
419            }
420        })
421    }
422
423    fn collect_from_trait(&mut self, trait_id: TraitId, trait_do_not_complete: Complete) {
424        let trait_data = TraitSignature::of(self.db, trait_id);
425        self.with_container_name(Some(Symbol::intern(trait_data.name.as_str())), |s| {
426            for &(ref name, assoc_item_id) in &trait_id.trait_items(self.db).items {
427                s.push_assoc_item(assoc_item_id, name, Some(trait_do_not_complete));
428            }
429        });
430    }
431
432    fn with_container_name(&mut self, container_name: Option<Symbol>, f: impl FnOnce(&mut Self)) {
433        if let Some(container_name) = container_name {
434            let prev = self.current_container_name.replace(container_name);
435            f(self);
436            self.current_container_name = prev;
437        } else {
438            f(self);
439        }
440    }
441
442    fn push_assoc_item(
443        &mut self,
444        assoc_item_id: AssocItemId,
445        name: &Name,
446        trait_do_not_complete: Option<Complete>,
447    ) {
448        match assoc_item_id {
449            AssocItemId::FunctionId(id) => self.push_decl(id, name, true, trait_do_not_complete),
450            AssocItemId::ConstId(id) => self.push_decl(id, name, true, trait_do_not_complete),
451            AssocItemId::TypeAliasId(id) => self.push_decl(id, name, true, trait_do_not_complete),
452        };
453    }
454
455    fn push_decl<L>(
456        &mut self,
457        id: L,
458        name: &Name,
459        is_assoc: bool,
460        trait_do_not_complete: Option<Complete>,
461    ) -> Complete
462    where
463        L: Lookup<Database = dyn DefDatabase> + Into<ModuleDefId>,
464        <L as Lookup>::Data: HasSource,
465        <<L as Lookup>::Data as HasSource>::Value: HasName,
466    {
467        let loc = id.lookup(self.db);
468        let source = loc.source(self.db);
469        let Some(name_node) = source.value.name() else { return Complete::Yes };
470        let def = ModuleDef::from(id.into());
471        let loc = DeclarationLocation {
472            hir_file_id: source.file_id,
473            ptr: SyntaxNodePtr::new(source.value.syntax()),
474            name_ptr: Some(AstPtr::new(&name_node).wrap_left()),
475        };
476
477        let mut do_not_complete = Complete::Yes;
478
479        if let Some(attrs) = def.attrs(self.db) {
480            do_not_complete = Complete::extract(matches!(def, ModuleDef::Trait(_)), attrs.attrs);
481            if let Some(trait_do_not_complete) = trait_do_not_complete {
482                do_not_complete = Complete::for_trait_item(trait_do_not_complete, do_not_complete);
483            }
484
485            for alias in attrs.doc_aliases(self.db) {
486                self.symbols.insert(FileSymbol {
487                    name: alias.clone(),
488                    def,
489                    loc,
490                    container_name: self.current_container_name.clone(),
491                    is_alias: true,
492                    is_assoc,
493                    is_import: false,
494                    do_not_complete,
495                    _marker: PhantomData,
496                });
497            }
498        }
499
500        self.symbols.insert(FileSymbol {
501            name: name.symbol().clone(),
502            def,
503            container_name: self.current_container_name.clone(),
504            loc,
505            is_alias: false,
506            is_assoc,
507            is_import: false,
508            do_not_complete,
509            _marker: PhantomData,
510        });
511
512        do_not_complete
513    }
514
515    fn push_module(&mut self, module_id: ModuleId, name: &Name) {
516        let def_map = module_id.def_map(self.db);
517        let module_data = &def_map[module_id];
518        let Some(declaration) = module_data.origin.declaration() else { return };
519        let module = declaration.to_node(self.db);
520        let Some(name_node) = module.name() else { return };
521        let loc = DeclarationLocation {
522            hir_file_id: declaration.file_id,
523            ptr: SyntaxNodePtr::new(module.syntax()),
524            name_ptr: Some(AstPtr::new(&name_node).wrap_left()),
525        };
526
527        let def = ModuleDef::Module(module_id.into());
528
529        let mut do_not_complete = Complete::Yes;
530        if let Some(attrs) = def.attrs(self.db) {
531            do_not_complete = Complete::extract(matches!(def, ModuleDef::Trait(_)), attrs.attrs);
532
533            for alias in attrs.doc_aliases(self.db) {
534                self.symbols.insert(FileSymbol {
535                    name: alias.clone(),
536                    def,
537                    loc,
538                    container_name: self.current_container_name.clone(),
539                    is_alias: true,
540                    is_assoc: false,
541                    is_import: false,
542                    do_not_complete,
543                    _marker: PhantomData,
544                });
545            }
546        }
547
548        self.symbols.insert(FileSymbol {
549            name: name.symbol().clone(),
550            def: ModuleDef::Module(module_id.into()),
551            container_name: self.current_container_name.clone(),
552            loc,
553            is_alias: false,
554            is_assoc: false,
555            is_import: false,
556            do_not_complete,
557            _marker: PhantomData,
558        });
559    }
560}