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