diff options
Diffstat (limited to 'crates/libanalysis/src/module_map.rs')
-rw-r--r-- | crates/libanalysis/src/module_map.rs | 386 |
1 files changed, 131 insertions, 255 deletions
diff --git a/crates/libanalysis/src/module_map.rs b/crates/libanalysis/src/module_map.rs index 79b88cac2..a21f55fff 100644 --- a/crates/libanalysis/src/module_map.rs +++ b/crates/libanalysis/src/module_map.rs | |||
@@ -1,281 +1,157 @@ | |||
1 | use relative_path::RelativePathBuf; | 1 | use std::sync::Arc; |
2 | use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; | 2 | use { |
3 | use libsyntax2::{ | 3 | FileId, |
4 | File, | 4 | db::{ |
5 | ast::{self, AstNode, NameOwner}, | 5 | Query, QueryRegistry, QueryCtx, |
6 | SyntaxNode, SmolStr, | 6 | file_set |
7 | }, | ||
8 | queries::file_syntax, | ||
9 | descriptors::{ModuleDescriptor, ModuleTreeDescriptor}, | ||
7 | }; | 10 | }; |
8 | use {FileId, imp::FileResolverImp}; | ||
9 | |||
10 | type SyntaxProvider<'a> = dyn Fn(FileId) -> &'a File + 'a; | ||
11 | |||
12 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] | ||
13 | pub struct ModuleId(FileId); | ||
14 | |||
15 | #[derive(Debug, Default)] | ||
16 | pub struct ModuleMap { | ||
17 | state: RwLock<State>, | ||
18 | } | ||
19 | |||
20 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
21 | pub enum ChangeKind { | ||
22 | Delete, Insert, Update | ||
23 | } | ||
24 | |||
25 | impl Clone for ModuleMap { | ||
26 | fn clone(&self) -> ModuleMap { | ||
27 | let state = self.state.read().clone(); | ||
28 | ModuleMap { state: RwLock::new(state) } | ||
29 | } | ||
30 | } | ||
31 | |||
32 | #[derive(Clone, Debug, Default)] | ||
33 | struct State { | ||
34 | file_resolver: FileResolverImp, | ||
35 | changes: Vec<(FileId, ChangeKind)>, | ||
36 | links: Vec<Link>, | ||
37 | } | ||
38 | 11 | ||
39 | #[derive(Clone, Debug)] | 12 | pub(crate) fn register_queries(reg: &mut QueryRegistry) { |
40 | struct Link { | 13 | reg.add(MODULE_DESCR, "MODULE_DESCR"); |
41 | owner: ModuleId, | 14 | reg.add(MODULE_TREE, "MODULE_TREE"); |
42 | syntax: SyntaxNode, | ||
43 | points_to: Vec<ModuleId>, | ||
44 | problem: Option<Problem>, | ||
45 | } | 15 | } |
46 | 16 | ||
47 | #[derive(Clone, Debug)] | 17 | pub(crate) fn module_tree(ctx: QueryCtx) -> Arc<ModuleTreeDescriptor> { |
48 | pub enum Problem { | 18 | ctx.get(MODULE_TREE, ()) |
49 | UnresolvedModule { | ||
50 | candidate: RelativePathBuf, | ||
51 | }, | ||
52 | NotDirOwner { | ||
53 | move_to: RelativePathBuf, | ||
54 | candidate: RelativePathBuf, | ||
55 | } | ||
56 | } | 19 | } |
57 | 20 | ||
58 | impl ModuleMap { | 21 | const MODULE_DESCR: Query<FileId, ModuleDescriptor> = Query(30, |ctx, &file_id| { |
59 | pub fn new() -> ModuleMap { | 22 | let file = file_syntax(ctx, file_id); |
60 | Default::default() | 23 | ModuleDescriptor::new(file.ast()) |
61 | } | 24 | }); |
62 | pub fn update_file(&mut self, file_id: FileId, change_kind: ChangeKind) { | 25 | |
63 | self.state.get_mut().changes.push((file_id, change_kind)); | 26 | const MODULE_TREE: Query<(), ModuleTreeDescriptor> = Query(31, |ctx, _| { |
64 | } | 27 | let file_set = file_set(ctx); |
65 | pub(crate) fn set_file_resolver(&mut self, file_resolver: FileResolverImp) { | 28 | let mut files = Vec::new(); |
66 | self.state.get_mut().file_resolver = file_resolver; | 29 | for &file_id in file_set.0.iter() { |
67 | } | 30 | let module_descr = ctx.get(MODULE_DESCR, file_id); |
68 | pub fn module2file(&self, m: ModuleId) -> FileId { | 31 | files.push((file_id, module_descr)); |
69 | m.0 | 32 | } |
70 | } | 33 | ModuleTreeDescriptor::new(files.iter().map(|(file_id, descr)| (*file_id, &**descr)), &file_set.1) |
71 | pub fn file2module(&self, file_id: FileId) -> ModuleId { | 34 | }); |
72 | ModuleId(file_id) | 35 | |
73 | } | 36 | #[cfg(test)] |
74 | pub fn child_module_by_name<'a>( | 37 | mod tests { |
75 | &self, | 38 | use std::collections::HashMap; |
76 | parent_mod: ModuleId, | 39 | use im; |
77 | child_mod: &str, | 40 | use relative_path::{RelativePath, RelativePathBuf}; |
78 | syntax_provider: &SyntaxProvider, | 41 | use { |
79 | ) -> Vec<ModuleId> { | 42 | db::{Db}, |
80 | self.links(syntax_provider) | 43 | imp::FileResolverImp, |
81 | .links | 44 | FileId, FileResolver, |
82 | .iter() | 45 | }; |
83 | .filter(|link| link.owner == parent_mod) | 46 | use super::*; |
84 | .filter(|link| link.name() == child_mod) | 47 | |
85 | .filter_map(|it| it.points_to.first()) | 48 | #[derive(Debug)] |
86 | .map(|&it| it) | 49 | struct FileMap(im::HashMap<FileId, RelativePathBuf>); |
87 | .collect() | 50 | |
88 | } | 51 | impl FileResolver for FileMap { |
89 | 52 | fn file_stem(&self, file_id: FileId) -> String { | |
90 | pub fn parent_modules( | 53 | self.0[&file_id].file_stem().unwrap().to_string() |
91 | &self, | 54 | } |
92 | m: ModuleId, | 55 | fn resolve(&self, file_id: FileId, rel: &RelativePath) -> Option<FileId> { |
93 | syntax_provider: &SyntaxProvider, | 56 | let path = self.0[&file_id].join(rel).normalize(); |
94 | ) -> Vec<(ModuleId, SmolStr, SyntaxNode)> { | 57 | self.0.iter() |
95 | let mut res = Vec::new(); | 58 | .filter_map(|&(id, ref p)| Some(id).filter(|_| p == &path)) |
96 | self.for_each_parent_link(m, syntax_provider, |link| { | 59 | .next() |
97 | res.push( | 60 | } |
98 | (link.owner, link.name().clone(), link.syntax.clone()) | ||
99 | ) | ||
100 | }); | ||
101 | res | ||
102 | } | ||
103 | |||
104 | pub fn parent_module_ids( | ||
105 | &self, | ||
106 | m: ModuleId, | ||
107 | syntax_provider: &SyntaxProvider, | ||
108 | ) -> Vec<ModuleId> { | ||
109 | let mut res = Vec::new(); | ||
110 | self.for_each_parent_link(m, syntax_provider, |link| res.push(link.owner)); | ||
111 | res | ||
112 | } | ||
113 | |||
114 | fn for_each_parent_link( | ||
115 | &self, | ||
116 | m: ModuleId, | ||
117 | syntax_provider: &SyntaxProvider, | ||
118 | f: impl FnMut(&Link) | ||
119 | ) { | ||
120 | self.links(syntax_provider) | ||
121 | .links | ||
122 | .iter() | ||
123 | .filter(move |link| link.points_to.iter().any(|&it| it == m)) | ||
124 | .for_each(f) | ||
125 | } | 61 | } |
126 | 62 | ||
127 | pub fn problems( | 63 | struct Fixture { |
128 | &self, | 64 | next_file_id: u32, |
129 | file: FileId, | 65 | fm: im::HashMap<FileId, RelativePathBuf>, |
130 | syntax_provider: &SyntaxProvider, | 66 | db: Db, |
131 | mut cb: impl FnMut(ast::Name, &Problem), | ||
132 | ) { | ||
133 | let module = self.file2module(file); | ||
134 | let links = self.links(syntax_provider); | ||
135 | links | ||
136 | .links | ||
137 | .iter() | ||
138 | .filter(|link| link.owner == module) | ||
139 | .filter_map(|link| { | ||
140 | let problem = link.problem.as_ref()?; | ||
141 | Some((link, problem)) | ||
142 | }) | ||
143 | .for_each(|(link, problem)| cb(link.name_node(), problem)) | ||
144 | } | 67 | } |
145 | 68 | ||
146 | fn links( | 69 | impl Fixture { |
147 | &self, | 70 | fn new() -> Fixture { |
148 | syntax_provider: &SyntaxProvider, | 71 | Fixture { |
149 | ) -> RwLockReadGuard<State> { | 72 | next_file_id: 1, |
150 | { | 73 | fm: im::HashMap::new(), |
151 | let guard = self.state.read(); | 74 | db: Db::new(), |
152 | if guard.changes.is_empty() { | ||
153 | return guard; | ||
154 | } | 75 | } |
155 | } | 76 | } |
156 | let mut guard = self.state.write(); | 77 | fn add_file(&mut self, path: &str, text: &str) -> FileId { |
157 | if !guard.changes.is_empty() { | 78 | assert!(path.starts_with("/")); |
158 | guard.apply_changes(syntax_provider); | 79 | let file_id = FileId(self.next_file_id); |
80 | self.next_file_id += 1; | ||
81 | self.fm.insert(file_id, RelativePathBuf::from(&path[1..])); | ||
82 | let mut new_state = self.db.state().clone(); | ||
83 | new_state.file_map.insert(file_id, Arc::new(text.to_string())); | ||
84 | new_state.file_resolver = FileResolverImp::new( | ||
85 | Arc::new(FileMap(self.fm.clone())) | ||
86 | ); | ||
87 | self.db = self.db.with_changes(new_state, &[file_id], true); | ||
88 | file_id | ||
159 | } | 89 | } |
160 | assert!(guard.changes.is_empty()); | 90 | fn remove_file(&mut self, file_id: FileId) { |
161 | RwLockWriteGuard::downgrade(guard) | 91 | self.fm.remove(&file_id); |
162 | } | 92 | let mut new_state = self.db.state().clone(); |
163 | } | 93 | new_state.file_map.remove(&file_id); |
164 | 94 | new_state.file_resolver = FileResolverImp::new( | |
165 | impl State { | 95 | Arc::new(FileMap(self.fm.clone())) |
166 | pub fn apply_changes( | 96 | ); |
167 | &mut self, | 97 | self.db = self.db.with_changes(new_state, &[file_id], true); |
168 | syntax_provider: &SyntaxProvider, | ||
169 | ) { | ||
170 | let mut reresolve = false; | ||
171 | for (file_id, kind) in self.changes.drain(..) { | ||
172 | let mod_id = ModuleId(file_id); | ||
173 | self.links.retain(|link| link.owner != mod_id); | ||
174 | match kind { | ||
175 | ChangeKind::Delete => { | ||
176 | for link in self.links.iter_mut() { | ||
177 | link.points_to.retain(|&x| x != mod_id); | ||
178 | } | ||
179 | } | ||
180 | ChangeKind::Insert => { | ||
181 | let file = syntax_provider(file_id); | ||
182 | self.links.extend( | ||
183 | file | ||
184 | .ast() | ||
185 | .modules() | ||
186 | .filter_map(|it| Link::new(mod_id, it)) | ||
187 | ); | ||
188 | reresolve = true; | ||
189 | } | ||
190 | ChangeKind::Update => { | ||
191 | let file = syntax_provider(file_id); | ||
192 | let resolver = &self.file_resolver; | ||
193 | self.links.extend( | ||
194 | file | ||
195 | .ast() | ||
196 | .modules() | ||
197 | .filter_map(|it| Link::new(mod_id, it)) | ||
198 | .map(|mut link| { | ||
199 | link.resolve(resolver); | ||
200 | link | ||
201 | }) | ||
202 | ); | ||
203 | } | ||
204 | } | ||
205 | } | 98 | } |
206 | if reresolve { | 99 | fn change_file(&mut self, file_id: FileId, new_text: &str) { |
207 | for link in self.links.iter_mut() { | 100 | let mut new_state = self.db.state().clone(); |
208 | link.resolve(&self.file_resolver) | 101 | new_state.file_map.insert(file_id, Arc::new(new_text.to_string())); |
209 | } | 102 | self.db = self.db.with_changes(new_state, &[file_id], false); |
210 | } | 103 | } |
211 | } | 104 | fn check_parent_modules( |
212 | } | 105 | &self, |
106 | file_id: FileId, | ||
107 | expected: &[FileId], | ||
108 | queries: &[(&'static str, u64)] | ||
109 | ) { | ||
110 | let (tree, events) = self.db.trace_query(|ctx| module_tree(ctx)); | ||
111 | let actual = tree.parent_modules(file_id) | ||
112 | .into_iter() | ||
113 | .map(|link| link.owner(&tree)) | ||
114 | .collect::<Vec<_>>(); | ||
115 | assert_eq!(actual.as_slice(), expected); | ||
116 | let mut counts = HashMap::new(); | ||
117 | events.into_iter() | ||
118 | .for_each(|event| *counts.entry(event).or_insert(0) += 1); | ||
119 | for &(query_id, expected_count) in queries.iter() { | ||
120 | let actual_count = *counts.get(&query_id).unwrap_or(&0); | ||
121 | assert_eq!( | ||
122 | actual_count, | ||
123 | expected_count, | ||
124 | "counts for {} differ", | ||
125 | query_id, | ||
126 | ) | ||
127 | } | ||
213 | 128 | ||
214 | impl Link { | ||
215 | fn new(owner: ModuleId, module: ast::Module) -> Option<Link> { | ||
216 | if module.name().is_none() { | ||
217 | return None; | ||
218 | } | 129 | } |
219 | let link = Link { | ||
220 | owner, | ||
221 | syntax: module.syntax().owned(), | ||
222 | points_to: Vec::new(), | ||
223 | problem: None, | ||
224 | }; | ||
225 | Some(link) | ||
226 | } | 130 | } |
227 | 131 | ||
228 | fn name(&self) -> SmolStr { | 132 | #[test] |
229 | self.name_node().text() | 133 | fn test_parent_module() { |
230 | } | 134 | let mut f = Fixture::new(); |
135 | let foo = f.add_file("/foo.rs", ""); | ||
136 | f.check_parent_modules(foo, &[], &[("MODULE_DESCR", 1)]); | ||
231 | 137 | ||
232 | fn name_node(&self) -> ast::Name { | 138 | let lib = f.add_file("/lib.rs", "mod foo;"); |
233 | self.ast().name().unwrap() | 139 | f.check_parent_modules(foo, &[lib], &[("MODULE_DESCR", 1)]); |
234 | } | 140 | f.check_parent_modules(foo, &[lib], &[("MODULE_DESCR", 0)]); |
235 | 141 | ||
236 | fn ast(&self) -> ast::Module { | 142 | f.change_file(lib, ""); |
237 | ast::Module::cast(self.syntax.borrowed()) | 143 | f.check_parent_modules(foo, &[], &[("MODULE_DESCR", 1)]); |
238 | .unwrap() | ||
239 | } | ||
240 | 144 | ||
241 | fn resolve(&mut self, file_resolver: &FileResolverImp) { | 145 | f.change_file(lib, "mod foo;"); |
242 | if !self.ast().has_semi() { | 146 | f.check_parent_modules(foo, &[lib], &[("MODULE_DESCR", 1)]); |
243 | self.problem = None; | ||
244 | self.points_to = Vec::new(); | ||
245 | return; | ||
246 | } | ||
247 | let (points_to, problem) = resolve_submodule(self.owner.0, &self.name(), file_resolver); | ||
248 | self.problem = problem; | ||
249 | self.points_to = points_to.into_iter().map(ModuleId).collect(); | ||
250 | } | ||
251 | } | ||
252 | 147 | ||
253 | pub(crate) fn resolve_submodule(file_id: FileId, name: &SmolStr, file_resolver: &FileResolverImp) -> (Vec<FileId>, Option<Problem>) { | 148 | f.change_file(lib, "mod bar;"); |
254 | let mod_name = file_resolver.file_stem(file_id); | 149 | f.check_parent_modules(foo, &[], &[("MODULE_DESCR", 1)]); |
255 | let is_dir_owner = | ||
256 | mod_name == "mod" || mod_name == "lib" || mod_name == "main"; | ||
257 | 150 | ||
258 | let file_mod = RelativePathBuf::from(format!("../{}.rs", name)); | 151 | f.change_file(lib, "mod foo;"); |
259 | let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name)); | 152 | f.check_parent_modules(foo, &[lib], &[("MODULE_DESCR", 1)]); |
260 | let points_to: Vec<FileId>; | 153 | |
261 | let problem: Option<Problem>; | 154 | f.remove_file(lib); |
262 | if is_dir_owner { | 155 | f.check_parent_modules(foo, &[], &[("MODULE_DESCR", 0)]); |
263 | points_to = [&file_mod, &dir_mod].iter() | ||
264 | .filter_map(|path| file_resolver.resolve(file_id, path)) | ||
265 | .collect(); | ||
266 | problem = if points_to.is_empty() { | ||
267 | Some(Problem::UnresolvedModule { | ||
268 | candidate: file_mod, | ||
269 | }) | ||
270 | } else { | ||
271 | None | ||
272 | } | ||
273 | } else { | ||
274 | points_to = Vec::new(); | ||
275 | problem = Some(Problem::NotDirOwner { | ||
276 | move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)), | ||
277 | candidate: file_mod, | ||
278 | }); | ||
279 | } | 156 | } |
280 | (points_to, problem) | ||
281 | } | 157 | } |