aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir/src/module
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir/src/module')
-rw-r--r--crates/ra_hir/src/module/imp.rs194
-rw-r--r--crates/ra_hir/src/module/mod.rs373
-rw-r--r--crates/ra_hir/src/module/nameres.rs434
3 files changed, 1001 insertions, 0 deletions
diff --git a/crates/ra_hir/src/module/imp.rs b/crates/ra_hir/src/module/imp.rs
new file mode 100644
index 000000000..76ea129a7
--- /dev/null
+++ b/crates/ra_hir/src/module/imp.rs
@@ -0,0 +1,194 @@
1use std::sync::Arc;
2
3use ra_syntax::{
4 ast::{self, NameOwner},
5 SmolStr,
6};
7use relative_path::RelativePathBuf;
8use rustc_hash::{FxHashMap, FxHashSet};
9use ra_db::{SourceRoot, SourceRootId, FileResolverImp, Cancelable, FileId,};
10
11use crate::{
12 HirDatabase,
13};
14
15use super::{
16 LinkData, LinkId, ModuleData, ModuleId, ModuleSource,
17 ModuleTree, Problem,
18};
19
20#[derive(Clone, Hash, PartialEq, Eq, Debug)]
21pub enum Submodule {
22 Declaration(SmolStr),
23 Definition(SmolStr, ModuleSource),
24}
25
26impl Submodule {
27 fn name(&self) -> &SmolStr {
28 match self {
29 Submodule::Declaration(name) => name,
30 Submodule::Definition(name, _) => name,
31 }
32 }
33}
34
35pub(crate) fn modules<'a>(
36 root: impl ast::ModuleItemOwner<'a>,
37) -> impl Iterator<Item = (SmolStr, ast::Module<'a>)> {
38 root.items()
39 .filter_map(|item| match item {
40 ast::ModuleItem::Module(m) => Some(m),
41 _ => None,
42 })
43 .filter_map(|module| {
44 let name = module.name()?.text();
45 Some((name, module))
46 })
47}
48
49pub(crate) fn module_tree(
50 db: &impl HirDatabase,
51 source_root: SourceRootId,
52) -> Cancelable<Arc<ModuleTree>> {
53 db.check_canceled()?;
54 let res = create_module_tree(db, source_root)?;
55 Ok(Arc::new(res))
56}
57
58fn create_module_tree<'a>(
59 db: &impl HirDatabase,
60 source_root: SourceRootId,
61) -> Cancelable<ModuleTree> {
62 let mut tree = ModuleTree::default();
63
64 let mut roots = FxHashMap::default();
65 let mut visited = FxHashSet::default();
66
67 let source_root = db.source_root(source_root);
68 for &file_id in source_root.files.iter() {
69 let source = ModuleSource::SourceFile(file_id);
70 if visited.contains(&source) {
71 continue; // TODO: use explicit crate_roots here
72 }
73 assert!(!roots.contains_key(&file_id));
74 let module_id = build_subtree(
75 db,
76 &source_root,
77 &mut tree,
78 &mut visited,
79 &mut roots,
80 None,
81 source,
82 )?;
83 roots.insert(file_id, module_id);
84 }
85 Ok(tree)
86}
87
88fn build_subtree(
89 db: &impl HirDatabase,
90 source_root: &SourceRoot,
91 tree: &mut ModuleTree,
92 visited: &mut FxHashSet<ModuleSource>,
93 roots: &mut FxHashMap<FileId, ModuleId>,
94 parent: Option<LinkId>,
95 source: ModuleSource,
96) -> Cancelable<ModuleId> {
97 visited.insert(source);
98 let id = tree.push_mod(ModuleData {
99 source,
100 parent,
101 children: Vec::new(),
102 });
103 for sub in db.submodules(source)?.iter() {
104 let link = tree.push_link(LinkData {
105 name: sub.name().clone(),
106 owner: id,
107 points_to: Vec::new(),
108 problem: None,
109 });
110
111 let (points_to, problem) = match sub {
112 Submodule::Declaration(name) => {
113 let (points_to, problem) =
114 resolve_submodule(source, &name, &source_root.file_resolver);
115 let points_to = points_to
116 .into_iter()
117 .map(|file_id| match roots.remove(&file_id) {
118 Some(module_id) => {
119 tree.mods[module_id].parent = Some(link);
120 Ok(module_id)
121 }
122 None => build_subtree(
123 db,
124 source_root,
125 tree,
126 visited,
127 roots,
128 Some(link),
129 ModuleSource::SourceFile(file_id),
130 ),
131 })
132 .collect::<Cancelable<Vec<_>>>()?;
133 (points_to, problem)
134 }
135 Submodule::Definition(_name, submodule_source) => {
136 let points_to = build_subtree(
137 db,
138 source_root,
139 tree,
140 visited,
141 roots,
142 Some(link),
143 *submodule_source,
144 )?;
145 (vec![points_to], None)
146 }
147 };
148
149 tree.links[link].points_to = points_to;
150 tree.links[link].problem = problem;
151 }
152 Ok(id)
153}
154
155fn resolve_submodule(
156 source: ModuleSource,
157 name: &SmolStr,
158 file_resolver: &FileResolverImp,
159) -> (Vec<FileId>, Option<Problem>) {
160 let file_id = match source {
161 ModuleSource::SourceFile(it) => it,
162 ModuleSource::Module(..) => {
163 // TODO
164 return (Vec::new(), None);
165 }
166 };
167 let mod_name = file_resolver.file_stem(file_id);
168 let is_dir_owner = mod_name == "mod" || mod_name == "lib" || mod_name == "main";
169
170 let file_mod = RelativePathBuf::from(format!("../{}.rs", name));
171 let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name));
172 let points_to: Vec<FileId>;
173 let problem: Option<Problem>;
174 if is_dir_owner {
175 points_to = [&file_mod, &dir_mod]
176 .iter()
177 .filter_map(|path| file_resolver.resolve(file_id, path))
178 .collect();
179 problem = if points_to.is_empty() {
180 Some(Problem::UnresolvedModule {
181 candidate: file_mod,
182 })
183 } else {
184 None
185 }
186 } else {
187 points_to = Vec::new();
188 problem = Some(Problem::NotDirOwner {
189 move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)),
190 candidate: file_mod,
191 });
192 }
193 (points_to, problem)
194}
diff --git a/crates/ra_hir/src/module/mod.rs b/crates/ra_hir/src/module/mod.rs
new file mode 100644
index 000000000..3ae83d8cb
--- /dev/null
+++ b/crates/ra_hir/src/module/mod.rs
@@ -0,0 +1,373 @@
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 ra_db::{SourceRootId, FileId, FilePosition, Cancelable};
14use relative_path::RelativePathBuf;
15
16use crate::{
17 DefLoc, DefId, Path, PathKind, HirDatabase, SourceItemId,
18 arena::{Arena, Id},
19};
20
21pub use self::nameres::ModuleScope;
22
23/// `Module` is API entry point to get all the information
24/// about a particular module.
25#[derive(Debug, Clone)]
26pub struct Module {
27 tree: Arc<ModuleTree>,
28 source_root_id: SourceRootId,
29 module_id: ModuleId,
30}
31
32impl Module {
33 /// Lookup `Module` by `FileId`. Note that this is inherently
34 /// lossy transformation: in general, a single source might correspond to
35 /// several modules.
36 pub fn guess_from_file_id(
37 db: &impl HirDatabase,
38 file_id: FileId,
39 ) -> Cancelable<Option<Module>> {
40 Module::guess_from_source(db, file_id, ModuleSource::SourceFile(file_id))
41 }
42
43 /// Lookup `Module` by position in the source code. Note that this
44 /// is inherently lossy transformation: in general, a single source might
45 /// correspond to several modules.
46 pub fn guess_from_position(
47 db: &impl HirDatabase,
48 position: FilePosition,
49 ) -> Cancelable<Option<Module>> {
50 let file = db.source_file(position.file_id);
51 let module_source = match find_node_at_offset::<ast::Module>(file.syntax(), position.offset)
52 {
53 Some(m) if !m.has_semi() => ModuleSource::new_inline(db, position.file_id, m),
54 _ => ModuleSource::SourceFile(position.file_id),
55 };
56 Module::guess_from_source(db, position.file_id, module_source)
57 }
58
59 fn guess_from_source(
60 db: &impl HirDatabase,
61 file_id: FileId,
62 module_source: ModuleSource,
63 ) -> Cancelable<Option<Module>> {
64 let source_root_id = db.file_source_root(file_id);
65 let module_tree = db.module_tree(source_root_id)?;
66
67 let res = match module_tree.any_module_for_source(module_source) {
68 None => None,
69 Some(module_id) => Some(Module {
70 tree: module_tree,
71 source_root_id,
72 module_id,
73 }),
74 };
75 Ok(res)
76 }
77
78 pub(super) fn new(
79 db: &impl HirDatabase,
80 source_root_id: SourceRootId,
81 module_id: ModuleId,
82 ) -> Cancelable<Module> {
83 let module_tree = db.module_tree(source_root_id)?;
84 let res = Module {
85 tree: module_tree,
86 source_root_id,
87 module_id,
88 };
89 Ok(res)
90 }
91
92 /// Returns `mod foo;` or `mod foo {}` node whihc declared this module.
93 /// Returns `None` for the root module
94 pub fn parent_link_source(&self, db: &impl HirDatabase) -> Option<(FileId, ast::ModuleNode)> {
95 let link = self.module_id.parent_link(&self.tree)?;
96 let file_id = link.owner(&self.tree).source(&self.tree).file_id();
97 let src = link.bind_source(&self.tree, db);
98 Some((file_id, src))
99 }
100
101 pub fn source(&self) -> ModuleSource {
102 self.module_id.source(&self.tree)
103 }
104
105 /// Parent module. Returns `None` if this is a root module.
106 pub fn parent(&self) -> Option<Module> {
107 let parent_id = self.module_id.parent(&self.tree)?;
108 Some(Module {
109 module_id: parent_id,
110 ..self.clone()
111 })
112 }
113
114 /// The root of the tree this module is part of
115 pub fn crate_root(&self) -> Module {
116 let root_id = self.module_id.crate_root(&self.tree);
117 Module {
118 module_id: root_id,
119 ..self.clone()
120 }
121 }
122
123 /// `name` is `None` for the crate's root module
124 pub fn name(&self) -> Option<SmolStr> {
125 let link = self.module_id.parent_link(&self.tree)?;
126 Some(link.name(&self.tree))
127 }
128
129 pub fn def_id(&self, db: &impl HirDatabase) -> DefId {
130 let def_loc = DefLoc::Module {
131 id: self.module_id,
132 source_root: self.source_root_id,
133 };
134 def_loc.id(db)
135 }
136
137 /// Finds a child module with the specified name.
138 pub fn child(&self, name: &str) -> Option<Module> {
139 let child_id = self.module_id.child(&self.tree, name)?;
140 Some(Module {
141 module_id: child_id,
142 ..self.clone()
143 })
144 }
145
146 /// Returns a `ModuleScope`: a set of items, visible in this module.
147 pub fn scope(&self, db: &impl HirDatabase) -> Cancelable<ModuleScope> {
148 let item_map = db.item_map(self.source_root_id)?;
149 let res = item_map.per_module[&self.module_id].clone();
150 Ok(res)
151 }
152
153 pub fn resolve_path(&self, db: &impl HirDatabase, path: Path) -> Cancelable<Option<DefId>> {
154 let mut curr = match path.kind {
155 PathKind::Crate => self.crate_root(),
156 PathKind::Self_ | PathKind::Plain => self.clone(),
157 PathKind::Super => ctry!(self.parent()),
158 }
159 .def_id(db);
160
161 let segments = path.segments;
162 for name in segments.iter() {
163 let module = match curr.loc(db) {
164 DefLoc::Module { id, source_root } => Module::new(db, source_root, id)?,
165 _ => return Ok(None),
166 };
167 let scope = module.scope(db)?;
168 curr = ctry!(ctry!(scope.get(&name)).def_id);
169 }
170 Ok(Some(curr))
171 }
172
173 pub fn problems(&self, db: &impl HirDatabase) -> Vec<(SyntaxNode, Problem)> {
174 self.module_id.problems(&self.tree, db)
175 }
176}
177
178/// Phisically, rust source is organized as a set of files, but logically it is
179/// organized as a tree of modules. Usually, a single file corresponds to a
180/// single module, but it is not nessary the case.
181///
182/// Module encapsulate the logic of transitioning from the fuzzy world of files
183/// (which can have multiple parents) to the precise world of modules (which
184/// always have one parent).
185#[derive(Default, Debug, PartialEq, Eq)]
186pub struct ModuleTree {
187 mods: Arena<ModuleData>,
188 links: Arena<LinkData>,
189}
190
191impl ModuleTree {
192 pub(crate) fn modules<'a>(&'a self) -> impl Iterator<Item = ModuleId> + 'a {
193 self.mods.iter().map(|(id, _)| id)
194 }
195
196 fn modules_for_source(&self, source: ModuleSource) -> Vec<ModuleId> {
197 self.mods
198 .iter()
199 .filter(|(_idx, it)| it.source == source)
200 .map(|(idx, _)| idx)
201 .collect()
202 }
203
204 fn any_module_for_source(&self, source: ModuleSource) -> Option<ModuleId> {
205 self.modules_for_source(source).pop()
206 }
207}
208
209/// `ModuleSource` is the syntax tree element that produced this module:
210/// either a file, or an inlinde module.
211#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
212pub enum ModuleSource {
213 SourceFile(FileId),
214 Module(SourceItemId),
215}
216
217/// An owned syntax node for a module. Unlike `ModuleSource`,
218/// this holds onto the AST for the whole file.
219pub(crate) enum ModuleSourceNode {
220 SourceFile(ast::SourceFileNode),
221 Module(ast::ModuleNode),
222}
223
224pub type ModuleId = Id<ModuleData>;
225type LinkId = Id<LinkData>;
226
227#[derive(Clone, Debug, Hash, PartialEq, Eq)]
228pub enum Problem {
229 UnresolvedModule {
230 candidate: RelativePathBuf,
231 },
232 NotDirOwner {
233 move_to: RelativePathBuf,
234 candidate: RelativePathBuf,
235 },
236}
237
238impl ModuleId {
239 pub(crate) fn source(self, tree: &ModuleTree) -> ModuleSource {
240 tree.mods[self].source
241 }
242 fn parent_link(self, tree: &ModuleTree) -> Option<LinkId> {
243 tree.mods[self].parent
244 }
245 fn parent(self, tree: &ModuleTree) -> Option<ModuleId> {
246 let link = self.parent_link(tree)?;
247 Some(tree.links[link].owner)
248 }
249 fn crate_root(self, tree: &ModuleTree) -> ModuleId {
250 generate(Some(self), move |it| it.parent(tree))
251 .last()
252 .unwrap()
253 }
254 fn child(self, tree: &ModuleTree, name: &str) -> Option<ModuleId> {
255 let link = tree.mods[self]
256 .children
257 .iter()
258 .map(|&it| &tree.links[it])
259 .find(|it| it.name == name)?;
260 Some(*link.points_to.first()?)
261 }
262 fn children<'a>(self, tree: &'a ModuleTree) -> impl Iterator<Item = (SmolStr, ModuleId)> + 'a {
263 tree.mods[self].children.iter().filter_map(move |&it| {
264 let link = &tree.links[it];
265 let module = *link.points_to.first()?;
266 Some((link.name.clone(), module))
267 })
268 }
269 fn problems(self, tree: &ModuleTree, db: &impl HirDatabase) -> Vec<(SyntaxNode, Problem)> {
270 tree.mods[self]
271 .children
272 .iter()
273 .filter_map(|&it| {
274 let p = tree.links[it].problem.clone()?;
275 let s = it.bind_source(tree, db);
276 let s = s.borrowed().name().unwrap().syntax().owned();
277 Some((s, p))
278 })
279 .collect()
280 }
281}
282
283impl LinkId {
284 fn owner(self, tree: &ModuleTree) -> ModuleId {
285 tree.links[self].owner
286 }
287 fn name(self, tree: &ModuleTree) -> SmolStr {
288 tree.links[self].name.clone()
289 }
290 fn bind_source<'a>(self, tree: &ModuleTree, db: &impl HirDatabase) -> ast::ModuleNode {
291 let owner = self.owner(tree);
292 match owner.source(tree).resolve(db) {
293 ModuleSourceNode::SourceFile(root) => {
294 let ast = imp::modules(root.borrowed())
295 .find(|(name, _)| name == &tree.links[self].name)
296 .unwrap()
297 .1;
298 ast.owned()
299 }
300 ModuleSourceNode::Module(it) => it,
301 }
302 }
303}
304
305#[derive(Debug, PartialEq, Eq, Hash)]
306pub struct ModuleData {
307 source: ModuleSource,
308 parent: Option<LinkId>,
309 children: Vec<LinkId>,
310}
311
312impl ModuleSource {
313 pub(crate) fn new_inline(
314 db: &impl HirDatabase,
315 file_id: FileId,
316 module: ast::Module,
317 ) -> ModuleSource {
318 assert!(!module.has_semi());
319 let items = db.file_items(file_id);
320 let item_id = items.id_of(module.syntax());
321 let id = SourceItemId { file_id, item_id };
322 ModuleSource::Module(id)
323 }
324
325 pub fn as_file(self) -> Option<FileId> {
326 match self {
327 ModuleSource::SourceFile(f) => Some(f),
328 ModuleSource::Module(..) => None,
329 }
330 }
331
332 pub fn file_id(self) -> FileId {
333 match self {
334 ModuleSource::SourceFile(f) => f,
335 ModuleSource::Module(source_item_id) => source_item_id.file_id,
336 }
337 }
338
339 pub(crate) fn resolve(self, db: &impl HirDatabase) -> ModuleSourceNode {
340 match self {
341 ModuleSource::SourceFile(file_id) => {
342 let syntax = db.source_file(file_id);
343 ModuleSourceNode::SourceFile(syntax.ast().owned())
344 }
345 ModuleSource::Module(item_id) => {
346 let syntax = db.file_item(item_id);
347 let syntax = syntax.borrowed();
348 let module = ast::Module::cast(syntax).unwrap();
349 ModuleSourceNode::Module(module.owned())
350 }
351 }
352 }
353}
354
355#[derive(Hash, Debug, PartialEq, Eq)]
356struct LinkData {
357 owner: ModuleId,
358 name: SmolStr,
359 points_to: Vec<ModuleId>,
360 problem: Option<Problem>,
361}
362
363impl ModuleTree {
364 fn push_mod(&mut self, data: ModuleData) -> ModuleId {
365 self.mods.alloc(data)
366 }
367 fn push_link(&mut self, data: LinkData) -> LinkId {
368 let owner = data.owner;
369 let id = self.links.alloc(data);
370 self.mods[owner].children.push(id);
371 id
372 }
373}
diff --git a/crates/ra_hir/src/module/nameres.rs b/crates/ra_hir/src/module/nameres.rs
new file mode 100644
index 000000000..8529e16b3
--- /dev/null
+++ b/crates/ra_hir/src/module/nameres.rs
@@ -0,0 +1,434 @@
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};
20
21use rustc_hash::FxHashMap;
22use ra_syntax::{
23 TextRange,
24 SmolStr, SyntaxKind::{self, *},
25 ast::{self, AstNode}
26};
27use ra_db::SourceRootId;
28
29use crate::{
30 Cancelable, FileId,
31 DefId, DefLoc,
32 SourceItemId, SourceFileItemId, SourceFileItems,
33 Path, PathKind,
34 HirDatabase,
35 module::{ModuleId, ModuleTree},
36};
37
38/// Item map is the result of the name resolution. Item map contains, for each
39/// module, the set of visible items.
40#[derive(Default, Debug, PartialEq, Eq)]
41pub struct ItemMap {
42 pub per_module: FxHashMap<ModuleId, ModuleScope>,
43}
44
45#[derive(Debug, Default, PartialEq, Eq, Clone)]
46pub struct ModuleScope {
47 items: FxHashMap<SmolStr, Resolution>,
48}
49
50impl ModuleScope {
51 pub fn entries<'a>(&'a self) -> impl Iterator<Item = (&'a SmolStr, &Resolution)> + 'a {
52 self.items.iter()
53 }
54 pub fn get(&self, name: &SmolStr) -> Option<&Resolution> {
55 self.items.get(name)
56 }
57}
58
59/// A set of items and imports declared inside a module, without relation to
60/// other modules.
61///
62/// This stands in-between raw syntax and name resolution and alow us to avoid
63/// recomputing name res: if `InputModuleItems` are the same, we can avoid
64/// running name resolution.
65#[derive(Debug, Default, PartialEq, Eq)]
66pub struct InputModuleItems {
67 items: Vec<ModuleItem>,
68 imports: Vec<Import>,
69}
70
71#[derive(Debug, PartialEq, Eq)]
72struct ModuleItem {
73 id: SourceFileItemId,
74 name: SmolStr,
75 kind: SyntaxKind,
76 vis: Vis,
77}
78
79#[derive(Debug, PartialEq, Eq)]
80enum Vis {
81 // Priv,
82 Other,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
86struct Import {
87 path: Path,
88 kind: ImportKind,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct NamedImport {
93 pub file_item_id: SourceFileItemId,
94 pub relative_range: TextRange,
95}
96
97impl NamedImport {
98 pub fn range(&self, db: &impl HirDatabase, file_id: FileId) -> TextRange {
99 let source_item_id = SourceItemId {
100 file_id,
101 item_id: self.file_item_id,
102 };
103 let syntax = db.file_item(source_item_id);
104 let offset = syntax.borrowed().range().start();
105 self.relative_range + offset
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
110enum ImportKind {
111 Glob,
112 Named(NamedImport),
113}
114
115/// Resolution is basically `DefId` atm, but it should account for stuff like
116/// multiple namespaces, ambiguity and errors.
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct Resolution {
119 /// None for unresolved
120 pub def_id: Option<DefId>,
121 /// ident by whitch this is imported into local scope.
122 pub import: Option<NamedImport>,
123}
124
125// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
126// enum Namespace {
127// Types,
128// Values,
129// }
130
131// #[derive(Debug)]
132// struct PerNs<T> {
133// types: Option<T>,
134// values: Option<T>,
135// }
136
137impl InputModuleItems {
138 pub(crate) fn new<'a>(
139 file_items: &SourceFileItems,
140 items: impl Iterator<Item = ast::ModuleItem<'a>>,
141 ) -> InputModuleItems {
142 let mut res = InputModuleItems::default();
143 for item in items {
144 res.add_item(file_items, item);
145 }
146 res
147 }
148
149 fn add_item(&mut self, file_items: &SourceFileItems, item: ast::ModuleItem) -> Option<()> {
150 match item {
151 ast::ModuleItem::StructDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
152 ast::ModuleItem::EnumDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
153 ast::ModuleItem::FnDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
154 ast::ModuleItem::TraitDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
155 ast::ModuleItem::TypeDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
156 ast::ModuleItem::ImplItem(_) => {
157 // impls don't define items
158 }
159 ast::ModuleItem::UseItem(it) => self.add_use_item(file_items, it),
160 ast::ModuleItem::ExternCrateItem(_) => {
161 // TODO
162 }
163 ast::ModuleItem::ConstDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
164 ast::ModuleItem::StaticDef(it) => self.items.push(ModuleItem::new(file_items, it)?),
165 ast::ModuleItem::Module(it) => self.items.push(ModuleItem::new(file_items, it)?),
166 }
167 Some(())
168 }
169
170 fn add_use_item(&mut self, file_items: &SourceFileItems, item: ast::UseItem) {
171 let file_item_id = file_items.id_of(item.syntax());
172 let start_offset = item.syntax().range().start();
173 Path::expand_use_item(item, |path, range| {
174 let kind = match range {
175 None => ImportKind::Glob,
176 Some(range) => ImportKind::Named(NamedImport {
177 file_item_id,
178 relative_range: range - start_offset,
179 }),
180 };
181 self.imports.push(Import { kind, path })
182 })
183 }
184}
185
186impl ModuleItem {
187 fn new<'a>(file_items: &SourceFileItems, item: impl ast::NameOwner<'a>) -> Option<ModuleItem> {
188 let name = item.name()?.text();
189 let kind = item.syntax().kind();
190 let vis = Vis::Other;
191 let id = file_items.id_of(item.syntax());
192 let res = ModuleItem {
193 id,
194 name,
195 kind,
196 vis,
197 };
198 Some(res)
199 }
200}
201
202pub(crate) struct Resolver<'a, DB> {
203 pub(crate) db: &'a DB,
204 pub(crate) input: &'a FxHashMap<ModuleId, Arc<InputModuleItems>>,
205 pub(crate) source_root: SourceRootId,
206 pub(crate) module_tree: Arc<ModuleTree>,
207 pub(crate) result: ItemMap,
208}
209
210impl<'a, DB> Resolver<'a, DB>
211where
212 DB: HirDatabase,
213{
214 pub(crate) fn resolve(mut self) -> Cancelable<ItemMap> {
215 for (&module_id, items) in self.input.iter() {
216 self.populate_module(module_id, items)
217 }
218
219 for &module_id in self.input.keys() {
220 self.db.check_canceled()?;
221 self.resolve_imports(module_id);
222 }
223 Ok(self.result)
224 }
225
226 fn populate_module(&mut self, module_id: ModuleId, input: &InputModuleItems) {
227 let file_id = module_id.source(&self.module_tree).file_id();
228
229 let mut module_items = ModuleScope::default();
230
231 for import in input.imports.iter() {
232 if let Some(name) = import.path.segments.iter().last() {
233 if let ImportKind::Named(import) = import.kind {
234 module_items.items.insert(
235 name.clone(),
236 Resolution {
237 def_id: None,
238 import: Some(import),
239 },
240 );
241 }
242 }
243 }
244
245 for item in input.items.iter() {
246 if item.kind == MODULE {
247 // handle submodules separatelly
248 continue;
249 }
250 let def_loc = DefLoc::Item {
251 source_item_id: SourceItemId {
252 file_id,
253 item_id: item.id,
254 },
255 };
256 let def_id = def_loc.id(self.db);
257 let resolution = Resolution {
258 def_id: Some(def_id),
259 import: None,
260 };
261 module_items.items.insert(item.name.clone(), resolution);
262 }
263
264 for (name, mod_id) in module_id.children(&self.module_tree) {
265 let def_loc = DefLoc::Module {
266 id: mod_id,
267 source_root: self.source_root,
268 };
269 let def_id = def_loc.id(self.db);
270 let resolution = Resolution {
271 def_id: Some(def_id),
272 import: None,
273 };
274 module_items.items.insert(name, resolution);
275 }
276
277 self.result.per_module.insert(module_id, module_items);
278 }
279
280 fn resolve_imports(&mut self, module_id: ModuleId) {
281 for import in self.input[&module_id].imports.iter() {
282 self.resolve_import(module_id, import);
283 }
284 }
285
286 fn resolve_import(&mut self, module_id: ModuleId, import: &Import) {
287 let ptr = match import.kind {
288 ImportKind::Glob => return,
289 ImportKind::Named(ptr) => ptr,
290 };
291
292 let mut curr = match import.path.kind {
293 // TODO: handle extern crates
294 PathKind::Plain => return,
295 PathKind::Self_ => module_id,
296 PathKind::Super => {
297 match module_id.parent(&self.module_tree) {
298 Some(it) => it,
299 // TODO: error
300 None => return,
301 }
302 }
303 PathKind::Crate => module_id.crate_root(&self.module_tree),
304 };
305
306 for (i, name) in import.path.segments.iter().enumerate() {
307 let is_last = i == import.path.segments.len() - 1;
308
309 let def_id = match self.result.per_module[&curr].items.get(name) {
310 None => return,
311 Some(res) => match res.def_id {
312 Some(it) => it,
313 None => return,
314 },
315 };
316
317 if !is_last {
318 curr = match def_id.loc(self.db) {
319 DefLoc::Module { id, .. } => id,
320 _ => return,
321 }
322 } else {
323 self.update(module_id, |items| {
324 let res = Resolution {
325 def_id: Some(def_id),
326 import: Some(ptr),
327 };
328 items.items.insert(name.clone(), res);
329 })
330 }
331 }
332 }
333
334 fn update(&mut self, module_id: ModuleId, f: impl FnOnce(&mut ModuleScope)) {
335 let module_items = self.result.per_module.get_mut(&module_id).unwrap();
336 f(module_items)
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use std::sync::Arc;
343
344 use salsa::Database;
345 use ra_db::FilesDatabase;
346 use ra_syntax::SmolStr;
347
348 use crate::{
349 self as hir,
350 db::HirDatabase,
351 mock::MockDatabase,
352};
353
354 fn item_map(fixture: &str) -> (Arc<hir::ItemMap>, hir::ModuleId) {
355 let (db, pos) = MockDatabase::with_position(fixture);
356 let source_root = db.file_source_root(pos.file_id);
357 let module = hir::Module::guess_from_position(&db, pos).unwrap().unwrap();
358 let module_id = module.module_id;
359 (db.item_map(source_root).unwrap(), module_id)
360 }
361
362 #[test]
363 fn test_item_map() {
364 let (item_map, module_id) = item_map(
365 "
366 //- /lib.rs
367 mod foo;
368
369 use crate::foo::bar::Baz;
370 <|>
371
372 //- /foo/mod.rs
373 pub mod bar;
374
375 //- /foo/bar.rs
376 pub struct Baz;
377 ",
378 );
379 let name = SmolStr::from("Baz");
380 let resolution = &item_map.per_module[&module_id].items[&name];
381 assert!(resolution.def_id.is_some());
382 }
383
384 #[test]
385 fn typing_inside_a_function_should_not_invalidate_item_map() {
386 let (mut db, pos) = MockDatabase::with_position(
387 "
388 //- /lib.rs
389 mod foo;<|>
390
391 use crate::foo::bar::Baz;
392
393 fn foo() -> i32 {
394 1 + 1
395 }
396 //- /foo/mod.rs
397 pub mod bar;
398
399 //- /foo/bar.rs
400 pub struct Baz;
401 ",
402 );
403 let source_root = db.file_source_root(pos.file_id);
404 {
405 let events = db.log_executed(|| {
406 db.item_map(source_root).unwrap();
407 });
408 assert!(format!("{:?}", events).contains("item_map"))
409 }
410
411 let new_text = "
412 mod foo;
413
414 use crate::foo::bar::Baz;
415
416 fn foo() -> i32 { 92 }
417 "
418 .to_string();
419
420 db.query_mut(ra_db::FileTextQuery)
421 .set(pos.file_id, Arc::new(new_text));
422
423 {
424 let events = db.log_executed(|| {
425 db.item_map(source_root).unwrap();
426 });
427 assert!(
428 !format!("{:?}", events).contains("_item_map"),
429 "{:#?}",
430 events
431 )
432 }
433 }
434}