aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir/src/module_tree.rs
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-01-06 14:51:10 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-01-06 14:51:10 +0000
commitcf0ce14351af03c620aca784ee2c03aad86b866e (patch)
treebffd84981df9cca1143807796dc6772ddcfe8e0b /crates/ra_hir/src/module_tree.rs
parenteaf553dade9a28b41631387d7c88b09fd0ba64e2 (diff)
parent733383446fc229a35d4432d14c295c5a01e5a87f (diff)
Merge #429
429: Reorganize hir public API in terms of code model r=matklad a=matklad Recently, I've been thinking about introducing "object orient code model" API for rust: a set of APIs with types like `Function`, `Module`, etc, with methods like `get_containing_declaration()`, `get_type()`, etc. Here's how a similar API might look like in .Net land: https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.semanticmodel?view=roslyn-dotnet https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.imethodsymbol?view=roslyn-dotnet The main feature of such API is that it can be powered by different backends. For example, one can imagine a backend based on salsa, and a backend which reads all the data from a specially prepared JSON file. The "OO" bit is interesting mostly in this "can swap implementations via dynamic dispatch" aspect, the actual API could have a more database/ECS flavored feeling. It's not clear at this moment how exactly should we implement such a dynamically (or if we even need dynamism in the first pace) swapable API in Rust, but I'd love to experiment with this a bit. For starters, I propose creating a `code_model_api` which contains various definition types and their public methods (mandatory implemented as one-liners, so that the API has a header-file feel). Specifically, I propose that each type is a wrapper around some integer ID, and that all methods of it accept a `&db` argument. The actual impl goes elsewhere: into the db queries or, absent a better place, into the `code_model_api_impl`. In the first commit, I've moved the simplest type, `Crate`, over to this pattern. I *think* that we, at least initially, will be used types from `code_model_api` *inside* `hir` as well, but this is not required: we might pick a different implementation down the line, while preserving the API. Long term I'd love to replace the `db: &impl HirDatabase` argument by a `mp: &dyn ModelProvider`, implement `ModelProvider` for `T: HirDatabase`, and move `code_model_api` into the separate crate, which does not depend on `hir`. @flodiebold you've recently done some `Def`s work, would do you think of this plan? Could it become a good API in the future, or is it just a useless boilerplate duplicating method signatures between `code_model_api` and `code_model_impl`? Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_hir/src/module_tree.rs')
-rw-r--r--crates/ra_hir/src/module_tree.rs409
1 files changed, 409 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..b7912ba5e
--- /dev/null
+++ b/crates/ra_hir/src/module_tree.rs
@@ -0,0 +1,409 @@
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 pub(crate) fn submodules_query(
24 db: &impl HirDatabase,
25 source: ModuleSource,
26 ) -> Cancelable<Arc<Vec<Submodule>>> {
27 db.check_canceled()?;
28 let file_id = source.file_id();
29 let submodules = match source.resolve(db) {
30 ModuleSourceNode::SourceFile(it) => collect_submodules(db, file_id, it.borrowed()),
31 ModuleSourceNode::Module(it) => it
32 .borrowed()
33 .item_list()
34 .map(|it| collect_submodules(db, file_id, it))
35 .unwrap_or_else(Vec::new),
36 };
37 return Ok(Arc::new(submodules));
38
39 fn collect_submodules<'a>(
40 db: &impl HirDatabase,
41 file_id: HirFileId,
42 root: impl ast::ModuleItemOwner<'a>,
43 ) -> Vec<Submodule> {
44 modules(root)
45 .map(|(name, m)| {
46 if m.has_semi() {
47 Submodule::Declaration(name)
48 } else {
49 let src = ModuleSource::new_inline(db, file_id, m);
50 Submodule::Definition(name, src)
51 }
52 })
53 .collect()
54 }
55 }
56
57 fn name(&self) -> &Name {
58 match self {
59 Submodule::Declaration(name) => name,
60 Submodule::Definition(name, _) => name,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
66pub struct ModuleId(RawId);
67impl_arena_id!(ModuleId);
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
70pub struct LinkId(RawId);
71impl_arena_id!(LinkId);
72
73/// Physically, rust source is organized as a set of files, but logically it is
74/// organized as a tree of modules. Usually, a single file corresponds to a
75/// single module, but it is not nessary the case.
76///
77/// Module encapsulate the logic of transitioning from the fuzzy world of files
78/// (which can have multiple parents) to the precise world of modules (which
79/// always have one parent).
80#[derive(Default, Debug, PartialEq, Eq)]
81pub struct ModuleTree {
82 mods: Arena<ModuleId, ModuleData>,
83 links: Arena<LinkId, LinkData>,
84}
85
86#[derive(Debug, PartialEq, Eq, Hash)]
87pub struct ModuleData {
88 source: ModuleSource,
89 parent: Option<LinkId>,
90 children: Vec<LinkId>,
91}
92
93#[derive(Hash, Debug, PartialEq, Eq)]
94struct LinkData {
95 owner: ModuleId,
96 name: Name,
97 points_to: Vec<ModuleId>,
98 problem: Option<Problem>,
99}
100
101impl ModuleTree {
102 pub(crate) fn module_tree_query(
103 db: &impl HirDatabase,
104 source_root: SourceRootId,
105 ) -> Cancelable<Arc<ModuleTree>> {
106 db.check_canceled()?;
107 let res = create_module_tree(db, source_root)?;
108 Ok(Arc::new(res))
109 }
110
111 pub(crate) fn modules<'a>(&'a self) -> impl Iterator<Item = ModuleId> + 'a {
112 self.mods.iter().map(|(id, _)| id)
113 }
114
115 pub(crate) fn modules_with_sources<'a>(
116 &'a self,
117 ) -> impl Iterator<Item = (ModuleId, ModuleSource)> + 'a {
118 self.mods.iter().map(|(id, m)| (id, m.source))
119 }
120}
121
122/// `ModuleSource` is the syntax tree element that produced this module:
123/// either a file, or an inlinde module.
124#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
125pub struct ModuleSource(pub(crate) SourceItemId);
126
127/// An owned syntax node for a module. Unlike `ModuleSource`,
128/// this holds onto the AST for the whole file.
129pub(crate) enum ModuleSourceNode {
130 SourceFile(ast::SourceFileNode),
131 Module(ast::ModuleNode),
132}
133
134impl ModuleId {
135 pub(crate) fn source(self, tree: &ModuleTree) -> ModuleSource {
136 tree.mods[self].source
137 }
138 pub(crate) fn parent_link(self, tree: &ModuleTree) -> Option<LinkId> {
139 tree.mods[self].parent
140 }
141 pub(crate) fn parent(self, tree: &ModuleTree) -> Option<ModuleId> {
142 let link = self.parent_link(tree)?;
143 Some(tree.links[link].owner)
144 }
145 pub(crate) fn crate_root(self, tree: &ModuleTree) -> ModuleId {
146 generate(Some(self), move |it| it.parent(tree))
147 .last()
148 .unwrap()
149 }
150 pub(crate) fn child(self, tree: &ModuleTree, name: &Name) -> Option<ModuleId> {
151 let link = tree.mods[self]
152 .children
153 .iter()
154 .map(|&it| &tree.links[it])
155 .find(|it| it.name == *name)?;
156 Some(*link.points_to.first()?)
157 }
158 pub(crate) fn children<'a>(
159 self,
160 tree: &'a ModuleTree,
161 ) -> impl Iterator<Item = (Name, ModuleId)> + 'a {
162 tree.mods[self].children.iter().filter_map(move |&it| {
163 let link = &tree.links[it];
164 let module = *link.points_to.first()?;
165 Some((link.name.clone(), module))
166 })
167 }
168 pub(crate) fn problems(
169 self,
170 tree: &ModuleTree,
171 db: &impl HirDatabase,
172 ) -> Vec<(SyntaxNode, Problem)> {
173 tree.mods[self]
174 .children
175 .iter()
176 .filter_map(|&it| {
177 let p = tree.links[it].problem.clone()?;
178 let s = it.bind_source(tree, db);
179 let s = s.borrowed().name().unwrap().syntax().owned();
180 Some((s, p))
181 })
182 .collect()
183 }
184}
185
186impl LinkId {
187 pub(crate) fn owner(self, tree: &ModuleTree) -> ModuleId {
188 tree.links[self].owner
189 }
190 pub(crate) fn name(self, tree: &ModuleTree) -> &Name {
191 &tree.links[self].name
192 }
193 pub(crate) fn bind_source<'a>(
194 self,
195 tree: &ModuleTree,
196 db: &impl HirDatabase,
197 ) -> ast::ModuleNode {
198 let owner = self.owner(tree);
199 match owner.source(tree).resolve(db) {
200 ModuleSourceNode::SourceFile(root) => {
201 let ast = modules(root.borrowed())
202 .find(|(name, _)| name == &tree.links[self].name)
203 .unwrap()
204 .1;
205 ast.owned()
206 }
207 ModuleSourceNode::Module(it) => it,
208 }
209 }
210}
211
212impl ModuleSource {
213 // precondition: item_id **must** point to module
214 fn new(file_id: HirFileId, item_id: Option<SourceFileItemId>) -> ModuleSource {
215 let source_item_id = SourceItemId { file_id, item_id };
216 ModuleSource(source_item_id)
217 }
218
219 pub(crate) fn new_file(file_id: HirFileId) -> ModuleSource {
220 ModuleSource::new(file_id, None)
221 }
222
223 pub(crate) fn new_inline(
224 db: &impl HirDatabase,
225 file_id: HirFileId,
226 m: ast::Module,
227 ) -> ModuleSource {
228 assert!(!m.has_semi());
229 let file_items = db.file_items(file_id);
230 let item_id = file_items.id_of(file_id, m.syntax());
231 ModuleSource::new(file_id, Some(item_id))
232 }
233
234 pub(crate) fn file_id(self) -> HirFileId {
235 self.0.file_id
236 }
237
238 pub(crate) fn resolve(self, db: &impl HirDatabase) -> ModuleSourceNode {
239 let syntax_node = db.file_item(self.0);
240 let syntax_node = syntax_node.borrowed();
241 if let Some(file) = ast::SourceFile::cast(syntax_node) {
242 return ModuleSourceNode::SourceFile(file.owned());
243 }
244 let module = ast::Module::cast(syntax_node).unwrap();
245 ModuleSourceNode::Module(module.owned())
246 }
247}
248
249impl ModuleTree {
250 fn push_mod(&mut self, data: ModuleData) -> ModuleId {
251 self.mods.alloc(data)
252 }
253 fn push_link(&mut self, data: LinkData) -> LinkId {
254 let owner = data.owner;
255 let id = self.links.alloc(data);
256 self.mods[owner].children.push(id);
257 id
258 }
259}
260
261fn modules<'a>(
262 root: impl ast::ModuleItemOwner<'a>,
263) -> impl Iterator<Item = (Name, ast::Module<'a>)> {
264 root.items()
265 .filter_map(|item| match item {
266 ast::ModuleItem::Module(m) => Some(m),
267 _ => None,
268 })
269 .filter_map(|module| {
270 let name = module.name()?.as_name();
271 Some((name, module))
272 })
273}
274
275fn create_module_tree<'a>(
276 db: &impl HirDatabase,
277 source_root: SourceRootId,
278) -> Cancelable<ModuleTree> {
279 let mut tree = ModuleTree::default();
280
281 let mut roots = FxHashMap::default();
282 let mut visited = FxHashSet::default();
283
284 let source_root = db.source_root(source_root);
285 for &file_id in source_root.files.values() {
286 let source = ModuleSource::new_file(file_id.into());
287 if visited.contains(&source) {
288 continue; // TODO: use explicit crate_roots here
289 }
290 assert!(!roots.contains_key(&file_id));
291 let module_id = build_subtree(
292 db,
293 &source_root,
294 &mut tree,
295 &mut visited,
296 &mut roots,
297 None,
298 source,
299 )?;
300 roots.insert(file_id, module_id);
301 }
302 Ok(tree)
303}
304
305fn build_subtree(
306 db: &impl HirDatabase,
307 source_root: &SourceRoot,
308 tree: &mut ModuleTree,
309 visited: &mut FxHashSet<ModuleSource>,
310 roots: &mut FxHashMap<FileId, ModuleId>,
311 parent: Option<LinkId>,
312 source: ModuleSource,
313) -> Cancelable<ModuleId> {
314 visited.insert(source);
315 let id = tree.push_mod(ModuleData {
316 source,
317 parent,
318 children: Vec::new(),
319 });
320 for sub in db.submodules(source)?.iter() {
321 let link = tree.push_link(LinkData {
322 name: sub.name().clone(),
323 owner: id,
324 points_to: Vec::new(),
325 problem: None,
326 });
327
328 let (points_to, problem) = match sub {
329 Submodule::Declaration(name) => {
330 let (points_to, problem) = resolve_submodule(db, source, &name);
331 let points_to = points_to
332 .into_iter()
333 .map(|file_id| match roots.remove(&file_id) {
334 Some(module_id) => {
335 tree.mods[module_id].parent = Some(link);
336 Ok(module_id)
337 }
338 None => build_subtree(
339 db,
340 source_root,
341 tree,
342 visited,
343 roots,
344 Some(link),
345 ModuleSource::new_file(file_id.into()),
346 ),
347 })
348 .collect::<Cancelable<Vec<_>>>()?;
349 (points_to, problem)
350 }
351 Submodule::Definition(_name, submodule_source) => {
352 let points_to = build_subtree(
353 db,
354 source_root,
355 tree,
356 visited,
357 roots,
358 Some(link),
359 *submodule_source,
360 )?;
361 (vec![points_to], None)
362 }
363 };
364
365 tree.links[link].points_to = points_to;
366 tree.links[link].problem = problem;
367 }
368 Ok(id)
369}
370
371fn resolve_submodule(
372 db: &impl HirDatabase,
373 source: ModuleSource,
374 name: &Name,
375) -> (Vec<FileId>, Option<Problem>) {
376 // FIXME: handle submodules of inline modules properly
377 let file_id = source.file_id().original_file(db);
378 let source_root_id = db.file_source_root(file_id);
379 let path = db.file_relative_path(file_id);
380 let root = RelativePathBuf::default();
381 let dir_path = path.parent().unwrap_or(&root);
382 let mod_name = path.file_stem().unwrap_or("unknown");
383 let is_dir_owner = mod_name == "mod" || mod_name == "lib" || mod_name == "main";
384
385 let file_mod = dir_path.join(format!("{}.rs", name));
386 let dir_mod = dir_path.join(format!("{}/mod.rs", name));
387 let file_dir_mod = dir_path.join(format!("{}/{}.rs", mod_name, name));
388 let mut candidates = ArrayVec::<[_; 2]>::new();
389 if is_dir_owner {
390 candidates.push(file_mod.clone());
391 candidates.push(dir_mod);
392 } else {
393 candidates.push(file_dir_mod.clone());
394 };
395 let sr = db.source_root(source_root_id);
396 let points_to = candidates
397 .into_iter()
398 .filter_map(|path| sr.files.get(&path))
399 .map(|&it| it)
400 .collect::<Vec<_>>();
401 let problem = if points_to.is_empty() {
402 Some(Problem::UnresolvedModule {
403 candidate: if is_dir_owner { file_mod } else { file_dir_mod },
404 })
405 } else {
406 None
407 };
408 (points_to, problem)
409}