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