diff options
-rw-r--r-- | crates/libanalysis/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/libanalysis/src/db.rs | 121 | ||||
-rw-r--r-- | crates/libanalysis/src/lib.rs | 3 | ||||
-rw-r--r-- | crates/libanalysis/src/module_map.rs | 51 | ||||
-rw-r--r-- | crates/libanalysis/src/module_map_db.rs | 189 | ||||
-rw-r--r-- | crates/libanalysis/tests/tests.rs | 35 | ||||
-rw-r--r-- | crates/server/Cargo.toml | 2 |
7 files changed, 361 insertions, 41 deletions
diff --git a/crates/libanalysis/Cargo.toml b/crates/libanalysis/Cargo.toml index 4d565e95f..4c92951b1 100644 --- a/crates/libanalysis/Cargo.toml +++ b/crates/libanalysis/Cargo.toml | |||
@@ -11,6 +11,7 @@ parking_lot = "0.6.3" | |||
11 | once_cell = "0.1.5" | 11 | once_cell = "0.1.5" |
12 | rayon = "1.0.2" | 12 | rayon = "1.0.2" |
13 | fst = "0.3.1" | 13 | fst = "0.3.1" |
14 | im = "12.0.0" | ||
14 | libsyntax2 = { path = "../libsyntax2" } | 15 | libsyntax2 = { path = "../libsyntax2" } |
15 | libeditor = { path = "../libeditor" } | 16 | libeditor = { path = "../libeditor" } |
16 | 17 | ||
diff --git a/crates/libanalysis/src/db.rs b/crates/libanalysis/src/db.rs new file mode 100644 index 000000000..335c79e76 --- /dev/null +++ b/crates/libanalysis/src/db.rs | |||
@@ -0,0 +1,121 @@ | |||
1 | use std::{ | ||
2 | hash::Hash, | ||
3 | sync::Arc, | ||
4 | }; | ||
5 | use libsyntax2::{File}; | ||
6 | use im; | ||
7 | use { | ||
8 | FileId, | ||
9 | imp::{FileResolverImp}, | ||
10 | }; | ||
11 | |||
12 | #[derive(Clone)] | ||
13 | pub(crate) struct Db { | ||
14 | file_resolver: FileResolverImp, | ||
15 | files: im::HashMap<FileId, Arc<String>>, | ||
16 | } | ||
17 | |||
18 | impl Db { | ||
19 | pub(crate) fn new() -> Db { | ||
20 | Db { | ||
21 | file_resolver: FileResolverImp::default(), | ||
22 | files: im::HashMap::new(), | ||
23 | } | ||
24 | } | ||
25 | pub(crate) fn change_file(&mut self, file_id: FileId, text: Option<String>) { | ||
26 | match text { | ||
27 | None => { | ||
28 | self.files.remove(&file_id); | ||
29 | } | ||
30 | Some(text) => { | ||
31 | self.files.insert(file_id, Arc::new(text)); | ||
32 | } | ||
33 | } | ||
34 | } | ||
35 | pub(crate) fn set_file_resolver(&mut self, file_resolver: FileResolverImp) { | ||
36 | self.file_resolver = file_resolver | ||
37 | } | ||
38 | pub(crate) fn query_ctx(&self) -> QueryCtx { | ||
39 | QueryCtx { db: self.clone() } | ||
40 | } | ||
41 | } | ||
42 | |||
43 | pub(crate) struct QueryCtx { | ||
44 | db: Db | ||
45 | } | ||
46 | |||
47 | impl QueryCtx { | ||
48 | pub(crate) fn get<Q: Get>(&self, params: &Q::Params) -> Q::Output { | ||
49 | Q::get(self, params) | ||
50 | } | ||
51 | } | ||
52 | |||
53 | pub(crate) trait Query { | ||
54 | const ID: u32; | ||
55 | type Params: Hash; | ||
56 | type Output; | ||
57 | } | ||
58 | |||
59 | pub(crate) trait Get: Query { | ||
60 | fn get(ctx: &QueryCtx, params: &Self::Params) -> Self::Output; | ||
61 | } | ||
62 | |||
63 | impl<T: Eval> Get for T { | ||
64 | fn get(ctx: &QueryCtx, params: &Self::Params) -> Self::Output { | ||
65 | Self::eval(ctx, params) | ||
66 | } | ||
67 | } | ||
68 | |||
69 | pub(crate) trait Eval: Query { | ||
70 | fn eval(ctx: &QueryCtx, params: &Self::Params) -> Self::Output; | ||
71 | } | ||
72 | |||
73 | pub(crate) struct DbFiles { | ||
74 | db: Db, | ||
75 | } | ||
76 | |||
77 | impl DbFiles { | ||
78 | pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item=FileId> + 'a { | ||
79 | self.db.files.keys().cloned() | ||
80 | } | ||
81 | pub(crate) fn file_resolver(&self) -> FileResolverImp { | ||
82 | self.db.file_resolver.clone() | ||
83 | } | ||
84 | } | ||
85 | |||
86 | pub(crate) enum Files {} | ||
87 | impl Query for Files { | ||
88 | const ID: u32 = 1; | ||
89 | type Params = (); | ||
90 | type Output = DbFiles; | ||
91 | } | ||
92 | impl Get for Files { | ||
93 | fn get(ctx: &QueryCtx, _params: &()) -> DbFiles { | ||
94 | DbFiles { db: ctx.db.clone() } | ||
95 | } | ||
96 | } | ||
97 | |||
98 | enum FileText {} | ||
99 | impl Query for FileText { | ||
100 | const ID: u32 = 10; | ||
101 | type Params = FileId; | ||
102 | type Output = Arc<String>; | ||
103 | } | ||
104 | impl Get for FileText { | ||
105 | fn get(ctx: &QueryCtx, file_id: &FileId) -> Arc<String> { | ||
106 | ctx.db.files[file_id].clone() | ||
107 | } | ||
108 | } | ||
109 | |||
110 | pub(crate) enum FileSyntax {} | ||
111 | impl Query for FileSyntax { | ||
112 | const ID: u32 = 20; | ||
113 | type Params = FileId; | ||
114 | type Output = File; | ||
115 | } | ||
116 | impl Eval for FileSyntax { | ||
117 | fn eval(ctx: &QueryCtx, file_id: &FileId) -> File { | ||
118 | let text = ctx.get::<FileText>(file_id); | ||
119 | File::parse(&text) | ||
120 | } | ||
121 | } | ||
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs index 80cde079f..68cf31e08 100644 --- a/crates/libanalysis/src/lib.rs +++ b/crates/libanalysis/src/lib.rs | |||
@@ -9,12 +9,15 @@ extern crate rayon; | |||
9 | extern crate relative_path; | 9 | extern crate relative_path; |
10 | #[macro_use] | 10 | #[macro_use] |
11 | extern crate crossbeam_channel; | 11 | extern crate crossbeam_channel; |
12 | extern crate im; | ||
12 | 13 | ||
13 | mod symbol_index; | 14 | mod symbol_index; |
14 | mod module_map; | 15 | mod module_map; |
16 | mod module_map_db; | ||
15 | mod imp; | 17 | mod imp; |
16 | mod job; | 18 | mod job; |
17 | mod roots; | 19 | mod roots; |
20 | mod db; | ||
18 | 21 | ||
19 | use std::{ | 22 | use std::{ |
20 | sync::Arc, | 23 | sync::Arc, |
diff --git a/crates/libanalysis/src/module_map.rs b/crates/libanalysis/src/module_map.rs index 9acebd6e2..79b88cac2 100644 --- a/crates/libanalysis/src/module_map.rs +++ b/crates/libanalysis/src/module_map.rs | |||
@@ -244,31 +244,38 @@ impl Link { | |||
244 | self.points_to = Vec::new(); | 244 | self.points_to = Vec::new(); |
245 | return; | 245 | return; |
246 | } | 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 | } | ||
247 | 252 | ||
248 | let mod_name = file_resolver.file_stem(self.owner.0); | 253 | pub(crate) fn resolve_submodule(file_id: FileId, name: &SmolStr, file_resolver: &FileResolverImp) -> (Vec<FileId>, Option<Problem>) { |
249 | let is_dir_owner = | 254 | let mod_name = file_resolver.file_stem(file_id); |
250 | mod_name == "mod" || mod_name == "lib" || mod_name == "main"; | 255 | let is_dir_owner = |
256 | mod_name == "mod" || mod_name == "lib" || mod_name == "main"; | ||
251 | 257 | ||
252 | let file_mod = RelativePathBuf::from(format!("../{}.rs", self.name())); | 258 | let file_mod = RelativePathBuf::from(format!("../{}.rs", name)); |
253 | let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", self.name())); | 259 | let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name)); |
254 | if is_dir_owner { | 260 | let points_to: Vec<FileId>; |
255 | self.points_to = [&file_mod, &dir_mod].iter() | 261 | let problem: Option<Problem>; |
256 | .filter_map(|path| file_resolver.resolve(self.owner.0, path)) | 262 | if is_dir_owner { |
257 | .map(ModuleId) | 263 | points_to = [&file_mod, &dir_mod].iter() |
258 | .collect(); | 264 | .filter_map(|path| file_resolver.resolve(file_id, path)) |
259 | self.problem = if self.points_to.is_empty() { | 265 | .collect(); |
260 | Some(Problem::UnresolvedModule { | 266 | problem = if points_to.is_empty() { |
261 | candidate: file_mod, | 267 | Some(Problem::UnresolvedModule { |
262 | }) | ||
263 | } else { | ||
264 | None | ||
265 | } | ||
266 | } else { | ||
267 | self.points_to = Vec::new(); | ||
268 | self.problem = Some(Problem::NotDirOwner { | ||
269 | move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)), | ||
270 | candidate: file_mod, | 268 | candidate: file_mod, |
271 | }); | 269 | }) |
270 | } else { | ||
271 | None | ||
272 | } | 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 | }); | ||
273 | } | 279 | } |
280 | (points_to, problem) | ||
274 | } | 281 | } |
diff --git a/crates/libanalysis/src/module_map_db.rs b/crates/libanalysis/src/module_map_db.rs new file mode 100644 index 000000000..1ef87ab3f --- /dev/null +++ b/crates/libanalysis/src/module_map_db.rs | |||
@@ -0,0 +1,189 @@ | |||
1 | use std::sync::Arc; | ||
2 | use { | ||
3 | FileId, | ||
4 | db::{Query, Eval, QueryCtx, FileSyntax, Files}, | ||
5 | module_map::resolve_submodule, | ||
6 | }; | ||
7 | |||
8 | enum ModuleDescr {} | ||
9 | impl Query for ModuleDescr { | ||
10 | const ID: u32 = 30; | ||
11 | type Params = FileId; | ||
12 | type Output = Arc<descr::ModuleDescr>; | ||
13 | } | ||
14 | |||
15 | enum ResolveSubmodule {} | ||
16 | impl Query for ResolveSubmodule { | ||
17 | const ID: u32 = 31; | ||
18 | type Params = (FileId, descr::Submodule); | ||
19 | type Output = Arc<Vec<FileId>>; | ||
20 | } | ||
21 | |||
22 | enum ParentModule {} | ||
23 | impl Query for ParentModule { | ||
24 | const ID: u32 = 40; | ||
25 | type Params = FileId; | ||
26 | type Output = Arc<Vec<FileId>>; | ||
27 | } | ||
28 | |||
29 | impl Eval for ModuleDescr { | ||
30 | fn eval(ctx: &QueryCtx, file_id: &FileId) -> Arc<descr::ModuleDescr> { | ||
31 | let file = ctx.get::<FileSyntax>(file_id); | ||
32 | Arc::new(descr::ModuleDescr::new(file.ast())) | ||
33 | } | ||
34 | } | ||
35 | |||
36 | impl Eval for ResolveSubmodule { | ||
37 | fn eval(ctx: &QueryCtx, &(file_id, ref submodule): &(FileId, descr::Submodule)) -> Arc<Vec<FileId>> { | ||
38 | let files = ctx.get::<Files>(&()); | ||
39 | let res = resolve_submodule(file_id, &submodule.name, &files.file_resolver()).0; | ||
40 | Arc::new(res) | ||
41 | } | ||
42 | } | ||
43 | |||
44 | impl Eval for ParentModule { | ||
45 | fn eval(ctx: &QueryCtx, file_id: &FileId) -> Arc<Vec<FileId>> { | ||
46 | let files = ctx.get::<Files>(&()); | ||
47 | let res = files.iter() | ||
48 | .map(|parent_id| (parent_id, ctx.get::<ModuleDescr>(&parent_id))) | ||
49 | .filter(|(parent_id, descr)| { | ||
50 | descr.submodules.iter() | ||
51 | .any(|subm| { | ||
52 | ctx.get::<ResolveSubmodule>(&(*parent_id, subm.clone())) | ||
53 | .iter() | ||
54 | .any(|it| it == file_id) | ||
55 | }) | ||
56 | }) | ||
57 | .map(|(id, _)| id) | ||
58 | .collect(); | ||
59 | Arc::new(res) | ||
60 | } | ||
61 | } | ||
62 | |||
63 | mod descr { | ||
64 | use libsyntax2::{ | ||
65 | SmolStr, | ||
66 | ast::{self, NameOwner}, | ||
67 | }; | ||
68 | |||
69 | pub struct ModuleDescr { | ||
70 | pub submodules: Vec<Submodule> | ||
71 | } | ||
72 | |||
73 | impl ModuleDescr { | ||
74 | pub fn new(root: ast::Root) -> ModuleDescr { | ||
75 | let submodules = root | ||
76 | .modules() | ||
77 | .filter_map(|module| { | ||
78 | let name = module.name()?.text(); | ||
79 | if !module.has_semi() { | ||
80 | return None; | ||
81 | } | ||
82 | Some(Submodule { name }) | ||
83 | }).collect(); | ||
84 | |||
85 | ModuleDescr { submodules } } | ||
86 | } | ||
87 | |||
88 | #[derive(Clone, Hash)] | ||
89 | pub struct Submodule { | ||
90 | pub name: SmolStr, | ||
91 | } | ||
92 | |||
93 | } | ||
94 | |||
95 | #[cfg(test)] | ||
96 | mod tests { | ||
97 | use super::*; | ||
98 | use im; | ||
99 | use relative_path::{RelativePath, RelativePathBuf}; | ||
100 | use { | ||
101 | db::Db, | ||
102 | imp::FileResolverImp, | ||
103 | FileId, FileResolver, | ||
104 | }; | ||
105 | |||
106 | #[derive(Debug)] | ||
107 | struct FileMap(im::HashMap<FileId, RelativePathBuf>); | ||
108 | |||
109 | impl FileResolver for FileMap { | ||
110 | fn file_stem(&self, file_id: FileId) -> String { | ||
111 | self.0[&file_id].file_stem().unwrap().to_string() | ||
112 | } | ||
113 | fn resolve(&self, file_id: FileId, rel: &RelativePath) -> Option<FileId> { | ||
114 | let path = self.0[&file_id].join(rel).normalize(); | ||
115 | self.0.iter() | ||
116 | .filter_map(|&(id, ref p)| Some(id).filter(|_| p == &path)) | ||
117 | .next() | ||
118 | } | ||
119 | } | ||
120 | |||
121 | struct Fixture { | ||
122 | next_file_id: u32, | ||
123 | fm: im::HashMap<FileId, RelativePathBuf>, | ||
124 | db: Db, | ||
125 | } | ||
126 | |||
127 | impl Fixture { | ||
128 | fn new() -> Fixture { | ||
129 | Fixture { | ||
130 | next_file_id: 1, | ||
131 | fm: im::HashMap::new(), | ||
132 | db: Db::new(), | ||
133 | } | ||
134 | } | ||
135 | fn add_file(&mut self, path: &str, text: &str) -> FileId { | ||
136 | assert!(path.starts_with("/")); | ||
137 | let file_id = FileId(self.next_file_id); | ||
138 | self.next_file_id += 1; | ||
139 | self.fm.insert(file_id, RelativePathBuf::from(&path[1..])); | ||
140 | self.db.change_file(file_id, Some(text.to_string())); | ||
141 | self.db.set_file_resolver(FileResolverImp::new( | ||
142 | Arc::new(FileMap(self.fm.clone())) | ||
143 | )); | ||
144 | |||
145 | file_id | ||
146 | } | ||
147 | fn remove_file(&mut self, file_id: FileId) { | ||
148 | self.fm.remove(&file_id); | ||
149 | self.db.change_file(file_id, None); | ||
150 | self.db.set_file_resolver(FileResolverImp::new( | ||
151 | Arc::new(FileMap(self.fm.clone())) | ||
152 | )) | ||
153 | } | ||
154 | fn change_file(&mut self, file_id: FileId, new_text: &str) { | ||
155 | self.db.change_file(file_id, Some(new_text.to_string())); | ||
156 | } | ||
157 | fn check_parent_modules(&self, file_id: FileId, expected: &[FileId]) { | ||
158 | let ctx = self.db.query_ctx(); | ||
159 | let actual = ctx.get::<ParentModule>(&file_id); | ||
160 | assert_eq!(actual.as_slice(), expected); | ||
161 | } | ||
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn test_parent_module() { | ||
166 | let mut f = Fixture::new(); | ||
167 | let foo = f.add_file("/foo.rs", ""); | ||
168 | f.check_parent_modules(foo, &[]); | ||
169 | |||
170 | let lib = f.add_file("/lib.rs", "mod foo;"); | ||
171 | f.check_parent_modules(foo, &[lib]); | ||
172 | |||
173 | f.change_file(lib, ""); | ||
174 | f.check_parent_modules(foo, &[]); | ||
175 | |||
176 | f.change_file(lib, "mod foo;"); | ||
177 | f.check_parent_modules(foo, &[lib]); | ||
178 | |||
179 | f.change_file(lib, "mod bar;"); | ||
180 | f.check_parent_modules(foo, &[]); | ||
181 | |||
182 | f.change_file(lib, "mod foo;"); | ||
183 | f.check_parent_modules(foo, &[lib]); | ||
184 | |||
185 | f.remove_file(lib); | ||
186 | f.check_parent_modules(foo, &[]); | ||
187 | } | ||
188 | |||
189 | } | ||
diff --git a/crates/libanalysis/tests/tests.rs b/crates/libanalysis/tests/tests.rs index 00efe059c..547f85958 100644 --- a/crates/libanalysis/tests/tests.rs +++ b/crates/libanalysis/tests/tests.rs | |||
@@ -14,24 +14,6 @@ use test_utils::assert_eq_dbg; | |||
14 | #[derive(Debug)] | 14 | #[derive(Debug)] |
15 | struct FileMap(Vec<(FileId, RelativePathBuf)>); | 15 | struct FileMap(Vec<(FileId, RelativePathBuf)>); |
16 | 16 | ||
17 | fn analysis_host(files: &'static [(&'static str, &'static str)]) -> AnalysisHost { | ||
18 | let mut host = AnalysisHost::new(); | ||
19 | let mut file_map = Vec::new(); | ||
20 | for (id, &(path, contents)) in files.iter().enumerate() { | ||
21 | let file_id = FileId((id + 1) as u32); | ||
22 | assert!(path.starts_with('/')); | ||
23 | let path = RelativePathBuf::from_path(&path[1..]).unwrap(); | ||
24 | host.change_file(file_id, Some(contents.to_string())); | ||
25 | file_map.push((file_id, path)); | ||
26 | } | ||
27 | host.set_file_resolver(Arc::new(FileMap(file_map))); | ||
28 | host | ||
29 | } | ||
30 | |||
31 | fn analysis(files: &'static [(&'static str, &'static str)]) -> Analysis { | ||
32 | analysis_host(files).analysis() | ||
33 | } | ||
34 | |||
35 | impl FileMap { | 17 | impl FileMap { |
36 | fn iter<'a>(&'a self) -> impl Iterator<Item=(FileId, &'a RelativePath)> + 'a { | 18 | fn iter<'a>(&'a self) -> impl Iterator<Item=(FileId, &'a RelativePath)> + 'a { |
37 | self.0.iter().map(|(id, path)| (*id, path.as_relative_path())) | 19 | self.0.iter().map(|(id, path)| (*id, path.as_relative_path())) |
@@ -56,6 +38,23 @@ impl FileResolver for FileMap { | |||
56 | } | 38 | } |
57 | } | 39 | } |
58 | 40 | ||
41 | fn analysis_host(files: &'static [(&'static str, &'static str)]) -> AnalysisHost { | ||
42 | let mut host = AnalysisHost::new(); | ||
43 | let mut file_map = Vec::new(); | ||
44 | for (id, &(path, contents)) in files.iter().enumerate() { | ||
45 | let file_id = FileId((id + 1) as u32); | ||
46 | assert!(path.starts_with('/')); | ||
47 | let path = RelativePathBuf::from_path(&path[1..]).unwrap(); | ||
48 | host.change_file(file_id, Some(contents.to_string())); | ||
49 | file_map.push((file_id, path)); | ||
50 | } | ||
51 | host.set_file_resolver(Arc::new(FileMap(file_map))); | ||
52 | host | ||
53 | } | ||
54 | |||
55 | fn analysis(files: &'static [(&'static str, &'static str)]) -> Analysis { | ||
56 | analysis_host(files).analysis() | ||
57 | } | ||
59 | 58 | ||
60 | #[test] | 59 | #[test] |
61 | fn test_resolve_module() { | 60 | fn test_resolve_module() { |
diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 9aeea9a9b..fc20730b8 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml | |||
@@ -17,7 +17,7 @@ log = "0.4.3" | |||
17 | url_serde = "0.2.0" | 17 | url_serde = "0.2.0" |
18 | languageserver-types = "0.49.0" | 18 | languageserver-types = "0.49.0" |
19 | walkdir = "2.2.0" | 19 | walkdir = "2.2.0" |
20 | im = { version = "11.0.1", features = ["arc"] } | 20 | im = "12.0.0" |
21 | cargo_metadata = "0.6.0" | 21 | cargo_metadata = "0.6.0" |
22 | text_unit = { version = "0.1.2", features = ["serde"] } | 22 | text_unit = { version = "0.1.2", features = ["serde"] } |
23 | smol_str = { version = "0.1.5", features = ["serde"] } | 23 | smol_str = { version = "0.1.5", features = ["serde"] } |