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