]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! File symbol extraction. |
2 | ||
3 | use base_db::FileRange; | |
4 | use hir_def::{ | |
5 | item_tree::ItemTreeNode, src::HasSource, AdtId, AssocItemId, AssocItemLoc, DefWithBodyId, | |
6 | HasModule, ImplId, ItemContainerId, Lookup, MacroId, ModuleDefId, ModuleId, TraitId, | |
7 | }; | |
8 | use hir_expand::{HirFileId, InFile}; | |
9 | use hir_ty::db::HirDatabase; | |
10 | use syntax::{ast::HasName, AstNode, SmolStr, SyntaxNode, SyntaxNodePtr}; | |
11 | ||
12 | use crate::{Module, Semantics}; | |
13 | ||
14 | /// The actual data that is stored in the index. It should be as compact as | |
15 | /// possible. | |
16 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | |
17 | pub struct FileSymbol { | |
18 | pub name: SmolStr, | |
19 | pub loc: DeclarationLocation, | |
20 | pub kind: FileSymbolKind, | |
21 | pub container_name: Option<SmolStr>, | |
22 | } | |
23 | ||
24 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | |
25 | pub struct DeclarationLocation { | |
26 | /// The file id for both the `ptr` and `name_ptr`. | |
27 | pub hir_file_id: HirFileId, | |
28 | /// This points to the whole syntax node of the declaration. | |
29 | pub ptr: SyntaxNodePtr, | |
30 | /// This points to the [`syntax::ast::Name`] identifier of the declaration. | |
31 | pub name_ptr: SyntaxNodePtr, | |
32 | } | |
33 | ||
34 | impl DeclarationLocation { | |
35 | pub fn syntax<DB: HirDatabase>(&self, sema: &Semantics<'_, DB>) -> Option<SyntaxNode> { | |
36 | let root = sema.parse_or_expand(self.hir_file_id)?; | |
37 | Some(self.ptr.to_node(&root)) | |
38 | } | |
39 | ||
40 | pub fn original_range(&self, db: &dyn HirDatabase) -> Option<FileRange> { | |
41 | let node = resolve_node(db, self.hir_file_id, &self.ptr)?; | |
42 | Some(node.as_ref().original_file_range(db.upcast())) | |
43 | } | |
44 | ||
45 | pub fn original_name_range(&self, db: &dyn HirDatabase) -> Option<FileRange> { | |
46 | let node = resolve_node(db, self.hir_file_id, &self.name_ptr)?; | |
47 | node.as_ref().original_file_range_opt(db.upcast()) | |
48 | } | |
49 | } | |
50 | ||
51 | fn resolve_node( | |
52 | db: &dyn HirDatabase, | |
53 | file_id: HirFileId, | |
54 | ptr: &SyntaxNodePtr, | |
55 | ) -> Option<InFile<SyntaxNode>> { | |
56 | let root = db.parse_or_expand(file_id)?; | |
57 | let node = ptr.to_node(&root); | |
58 | Some(InFile::new(file_id, node)) | |
59 | } | |
60 | ||
61 | #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] | |
62 | pub enum FileSymbolKind { | |
63 | Const, | |
64 | Enum, | |
65 | Function, | |
66 | Macro, | |
67 | Module, | |
68 | Static, | |
69 | Struct, | |
70 | Trait, | |
71 | TypeAlias, | |
72 | Union, | |
73 | } | |
74 | ||
75 | impl FileSymbolKind { | |
76 | pub fn is_type(self: FileSymbolKind) -> bool { | |
77 | matches!( | |
78 | self, | |
79 | FileSymbolKind::Struct | |
80 | | FileSymbolKind::Enum | |
81 | | FileSymbolKind::Trait | |
82 | | FileSymbolKind::TypeAlias | |
83 | | FileSymbolKind::Union | |
84 | ) | |
85 | } | |
86 | } | |
87 | ||
88 | /// Represents an outstanding module that the symbol collector must collect symbols from. | |
89 | struct SymbolCollectorWork { | |
90 | module_id: ModuleId, | |
91 | parent: Option<DefWithBodyId>, | |
92 | } | |
93 | ||
94 | pub struct SymbolCollector<'a> { | |
95 | db: &'a dyn HirDatabase, | |
96 | symbols: Vec<FileSymbol>, | |
97 | work: Vec<SymbolCollectorWork>, | |
98 | current_container_name: Option<SmolStr>, | |
99 | } | |
100 | ||
101 | /// Given a [`ModuleId`] and a [`HirDatabase`], use the DefMap for the module's crate to collect | |
102 | /// all symbols that should be indexed for the given module. | |
103 | impl<'a> SymbolCollector<'a> { | |
104 | pub fn collect(db: &dyn HirDatabase, module: Module) -> Vec<FileSymbol> { | |
105 | let mut symbol_collector = SymbolCollector { | |
106 | db, | |
107 | symbols: Default::default(), | |
108 | current_container_name: None, | |
109 | // The initial work is the root module we're collecting, additional work will | |
110 | // be populated as we traverse the module's definitions. | |
111 | work: vec![SymbolCollectorWork { module_id: module.into(), parent: None }], | |
112 | }; | |
113 | ||
114 | while let Some(work) = symbol_collector.work.pop() { | |
115 | symbol_collector.do_work(work); | |
116 | } | |
117 | ||
118 | symbol_collector.symbols | |
119 | } | |
120 | ||
121 | fn do_work(&mut self, work: SymbolCollectorWork) { | |
122 | self.db.unwind_if_cancelled(); | |
123 | ||
124 | let parent_name = work.parent.and_then(|id| self.def_with_body_id_name(id)); | |
125 | self.with_container_name(parent_name, |s| s.collect_from_module(work.module_id)); | |
126 | } | |
127 | ||
128 | fn collect_from_module(&mut self, module_id: ModuleId) { | |
129 | let def_map = module_id.def_map(self.db.upcast()); | |
130 | let scope = &def_map[module_id.local_id].scope; | |
131 | ||
132 | for module_def_id in scope.declarations() { | |
133 | match module_def_id { | |
134 | ModuleDefId::ModuleId(id) => self.push_module(id), | |
135 | ModuleDefId::FunctionId(id) => { | |
136 | self.push_decl_assoc(id, FileSymbolKind::Function); | |
137 | self.collect_from_body(id); | |
138 | } | |
139 | ModuleDefId::AdtId(AdtId::StructId(id)) => { | |
140 | self.push_decl(id, FileSymbolKind::Struct) | |
141 | } | |
142 | ModuleDefId::AdtId(AdtId::EnumId(id)) => self.push_decl(id, FileSymbolKind::Enum), | |
143 | ModuleDefId::AdtId(AdtId::UnionId(id)) => self.push_decl(id, FileSymbolKind::Union), | |
144 | ModuleDefId::ConstId(id) => { | |
145 | self.push_decl_assoc(id, FileSymbolKind::Const); | |
146 | self.collect_from_body(id); | |
147 | } | |
148 | ModuleDefId::StaticId(id) => { | |
149 | self.push_decl_assoc(id, FileSymbolKind::Static); | |
150 | self.collect_from_body(id); | |
151 | } | |
152 | ModuleDefId::TraitId(id) => { | |
153 | self.push_decl(id, FileSymbolKind::Trait); | |
154 | self.collect_from_trait(id); | |
155 | } | |
156 | ModuleDefId::TypeAliasId(id) => { | |
157 | self.push_decl_assoc(id, FileSymbolKind::TypeAlias); | |
158 | } | |
159 | ModuleDefId::MacroId(id) => match id { | |
160 | MacroId::Macro2Id(id) => self.push_decl(id, FileSymbolKind::Macro), | |
161 | MacroId::MacroRulesId(id) => self.push_decl(id, FileSymbolKind::Macro), | |
162 | MacroId::ProcMacroId(id) => self.push_decl(id, FileSymbolKind::Macro), | |
163 | }, | |
164 | // Don't index these. | |
165 | ModuleDefId::BuiltinType(_) => {} | |
166 | ModuleDefId::EnumVariantId(_) => {} | |
167 | } | |
168 | } | |
169 | ||
170 | for impl_id in scope.impls() { | |
171 | self.collect_from_impl(impl_id); | |
172 | } | |
173 | ||
174 | for const_id in scope.unnamed_consts() { | |
175 | self.collect_from_body(const_id); | |
176 | } | |
177 | ||
178 | for (_, id) in scope.legacy_macros() { | |
179 | for &id in id { | |
180 | if id.module(self.db.upcast()) == module_id { | |
181 | match id { | |
182 | MacroId::Macro2Id(id) => self.push_decl(id, FileSymbolKind::Macro), | |
183 | MacroId::MacroRulesId(id) => self.push_decl(id, FileSymbolKind::Macro), | |
184 | MacroId::ProcMacroId(id) => self.push_decl(id, FileSymbolKind::Macro), | |
185 | } | |
186 | } | |
187 | } | |
188 | } | |
189 | } | |
190 | ||
191 | fn collect_from_body(&mut self, body_id: impl Into<DefWithBodyId>) { | |
192 | let body_id = body_id.into(); | |
193 | let body = self.db.body(body_id); | |
194 | ||
195 | // Descend into the blocks and enqueue collection of all modules within. | |
196 | for (_, def_map) in body.blocks(self.db.upcast()) { | |
197 | for (id, _) in def_map.modules() { | |
198 | self.work.push(SymbolCollectorWork { | |
199 | module_id: def_map.module_id(id), | |
200 | parent: Some(body_id), | |
201 | }); | |
202 | } | |
203 | } | |
204 | } | |
205 | ||
206 | fn collect_from_impl(&mut self, impl_id: ImplId) { | |
207 | let impl_data = self.db.impl_data(impl_id); | |
208 | for &assoc_item_id in &impl_data.items { | |
209 | self.push_assoc_item(assoc_item_id) | |
210 | } | |
211 | } | |
212 | ||
213 | fn collect_from_trait(&mut self, trait_id: TraitId) { | |
214 | let trait_data = self.db.trait_data(trait_id); | |
215 | self.with_container_name(trait_data.name.as_text(), |s| { | |
216 | for &(_, assoc_item_id) in &trait_data.items { | |
217 | s.push_assoc_item(assoc_item_id); | |
218 | } | |
219 | }); | |
220 | } | |
221 | ||
222 | fn with_container_name(&mut self, container_name: Option<SmolStr>, f: impl FnOnce(&mut Self)) { | |
223 | if let Some(container_name) = container_name { | |
224 | let prev = self.current_container_name.replace(container_name); | |
225 | f(self); | |
226 | self.current_container_name = prev; | |
227 | } else { | |
228 | f(self); | |
229 | } | |
230 | } | |
231 | ||
232 | fn current_container_name(&self) -> Option<SmolStr> { | |
233 | self.current_container_name.clone() | |
234 | } | |
235 | ||
236 | fn def_with_body_id_name(&self, body_id: DefWithBodyId) -> Option<SmolStr> { | |
237 | match body_id { | |
238 | DefWithBodyId::FunctionId(id) => Some( | |
239 | id.lookup(self.db.upcast()).source(self.db.upcast()).value.name()?.text().into(), | |
240 | ), | |
241 | DefWithBodyId::StaticId(id) => Some( | |
242 | id.lookup(self.db.upcast()).source(self.db.upcast()).value.name()?.text().into(), | |
243 | ), | |
244 | DefWithBodyId::ConstId(id) => Some( | |
245 | id.lookup(self.db.upcast()).source(self.db.upcast()).value.name()?.text().into(), | |
246 | ), | |
2b03887a FG |
247 | DefWithBodyId::VariantId(id) => Some({ |
248 | let db = self.db.upcast(); | |
249 | id.parent.lookup(db).source(db).value.name()?.text().into() | |
250 | }), | |
064997fb FG |
251 | } |
252 | } | |
253 | ||
254 | fn push_assoc_item(&mut self, assoc_item_id: AssocItemId) { | |
255 | match assoc_item_id { | |
256 | AssocItemId::FunctionId(id) => self.push_decl_assoc(id, FileSymbolKind::Function), | |
257 | AssocItemId::ConstId(id) => self.push_decl_assoc(id, FileSymbolKind::Const), | |
258 | AssocItemId::TypeAliasId(id) => self.push_decl_assoc(id, FileSymbolKind::TypeAlias), | |
259 | } | |
260 | } | |
261 | ||
262 | fn push_decl_assoc<L, T>(&mut self, id: L, kind: FileSymbolKind) | |
263 | where | |
264 | L: Lookup<Data = AssocItemLoc<T>>, | |
265 | T: ItemTreeNode, | |
266 | <T as ItemTreeNode>::Source: HasName, | |
267 | { | |
268 | fn container_name(db: &dyn HirDatabase, container: ItemContainerId) -> Option<SmolStr> { | |
269 | match container { | |
270 | ItemContainerId::ModuleId(module_id) => { | |
271 | let module = Module::from(module_id); | |
272 | module.name(db).and_then(|name| name.as_text()) | |
273 | } | |
274 | ItemContainerId::TraitId(trait_id) => { | |
275 | let trait_data = db.trait_data(trait_id); | |
276 | trait_data.name.as_text() | |
277 | } | |
278 | ItemContainerId::ImplId(_) | ItemContainerId::ExternBlockId(_) => None, | |
279 | } | |
280 | } | |
281 | ||
282 | self.push_file_symbol(|s| { | |
283 | let loc = id.lookup(s.db.upcast()); | |
284 | let source = loc.source(s.db.upcast()); | |
285 | let name_node = source.value.name()?; | |
286 | let container_name = | |
287 | container_name(s.db, loc.container).or_else(|| s.current_container_name()); | |
288 | ||
289 | Some(FileSymbol { | |
290 | name: name_node.text().into(), | |
291 | kind, | |
292 | container_name, | |
293 | loc: DeclarationLocation { | |
294 | hir_file_id: source.file_id, | |
295 | ptr: SyntaxNodePtr::new(source.value.syntax()), | |
296 | name_ptr: SyntaxNodePtr::new(name_node.syntax()), | |
297 | }, | |
298 | }) | |
299 | }) | |
300 | } | |
301 | ||
302 | fn push_decl<L>(&mut self, id: L, kind: FileSymbolKind) | |
303 | where | |
304 | L: Lookup, | |
305 | <L as Lookup>::Data: HasSource, | |
306 | <<L as Lookup>::Data as HasSource>::Value: HasName, | |
307 | { | |
308 | self.push_file_symbol(|s| { | |
309 | let loc = id.lookup(s.db.upcast()); | |
310 | let source = loc.source(s.db.upcast()); | |
311 | let name_node = source.value.name()?; | |
312 | ||
313 | Some(FileSymbol { | |
314 | name: name_node.text().into(), | |
315 | kind, | |
316 | container_name: s.current_container_name(), | |
317 | loc: DeclarationLocation { | |
318 | hir_file_id: source.file_id, | |
319 | ptr: SyntaxNodePtr::new(source.value.syntax()), | |
320 | name_ptr: SyntaxNodePtr::new(name_node.syntax()), | |
321 | }, | |
322 | }) | |
323 | }) | |
324 | } | |
325 | ||
326 | fn push_module(&mut self, module_id: ModuleId) { | |
327 | self.push_file_symbol(|s| { | |
328 | let def_map = module_id.def_map(s.db.upcast()); | |
329 | let module_data = &def_map[module_id.local_id]; | |
330 | let declaration = module_data.origin.declaration()?; | |
331 | let module = declaration.to_node(s.db.upcast()); | |
332 | let name_node = module.name()?; | |
333 | ||
334 | Some(FileSymbol { | |
335 | name: name_node.text().into(), | |
336 | kind: FileSymbolKind::Module, | |
337 | container_name: s.current_container_name(), | |
338 | loc: DeclarationLocation { | |
339 | hir_file_id: declaration.file_id, | |
340 | ptr: SyntaxNodePtr::new(module.syntax()), | |
341 | name_ptr: SyntaxNodePtr::new(name_node.syntax()), | |
342 | }, | |
343 | }) | |
344 | }) | |
345 | } | |
346 | ||
347 | fn push_file_symbol(&mut self, f: impl FnOnce(&Self) -> Option<FileSymbol>) { | |
348 | if let Some(file_symbol) = f(self) { | |
349 | self.symbols.push(file_symbol); | |
350 | } | |
351 | } | |
352 | } |