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