diff options
Diffstat (limited to 'crates/ra_hir/src/module_tree.rs')
-rw-r--r-- | crates/ra_hir/src/module_tree.rs | 331 |
1 files changed, 0 insertions, 331 deletions
diff --git a/crates/ra_hir/src/module_tree.rs b/crates/ra_hir/src/module_tree.rs index 99c2115e1..e69de29bb 100644 --- a/crates/ra_hir/src/module_tree.rs +++ b/crates/ra_hir/src/module_tree.rs | |||
@@ -1,331 +0,0 @@ | |||
1 | use std::sync::Arc; | ||
2 | |||
3 | use arrayvec::ArrayVec; | ||
4 | use relative_path::RelativePathBuf; | ||
5 | use ra_db::{FileId, SourceRoot}; | ||
6 | use ra_syntax::{ | ||
7 | SyntaxNode, TreeArc, | ||
8 | algo::generate, | ||
9 | ast::{self, AstNode, NameOwner}, | ||
10 | }; | ||
11 | use ra_arena::{Arena, RawId, impl_arena_id}; | ||
12 | use test_utils::tested_by; | ||
13 | |||
14 | use crate::{ | ||
15 | Name, AsName, HirDatabase, SourceItemId, HirFileId, Problem, SourceFileItems, ModuleSource, | ||
16 | PersistentHirDatabase, | ||
17 | Crate, | ||
18 | ids::SourceFileItemId, | ||
19 | }; | ||
20 | |||
21 | impl ModuleSource { | ||
22 | pub(crate) fn new( | ||
23 | db: &impl PersistentHirDatabase, | ||
24 | file_id: HirFileId, | ||
25 | decl_id: Option<SourceFileItemId>, | ||
26 | ) -> ModuleSource { | ||
27 | match decl_id { | ||
28 | Some(item_id) => { | ||
29 | let module = db.file_item(SourceItemId { file_id, item_id }); | ||
30 | let module = ast::Module::cast(&*module).unwrap(); | ||
31 | assert!(module.item_list().is_some(), "expected inline module"); | ||
32 | ModuleSource::Module(module.to_owned()) | ||
33 | } | ||
34 | None => { | ||
35 | let source_file = db.hir_parse(file_id); | ||
36 | ModuleSource::SourceFile(source_file) | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | } | ||
41 | |||
42 | #[derive(Clone, Hash, PartialEq, Eq, Debug)] | ||
43 | pub struct Submodule { | ||
44 | name: Name, | ||
45 | is_declaration: bool, | ||
46 | decl_id: SourceFileItemId, | ||
47 | } | ||
48 | |||
49 | impl Submodule { | ||
50 | pub(crate) fn submodules_query( | ||
51 | db: &impl PersistentHirDatabase, | ||
52 | file_id: HirFileId, | ||
53 | decl_id: Option<SourceFileItemId>, | ||
54 | ) -> Arc<Vec<Submodule>> { | ||
55 | db.check_canceled(); | ||
56 | let file_items = db.file_items(file_id); | ||
57 | let module_source = ModuleSource::new(db, file_id, decl_id); | ||
58 | let submodules = match module_source { | ||
59 | ModuleSource::SourceFile(source_file) => { | ||
60 | collect_submodules(file_id, &file_items, &*source_file) | ||
61 | } | ||
62 | ModuleSource::Module(module) => { | ||
63 | collect_submodules(file_id, &file_items, module.item_list().unwrap()) | ||
64 | } | ||
65 | }; | ||
66 | |||
67 | return Arc::new(submodules); | ||
68 | |||
69 | fn collect_submodules( | ||
70 | file_id: HirFileId, | ||
71 | file_items: &SourceFileItems, | ||
72 | root: &impl ast::ModuleItemOwner, | ||
73 | ) -> Vec<Submodule> { | ||
74 | root.items() | ||
75 | .filter_map(|item| match item.kind() { | ||
76 | ast::ModuleItemKind::Module(m) => Some(m), | ||
77 | _ => None, | ||
78 | }) | ||
79 | .filter_map(|module| { | ||
80 | let name = module.name()?.as_name(); | ||
81 | if !module.has_semi() && module.item_list().is_none() { | ||
82 | tested_by!(name_res_works_for_broken_modules); | ||
83 | return None; | ||
84 | } | ||
85 | let sub = Submodule { | ||
86 | name, | ||
87 | is_declaration: module.has_semi(), | ||
88 | decl_id: file_items.id_of(file_id, module.syntax()), | ||
89 | }; | ||
90 | Some(sub) | ||
91 | }) | ||
92 | .collect() | ||
93 | } | ||
94 | } | ||
95 | } | ||
96 | |||
97 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
98 | pub struct ModuleId(RawId); | ||
99 | impl_arena_id!(ModuleId); | ||
100 | |||
101 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
102 | pub struct LinkId(RawId); | ||
103 | impl_arena_id!(LinkId); | ||
104 | |||
105 | /// Physically, rust source is organized as a set of files, but logically it is | ||
106 | /// organized as a tree of modules. Usually, a single file corresponds to a | ||
107 | /// single module, but it is not neccessarily always the case. | ||
108 | /// | ||
109 | /// `ModuleTree` encapsulates the logic of transitioning from the fuzzy world of files | ||
110 | /// (which can have multiple parents) to the precise world of modules (which | ||
111 | /// always have one parent). | ||
112 | #[derive(Default, Debug, PartialEq, Eq)] | ||
113 | pub struct ModuleTree { | ||
114 | mods: Arena<ModuleId, ModuleData>, | ||
115 | links: Arena<LinkId, LinkData>, | ||
116 | } | ||
117 | |||
118 | #[derive(Debug, PartialEq, Eq)] | ||
119 | pub struct ModuleData { | ||
120 | file_id: HirFileId, | ||
121 | /// Points to `ast::Module`, `None` for the whole file. | ||
122 | decl_id: Option<SourceFileItemId>, | ||
123 | parent: Option<LinkId>, | ||
124 | children: Vec<LinkId>, | ||
125 | } | ||
126 | |||
127 | #[derive(Hash, Debug, PartialEq, Eq)] | ||
128 | struct LinkData { | ||
129 | source: SourceItemId, | ||
130 | owner: ModuleId, | ||
131 | name: Name, | ||
132 | points_to: Vec<ModuleId>, | ||
133 | problem: Option<Problem>, | ||
134 | } | ||
135 | |||
136 | impl ModuleTree { | ||
137 | pub(crate) fn module_tree_query( | ||
138 | db: &impl PersistentHirDatabase, | ||
139 | krate: Crate, | ||
140 | ) -> Arc<ModuleTree> { | ||
141 | db.check_canceled(); | ||
142 | let mut res = ModuleTree::default(); | ||
143 | res.init_crate(db, krate); | ||
144 | Arc::new(res) | ||
145 | } | ||
146 | |||
147 | pub(crate) fn modules<'a>(&'a self) -> impl Iterator<Item = ModuleId> + 'a { | ||
148 | self.mods.iter().map(|(id, _)| id) | ||
149 | } | ||
150 | |||
151 | pub(crate) fn find_module_by_source( | ||
152 | &self, | ||
153 | file_id: HirFileId, | ||
154 | decl_id: Option<SourceFileItemId>, | ||
155 | ) -> Option<ModuleId> { | ||
156 | let (res, _) = | ||
157 | self.mods.iter().find(|(_, m)| (m.file_id, m.decl_id) == (file_id, decl_id))?; | ||
158 | Some(res) | ||
159 | } | ||
160 | |||
161 | fn init_crate(&mut self, db: &impl PersistentHirDatabase, krate: Crate) { | ||
162 | let crate_graph = db.crate_graph(); | ||
163 | let file_id = crate_graph.crate_root(krate.crate_id); | ||
164 | let source_root_id = db.file_source_root(file_id); | ||
165 | |||
166 | let source_root = db.source_root(source_root_id); | ||
167 | self.init_subtree(db, &source_root, None, file_id.into(), None); | ||
168 | } | ||
169 | |||
170 | fn init_subtree( | ||
171 | &mut self, | ||
172 | db: &impl PersistentHirDatabase, | ||
173 | source_root: &SourceRoot, | ||
174 | parent: Option<LinkId>, | ||
175 | file_id: HirFileId, | ||
176 | decl_id: Option<SourceFileItemId>, | ||
177 | ) -> ModuleId { | ||
178 | let is_root = parent.is_none(); | ||
179 | let id = self.alloc_mod(ModuleData { file_id, decl_id, parent, children: Vec::new() }); | ||
180 | for sub in db.submodules(file_id, decl_id).iter() { | ||
181 | let link = self.alloc_link(LinkData { | ||
182 | source: SourceItemId { file_id, item_id: sub.decl_id }, | ||
183 | name: sub.name.clone(), | ||
184 | owner: id, | ||
185 | points_to: Vec::new(), | ||
186 | problem: None, | ||
187 | }); | ||
188 | |||
189 | let (points_to, problem) = if sub.is_declaration { | ||
190 | let (points_to, problem) = resolve_submodule(db, file_id, &sub.name, is_root); | ||
191 | let points_to = points_to | ||
192 | .into_iter() | ||
193 | .map(|file_id| { | ||
194 | self.init_subtree(db, source_root, Some(link), file_id.into(), None) | ||
195 | }) | ||
196 | .collect::<Vec<_>>(); | ||
197 | (points_to, problem) | ||
198 | } else { | ||
199 | let points_to = | ||
200 | self.init_subtree(db, source_root, Some(link), file_id, Some(sub.decl_id)); | ||
201 | (vec![points_to], None) | ||
202 | }; | ||
203 | |||
204 | self.links[link].points_to = points_to; | ||
205 | self.links[link].problem = problem; | ||
206 | } | ||
207 | id | ||
208 | } | ||
209 | |||
210 | fn alloc_mod(&mut self, data: ModuleData) -> ModuleId { | ||
211 | self.mods.alloc(data) | ||
212 | } | ||
213 | |||
214 | fn alloc_link(&mut self, data: LinkData) -> LinkId { | ||
215 | let owner = data.owner; | ||
216 | let id = self.links.alloc(data); | ||
217 | self.mods[owner].children.push(id); | ||
218 | id | ||
219 | } | ||
220 | } | ||
221 | |||
222 | impl ModuleId { | ||
223 | pub(crate) fn file_id(self, tree: &ModuleTree) -> HirFileId { | ||
224 | tree.mods[self].file_id | ||
225 | } | ||
226 | pub(crate) fn decl_id(self, tree: &ModuleTree) -> Option<SourceFileItemId> { | ||
227 | tree.mods[self].decl_id | ||
228 | } | ||
229 | pub(crate) fn parent_link(self, tree: &ModuleTree) -> Option<LinkId> { | ||
230 | tree.mods[self].parent | ||
231 | } | ||
232 | pub(crate) fn parent(self, tree: &ModuleTree) -> Option<ModuleId> { | ||
233 | let link = self.parent_link(tree)?; | ||
234 | Some(tree.links[link].owner) | ||
235 | } | ||
236 | pub(crate) fn crate_root(self, tree: &ModuleTree) -> ModuleId { | ||
237 | generate(Some(self), move |it| it.parent(tree)).last().unwrap() | ||
238 | } | ||
239 | pub(crate) fn child(self, tree: &ModuleTree, name: &Name) -> Option<ModuleId> { | ||
240 | let link = tree.mods[self] | ||
241 | .children | ||
242 | .iter() | ||
243 | .map(|&it| &tree.links[it]) | ||
244 | .find(|it| it.name == *name)?; | ||
245 | Some(*link.points_to.first()?) | ||
246 | } | ||
247 | pub(crate) fn children<'a>( | ||
248 | self, | ||
249 | tree: &'a ModuleTree, | ||
250 | ) -> impl Iterator<Item = (Name, ModuleId)> + 'a { | ||
251 | tree.mods[self].children.iter().filter_map(move |&it| { | ||
252 | let link = &tree.links[it]; | ||
253 | let module = *link.points_to.first()?; | ||
254 | Some((link.name.clone(), module)) | ||
255 | }) | ||
256 | } | ||
257 | pub(crate) fn problems( | ||
258 | self, | ||
259 | tree: &ModuleTree, | ||
260 | db: &impl HirDatabase, | ||
261 | ) -> Vec<(TreeArc<SyntaxNode>, Problem)> { | ||
262 | tree.mods[self] | ||
263 | .children | ||
264 | .iter() | ||
265 | .filter_map(|&link| { | ||
266 | let p = tree.links[link].problem.clone()?; | ||
267 | let s = link.source(tree, db); | ||
268 | let s = s.name().unwrap().syntax().to_owned(); | ||
269 | Some((s, p)) | ||
270 | }) | ||
271 | .collect() | ||
272 | } | ||
273 | } | ||
274 | |||
275 | impl LinkId { | ||
276 | pub(crate) fn owner(self, tree: &ModuleTree) -> ModuleId { | ||
277 | tree.links[self].owner | ||
278 | } | ||
279 | pub(crate) fn name(self, tree: &ModuleTree) -> &Name { | ||
280 | &tree.links[self].name | ||
281 | } | ||
282 | pub(crate) fn source( | ||
283 | self, | ||
284 | tree: &ModuleTree, | ||
285 | db: &impl PersistentHirDatabase, | ||
286 | ) -> TreeArc<ast::Module> { | ||
287 | let syntax_node = db.file_item(tree.links[self].source); | ||
288 | ast::Module::cast(&syntax_node).unwrap().to_owned() | ||
289 | } | ||
290 | } | ||
291 | |||
292 | fn resolve_submodule( | ||
293 | db: &impl PersistentHirDatabase, | ||
294 | file_id: HirFileId, | ||
295 | name: &Name, | ||
296 | is_root: bool, | ||
297 | ) -> (Vec<FileId>, Option<Problem>) { | ||
298 | // FIXME: handle submodules of inline modules properly | ||
299 | let file_id = file_id.original_file(db); | ||
300 | let source_root_id = db.file_source_root(file_id); | ||
301 | let path = db.file_relative_path(file_id); | ||
302 | let root = RelativePathBuf::default(); | ||
303 | let dir_path = path.parent().unwrap_or(&root); | ||
304 | let mod_name = path.file_stem().unwrap_or("unknown"); | ||
305 | let is_dir_owner = is_root || mod_name == "mod"; | ||
306 | |||
307 | let file_mod = dir_path.join(format!("{}.rs", name)); | ||
308 | let dir_mod = dir_path.join(format!("{}/mod.rs", name)); | ||
309 | let file_dir_mod = dir_path.join(format!("{}/{}.rs", mod_name, name)); | ||
310 | let mut candidates = ArrayVec::<[_; 2]>::new(); | ||
311 | if is_dir_owner { | ||
312 | candidates.push(file_mod.clone()); | ||
313 | candidates.push(dir_mod); | ||
314 | } else { | ||
315 | candidates.push(file_dir_mod.clone()); | ||
316 | }; | ||
317 | let sr = db.source_root(source_root_id); | ||
318 | let points_to = candidates | ||
319 | .into_iter() | ||
320 | .filter_map(|path| sr.files.get(&path)) | ||
321 | .map(|&it| it) | ||
322 | .collect::<Vec<_>>(); | ||
323 | let problem = if points_to.is_empty() { | ||
324 | Some(Problem::UnresolvedModule { | ||
325 | candidate: if is_dir_owner { file_mod } else { file_dir_mod }, | ||
326 | }) | ||
327 | } else { | ||
328 | None | ||
329 | }; | ||
330 | (points_to, problem) | ||
331 | } | ||