Skip to main content

hir/
symbols.rs

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