diff options
Diffstat (limited to 'crates/ra_analysis/src/hir/module/imp.rs')
-rw-r--r-- | crates/ra_analysis/src/hir/module/imp.rs | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/hir/module/imp.rs b/crates/ra_analysis/src/hir/module/imp.rs new file mode 100644 index 000000000..d8539ed85 --- /dev/null +++ b/crates/ra_analysis/src/hir/module/imp.rs | |||
@@ -0,0 +1,229 @@ | |||
1 | use std::sync::Arc; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | ast::{self, NameOwner}, | ||
5 | SmolStr, | ||
6 | }; | ||
7 | use relative_path::RelativePathBuf; | ||
8 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
9 | |||
10 | use crate::{ | ||
11 | db, | ||
12 | hir::DescriptorDatabase, | ||
13 | input::{SourceRoot, SourceRootId}, | ||
14 | Cancelable, FileId, FileResolverImp, | ||
15 | }; | ||
16 | |||
17 | use super::{ | ||
18 | LinkData, LinkId, ModuleData, ModuleId, ModuleSource, ModuleSourceNode, | ||
19 | ModuleTree, Problem, | ||
20 | }; | ||
21 | |||
22 | #[derive(Clone, Hash, PartialEq, Eq, Debug)] | ||
23 | pub(crate) enum Submodule { | ||
24 | Declaration(SmolStr), | ||
25 | Definition(SmolStr, ModuleSource), | ||
26 | } | ||
27 | |||
28 | impl Submodule { | ||
29 | fn name(&self) -> &SmolStr { | ||
30 | match self { | ||
31 | Submodule::Declaration(name) => name, | ||
32 | Submodule::Definition(name, _) => name, | ||
33 | } | ||
34 | } | ||
35 | } | ||
36 | |||
37 | pub(crate) fn submodules( | ||
38 | db: &impl DescriptorDatabase, | ||
39 | source: ModuleSource, | ||
40 | ) -> Cancelable<Arc<Vec<Submodule>>> { | ||
41 | db::check_canceled(db)?; | ||
42 | let file_id = source.file_id(); | ||
43 | let submodules = match source.resolve(db) { | ||
44 | ModuleSourceNode::SourceFile(it) => collect_submodules(file_id, it.borrowed()), | ||
45 | ModuleSourceNode::Module(it) => it | ||
46 | .borrowed() | ||
47 | .item_list() | ||
48 | .map(|it| collect_submodules(file_id, it)) | ||
49 | .unwrap_or_else(Vec::new), | ||
50 | }; | ||
51 | return Ok(Arc::new(submodules)); | ||
52 | |||
53 | fn collect_submodules<'a>( | ||
54 | file_id: FileId, | ||
55 | root: impl ast::ModuleItemOwner<'a>, | ||
56 | ) -> Vec<Submodule> { | ||
57 | modules(root) | ||
58 | .map(|(name, m)| { | ||
59 | if m.has_semi() { | ||
60 | Submodule::Declaration(name) | ||
61 | } else { | ||
62 | let src = ModuleSource::new_inline(file_id, m); | ||
63 | Submodule::Definition(name, src) | ||
64 | } | ||
65 | }) | ||
66 | .collect() | ||
67 | } | ||
68 | } | ||
69 | |||
70 | pub(crate) fn modules<'a>( | ||
71 | root: impl ast::ModuleItemOwner<'a>, | ||
72 | ) -> impl Iterator<Item = (SmolStr, ast::Module<'a>)> { | ||
73 | root.items() | ||
74 | .filter_map(|item| match item { | ||
75 | ast::ModuleItem::Module(m) => Some(m), | ||
76 | _ => None, | ||
77 | }) | ||
78 | .filter_map(|module| { | ||
79 | let name = module.name()?.text(); | ||
80 | Some((name, module)) | ||
81 | }) | ||
82 | } | ||
83 | |||
84 | pub(crate) fn module_tree( | ||
85 | db: &impl DescriptorDatabase, | ||
86 | source_root: SourceRootId, | ||
87 | ) -> Cancelable<Arc<ModuleTree>> { | ||
88 | db::check_canceled(db)?; | ||
89 | let res = create_module_tree(db, source_root)?; | ||
90 | Ok(Arc::new(res)) | ||
91 | } | ||
92 | |||
93 | fn create_module_tree<'a>( | ||
94 | db: &impl DescriptorDatabase, | ||
95 | source_root: SourceRootId, | ||
96 | ) -> Cancelable<ModuleTree> { | ||
97 | let mut tree = ModuleTree::default(); | ||
98 | |||
99 | let mut roots = FxHashMap::default(); | ||
100 | let mut visited = FxHashSet::default(); | ||
101 | |||
102 | let source_root = db.source_root(source_root); | ||
103 | for &file_id in source_root.files.iter() { | ||
104 | let source = ModuleSource::SourceFile(file_id); | ||
105 | if visited.contains(&source) { | ||
106 | continue; // TODO: use explicit crate_roots here | ||
107 | } | ||
108 | assert!(!roots.contains_key(&file_id)); | ||
109 | let module_id = build_subtree( | ||
110 | db, | ||
111 | &source_root, | ||
112 | &mut tree, | ||
113 | &mut visited, | ||
114 | &mut roots, | ||
115 | None, | ||
116 | source, | ||
117 | )?; | ||
118 | roots.insert(file_id, module_id); | ||
119 | } | ||
120 | Ok(tree) | ||
121 | } | ||
122 | |||
123 | fn build_subtree( | ||
124 | db: &impl DescriptorDatabase, | ||
125 | source_root: &SourceRoot, | ||
126 | tree: &mut ModuleTree, | ||
127 | visited: &mut FxHashSet<ModuleSource>, | ||
128 | roots: &mut FxHashMap<FileId, ModuleId>, | ||
129 | parent: Option<LinkId>, | ||
130 | source: ModuleSource, | ||
131 | ) -> Cancelable<ModuleId> { | ||
132 | visited.insert(source); | ||
133 | let id = tree.push_mod(ModuleData { | ||
134 | source, | ||
135 | parent, | ||
136 | children: Vec::new(), | ||
137 | }); | ||
138 | for sub in db._submodules(source)?.iter() { | ||
139 | let link = tree.push_link(LinkData { | ||
140 | name: sub.name().clone(), | ||
141 | owner: id, | ||
142 | points_to: Vec::new(), | ||
143 | problem: None, | ||
144 | }); | ||
145 | |||
146 | let (points_to, problem) = match sub { | ||
147 | Submodule::Declaration(name) => { | ||
148 | let (points_to, problem) = | ||
149 | resolve_submodule(source, &name, &source_root.file_resolver); | ||
150 | let points_to = points_to | ||
151 | .into_iter() | ||
152 | .map(|file_id| match roots.remove(&file_id) { | ||
153 | Some(module_id) => { | ||
154 | tree.mods[module_id].parent = Some(link); | ||
155 | Ok(module_id) | ||
156 | } | ||
157 | None => build_subtree( | ||
158 | db, | ||
159 | source_root, | ||
160 | tree, | ||
161 | visited, | ||
162 | roots, | ||
163 | Some(link), | ||
164 | ModuleSource::SourceFile(file_id), | ||
165 | ), | ||
166 | }) | ||
167 | .collect::<Cancelable<Vec<_>>>()?; | ||
168 | (points_to, problem) | ||
169 | } | ||
170 | Submodule::Definition(_name, submodule_source) => { | ||
171 | let points_to = build_subtree( | ||
172 | db, | ||
173 | source_root, | ||
174 | tree, | ||
175 | visited, | ||
176 | roots, | ||
177 | Some(link), | ||
178 | *submodule_source, | ||
179 | )?; | ||
180 | (vec![points_to], None) | ||
181 | } | ||
182 | }; | ||
183 | |||
184 | tree.links[link].points_to = points_to; | ||
185 | tree.links[link].problem = problem; | ||
186 | } | ||
187 | Ok(id) | ||
188 | } | ||
189 | |||
190 | fn resolve_submodule( | ||
191 | source: ModuleSource, | ||
192 | name: &SmolStr, | ||
193 | file_resolver: &FileResolverImp, | ||
194 | ) -> (Vec<FileId>, Option<Problem>) { | ||
195 | let file_id = match source { | ||
196 | ModuleSource::SourceFile(it) => it, | ||
197 | ModuleSource::Module(..) => { | ||
198 | // TODO | ||
199 | return (Vec::new(), None); | ||
200 | } | ||
201 | }; | ||
202 | let mod_name = file_resolver.file_stem(file_id); | ||
203 | let is_dir_owner = mod_name == "mod" || mod_name == "lib" || mod_name == "main"; | ||
204 | |||
205 | let file_mod = RelativePathBuf::from(format!("../{}.rs", name)); | ||
206 | let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name)); | ||
207 | let points_to: Vec<FileId>; | ||
208 | let problem: Option<Problem>; | ||
209 | if is_dir_owner { | ||
210 | points_to = [&file_mod, &dir_mod] | ||
211 | .iter() | ||
212 | .filter_map(|path| file_resolver.resolve(file_id, path)) | ||
213 | .collect(); | ||
214 | problem = if points_to.is_empty() { | ||
215 | Some(Problem::UnresolvedModule { | ||
216 | candidate: file_mod, | ||
217 | }) | ||
218 | } else { | ||
219 | None | ||
220 | } | ||
221 | } else { | ||
222 | points_to = Vec::new(); | ||
223 | problem = Some(Problem::NotDirOwner { | ||
224 | move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)), | ||
225 | candidate: file_mod, | ||
226 | }); | ||
227 | } | ||
228 | (points_to, problem) | ||
229 | } | ||