aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/hir/module
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis/src/hir/module')
-rw-r--r--crates/ra_analysis/src/hir/module/imp.rs229
-rw-r--r--crates/ra_analysis/src/hir/module/mod.rs378
-rw-r--r--crates/ra_analysis/src/hir/module/nameres.rs549
3 files changed, 1156 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/hir/module/imp.rs b/crates/ra_analysis/src/hir/module/imp.rs
new file mode 100644
index 000000000..d8539ed85
--- /dev/null
+++ b/crates/ra_analysis/src/hir/module/imp.rs
@@ -0,0 +1,229 @@
1use std::sync::Arc;
2
3use ra_syntax::{
4 ast::{self, NameOwner},
5 SmolStr,
6};
7use relative_path::RelativePathBuf;
8use rustc_hash::{FxHashMap, FxHashSet};
9
10use crate::{
11 db,
12 hir::DescriptorDatabase,
13 input::{SourceRoot, SourceRootId},
14 Cancelable, FileId, FileResolverImp,
15};
16
17use super::{
18 LinkData, LinkId, ModuleData, ModuleId, ModuleSource, ModuleSourceNode,
19 ModuleTree, Problem,
20};
21
22#[derive(Clone, Hash, PartialEq, Eq, Debug)]
23pub(crate) enum Submodule {
24 Declaration(SmolStr),
25 Definition(SmolStr, ModuleSource),
26}
27
28impl Submodule {
29 fn name(&self) -> &SmolStr {
30 match self {
31 Submodule::Declaration(name) => name,
32 Submodule::Definition(name, _) => name,
33 }
34 }
35}
36
37pub(crate) fn submodules(
38 db: &impl DescriptorDatabase,
39 source: ModuleSource,
40) -> Cancelable<Arc<Vec<Submodule>>> {
41 db::check_canceled(db)?;
42 let file_id = source.file_id();
43 let submodules = match source.resolve(db) {
44 ModuleSourceNode::SourceFile(it) => collect_submodules(file_id, it.borrowed()),
45 ModuleSourceNode::Module(it) => it
46 .borrowed()
47 .item_list()
48 .map(|it| collect_submodules(file_id, it))
49 .unwrap_or_else(Vec::new),
50 };
51 return Ok(Arc::new(submodules));
52
53 fn collect_submodules<'a>(
54 file_id: FileId,
55 root: impl ast::ModuleItemOwner<'a>,
56 ) -> Vec<Submodule> {
57 modules(root)
58 .map(|(name, m)| {
59 if m.has_semi() {
60 Submodule::Declaration(name)
61 } else {
62 let src = ModuleSource::new_inline(file_id, m);
63 Submodule::Definition(name, src)
64 }
65 })
66 .collect()
67 }
68}
69
70pub(crate) fn modules<'a>(
71 root: impl ast::ModuleItemOwner<'a>,
72) -> impl Iterator<Item = (SmolStr, ast::Module<'a>)> {
73 root.items()
74 .filter_map(|item| match item {
75 ast::ModuleItem::Module(m) => Some(m),
76 _ => None,
77 })
78 .filter_map(|module| {
79 let name = module.name()?.text();
80 Some((name, module))
81 })
82}
83
84pub(crate) fn module_tree(
85 db: &impl DescriptorDatabase,
86 source_root: SourceRootId,
87) -> Cancelable<Arc<ModuleTree>> {
88 db::check_canceled(db)?;
89 let res = create_module_tree(db, source_root)?;
90 Ok(Arc::new(res))
91}
92
93fn create_module_tree<'a>(
94 db: &impl DescriptorDatabase,
95 source_root: SourceRootId,
96) -> Cancelable<ModuleTree> {
97 let mut tree = ModuleTree::default();
98
99 let mut roots = FxHashMap::default();
100 let mut visited = FxHashSet::default();
101
102 let source_root = db.source_root(source_root);
103 for &file_id in source_root.files.iter() {
104 let source = ModuleSource::SourceFile(file_id);
105 if visited.contains(&source) {
106 continue; // TODO: use explicit crate_roots here
107 }
108 assert!(!roots.contains_key(&file_id));
109 let module_id = build_subtree(
110 db,
111 &source_root,
112 &mut tree,
113 &mut visited,
114 &mut roots,
115 None,
116 source,
117 )?;
118 roots.insert(file_id, module_id);
119 }
120 Ok(tree)
121}
122
123fn build_subtree(
124 db: &impl DescriptorDatabase,
125 source_root: &SourceRoot,
126 tree: &mut ModuleTree,
127 visited: &mut FxHashSet<ModuleSource>,
128 roots: &mut FxHashMap<FileId, ModuleId>,
129 parent: Option<LinkId>,
130 source: ModuleSource,
131) -> Cancelable<ModuleId> {
132 visited.insert(source);
133 let id = tree.push_mod(ModuleData {
134 source,
135 parent,
136 children: Vec::new(),
137 });
138 for sub in db._submodules(source)?.iter() {
139 let link = tree.push_link(LinkData {
140 name: sub.name().clone(),
141 owner: id,
142 points_to: Vec::new(),
143 problem: None,
144 });
145
146 let (points_to, problem) = match sub {
147 Submodule::Declaration(name) => {
148 let (points_to, problem) =
149 resolve_submodule(source, &name, &source_root.file_resolver);
150 let points_to = points_to
151 .into_iter()
152 .map(|file_id| match roots.remove(&file_id) {
153 Some(module_id) => {
154 tree.mods[module_id].parent = Some(link);
155 Ok(module_id)
156 }
157 None => build_subtree(
158 db,
159 source_root,
160 tree,
161 visited,
162 roots,
163 Some(link),
164 ModuleSource::SourceFile(file_id),
165 ),
166 })
167 .collect::<Cancelable<Vec<_>>>()?;
168 (points_to, problem)
169 }
170 Submodule::Definition(_name, submodule_source) => {
171 let points_to = build_subtree(
172 db,
173 source_root,
174 tree,
175 visited,
176 roots,
177 Some(link),
178 *submodule_source,
179 )?;
180 (vec![points_to], None)
181 }
182 };
183
184 tree.links[link].points_to = points_to;
185 tree.links[link].problem = problem;
186 }
187 Ok(id)
188}
189
190fn resolve_submodule(
191 source: ModuleSource,
192 name: &SmolStr,
193 file_resolver: &FileResolverImp,
194) -> (Vec<FileId>, Option<Problem>) {
195 let file_id = match source {
196 ModuleSource::SourceFile(it) => it,
197 ModuleSource::Module(..) => {
198 // TODO
199 return (Vec::new(), None);
200 }
201 };
202 let mod_name = file_resolver.file_stem(file_id);
203 let is_dir_owner = mod_name == "mod" || mod_name == "lib" || mod_name == "main";
204
205 let file_mod = RelativePathBuf::from(format!("../{}.rs", name));
206 let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name));
207 let points_to: Vec<FileId>;
208 let problem: Option<Problem>;
209 if is_dir_owner {
210 points_to = [&file_mod, &dir_mod]
211 .iter()
212 .filter_map(|path| file_resolver.resolve(file_id, path))
213 .collect();
214 problem = if points_to.is_empty() {
215 Some(Problem::UnresolvedModule {
216 candidate: file_mod,
217 })
218 } else {
219 None
220 }
221 } else {
222 points_to = Vec::new();
223 problem = Some(Problem::NotDirOwner {
224 move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)),
225 candidate: file_mod,
226 });
227 }
228 (points_to, problem)
229}
diff --git a/crates/ra_analysis/src/hir/module/mod.rs b/crates/ra_analysis/src/hir/module/mod.rs
new file mode 100644
index 000000000..f374a079f
--- /dev/null
+++ b/crates/ra_analysis/src/hir/module/mod.rs
@@ -0,0 +1,378 @@
1pub(super) mod imp;
2pub(super) mod nameres;
3
4use std::sync::Arc;
5
6use ra_editor::find_node_at_offset;
7
8use ra_syntax::{
9 algo::generate,
10 ast::{self, AstNode, NameOwner},
11 SmolStr, SyntaxNode,
12};
13use relative_path::RelativePathBuf;
14
15use crate::{
16 db::SyntaxDatabase, syntax_ptr::SyntaxPtr, FileId, FilePosition, Cancelable,
17 hir::{Path, PathKind, DescriptorDatabase},
18 input::SourceRootId,
19 arena::{Arena, Id},
20 loc2id::{DefLoc, DefId},
21};
22
23pub(crate) use self::nameres::ModuleScope;
24
25/// `ModuleDescriptor` is API entry point to get all the information
26/// about a particular module.
27#[derive(Debug, Clone)]
28pub(crate) struct ModuleDescriptor {
29 tree: Arc<ModuleTree>,
30 source_root_id: SourceRootId,
31 module_id: ModuleId,
32}
33
34impl ModuleDescriptor {
35 /// Lookup `ModuleDescriptor` by `FileId`. Note that this is inherently
36 /// lossy transformation: in general, a single source might correspond to
37 /// several modules.
38 pub fn guess_from_file_id(
39 db: &impl DescriptorDatabase,
40 file_id: FileId,
41 ) -> Cancelable<Option<ModuleDescriptor>> {
42 ModuleDescriptor::guess_from_source(db, file_id, ModuleSource::SourceFile(file_id))
43 }
44
45 /// Lookup `ModuleDescriptor` by position in the source code. Note that this
46 /// is inherently lossy transformation: in general, a single source might
47 /// correspond to several modules.
48 pub fn guess_from_position(
49 db: &impl DescriptorDatabase,
50 position: FilePosition,
51 ) -> Cancelable<Option<ModuleDescriptor>> {
52 let file = db.file_syntax(position.file_id);
53 let module_source = match find_node_at_offset::<ast::Module>(file.syntax(), position.offset)
54 {
55 Some(m) if !m.has_semi() => ModuleSource::new_inline(position.file_id, m),
56 _ => ModuleSource::SourceFile(position.file_id),
57 };
58 ModuleDescriptor::guess_from_source(db, position.file_id, module_source)
59 }
60
61 fn guess_from_source(
62 db: &impl DescriptorDatabase,
63 file_id: FileId,
64 module_source: ModuleSource,
65 ) -> Cancelable<Option<ModuleDescriptor>> {
66 let source_root_id = db.file_source_root(file_id);
67 let module_tree = db._module_tree(source_root_id)?;
68
69 let res = match module_tree.any_module_for_source(module_source) {
70 None => None,
71 Some(module_id) => Some(ModuleDescriptor {
72 tree: module_tree,
73 source_root_id,
74 module_id,
75 }),
76 };
77 Ok(res)
78 }
79
80 pub(super) fn new(
81 db: &impl DescriptorDatabase,
82 source_root_id: SourceRootId,
83 module_id: ModuleId,
84 ) -> Cancelable<ModuleDescriptor> {
85 let module_tree = db._module_tree(source_root_id)?;
86 let res = ModuleDescriptor {
87 tree: module_tree,
88 source_root_id,
89 module_id,
90 };
91 Ok(res)
92 }
93
94 /// Returns `mod foo;` or `mod foo {}` node whihc declared this module.
95 /// Returns `None` for the root module
96 pub fn parent_link_source(
97 &self,
98 db: &impl DescriptorDatabase,
99 ) -> Option<(FileId, ast::ModuleNode)> {
100 let link = self.module_id.parent_link(&self.tree)?;
101 let file_id = link.owner(&self.tree).source(&self.tree).file_id();
102 let src = link.bind_source(&self.tree, db);
103 Some((file_id, src))
104 }
105
106 pub fn source(&self) -> ModuleSource {
107 self.module_id.source(&self.tree)
108 }
109
110 /// Parent module. Returns `None` if this is a root module.
111 pub fn parent(&self) -> Option<ModuleDescriptor> {
112 let parent_id = self.module_id.parent(&self.tree)?;
113 Some(ModuleDescriptor {
114 module_id: parent_id,
115 ..self.clone()
116 })
117 }
118
119 /// The root of the tree this module is part of
120 pub fn crate_root(&self) -> ModuleDescriptor {
121 let root_id = self.module_id.crate_root(&self.tree);
122 ModuleDescriptor {
123 module_id: root_id,
124 ..self.clone()
125 }
126 }
127
128 /// `name` is `None` for the crate's root module
129 #[allow(unused)]
130 pub fn name(&self) -> Option<SmolStr> {
131 let link = self.module_id.parent_link(&self.tree)?;
132 Some(link.name(&self.tree))
133 }
134
135 pub fn def_id(&self, db: &impl DescriptorDatabase) -> DefId {
136 let def_loc = DefLoc::Module {
137 id: self.module_id,
138 source_root: self.source_root_id,
139 };
140 db.id_maps().def_id(def_loc)
141 }
142
143 /// Finds a child module with the specified name.
144 pub fn child(&self, name: &str) -> Option<ModuleDescriptor> {
145 let child_id = self.module_id.child(&self.tree, name)?;
146 Some(ModuleDescriptor {
147 module_id: child_id,
148 ..self.clone()
149 })
150 }
151
152 /// Returns a `ModuleScope`: a set of items, visible in this module.
153 pub(crate) fn scope(&self, db: &impl DescriptorDatabase) -> Cancelable<ModuleScope> {
154 let item_map = db._item_map(self.source_root_id)?;
155 let res = item_map.per_module[&self.module_id].clone();
156 Ok(res)
157 }
158
159 pub(crate) fn resolve_path(
160 &self,
161 db: &impl DescriptorDatabase,
162 path: Path,
163 ) -> Cancelable<Option<DefId>> {
164 let mut curr = match path.kind {
165 PathKind::Crate => self.crate_root(),
166 PathKind::Self_ | PathKind::Plain => self.clone(),
167 PathKind::Super => ctry!(self.parent()),
168 }
169 .def_id(db);
170
171 let segments = path.segments;
172 for name in segments.iter() {
173 let module = match db.id_maps().def_loc(curr) {
174 DefLoc::Module { id, source_root } => ModuleDescriptor::new(db, source_root, id)?,
175 _ => return Ok(None),
176 };
177 let scope = module.scope(db)?;
178 curr = ctry!(ctry!(scope.get(&name)).def_id);
179 }
180 Ok(Some(curr))
181 }
182
183 pub fn problems(&self, db: &impl DescriptorDatabase) -> Vec<(SyntaxNode, Problem)> {
184 self.module_id.problems(&self.tree, db)
185 }
186}
187
188/// Phisically, rust source is organized as a set of files, but logically it is
189/// organized as a tree of modules. Usually, a single file corresponds to a
190/// single module, but it is not nessary the case.
191///
192/// Module encapsulate the logic of transitioning from the fuzzy world of files
193/// (which can have multiple parents) to the precise world of modules (which
194/// always have one parent).
195#[derive(Default, Debug, PartialEq, Eq)]
196pub(crate) struct ModuleTree {
197 mods: Arena<ModuleData>,
198 links: Arena<LinkData>,
199}
200
201impl ModuleTree {
202 fn modules<'a>(&'a self) -> impl Iterator<Item = ModuleId> + 'a {
203 self.mods.iter().map(|(id, _)| id)
204 }
205
206 fn modules_for_source(&self, source: ModuleSource) -> Vec<ModuleId> {
207 self.mods
208 .iter()
209 .filter(|(_idx, it)| it.source == source)
210 .map(|(idx, _)| idx)
211 .collect()
212 }
213
214 fn any_module_for_source(&self, source: ModuleSource) -> Option<ModuleId> {
215 self.modules_for_source(source).pop()
216 }
217}
218
219/// `ModuleSource` is the syntax tree element that produced this module:
220/// either a file, or an inlinde module.
221#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
222pub(crate) enum ModuleSource {
223 SourceFile(FileId),
224 #[allow(dead_code)]
225 Module(SyntaxPtr),
226}
227
228/// An owned syntax node for a module. Unlike `ModuleSource`,
229/// this holds onto the AST for the whole file.
230enum ModuleSourceNode {
231 SourceFile(ast::SourceFileNode),
232 Module(ast::ModuleNode),
233}
234
235pub(crate) type ModuleId = Id<ModuleData>;
236type LinkId = Id<LinkData>;
237
238#[derive(Clone, Debug, Hash, PartialEq, Eq)]
239pub enum Problem {
240 UnresolvedModule {
241 candidate: RelativePathBuf,
242 },
243 NotDirOwner {
244 move_to: RelativePathBuf,
245 candidate: RelativePathBuf,
246 },
247}
248
249impl ModuleId {
250 fn source(self, tree: &ModuleTree) -> ModuleSource {
251 tree.mods[self].source
252 }
253 fn parent_link(self, tree: &ModuleTree) -> Option<LinkId> {
254 tree.mods[self].parent
255 }
256 fn parent(self, tree: &ModuleTree) -> Option<ModuleId> {
257 let link = self.parent_link(tree)?;
258 Some(tree.links[link].owner)
259 }
260 fn crate_root(self, tree: &ModuleTree) -> ModuleId {
261 generate(Some(self), move |it| it.parent(tree))
262 .last()
263 .unwrap()
264 }
265 fn child(self, tree: &ModuleTree, name: &str) -> Option<ModuleId> {
266 let link = tree.mods[self]
267 .children
268 .iter()
269 .map(|&it| &tree.links[it])
270 .find(|it| it.name == name)?;
271 Some(*link.points_to.first()?)
272 }
273 fn children<'a>(self, tree: &'a ModuleTree) -> impl Iterator<Item = (SmolStr, ModuleId)> + 'a {
274 tree.mods[self].children.iter().filter_map(move |&it| {
275 let link = &tree.links[it];
276 let module = *link.points_to.first()?;
277 Some((link.name.clone(), module))
278 })
279 }
280 fn problems(self, tree: &ModuleTree, db: &impl SyntaxDatabase) -> Vec<(SyntaxNode, Problem)> {
281 tree.mods[self]
282 .children
283 .iter()
284 .filter_map(|&it| {
285 let p = tree.links[it].problem.clone()?;
286 let s = it.bind_source(tree, db);
287 let s = s.borrowed().name().unwrap().syntax().owned();
288 Some((s, p))
289 })
290 .collect()
291 }
292}
293
294impl LinkId {
295 fn owner(self, tree: &ModuleTree) -> ModuleId {
296 tree.links[self].owner
297 }
298 fn name(self, tree: &ModuleTree) -> SmolStr {
299 tree.links[self].name.clone()
300 }
301 fn bind_source<'a>(self, tree: &ModuleTree, db: &impl SyntaxDatabase) -> ast::ModuleNode {
302 let owner = self.owner(tree);
303 match owner.source(tree).resolve(db) {
304 ModuleSourceNode::SourceFile(root) => {
305 let ast = imp::modules(root.borrowed())
306 .find(|(name, _)| name == &tree.links[self].name)
307 .unwrap()
308 .1;
309 ast.owned()
310 }
311 ModuleSourceNode::Module(it) => it,
312 }
313 }
314}
315
316#[derive(Debug, PartialEq, Eq, Hash)]
317pub(crate) struct ModuleData {
318 source: ModuleSource,
319 parent: Option<LinkId>,
320 children: Vec<LinkId>,
321}
322
323impl ModuleSource {
324 fn new_inline(file_id: FileId, module: ast::Module) -> ModuleSource {
325 assert!(!module.has_semi());
326 let ptr = SyntaxPtr::new(file_id, module.syntax());
327 ModuleSource::Module(ptr)
328 }
329
330 pub(crate) fn as_file(self) -> Option<FileId> {
331 match self {
332 ModuleSource::SourceFile(f) => Some(f),
333 ModuleSource::Module(..) => None,
334 }
335 }
336
337 pub(crate) fn file_id(self) -> FileId {
338 match self {
339 ModuleSource::SourceFile(f) => f,
340 ModuleSource::Module(ptr) => ptr.file_id(),
341 }
342 }
343
344 fn resolve(self, db: &impl SyntaxDatabase) -> ModuleSourceNode {
345 match self {
346 ModuleSource::SourceFile(file_id) => {
347 let syntax = db.file_syntax(file_id);
348 ModuleSourceNode::SourceFile(syntax.ast().owned())
349 }
350 ModuleSource::Module(ptr) => {
351 let syntax = db.resolve_syntax_ptr(ptr);
352 let syntax = syntax.borrowed();
353 let module = ast::Module::cast(syntax).unwrap();
354 ModuleSourceNode::Module(module.owned())
355 }
356 }
357 }
358}
359
360#[derive(Hash, Debug, PartialEq, Eq)]
361struct LinkData {
362 owner: ModuleId,
363 name: SmolStr,
364 points_to: Vec<ModuleId>,
365 problem: Option<Problem>,
366}
367
368impl ModuleTree {
369 fn push_mod(&mut self, data: ModuleData) -> ModuleId {
370 self.mods.alloc(data)
371 }
372 fn push_link(&mut self, data: LinkData) -> LinkId {
373 let owner = data.owner;
374 let id = self.links.alloc(data);
375 self.mods[owner].children.push(id);
376 id
377 }
378}
diff --git a/crates/ra_analysis/src/hir/module/nameres.rs b/crates/ra_analysis/src/hir/module/nameres.rs
new file mode 100644
index 000000000..1ec85fbf2
--- /dev/null
+++ b/crates/ra_analysis/src/hir/module/nameres.rs
@@ -0,0 +1,549 @@
1//! Name resolution algorithm. The end result of the algorithm is `ItemMap`: a
2//! map with maps each module to it's scope: the set of items, visible in the
3//! module. That is, we only resolve imports here, name resolution of item
4//! bodies will be done in a separate step.
5//!
6//! Like Rustc, we use an interative per-crate algorithm: we start with scopes
7//! containing only directly defined items, and then iteratively resolve
8//! imports.
9//!
10//! To make this work nicely in the IDE scenarios, we place `InputModuleItems`
11//! in between raw syntax and name resolution. `InputModuleItems` are computed
12//! using only the module's syntax, and it is all directly defined items plus
13//! imports. The plain is to make `InputModuleItems` independent of local
14//! modifications (that is, typing inside a function shold not change IMIs),
15//! such that the results of name resolution can be preserved unless the module
16//! structure itself is modified.
17use std::{
18 sync::Arc,
19 time::Instant,
20 ops::Index,
21};
22
23use rustc_hash::FxHashMap;
24
25use ra_syntax::{
26 SyntaxNode, SyntaxNodeRef, TextRange,
27 SmolStr, SyntaxKind::{self, *},
28 ast::{self, ModuleItemOwner, AstNode}
29};
30
31use crate::{
32 Cancelable, FileId,
33 loc2id::{DefId, DefLoc},
34 hir::{
35 Path, PathKind,
36 DescriptorDatabase,
37 module::{ModuleId, ModuleTree, ModuleSourceNode},
38 },
39 input::SourceRootId,
40 arena::{Arena, Id}
41};
42
43/// Identifier of item within a specific file. This is stable over reparses, so
44/// it's OK to use it as a salsa key/value.
45pub(crate) type FileItemId = Id<SyntaxNode>;
46
47/// Maps item's `SyntaxNode`s to `FileItemId` and back.
48#[derive(Debug, PartialEq, Eq, Default)]
49pub(crate) struct FileItems {
50 arena: Arena<SyntaxNode>,
51}
52
53impl FileItems {
54 fn alloc(&mut self, item: SyntaxNode) -> FileItemId {
55 self.arena.alloc(item)
56 }
57 fn id_of(&self, item: SyntaxNodeRef) -> FileItemId {
58 let (id, _item) = self
59 .arena
60 .iter()
61 .find(|(_id, i)| i.borrowed() == item)
62 .unwrap();
63 id
64 }
65}
66
67impl Index<FileItemId> for FileItems {
68 type Output = SyntaxNode;
69 fn index(&self, idx: FileItemId) -> &SyntaxNode {
70 &self.arena[idx]
71 }
72}
73
74pub(crate) fn file_items(db: &impl DescriptorDatabase, file_id: FileId) -> Arc<FileItems> {
75 let source_file = db.file_syntax(file_id);
76 let source_file = source_file.borrowed();
77 let mut res = FileItems::default();
78 source_file
79 .syntax()
80 .descendants()
81 .filter_map(ast::ModuleItem::cast)
82 .map(|it| it.syntax().owned())
83 .for_each(|it| {
84 res.alloc(it);
85 });
86 Arc::new(res)
87}
88
89pub(crate) fn file_item(
90 db: &impl DescriptorDatabase,
91 file_id: FileId,
92 file_item_id: FileItemId,
93) -> SyntaxNode {
94 db._file_items(file_id)[file_item_id].clone()
95}
96
97/// Item map is the result of the name resolution. Item map contains, for each
98/// module, the set of visible items.
99#[derive(Default, Debug, PartialEq, Eq)]
100pub(crate) struct ItemMap {
101 pub(crate) per_module: FxHashMap<ModuleId, ModuleScope>,
102}
103
104#[derive(Debug, Default, PartialEq, Eq, Clone)]
105pub(crate) struct ModuleScope {
106 items: FxHashMap<SmolStr, Resolution>,
107}
108
109impl ModuleScope {
110 pub(crate) fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a SmolStr, &Resolution)> + 'a {
111 self.items.iter()
112 }
113 pub(crate) fn get(&self, name: &SmolStr) -> Option<&Resolution> {
114 self.items.get(name)
115 }
116}
117
118/// A set of items and imports declared inside a module, without relation to
119/// other modules.
120///
121/// This stands in-between raw syntax and name resolution and alow us to avoid
122/// recomputing name res: if `InputModuleItems` are the same, we can avoid
123/// running name resolution.
124#[derive(Debug, Default, PartialEq, Eq)]
125pub(crate) struct InputModuleItems {
126 items: Vec<ModuleItem>,
127 imports: Vec<Import>,
128}
129
130#[derive(Debug, PartialEq, Eq)]
131struct ModuleItem {
132 id: FileItemId,
133 name: SmolStr,
134 kind: SyntaxKind,
135 vis: Vis,
136}
137
138#[derive(Debug, PartialEq, Eq)]
139enum Vis {
140 // Priv,
141 Other,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
145struct Import {
146 path: Path,
147 kind: ImportKind,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub(crate) struct NamedImport {
152 file_item_id: FileItemId,
153 relative_range: TextRange,
154}
155
156impl NamedImport {
157 pub(crate) fn range(&self, db: &impl DescriptorDatabase, file_id: FileId) -> TextRange {
158 let syntax = db._file_item(file_id, self.file_item_id);
159 let offset = syntax.borrowed().range().start();
160 self.relative_range + offset
161 }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
165enum ImportKind {
166 Glob,
167 Named(NamedImport),
168}
169
170pub(crate) fn input_module_items(
171 db: &impl DescriptorDatabase,
172 source_root: SourceRootId,
173 module_id: ModuleId,
174) -> Cancelable<Arc<InputModuleItems>> {
175 let module_tree = db._module_tree(source_root)?;
176 let source = module_id.source(&module_tree);
177 let file_items = db._file_items(source.file_id());
178 let res = match source.resolve(db) {
179 ModuleSourceNode::SourceFile(it) => {
180 let items = it.borrowed().items();
181 InputModuleItems::new(&file_items, items)
182 }
183 ModuleSourceNode::Module(it) => {
184 let items = it
185 .borrowed()
186 .item_list()
187 .into_iter()
188 .flat_map(|it| it.items());
189 InputModuleItems::new(&file_items, items)
190 }
191 };
192 Ok(Arc::new(res))
193}
194
195pub(crate) fn item_map(
196 db: &impl DescriptorDatabase,
197 source_root: SourceRootId,
198) -> Cancelable<Arc<ItemMap>> {
199 let start = Instant::now();
200 let module_tree = db._module_tree(source_root)?;
201 let input = module_tree
202 .modules()
203 .map(|id| {
204 let items = db._input_module_items(source_root, id)?;
205 Ok((id, items))
206 })
207 .collect::<Cancelable<FxHashMap<_, _>>>()?;
208 let mut resolver = Resolver {
209 db: db,
210 input: &input,
211 source_root,
212 module_tree,
213 result: ItemMap::default(),
214 };
215 resolver.resolve()?;
216 let res = resolver.result;
217 let elapsed = start.elapsed();
218 log::info!("item_map: {:?}", elapsed);
219 Ok(Arc::new(res))
220}
221
222/// Resolution is basically `DefId` atm, but it should account for stuff like
223/// multiple namespaces, ambiguity and errors.
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub(crate) struct Resolution {
226 /// None for unresolved
227 pub(crate) def_id: Option<DefId>,
228 /// ident by whitch this is imported into local scope.
229 pub(crate) import: Option<NamedImport>,
230}
231
232// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
233// enum Namespace {
234// Types,
235// Values,
236// }
237
238// #[derive(Debug)]
239// struct PerNs<T> {
240// types: Option<T>,
241// values: Option<T>,
242// }
243
244impl InputModuleItems {
245 fn new<'a>(
246 file_items: &FileItems,
247 items: impl Iterator<Item = ast::ModuleItem<'a>>,
248 ) -> InputModuleItems {
249 let mut res = InputModuleItems::default();
250 for item in items {
251 res.add_item(file_items, item);
252 }
253 res
254 }
255
256 fn add_item(&mut self, file_items: &FileItems, item: ast::ModuleItem) -> Option<()> {
257 match item {
258 ast::ModuleItem::StructDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
259 ast::ModuleItem::EnumDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
260 ast::ModuleItem::FnDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
261 ast::ModuleItem::TraitDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
262 ast::ModuleItem::TypeDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
263 ast::ModuleItem::ImplItem(_) => {
264 // impls don't define items
265 }
266 ast::ModuleItem::UseItem(it) => self.add_use_item(file_items, it),
267 ast::ModuleItem::ExternCrateItem(_) => {
268 // TODO
269 }
270 ast::ModuleItem::ConstDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
271 ast::ModuleItem::StaticDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
272 ast::ModuleItem::Module(it) => self.items.push(ModuleItem::new(file_items, it)?),
273 }
274 Some(())
275 }
276
277 fn add_use_item(&mut self, file_items: &FileItems, item: ast::UseItem) {
278 let file_item_id = file_items.id_of(item.syntax());
279 let start_offset = item.syntax().range().start();
280 Path::expand_use_item(item, |path, range| {
281 let kind = match range {
282 None => ImportKind::Glob,
283 Some(range) => ImportKind::Named(NamedImport {
284 file_item_id,
285 relative_range: range - start_offset,
286 }),
287 };
288 self.imports.push(Import { kind, path })
289 })
290 }
291}
292
293impl ModuleItem {
294 fn new<'a>(file_items: &FileItems, item: impl ast::NameOwner<'a>) -> Option<ModuleItem> {
295 let name = item.name()?.text();
296 let kind = item.syntax().kind();
297 let vis = Vis::Other;
298 let id = file_items.id_of(item.syntax());
299 let res = ModuleItem {
300 id,
301 name,
302 kind,
303 vis,
304 };
305 Some(res)
306 }
307}
308
309struct Resolver<'a, DB> {
310 db: &'a DB,
311 input: &'a FxHashMap<ModuleId, Arc<InputModuleItems>>,
312 source_root: SourceRootId,
313 module_tree: Arc<ModuleTree>,
314 result: ItemMap,
315}
316
317impl<'a, DB> Resolver<'a, DB>
318where
319 DB: DescriptorDatabase,
320{
321 fn resolve(&mut self) -> Cancelable<()> {
322 for (&module_id, items) in self.input.iter() {
323 self.populate_module(module_id, items)
324 }
325
326 for &module_id in self.input.keys() {
327 crate::db::check_canceled(self.db)?;
328 self.resolve_imports(module_id);
329 }
330 Ok(())
331 }
332
333 fn populate_module(&mut self, module_id: ModuleId, input: &InputModuleItems) {
334 let file_id = module_id.source(&self.module_tree).file_id();
335
336 let mut module_items = ModuleScope::default();
337
338 for import in input.imports.iter() {
339 if let Some(name) = import.path.segments.iter().last() {
340 if let ImportKind::Named(import) = import.kind {
341 module_items.items.insert(
342 name.clone(),
343 Resolution {
344 def_id: None,
345 import: Some(import),
346 },
347 );
348 }
349 }
350 }
351
352 for item in input.items.iter() {
353 if item.kind == MODULE {
354 // handle submodules separatelly
355 continue;
356 }
357 let def_loc = DefLoc::Item {
358 file_id,
359 id: item.id,
360 };
361 let def_id = self.db.id_maps().def_id(def_loc);
362 let resolution = Resolution {
363 def_id: Some(def_id),
364 import: None,
365 };
366 module_items.items.insert(item.name.clone(), resolution);
367 }
368
369 for (name, mod_id) in module_id.children(&self.module_tree) {
370 let def_loc = DefLoc::Module {
371 id: mod_id,
372 source_root: self.source_root,
373 };
374 let def_id = self.db.id_maps().def_id(def_loc);
375 let resolution = Resolution {
376 def_id: Some(def_id),
377 import: None,
378 };
379 module_items.items.insert(name, resolution);
380 }
381
382 self.result.per_module.insert(module_id, module_items);
383 }
384
385 fn resolve_imports(&mut self, module_id: ModuleId) {
386 for import in self.input[&module_id].imports.iter() {
387 self.resolve_import(module_id, import);
388 }
389 }
390
391 fn resolve_import(&mut self, module_id: ModuleId, import: &Import) {
392 let ptr = match import.kind {
393 ImportKind::Glob => return,
394 ImportKind::Named(ptr) => ptr,
395 };
396
397 let mut curr = match import.path.kind {
398 // TODO: handle extern crates
399 PathKind::Plain => return,
400 PathKind::Self_ => module_id,
401 PathKind::Super => {
402 match module_id.parent(&self.module_tree) {
403 Some(it) => it,
404 // TODO: error
405 None => return,
406 }
407 }
408 PathKind::Crate => module_id.crate_root(&self.module_tree),
409 };
410
411 for (i, name) in import.path.segments.iter().enumerate() {
412 let is_last = i == import.path.segments.len() - 1;
413
414 let def_id = match self.result.per_module[&curr].items.get(name) {
415 None => return,
416 Some(res) => match res.def_id {
417 Some(it) => it,
418 None => return,
419 },
420 };
421
422 if !is_last {
423 curr = match self.db.id_maps().def_loc(def_id) {
424 DefLoc::Module { id, .. } => id,
425 _ => return,
426 }
427 } else {
428 self.update(module_id, |items| {
429 let res = Resolution {
430 def_id: Some(def_id),
431 import: Some(ptr),
432 };
433 items.items.insert(name.clone(), res);
434 })
435 }
436 }
437 }
438
439 fn update(&mut self, module_id: ModuleId, f: impl FnOnce(&mut ModuleScope)) {
440 let module_items = self.result.per_module.get_mut(&module_id).unwrap();
441 f(module_items)
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use crate::{
448 AnalysisChange,
449 mock_analysis::{MockAnalysis, analysis_and_position},
450 hir::{DescriptorDatabase, module::ModuleDescriptor},
451 input::FilesDatabase,
452};
453 use super::*;
454
455 fn item_map(fixture: &str) -> (Arc<ItemMap>, ModuleId) {
456 let (analysis, pos) = analysis_and_position(fixture);
457 let db = analysis.imp.db;
458 let source_root = db.file_source_root(pos.file_id);
459 let descr = ModuleDescriptor::guess_from_position(&*db, pos)
460 .unwrap()
461 .unwrap();
462 let module_id = descr.module_id;
463 (db._item_map(source_root).unwrap(), module_id)
464 }
465
466 #[test]
467 fn test_item_map() {
468 let (item_map, module_id) = item_map(
469 "
470 //- /lib.rs
471 mod foo;
472
473 use crate::foo::bar::Baz;
474 <|>
475
476 //- /foo/mod.rs
477 pub mod bar;
478
479 //- /foo/bar.rs
480 pub struct Baz;
481 ",
482 );
483 let name = SmolStr::from("Baz");
484 let resolution = &item_map.per_module[&module_id].items[&name];
485 assert!(resolution.def_id.is_some());
486 }
487
488 #[test]
489 fn typing_inside_a_function_should_not_invalidate_item_map() {
490 let mock_analysis = MockAnalysis::with_files(
491 "
492 //- /lib.rs
493 mod foo;
494
495 use crate::foo::bar::Baz;
496
497 fn foo() -> i32 {
498 1 + 1
499 }
500 //- /foo/mod.rs
501 pub mod bar;
502
503 //- /foo/bar.rs
504 pub struct Baz;
505 ",
506 );
507
508 let file_id = mock_analysis.id_of("/lib.rs");
509 let mut host = mock_analysis.analysis_host();
510
511 let source_root = host.analysis().imp.db.file_source_root(file_id);
512
513 {
514 let db = host.analysis().imp.db;
515 let events = db.log_executed(|| {
516 db._item_map(source_root).unwrap();
517 });
518 assert!(format!("{:?}", events).contains("_item_map"))
519 }
520
521 let mut change = AnalysisChange::new();
522
523 change.change_file(
524 file_id,
525 "
526 mod foo;
527
528 use crate::foo::bar::Baz;
529
530 fn foo() -> i32 { 92 }
531 "
532 .to_string(),
533 );
534
535 host.apply_change(change);
536
537 {
538 let db = host.analysis().imp.db;
539 let events = db.log_executed(|| {
540 db._item_map(source_root).unwrap();
541 });
542 assert!(
543 !format!("{:?}", events).contains("_item_map"),
544 "{:#?}",
545 events
546 )
547 }
548 }
549}