aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir/src/module/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir/src/module/mod.rs')
-rw-r--r--crates/ra_hir/src/module/mod.rs378
1 files changed, 378 insertions, 0 deletions
diff --git a/crates/ra_hir/src/module/mod.rs b/crates/ra_hir/src/module/mod.rs
new file mode 100644
index 000000000..81b9f948d
--- /dev/null
+++ b/crates/ra_hir/src/module/mod.rs
@@ -0,0 +1,378 @@
1pub(super) mod imp;
2pub(super) mod nameres;
3
4use std::sync::Arc;
5
6use ra_editor::find_node_at_offset;
7
8use ra_syntax::{
9 algo::generate,
10 ast::{self, AstNode, NameOwner},
11 SmolStr, SyntaxNode,
12};
13use ra_db::{SourceRootId, FileId, FilePosition, Cancelable};
14use relative_path::RelativePathBuf;
15
16use crate::{
17 DefLoc, DefId, Path, PathKind, HirDatabase, SourceItemId,
18 arena::{Arena, Id},
19};
20
21pub(crate) use self::nameres::ModuleScope;
22
23/// `Module` is API entry point to get all the information
24/// about a particular module.
25#[derive(Debug, Clone)]
26pub(crate) struct Module {
27 tree: Arc<ModuleTree>,
28 source_root_id: SourceRootId,
29 module_id: ModuleId,
30}
31
32impl Module {
33 /// Lookup `Module` by `FileId`. Note that this is inherently
34 /// lossy transformation: in general, a single source might correspond to
35 /// several modules.
36 pub fn guess_from_file_id(
37 db: &impl HirDatabase,
38 file_id: FileId,
39 ) -> Cancelable<Option<Module>> {
40 Module::guess_from_source(db, file_id, ModuleSource::SourceFile(file_id))
41 }
42
43 /// Lookup `Module` by position in the source code. Note that this
44 /// is inherently lossy transformation: in general, a single source might
45 /// correspond to several modules.
46 pub fn guess_from_position(
47 db: &impl HirDatabase,
48 position: FilePosition,
49 ) -> Cancelable<Option<Module>> {
50 let file = db.source_file(position.file_id);
51 let module_source = match find_node_at_offset::<ast::Module>(file.syntax(), position.offset)
52 {
53 Some(m) if !m.has_semi() => ModuleSource::new_inline(db, position.file_id, m),
54 _ => ModuleSource::SourceFile(position.file_id),
55 };
56 Module::guess_from_source(db, position.file_id, module_source)
57 }
58
59 fn guess_from_source(
60 db: &impl HirDatabase,
61 file_id: FileId,
62 module_source: ModuleSource,
63 ) -> Cancelable<Option<Module>> {
64 let source_root_id = db.file_source_root(file_id);
65 let module_tree = db.module_tree(source_root_id)?;
66
67 let res = match module_tree.any_module_for_source(module_source) {
68 None => None,
69 Some(module_id) => Some(Module {
70 tree: module_tree,
71 source_root_id,
72 module_id,
73 }),
74 };
75 Ok(res)
76 }
77
78 pub(super) fn new(
79 db: &impl HirDatabase,
80 source_root_id: SourceRootId,
81 module_id: ModuleId,
82 ) -> Cancelable<Module> {
83 let module_tree = db.module_tree(source_root_id)?;
84 let res = Module {
85 tree: module_tree,
86 source_root_id,
87 module_id,
88 };
89 Ok(res)
90 }
91
92 /// Returns `mod foo;` or `mod foo {}` node whihc declared this module.
93 /// Returns `None` for the root module
94 pub fn parent_link_source(&self, db: &impl HirDatabase) -> Option<(FileId, ast::ModuleNode)> {
95 let link = self.module_id.parent_link(&self.tree)?;
96 let file_id = link.owner(&self.tree).source(&self.tree).file_id();
97 let src = link.bind_source(&self.tree, db);
98 Some((file_id, src))
99 }
100
101 pub fn source(&self) -> ModuleSource {
102 self.module_id.source(&self.tree)
103 }
104
105 /// Parent module. Returns `None` if this is a root module.
106 pub fn parent(&self) -> Option<Module> {
107 let parent_id = self.module_id.parent(&self.tree)?;
108 Some(Module {
109 module_id: parent_id,
110 ..self.clone()
111 })
112 }
113
114 /// The root of the tree this module is part of
115 pub fn crate_root(&self) -> Module {
116 let root_id = self.module_id.crate_root(&self.tree);
117 Module {
118 module_id: root_id,
119 ..self.clone()
120 }
121 }
122
123 /// `name` is `None` for the crate's root module
124 #[allow(unused)]
125 pub fn name(&self) -> Option<SmolStr> {
126 let link = self.module_id.parent_link(&self.tree)?;
127 Some(link.name(&self.tree))
128 }
129
130 pub fn def_id(&self, db: &impl HirDatabase) -> DefId {
131 let def_loc = DefLoc::Module {
132 id: self.module_id,
133 source_root: self.source_root_id,
134 };
135 def_loc.id(db)
136 }
137
138 /// Finds a child module with the specified name.
139 pub fn child(&self, name: &str) -> Option<Module> {
140 let child_id = self.module_id.child(&self.tree, name)?;
141 Some(Module {
142 module_id: child_id,
143 ..self.clone()
144 })
145 }
146
147 /// Returns a `ModuleScope`: a set of items, visible in this module.
148 pub(crate) fn scope(&self, db: &impl HirDatabase) -> Cancelable<ModuleScope> {
149 let item_map = db.item_map(self.source_root_id)?;
150 let res = item_map.per_module[&self.module_id].clone();
151 Ok(res)
152 }
153
154 pub(crate) fn resolve_path(
155 &self,
156 db: &impl HirDatabase,
157 path: Path,
158 ) -> Cancelable<Option<DefId>> {
159 let mut curr = match path.kind {
160 PathKind::Crate => self.crate_root(),
161 PathKind::Self_ | PathKind::Plain => self.clone(),
162 PathKind::Super => ctry!(self.parent()),
163 }
164 .def_id(db);
165
166 let segments = path.segments;
167 for name in segments.iter() {
168 let module = match curr.loc(db) {
169 DefLoc::Module { id, source_root } => Module::new(db, source_root, id)?,
170 _ => return Ok(None),
171 };
172 let scope = module.scope(db)?;
173 curr = ctry!(ctry!(scope.get(&name)).def_id);
174 }
175 Ok(Some(curr))
176 }
177
178 pub fn problems(&self, db: &impl HirDatabase) -> Vec<(SyntaxNode, Problem)> {
179 self.module_id.problems(&self.tree, db)
180 }
181}
182
183/// Phisically, rust source is organized as a set of files, but logically it is
184/// organized as a tree of modules. Usually, a single file corresponds to a
185/// single module, but it is not nessary the case.
186///
187/// Module encapsulate the logic of transitioning from the fuzzy world of files
188/// (which can have multiple parents) to the precise world of modules (which
189/// always have one parent).
190#[derive(Default, Debug, PartialEq, Eq)]
191pub(crate) struct ModuleTree {
192 mods: Arena<ModuleData>,
193 links: Arena<LinkData>,
194}
195
196impl ModuleTree {
197 pub(crate) fn modules<'a>(&'a self) -> impl Iterator<Item = ModuleId> + 'a {
198 self.mods.iter().map(|(id, _)| id)
199 }
200
201 fn modules_for_source(&self, source: ModuleSource) -> Vec<ModuleId> {
202 self.mods
203 .iter()
204 .filter(|(_idx, it)| it.source == source)
205 .map(|(idx, _)| idx)
206 .collect()
207 }
208
209 fn any_module_for_source(&self, source: ModuleSource) -> Option<ModuleId> {
210 self.modules_for_source(source).pop()
211 }
212}
213
214/// `ModuleSource` is the syntax tree element that produced this module:
215/// either a file, or an inlinde module.
216#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
217pub(crate) enum ModuleSource {
218 SourceFile(FileId),
219 Module(SourceItemId),
220}
221
222/// An owned syntax node for a module. Unlike `ModuleSource`,
223/// this holds onto the AST for the whole file.
224pub(crate) enum ModuleSourceNode {
225 SourceFile(ast::SourceFileNode),
226 Module(ast::ModuleNode),
227}
228
229pub(crate) type ModuleId = Id<ModuleData>;
230type LinkId = Id<LinkData>;
231
232#[derive(Clone, Debug, Hash, PartialEq, Eq)]
233pub enum Problem {
234 UnresolvedModule {
235 candidate: RelativePathBuf,
236 },
237 NotDirOwner {
238 move_to: RelativePathBuf,
239 candidate: RelativePathBuf,
240 },
241}
242
243impl ModuleId {
244 pub(crate) fn source(self, tree: &ModuleTree) -> ModuleSource {
245 tree.mods[self].source
246 }
247 fn parent_link(self, tree: &ModuleTree) -> Option<LinkId> {
248 tree.mods[self].parent
249 }
250 fn parent(self, tree: &ModuleTree) -> Option<ModuleId> {
251 let link = self.parent_link(tree)?;
252 Some(tree.links[link].owner)
253 }
254 fn crate_root(self, tree: &ModuleTree) -> ModuleId {
255 generate(Some(self), move |it| it.parent(tree))
256 .last()
257 .unwrap()
258 }
259 fn child(self, tree: &ModuleTree, name: &str) -> Option<ModuleId> {
260 let link = tree.mods[self]
261 .children
262 .iter()
263 .map(|&it| &tree.links[it])
264 .find(|it| it.name == name)?;
265 Some(*link.points_to.first()?)
266 }
267 fn children<'a>(self, tree: &'a ModuleTree) -> impl Iterator<Item = (SmolStr, ModuleId)> + 'a {
268 tree.mods[self].children.iter().filter_map(move |&it| {
269 let link = &tree.links[it];
270 let module = *link.points_to.first()?;
271 Some((link.name.clone(), module))
272 })
273 }
274 fn problems(self, tree: &ModuleTree, db: &impl HirDatabase) -> Vec<(SyntaxNode, Problem)> {
275 tree.mods[self]
276 .children
277 .iter()
278 .filter_map(|&it| {
279 let p = tree.links[it].problem.clone()?;
280 let s = it.bind_source(tree, db);
281 let s = s.borrowed().name().unwrap().syntax().owned();
282 Some((s, p))
283 })
284 .collect()
285 }
286}
287
288impl LinkId {
289 fn owner(self, tree: &ModuleTree) -> ModuleId {
290 tree.links[self].owner
291 }
292 fn name(self, tree: &ModuleTree) -> SmolStr {
293 tree.links[self].name.clone()
294 }
295 fn bind_source<'a>(self, tree: &ModuleTree, db: &impl HirDatabase) -> ast::ModuleNode {
296 let owner = self.owner(tree);
297 match owner.source(tree).resolve(db) {
298 ModuleSourceNode::SourceFile(root) => {
299 let ast = imp::modules(root.borrowed())
300 .find(|(name, _)| name == &tree.links[self].name)
301 .unwrap()
302 .1;
303 ast.owned()
304 }
305 ModuleSourceNode::Module(it) => it,
306 }
307 }
308}
309
310#[derive(Debug, PartialEq, Eq, Hash)]
311pub(crate) struct ModuleData {
312 source: ModuleSource,
313 parent: Option<LinkId>,
314 children: Vec<LinkId>,
315}
316
317impl ModuleSource {
318 pub(crate) fn new_inline(
319 db: &impl HirDatabase,
320 file_id: FileId,
321 module: ast::Module,
322 ) -> ModuleSource {
323 assert!(!module.has_semi());
324 let items = db.file_items(file_id);
325 let item_id = items.id_of(module.syntax());
326 let id = SourceItemId { file_id, item_id };
327 ModuleSource::Module(id)
328 }
329
330 pub(crate) fn as_file(self) -> Option<FileId> {
331 match self {
332 ModuleSource::SourceFile(f) => Some(f),
333 ModuleSource::Module(..) => None,
334 }
335 }
336
337 pub(crate) fn file_id(self) -> FileId {
338 match self {
339 ModuleSource::SourceFile(f) => f,
340 ModuleSource::Module(source_item_id) => source_item_id.file_id,
341 }
342 }
343
344 pub(crate) fn resolve(self, db: &impl HirDatabase) -> ModuleSourceNode {
345 match self {
346 ModuleSource::SourceFile(file_id) => {
347 let syntax = db.source_file(file_id);
348 ModuleSourceNode::SourceFile(syntax.ast().owned())
349 }
350 ModuleSource::Module(item_id) => {
351 let syntax = db.file_item(item_id);
352 let syntax = syntax.borrowed();
353 let module = ast::Module::cast(syntax).unwrap();
354 ModuleSourceNode::Module(module.owned())
355 }
356 }
357 }
358}
359
360#[derive(Hash, Debug, PartialEq, Eq)]
361struct LinkData {
362 owner: ModuleId,
363 name: SmolStr,
364 points_to: Vec<ModuleId>,
365 problem: Option<Problem>,
366}
367
368impl ModuleTree {
369 fn push_mod(&mut self, data: ModuleData) -> ModuleId {
370 self.mods.alloc(data)
371 }
372 fn push_link(&mut self, data: LinkData) -> LinkId {
373 let owner = data.owner;
374 let id = self.links.alloc(data);
375 self.mods[owner].children.push(id);
376 id
377 }
378}