1use 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#[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 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 pub hir_file_id: HirFileId,
47 pub ptr: SyntaxNodePtr,
49 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#[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
75impl<'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 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 ModuleDefId::BuiltinType(_) => {}
176 ModuleDefId::EnumVariantId(_) => {}
177 }
178 };
179
180 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 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 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 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 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}