1use 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#[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 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 pub hir_file_id: HirFileId,
75 pub ptr: SyntaxNodePtr,
77 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#[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
103impl<'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 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 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 ModuleDefId::BuiltinType(_) => {}
250 ModuleDefId::EnumVariantId(_) => {}
251 }
252 };
253
254 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 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 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 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 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 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 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}