diff options
Diffstat (limited to 'crates/ra_analysis/src/descriptors/module/nameres.rs')
-rw-r--r-- | crates/ra_analysis/src/descriptors/module/nameres.rs | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/descriptors/module/nameres.rs b/crates/ra_analysis/src/descriptors/module/nameres.rs new file mode 100644 index 000000000..c5bf467ca --- /dev/null +++ b/crates/ra_analysis/src/descriptors/module/nameres.rs | |||
@@ -0,0 +1,453 @@ | |||
1 | //! Name resolution algorithm | ||
2 | use std::{ | ||
3 | sync::Arc, | ||
4 | time::Instant, | ||
5 | }; | ||
6 | |||
7 | use rustc_hash::FxHashMap; | ||
8 | |||
9 | use ra_syntax::{ | ||
10 | SmolStr, SyntaxKind::{self, *}, | ||
11 | ast::{self, AstNode, ModuleItemOwner} | ||
12 | }; | ||
13 | |||
14 | use crate::{ | ||
15 | Cancelable, | ||
16 | loc2id::{DefId, DefLoc}, | ||
17 | descriptors::{ | ||
18 | DescriptorDatabase, | ||
19 | module::{ModuleId, ModuleTree, ModuleSourceNode}, | ||
20 | }, | ||
21 | syntax_ptr::{LocalSyntaxPtr}, | ||
22 | input::SourceRootId, | ||
23 | }; | ||
24 | |||
25 | /// Item map is the result of the name resolution. Item map contains, for each | ||
26 | /// module, the set of visible items. | ||
27 | #[derive(Default, Debug, PartialEq, Eq)] | ||
28 | pub(crate) struct ItemMap { | ||
29 | pub(crate) per_module: FxHashMap<ModuleId, ModuleScope>, | ||
30 | } | ||
31 | |||
32 | #[derive(Debug, Default, PartialEq, Eq, Clone)] | ||
33 | pub(crate) struct ModuleScope { | ||
34 | pub(crate) items: FxHashMap<SmolStr, Resolution>, | ||
35 | pub(crate) import_resolutions: FxHashMap<LocalSyntaxPtr, DefId>, | ||
36 | } | ||
37 | |||
38 | /// A set of items and imports declared inside a module, without relation to | ||
39 | /// other modules. | ||
40 | /// | ||
41 | /// This stands in-between raw syntax and name resolution and alow us to avoid | ||
42 | /// recomputing name res: if `InputModuleItems` are the same, we can avoid | ||
43 | /// running name resolution. | ||
44 | #[derive(Debug, Default, PartialEq, Eq)] | ||
45 | pub(crate) struct InputModuleItems { | ||
46 | items: Vec<ModuleItem>, | ||
47 | glob_imports: Vec<Path>, | ||
48 | imports: Vec<Path>, | ||
49 | } | ||
50 | |||
51 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
52 | struct Path { | ||
53 | kind: PathKind, | ||
54 | segments: Vec<(LocalSyntaxPtr, SmolStr)>, | ||
55 | } | ||
56 | |||
57 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
58 | enum PathKind { | ||
59 | Abs, | ||
60 | Self_, | ||
61 | Super, | ||
62 | Crate, | ||
63 | } | ||
64 | |||
65 | pub(crate) fn input_module_items( | ||
66 | db: &impl DescriptorDatabase, | ||
67 | source_root: SourceRootId, | ||
68 | module_id: ModuleId, | ||
69 | ) -> Cancelable<Arc<InputModuleItems>> { | ||
70 | let module_tree = db._module_tree(source_root)?; | ||
71 | let source = module_id.source(&module_tree); | ||
72 | let res = match source.resolve(db) { | ||
73 | ModuleSourceNode::SourceFile(it) => { | ||
74 | let items = it.borrowed().items(); | ||
75 | InputModuleItems::new(items) | ||
76 | } | ||
77 | ModuleSourceNode::Module(it) => { | ||
78 | let items = it | ||
79 | .borrowed() | ||
80 | .item_list() | ||
81 | .into_iter() | ||
82 | .flat_map(|it| it.items()); | ||
83 | InputModuleItems::new(items) | ||
84 | } | ||
85 | }; | ||
86 | Ok(Arc::new(res)) | ||
87 | } | ||
88 | |||
89 | pub(crate) fn item_map( | ||
90 | db: &impl DescriptorDatabase, | ||
91 | source_root: SourceRootId, | ||
92 | ) -> Cancelable<Arc<ItemMap>> { | ||
93 | let start = Instant::now(); | ||
94 | let module_tree = db._module_tree(source_root)?; | ||
95 | let input = module_tree | ||
96 | .modules() | ||
97 | .map(|id| { | ||
98 | let items = db._input_module_items(source_root, id)?; | ||
99 | Ok((id, items)) | ||
100 | }) | ||
101 | .collect::<Cancelable<FxHashMap<_, _>>>()?; | ||
102 | |||
103 | let mut resolver = Resolver { | ||
104 | db: db, | ||
105 | input: &input, | ||
106 | source_root, | ||
107 | module_tree, | ||
108 | result: ItemMap::default(), | ||
109 | }; | ||
110 | resolver.resolve()?; | ||
111 | let res = resolver.result; | ||
112 | let elapsed = start.elapsed(); | ||
113 | log::info!("item_map: {:?}", elapsed); | ||
114 | Ok(Arc::new(res)) | ||
115 | } | ||
116 | |||
117 | /// Resolution is basically `DefId` atm, but it should account for stuff like | ||
118 | /// multiple namespaces, ambiguity and errors. | ||
119 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
120 | pub(crate) struct Resolution { | ||
121 | /// None for unresolved | ||
122 | pub(crate) def_id: Option<DefId>, | ||
123 | /// ident by whitch this is imported into local scope. | ||
124 | /// TODO: make this offset-independent. | ||
125 | pub(crate) import_name: Option<LocalSyntaxPtr>, | ||
126 | } | ||
127 | |||
128 | // #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
129 | // enum Namespace { | ||
130 | // Types, | ||
131 | // Values, | ||
132 | // } | ||
133 | |||
134 | // #[derive(Debug)] | ||
135 | // struct PerNs<T> { | ||
136 | // types: Option<T>, | ||
137 | // values: Option<T>, | ||
138 | // } | ||
139 | |||
140 | #[derive(Debug, PartialEq, Eq)] | ||
141 | struct ModuleItem { | ||
142 | ptr: LocalSyntaxPtr, | ||
143 | name: SmolStr, | ||
144 | kind: SyntaxKind, | ||
145 | vis: Vis, | ||
146 | } | ||
147 | |||
148 | #[derive(Debug, PartialEq, Eq)] | ||
149 | enum Vis { | ||
150 | // Priv, | ||
151 | Other, | ||
152 | } | ||
153 | |||
154 | impl InputModuleItems { | ||
155 | fn new<'a>(items: impl Iterator<Item = ast::ModuleItem<'a>>) -> InputModuleItems { | ||
156 | let mut res = InputModuleItems::default(); | ||
157 | for item in items { | ||
158 | res.add_item(item); | ||
159 | } | ||
160 | res | ||
161 | } | ||
162 | |||
163 | fn add_item(&mut self, item: ast::ModuleItem) -> Option<()> { | ||
164 | match item { | ||
165 | ast::ModuleItem::StructDef(it) => self.items.push(ModuleItem::new(it)?), | ||
166 | ast::ModuleItem::EnumDef(it) => self.items.push(ModuleItem::new(it)?), | ||
167 | ast::ModuleItem::FnDef(it) => self.items.push(ModuleItem::new(it)?), | ||
168 | ast::ModuleItem::TraitDef(it) => self.items.push(ModuleItem::new(it)?), | ||
169 | ast::ModuleItem::TypeDef(it) => self.items.push(ModuleItem::new(it)?), | ||
170 | ast::ModuleItem::ImplItem(_) => { | ||
171 | // impls don't define items | ||
172 | } | ||
173 | ast::ModuleItem::UseItem(it) => self.add_use_item(it), | ||
174 | ast::ModuleItem::ExternCrateItem(_) => { | ||
175 | // TODO | ||
176 | } | ||
177 | ast::ModuleItem::ConstDef(it) => self.items.push(ModuleItem::new(it)?), | ||
178 | ast::ModuleItem::StaticDef(it) => self.items.push(ModuleItem::new(it)?), | ||
179 | ast::ModuleItem::Module(it) => self.items.push(ModuleItem::new(it)?), | ||
180 | } | ||
181 | Some(()) | ||
182 | } | ||
183 | |||
184 | fn add_use_item(&mut self, item: ast::UseItem) { | ||
185 | if let Some(tree) = item.use_tree() { | ||
186 | self.add_use_tree(None, tree); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | fn add_use_tree(&mut self, prefix: Option<Path>, tree: ast::UseTree) { | ||
191 | if let Some(use_tree_list) = tree.use_tree_list() { | ||
192 | let prefix = match tree.path() { | ||
193 | None => prefix, | ||
194 | Some(path) => match convert_path(prefix, path) { | ||
195 | Some(it) => Some(it), | ||
196 | None => return, // TODO: report errors somewhere | ||
197 | }, | ||
198 | }; | ||
199 | for tree in use_tree_list.use_trees() { | ||
200 | self.add_use_tree(prefix.clone(), tree); | ||
201 | } | ||
202 | } else { | ||
203 | if let Some(path) = tree.path() { | ||
204 | if let Some(path) = convert_path(prefix, path) { | ||
205 | if tree.has_star() { | ||
206 | &mut self.glob_imports | ||
207 | } else { | ||
208 | &mut self.imports | ||
209 | } | ||
210 | .push(path); | ||
211 | } | ||
212 | } | ||
213 | } | ||
214 | } | ||
215 | } | ||
216 | |||
217 | fn convert_path(prefix: Option<Path>, path: ast::Path) -> Option<Path> { | ||
218 | let prefix = if let Some(qual) = path.qualifier() { | ||
219 | Some(convert_path(prefix, qual)?) | ||
220 | } else { | ||
221 | None | ||
222 | }; | ||
223 | let segment = path.segment()?; | ||
224 | let res = match segment.kind()? { | ||
225 | ast::PathSegmentKind::Name(name) => { | ||
226 | let mut res = prefix.unwrap_or_else(|| Path { | ||
227 | kind: PathKind::Abs, | ||
228 | segments: Vec::with_capacity(1), | ||
229 | }); | ||
230 | let ptr = LocalSyntaxPtr::new(name.syntax()); | ||
231 | res.segments.push((ptr, name.text())); | ||
232 | res | ||
233 | } | ||
234 | ast::PathSegmentKind::CrateKw => { | ||
235 | if prefix.is_some() { | ||
236 | return None; | ||
237 | } | ||
238 | Path { | ||
239 | kind: PathKind::Crate, | ||
240 | segments: Vec::new(), | ||
241 | } | ||
242 | } | ||
243 | ast::PathSegmentKind::SelfKw => { | ||
244 | if prefix.is_some() { | ||
245 | return None; | ||
246 | } | ||
247 | Path { | ||
248 | kind: PathKind::Self_, | ||
249 | segments: Vec::new(), | ||
250 | } | ||
251 | } | ||
252 | ast::PathSegmentKind::SuperKw => { | ||
253 | if prefix.is_some() { | ||
254 | return None; | ||
255 | } | ||
256 | Path { | ||
257 | kind: PathKind::Super, | ||
258 | segments: Vec::new(), | ||
259 | } | ||
260 | } | ||
261 | }; | ||
262 | Some(res) | ||
263 | } | ||
264 | |||
265 | impl ModuleItem { | ||
266 | fn new<'a>(item: impl ast::NameOwner<'a>) -> Option<ModuleItem> { | ||
267 | let name = item.name()?.text(); | ||
268 | let ptr = LocalSyntaxPtr::new(item.syntax()); | ||
269 | let kind = item.syntax().kind(); | ||
270 | let vis = Vis::Other; | ||
271 | let res = ModuleItem { | ||
272 | ptr, | ||
273 | name, | ||
274 | kind, | ||
275 | vis, | ||
276 | }; | ||
277 | Some(res) | ||
278 | } | ||
279 | } | ||
280 | |||
281 | struct Resolver<'a, DB> { | ||
282 | db: &'a DB, | ||
283 | input: &'a FxHashMap<ModuleId, Arc<InputModuleItems>>, | ||
284 | source_root: SourceRootId, | ||
285 | module_tree: Arc<ModuleTree>, | ||
286 | result: ItemMap, | ||
287 | } | ||
288 | |||
289 | impl<'a, DB> Resolver<'a, DB> | ||
290 | where | ||
291 | DB: DescriptorDatabase, | ||
292 | { | ||
293 | fn resolve(&mut self) -> Cancelable<()> { | ||
294 | for (&module_id, items) in self.input.iter() { | ||
295 | self.populate_module(module_id, items) | ||
296 | } | ||
297 | |||
298 | for &module_id in self.input.keys() { | ||
299 | crate::db::check_canceled(self.db)?; | ||
300 | self.resolve_imports(module_id); | ||
301 | } | ||
302 | Ok(()) | ||
303 | } | ||
304 | |||
305 | fn populate_module(&mut self, module_id: ModuleId, input: &InputModuleItems) { | ||
306 | let file_id = module_id.source(&self.module_tree).file_id(); | ||
307 | |||
308 | let mut module_items = ModuleScope::default(); | ||
309 | |||
310 | for import in input.imports.iter() { | ||
311 | if let Some((ptr, name)) = import.segments.last() { | ||
312 | module_items.items.insert( | ||
313 | name.clone(), | ||
314 | Resolution { | ||
315 | def_id: None, | ||
316 | import_name: Some(*ptr), | ||
317 | }, | ||
318 | ); | ||
319 | } | ||
320 | } | ||
321 | |||
322 | for item in input.items.iter() { | ||
323 | if item.kind == MODULE { | ||
324 | // handle submodules separatelly | ||
325 | continue; | ||
326 | } | ||
327 | let ptr = item.ptr.into_global(file_id); | ||
328 | let def_loc = DefLoc::Item { ptr }; | ||
329 | let def_id = self.db.id_maps().def_id(def_loc); | ||
330 | let resolution = Resolution { | ||
331 | def_id: Some(def_id), | ||
332 | import_name: None, | ||
333 | }; | ||
334 | module_items.items.insert(item.name.clone(), resolution); | ||
335 | } | ||
336 | |||
337 | for (name, mod_id) in module_id.children(&self.module_tree) { | ||
338 | let def_loc = DefLoc::Module { | ||
339 | id: mod_id, | ||
340 | source_root: self.source_root, | ||
341 | }; | ||
342 | let def_id = self.db.id_maps().def_id(def_loc); | ||
343 | let resolution = Resolution { | ||
344 | def_id: Some(def_id), | ||
345 | import_name: None, | ||
346 | }; | ||
347 | module_items.items.insert(name, resolution); | ||
348 | } | ||
349 | |||
350 | self.result.per_module.insert(module_id, module_items); | ||
351 | } | ||
352 | |||
353 | fn resolve_imports(&mut self, module_id: ModuleId) { | ||
354 | for import in self.input[&module_id].imports.iter() { | ||
355 | self.resolve_import(module_id, import); | ||
356 | } | ||
357 | } | ||
358 | |||
359 | fn resolve_import(&mut self, module_id: ModuleId, import: &Path) { | ||
360 | let mut curr = match import.kind { | ||
361 | // TODO: handle extern crates | ||
362 | PathKind::Abs => return, | ||
363 | PathKind::Self_ => module_id, | ||
364 | PathKind::Super => { | ||
365 | match module_id.parent(&self.module_tree) { | ||
366 | Some(it) => it, | ||
367 | // TODO: error | ||
368 | None => return, | ||
369 | } | ||
370 | } | ||
371 | PathKind::Crate => module_id.crate_root(&self.module_tree), | ||
372 | }; | ||
373 | |||
374 | for (i, (ptr, name)) in import.segments.iter().enumerate() { | ||
375 | let is_last = i == import.segments.len() - 1; | ||
376 | |||
377 | let def_id = match self.result.per_module[&curr].items.get(name) { | ||
378 | None => return, | ||
379 | Some(res) => match res.def_id { | ||
380 | Some(it) => it, | ||
381 | None => return, | ||
382 | }, | ||
383 | }; | ||
384 | |||
385 | self.update(module_id, |items| { | ||
386 | items.import_resolutions.insert(*ptr, def_id); | ||
387 | }); | ||
388 | |||
389 | if !is_last { | ||
390 | curr = match self.db.id_maps().def_loc(def_id) { | ||
391 | DefLoc::Module { id, .. } => id, | ||
392 | _ => return, | ||
393 | } | ||
394 | } else { | ||
395 | self.update(module_id, |items| { | ||
396 | let res = Resolution { | ||
397 | def_id: Some(def_id), | ||
398 | import_name: Some(*ptr), | ||
399 | }; | ||
400 | items.items.insert(name.clone(), res); | ||
401 | }) | ||
402 | } | ||
403 | } | ||
404 | } | ||
405 | |||
406 | fn update(&mut self, module_id: ModuleId, f: impl FnOnce(&mut ModuleScope)) { | ||
407 | let module_items = self.result.per_module.get_mut(&module_id).unwrap(); | ||
408 | f(module_items) | ||
409 | } | ||
410 | } | ||
411 | |||
412 | #[cfg(test)] | ||
413 | mod tests { | ||
414 | use crate::{ | ||
415 | mock_analysis::analysis_and_position, | ||
416 | descriptors::{DescriptorDatabase, module::ModuleDescriptor}, | ||
417 | input::FilesDatabase, | ||
418 | }; | ||
419 | use super::*; | ||
420 | |||
421 | fn item_map(fixture: &str) -> (Arc<ItemMap>, ModuleId) { | ||
422 | let (analysis, pos) = analysis_and_position(fixture); | ||
423 | let db = analysis.imp.db; | ||
424 | let source_root = db.file_source_root(pos.file_id); | ||
425 | let descr = ModuleDescriptor::guess_from_position(&*db, pos) | ||
426 | .unwrap() | ||
427 | .unwrap(); | ||
428 | let module_id = descr.module_id; | ||
429 | (db._item_map(source_root).unwrap(), module_id) | ||
430 | } | ||
431 | |||
432 | #[test] | ||
433 | fn test_item_map() { | ||
434 | let (item_map, module_id) = item_map( | ||
435 | " | ||
436 | //- /lib.rs | ||
437 | mod foo; | ||
438 | |||
439 | use crate::foo::bar::Baz; | ||
440 | <|> | ||
441 | |||
442 | //- /foo/mod.rs | ||
443 | pub mod bar; | ||
444 | |||
445 | //- /foo/bar.rs | ||
446 | pub struct Baz; | ||
447 | ", | ||
448 | ); | ||
449 | let name = SmolStr::from("Baz"); | ||
450 | let resolution = &item_map.per_module[&module_id].items[&name]; | ||
451 | assert!(resolution.def_id.is_some()); | ||
452 | } | ||
453 | } | ||