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