hir/
symbols.rs

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