1use 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#[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 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 pub hir_file_id: HirFileId,
50 pub ptr: SyntaxNodePtr,
52 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#[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
78impl<'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 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 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 ModuleDefId::BuiltinType(_) => {}
225 ModuleDefId::EnumVariantId(_) => {}
226 }
227 };
228
229 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 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 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 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 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 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 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}