diff options
author | Aleksey Kladov <[email protected]> | 2018-10-23 17:15:31 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-10-23 18:44:23 +0100 |
commit | dc477db757247d5184250bffe9dd0c38dd867778 (patch) | |
tree | 1eebf0ff17462a44668f95b3e042de09d09985d0 /crates/ra_analysis/src/descriptors/module | |
parent | 1d574ed6543936af7d1d16c4b4ea9b4bd858aa41 (diff) |
Introduce ModuleId
Previously, module was synonym with a file, and so a module could have
had several parents. This commit introduces a separate module concept,
such that each module has only one parent, but a single file can
correspond to different modules.
Diffstat (limited to 'crates/ra_analysis/src/descriptors/module')
-rw-r--r-- | crates/ra_analysis/src/descriptors/module/imp.rs | 146 | ||||
-rw-r--r-- | crates/ra_analysis/src/descriptors/module/mod.rs | 176 |
2 files changed, 322 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/descriptors/module/imp.rs b/crates/ra_analysis/src/descriptors/module/imp.rs new file mode 100644 index 000000000..22e4bd785 --- /dev/null +++ b/crates/ra_analysis/src/descriptors/module/imp.rs | |||
@@ -0,0 +1,146 @@ | |||
1 | use std::sync::Arc; | ||
2 | |||
3 | use relative_path::RelativePathBuf; | ||
4 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
5 | use ra_syntax::{ | ||
6 | SmolStr, | ||
7 | ast::{self, NameOwner}, | ||
8 | }; | ||
9 | |||
10 | use crate::{ | ||
11 | FileId, Cancelable, FileResolverImp, | ||
12 | db, | ||
13 | }; | ||
14 | |||
15 | use super::{ | ||
16 | ModuleData, ModuleTree, ModuleId, LinkId, LinkData, Problem, ModulesDatabase | ||
17 | }; | ||
18 | |||
19 | |||
20 | pub(super) fn submodules(db: &impl ModulesDatabase, file_id: FileId) -> Cancelable<Arc<Vec<SmolStr>>> { | ||
21 | db::check_canceled(db)?; | ||
22 | let file = db.file_syntax(file_id); | ||
23 | let root = file.ast(); | ||
24 | let submodules = modules(root).map(|(name, _)| name).collect(); | ||
25 | Ok(Arc::new(submodules)) | ||
26 | } | ||
27 | |||
28 | pub(super) fn modules(root: ast::Root<'_>) -> impl Iterator<Item = (SmolStr, ast::Module<'_>)> { | ||
29 | root.modules().filter_map(|module| { | ||
30 | let name = module.name()?.text(); | ||
31 | if !module.has_semi() { | ||
32 | return None; | ||
33 | } | ||
34 | Some((name, module)) | ||
35 | }) | ||
36 | } | ||
37 | |||
38 | pub(super) fn module_tree(db: &impl ModulesDatabase) -> Cancelable<Arc<ModuleTree>> { | ||
39 | db::check_canceled(db)?; | ||
40 | let res = create_module_tree(db)?; | ||
41 | Ok(Arc::new(res)) | ||
42 | } | ||
43 | |||
44 | |||
45 | #[derive(Clone, Hash, PartialEq, Eq, Debug)] | ||
46 | pub struct Submodule { | ||
47 | pub name: SmolStr, | ||
48 | } | ||
49 | |||
50 | |||
51 | fn create_module_tree<'a>( | ||
52 | db: &impl ModulesDatabase, | ||
53 | ) -> Cancelable<ModuleTree> { | ||
54 | let mut tree = ModuleTree { | ||
55 | mods: Vec::new(), | ||
56 | links: Vec::new(), | ||
57 | }; | ||
58 | |||
59 | let mut roots = FxHashMap::default(); | ||
60 | let mut visited = FxHashSet::default(); | ||
61 | |||
62 | for &file_id in db.file_set().files.iter() { | ||
63 | if visited.contains(&file_id) { | ||
64 | continue; // TODO: use explicit crate_roots here | ||
65 | } | ||
66 | assert!(!roots.contains_key(&file_id)); | ||
67 | let module_id = build_subtree(db, &mut tree, &mut visited, &mut roots, None, file_id)?; | ||
68 | roots.insert(file_id, module_id); | ||
69 | } | ||
70 | Ok(tree) | ||
71 | } | ||
72 | |||
73 | fn build_subtree( | ||
74 | db: &impl ModulesDatabase, | ||
75 | tree: &mut ModuleTree, | ||
76 | visited: &mut FxHashSet<FileId>, | ||
77 | roots: &mut FxHashMap<FileId, ModuleId>, | ||
78 | parent: Option<LinkId>, | ||
79 | file_id: FileId, | ||
80 | ) -> Cancelable<ModuleId> { | ||
81 | visited.insert(file_id); | ||
82 | let id = tree.push_mod(ModuleData { | ||
83 | file_id, | ||
84 | parent, | ||
85 | children: Vec::new(), | ||
86 | }); | ||
87 | let file_set = db.file_set(); | ||
88 | let file_resolver = &file_set.resolver; | ||
89 | for name in db.submodules(file_id)?.iter() { | ||
90 | let (points_to, problem) = resolve_submodule(file_id, name, file_resolver); | ||
91 | let link = tree.push_link(LinkData { | ||
92 | name: name.clone(), | ||
93 | owner: id, | ||
94 | points_to: Vec::new(), | ||
95 | problem: None, | ||
96 | }); | ||
97 | |||
98 | let points_to = points_to | ||
99 | .into_iter() | ||
100 | .map(|file_id| match roots.remove(&file_id) { | ||
101 | Some(module_id) => { | ||
102 | tree.module_mut(module_id).parent = Some(link); | ||
103 | Ok(module_id) | ||
104 | } | ||
105 | None => build_subtree(db, tree, visited, roots, Some(link), file_id), | ||
106 | }) | ||
107 | .collect::<Cancelable<Vec<_>>>()?; | ||
108 | tree.link_mut(link).points_to = points_to; | ||
109 | tree.link_mut(link).problem = problem; | ||
110 | } | ||
111 | Ok(id) | ||
112 | } | ||
113 | |||
114 | fn resolve_submodule( | ||
115 | file_id: FileId, | ||
116 | name: &SmolStr, | ||
117 | file_resolver: &FileResolverImp, | ||
118 | ) -> (Vec<FileId>, Option<Problem>) { | ||
119 | let mod_name = file_resolver.file_stem(file_id); | ||
120 | let is_dir_owner = mod_name == "mod" || mod_name == "lib" || mod_name == "main"; | ||
121 | |||
122 | let file_mod = RelativePathBuf::from(format!("../{}.rs", name)); | ||
123 | let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name)); | ||
124 | let points_to: Vec<FileId>; | ||
125 | let problem: Option<Problem>; | ||
126 | if is_dir_owner { | ||
127 | points_to = [&file_mod, &dir_mod] | ||
128 | .iter() | ||
129 | .filter_map(|path| file_resolver.resolve(file_id, path)) | ||
130 | .collect(); | ||
131 | problem = if points_to.is_empty() { | ||
132 | Some(Problem::UnresolvedModule { | ||
133 | candidate: file_mod, | ||
134 | }) | ||
135 | } else { | ||
136 | None | ||
137 | } | ||
138 | } else { | ||
139 | points_to = Vec::new(); | ||
140 | problem = Some(Problem::NotDirOwner { | ||
141 | move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)), | ||
142 | candidate: file_mod, | ||
143 | }); | ||
144 | } | ||
145 | (points_to, problem) | ||
146 | } | ||
diff --git a/crates/ra_analysis/src/descriptors/module/mod.rs b/crates/ra_analysis/src/descriptors/module/mod.rs new file mode 100644 index 000000000..52da650b3 --- /dev/null +++ b/crates/ra_analysis/src/descriptors/module/mod.rs | |||
@@ -0,0 +1,176 @@ | |||
1 | mod imp; | ||
2 | |||
3 | use std::sync::Arc; | ||
4 | |||
5 | use relative_path::RelativePathBuf; | ||
6 | use ra_syntax::{ast::{self, NameOwner, AstNode}, SmolStr, SyntaxNode}; | ||
7 | |||
8 | use crate::{ | ||
9 | FileId, Cancelable, | ||
10 | db::SyntaxDatabase, | ||
11 | }; | ||
12 | |||
13 | salsa::query_group! { | ||
14 | pub(crate) trait ModulesDatabase: SyntaxDatabase { | ||
15 | fn module_tree() -> Cancelable<Arc<ModuleTree>> { | ||
16 | type ModuleTreeQuery; | ||
17 | use fn imp::module_tree; | ||
18 | } | ||
19 | fn submodules(file_id: FileId) -> Cancelable<Arc<Vec<SmolStr>>> { | ||
20 | type SubmodulesQuery; | ||
21 | use fn imp::submodules; | ||
22 | } | ||
23 | } | ||
24 | } | ||
25 | |||
26 | |||
27 | #[derive(Debug, PartialEq, Eq, Hash)] | ||
28 | pub(crate) struct ModuleTree { | ||
29 | mods: Vec<ModuleData>, | ||
30 | links: Vec<LinkData>, | ||
31 | } | ||
32 | |||
33 | impl ModuleTree { | ||
34 | pub(crate) fn modules_for_file(&self, file_id: FileId) -> Vec<ModuleId> { | ||
35 | self.mods.iter() | ||
36 | .enumerate() | ||
37 | .filter(|(_idx, it)| it.file_id == file_id).map(|(idx, _)| ModuleId(idx as u32)) | ||
38 | .collect() | ||
39 | } | ||
40 | |||
41 | pub(crate) fn any_module_for_file(&self, file_id: FileId) -> Option<ModuleId> { | ||
42 | self.modules_for_file(file_id).pop() | ||
43 | } | ||
44 | } | ||
45 | |||
46 | #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] | ||
47 | pub(crate) struct ModuleId(u32); | ||
48 | |||
49 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] | ||
50 | pub(crate) struct LinkId(u32); | ||
51 | |||
52 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] | ||
53 | pub enum Problem { | ||
54 | UnresolvedModule { | ||
55 | candidate: RelativePathBuf, | ||
56 | }, | ||
57 | NotDirOwner { | ||
58 | move_to: RelativePathBuf, | ||
59 | candidate: RelativePathBuf, | ||
60 | }, | ||
61 | } | ||
62 | |||
63 | impl ModuleId { | ||
64 | pub(crate) fn file_id(self, tree: &ModuleTree) -> FileId { | ||
65 | tree.module(self).file_id | ||
66 | } | ||
67 | pub(crate) fn parent_link(self, tree: &ModuleTree) -> Option<LinkId> { | ||
68 | tree.module(self).parent | ||
69 | } | ||
70 | pub(crate) fn parent(self, tree: &ModuleTree) -> Option<ModuleId> { | ||
71 | let link = self.parent_link(tree)?; | ||
72 | Some(tree.link(link).owner) | ||
73 | } | ||
74 | pub(crate) fn root(self, tree: &ModuleTree) -> ModuleId { | ||
75 | let mut curr = self; | ||
76 | let mut i = 0; | ||
77 | while let Some(next) = curr.parent(tree) { | ||
78 | curr = next; | ||
79 | i += 1; | ||
80 | if i > 100 { | ||
81 | return self; | ||
82 | } | ||
83 | } | ||
84 | curr | ||
85 | } | ||
86 | pub(crate) fn child(self, tree: &ModuleTree, name: &str) -> Option<ModuleId> { | ||
87 | let link = tree.module(self) | ||
88 | .children | ||
89 | .iter() | ||
90 | .map(|&it| tree.link(it)) | ||
91 | .find(|it| it.name == name)?; | ||
92 | Some(*link.points_to.first()?) | ||
93 | } | ||
94 | pub(crate) fn problems( | ||
95 | self, | ||
96 | tree: &ModuleTree, | ||
97 | root: ast::Root, | ||
98 | ) -> Vec<(SyntaxNode, Problem)> { | ||
99 | tree.module(self) | ||
100 | .children | ||
101 | .iter() | ||
102 | .filter_map(|&it| { | ||
103 | let p = tree.link(it).problem.clone()?; | ||
104 | let s = it.bind_source(tree, root); | ||
105 | let s = s.name().unwrap().syntax().owned(); | ||
106 | Some((s, p)) | ||
107 | }) | ||
108 | .collect() | ||
109 | } | ||
110 | } | ||
111 | |||
112 | impl LinkId { | ||
113 | pub(crate) fn name(self, tree: &ModuleTree) -> SmolStr { | ||
114 | tree.link(self).name.clone() | ||
115 | } | ||
116 | pub(crate) fn owner(self, tree: &ModuleTree) -> ModuleId { | ||
117 | tree.link(self).owner | ||
118 | } | ||
119 | fn points_to(self, tree: &ModuleTree) -> &[ModuleId] { | ||
120 | &tree.link(self).points_to | ||
121 | } | ||
122 | pub(crate) fn bind_source<'a>( | ||
123 | self, | ||
124 | tree: &ModuleTree, | ||
125 | root: ast::Root<'a>, | ||
126 | ) -> ast::Module<'a> { | ||
127 | imp::modules(root) | ||
128 | .find(|(name, _)| name == &tree.link(self).name) | ||
129 | .unwrap() | ||
130 | .1 | ||
131 | } | ||
132 | } | ||
133 | |||
134 | #[derive(Debug, PartialEq, Eq, Hash)] | ||
135 | struct ModuleData { | ||
136 | file_id: FileId, | ||
137 | parent: Option<LinkId>, | ||
138 | children: Vec<LinkId>, | ||
139 | } | ||
140 | |||
141 | #[derive(Hash, Debug, PartialEq, Eq)] | ||
142 | struct LinkData { | ||
143 | owner: ModuleId, | ||
144 | name: SmolStr, | ||
145 | points_to: Vec<ModuleId>, | ||
146 | problem: Option<Problem>, | ||
147 | } | ||
148 | |||
149 | |||
150 | impl ModuleTree { | ||
151 | fn module(&self, id: ModuleId) -> &ModuleData { | ||
152 | &self.mods[id.0 as usize] | ||
153 | } | ||
154 | fn module_mut(&mut self, id: ModuleId) -> &mut ModuleData { | ||
155 | &mut self.mods[id.0 as usize] | ||
156 | } | ||
157 | fn link(&self, id: LinkId) -> &LinkData { | ||
158 | &self.links[id.0 as usize] | ||
159 | } | ||
160 | fn link_mut(&mut self, id: LinkId) -> &mut LinkData { | ||
161 | &mut self.links[id.0 as usize] | ||
162 | } | ||
163 | |||
164 | fn push_mod(&mut self, data: ModuleData) -> ModuleId { | ||
165 | let id = ModuleId(self.mods.len() as u32); | ||
166 | self.mods.push(data); | ||
167 | id | ||
168 | } | ||
169 | fn push_link(&mut self, data: LinkData) -> LinkId { | ||
170 | let id = LinkId(self.links.len() as u32); | ||
171 | self.mods[data.owner.0 as usize].children.push(id); | ||
172 | self.links.push(data); | ||
173 | id | ||
174 | } | ||
175 | } | ||
176 | |||