diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/libanalysis/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/libanalysis/src/db/imp.rs | 152 | ||||
-rw-r--r-- | crates/libanalysis/src/db/mod.rs | 85 | ||||
-rw-r--r-- | crates/libanalysis/src/descriptors.rs | 220 | ||||
-rw-r--r-- | crates/libanalysis/src/imp.rs | 146 | ||||
-rw-r--r-- | crates/libanalysis/src/lib.rs | 23 | ||||
-rw-r--r-- | crates/libanalysis/src/module_map.rs | 379 | ||||
-rw-r--r-- | crates/libanalysis/src/queries.rs | 39 | ||||
-rw-r--r-- | crates/libanalysis/src/roots.rs | 166 | ||||
-rw-r--r-- | crates/libanalysis/src/symbol_index.rs | 12 | ||||
-rw-r--r-- | crates/libanalysis/tests/tests.rs | 35 | ||||
-rw-r--r-- | crates/libeditor/src/line_index.rs | 2 | ||||
-rw-r--r-- | crates/libeditor/src/symbols.rs | 2 | ||||
-rw-r--r-- | crates/libsyntax2/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/salsa/Cargo.toml | 8 | ||||
-rw-r--r-- | crates/salsa/src/lib.rs | 293 | ||||
-rw-r--r-- | crates/salsa/tests/integration.rs | 170 | ||||
-rw-r--r-- | crates/server/Cargo.toml | 2 |
18 files changed, 1285 insertions, 453 deletions
diff --git a/crates/libanalysis/Cargo.toml b/crates/libanalysis/Cargo.toml index 4d565e95f..88f29d7c8 100644 --- a/crates/libanalysis/Cargo.toml +++ b/crates/libanalysis/Cargo.toml | |||
@@ -11,8 +11,10 @@ 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" } |
17 | salsa = { path = "../salsa" } | ||
16 | 18 | ||
17 | [dev-dependencies] | 19 | [dev-dependencies] |
18 | test_utils = { path = "../test_utils" } | 20 | test_utils = { path = "../test_utils" } |
diff --git a/crates/libanalysis/src/db/imp.rs b/crates/libanalysis/src/db/imp.rs new file mode 100644 index 000000000..f26be1046 --- /dev/null +++ b/crates/libanalysis/src/db/imp.rs | |||
@@ -0,0 +1,152 @@ | |||
1 | use std::{ | ||
2 | sync::Arc, | ||
3 | any::Any, | ||
4 | hash::{Hash, Hasher}, | ||
5 | collections::hash_map::{DefaultHasher, HashMap}, | ||
6 | iter, | ||
7 | }; | ||
8 | use salsa; | ||
9 | use {FileId, imp::FileResolverImp}; | ||
10 | use super::{State, Query, QueryCtx}; | ||
11 | |||
12 | pub(super) type Data = Arc<Any + Send + Sync + 'static>; | ||
13 | |||
14 | #[derive(Debug)] | ||
15 | pub(super) struct Db { | ||
16 | names: Arc<HashMap<salsa::QueryTypeId, &'static str>>, | ||
17 | pub(super) imp: salsa::Db<State, Data>, | ||
18 | } | ||
19 | |||
20 | impl Db { | ||
21 | pub(super) fn new(mut reg: QueryRegistry) -> Db { | ||
22 | let config = reg.config.take().unwrap(); | ||
23 | Db { | ||
24 | names: Arc::new(reg.names), | ||
25 | imp: salsa::Db::new(config, State::default()) | ||
26 | } | ||
27 | } | ||
28 | pub(crate) fn with_changes(&self, new_state: State, changed_files: &[FileId], resolver_changed: bool) -> Db { | ||
29 | let names = self.names.clone(); | ||
30 | let mut invalidations = salsa::Invalidations::new(); | ||
31 | invalidations.invalidate(FILE_TEXT, changed_files.iter().map(hash).map(salsa::InputFingerprint)); | ||
32 | if resolver_changed { | ||
33 | invalidations.invalidate(FILE_SET, iter::once(salsa::InputFingerprint(hash(&())))); | ||
34 | } else { | ||
35 | invalidations.invalidate(FILE_SET, iter::empty()); | ||
36 | } | ||
37 | let imp = self.imp.with_ground_data( | ||
38 | new_state, | ||
39 | invalidations, | ||
40 | ); | ||
41 | Db { names, imp } | ||
42 | } | ||
43 | pub(super) fn extract_trace(&self, ctx: &salsa::QueryCtx<State, Data>) -> Vec<&'static str> { | ||
44 | ctx.trace().into_iter().map(|it| self.names[&it]).collect() | ||
45 | } | ||
46 | } | ||
47 | |||
48 | pub(crate) trait EvalQuery { | ||
49 | type Params; | ||
50 | type Output; | ||
51 | fn query_type(&self) -> salsa::QueryTypeId; | ||
52 | fn f(&self) -> salsa::QueryFn<State, Data>; | ||
53 | fn get(&self, &QueryCtx, Self::Params) -> Arc<Self::Output>; | ||
54 | } | ||
55 | |||
56 | impl<T, R> EvalQuery for Query<T, R> | ||
57 | where | ||
58 | T: Hash + Send + Sync + 'static, | ||
59 | R: Hash + Send + Sync + 'static, | ||
60 | { | ||
61 | type Params = T; | ||
62 | type Output = R; | ||
63 | fn query_type(&self) -> salsa::QueryTypeId { | ||
64 | salsa::QueryTypeId(self.0) | ||
65 | } | ||
66 | fn f(&self) -> salsa::QueryFn<State, Data> { | ||
67 | let f = self.1; | ||
68 | Box::new(move |ctx, data| { | ||
69 | let ctx = QueryCtx { imp: ctx }; | ||
70 | let data: &T = data.downcast_ref().unwrap(); | ||
71 | let res = f(ctx, data); | ||
72 | let h = hash(&res); | ||
73 | (Arc::new(res), salsa::OutputFingerprint(h)) | ||
74 | }) | ||
75 | } | ||
76 | fn get(&self, ctx: &QueryCtx, params: Self::Params) -> Arc<Self::Output> { | ||
77 | let query_id = salsa::QueryId( | ||
78 | self.query_type(), | ||
79 | salsa::InputFingerprint(hash(¶ms)), | ||
80 | ); | ||
81 | let res = ctx.imp.get(query_id, Arc::new(params)); | ||
82 | res.downcast().unwrap() | ||
83 | } | ||
84 | } | ||
85 | |||
86 | pub(super) struct QueryRegistry { | ||
87 | config: Option<salsa::QueryConfig<State, Data>>, | ||
88 | names: HashMap<salsa::QueryTypeId, &'static str>, | ||
89 | } | ||
90 | |||
91 | impl QueryRegistry { | ||
92 | pub(super) fn new() -> QueryRegistry { | ||
93 | let mut config = salsa::QueryConfig::<State, Data>::new(); | ||
94 | config = config.with_ground_query( | ||
95 | FILE_TEXT, Box::new(|state, params| { | ||
96 | let file_id: &FileId = params.downcast_ref().unwrap(); | ||
97 | let res = state.file_map[file_id].clone(); | ||
98 | let fingerprint = salsa::OutputFingerprint(hash(&res)); | ||
99 | (res, fingerprint) | ||
100 | }) | ||
101 | ); | ||
102 | config = config.with_ground_query( | ||
103 | FILE_SET, Box::new(|state, _params| { | ||
104 | let file_ids: Vec<FileId> = state.file_map.keys().cloned().collect(); | ||
105 | let hash = hash(&file_ids); | ||
106 | let file_resolver = state.file_resolver.clone(); | ||
107 | let res = (file_ids, file_resolver); | ||
108 | let fingerprint = salsa::OutputFingerprint(hash); | ||
109 | (Arc::new(res), fingerprint) | ||
110 | }) | ||
111 | ); | ||
112 | let mut names = HashMap::new(); | ||
113 | names.insert(FILE_TEXT, "FILE_TEXT"); | ||
114 | names.insert(FILE_SET, "FILE_SET"); | ||
115 | QueryRegistry { config: Some(config), names } | ||
116 | } | ||
117 | pub(super) fn add<Q: EvalQuery>(&mut self, q: Q, name: &'static str) { | ||
118 | let id = q.query_type(); | ||
119 | let prev = self.names.insert(id, name); | ||
120 | assert!(prev.is_none(), "duplicate query: {:?}", id); | ||
121 | let config = self.config.take().unwrap(); | ||
122 | let config = config.with_query(id, q.f()); | ||
123 | self.config= Some(config); | ||
124 | } | ||
125 | } | ||
126 | |||
127 | fn hash<T: Hash>(x: &T) -> u64 { | ||
128 | let mut hasher = DefaultHasher::new(); | ||
129 | x.hash(&mut hasher); | ||
130 | hasher.finish() | ||
131 | } | ||
132 | |||
133 | const FILE_TEXT: salsa::QueryTypeId = salsa::QueryTypeId(0); | ||
134 | pub(super) fn file_text(ctx: QueryCtx, file_id: FileId) -> Arc<String> { | ||
135 | let query_id = salsa::QueryId( | ||
136 | FILE_TEXT, | ||
137 | salsa::InputFingerprint(hash(&file_id)), | ||
138 | ); | ||
139 | let res = ctx.imp.get(query_id, Arc::new(file_id)); | ||
140 | res.downcast().unwrap() | ||
141 | } | ||
142 | |||
143 | const FILE_SET: salsa::QueryTypeId = salsa::QueryTypeId(1); | ||
144 | pub(super) fn file_set(ctx: QueryCtx) -> Arc<(Vec<FileId>, FileResolverImp)> { | ||
145 | let query_id = salsa::QueryId( | ||
146 | FILE_SET, | ||
147 | salsa::InputFingerprint(hash(&())), | ||
148 | ); | ||
149 | let res = ctx.imp.get(query_id, Arc::new(())); | ||
150 | res.downcast().unwrap() | ||
151 | } | ||
152 | |||
diff --git a/crates/libanalysis/src/db/mod.rs b/crates/libanalysis/src/db/mod.rs new file mode 100644 index 000000000..22769d112 --- /dev/null +++ b/crates/libanalysis/src/db/mod.rs | |||
@@ -0,0 +1,85 @@ | |||
1 | mod imp; | ||
2 | |||
3 | use std::{ | ||
4 | sync::Arc, | ||
5 | }; | ||
6 | use im; | ||
7 | use salsa; | ||
8 | use {FileId, imp::FileResolverImp}; | ||
9 | |||
10 | #[derive(Debug, Default, Clone)] | ||
11 | pub(crate) struct State { | ||
12 | pub(crate) file_map: im::HashMap<FileId, Arc<String>>, | ||
13 | pub(crate) file_resolver: FileResolverImp | ||
14 | } | ||
15 | |||
16 | #[derive(Debug)] | ||
17 | pub(crate) struct Db { | ||
18 | imp: imp::Db, | ||
19 | } | ||
20 | |||
21 | #[derive(Clone, Copy)] | ||
22 | pub(crate) struct QueryCtx<'a> { | ||
23 | imp: &'a salsa::QueryCtx<State, imp::Data>, | ||
24 | } | ||
25 | |||
26 | pub(crate) struct Query<T, R>(pub(crate) u16, pub(crate) fn(QueryCtx, &T) -> R); | ||
27 | |||
28 | pub(crate) struct QueryRegistry { | ||
29 | imp: imp::QueryRegistry, | ||
30 | } | ||
31 | |||
32 | impl Default for Db { | ||
33 | fn default() -> Db { | ||
34 | Db::new() | ||
35 | } | ||
36 | } | ||
37 | |||
38 | impl Db { | ||
39 | pub(crate) fn new() -> Db { | ||
40 | let reg = QueryRegistry::new(); | ||
41 | Db { imp: imp::Db::new(reg.imp) } | ||
42 | } | ||
43 | pub(crate) fn state(&self) -> &State { | ||
44 | self.imp.imp.ground_data() | ||
45 | } | ||
46 | pub(crate) fn with_changes(&self, new_state: State, changed_files: &[FileId], resolver_changed: bool) -> Db { | ||
47 | Db { imp: self.imp.with_changes(new_state, changed_files, resolver_changed) } | ||
48 | } | ||
49 | pub(crate) fn make_query<F: FnOnce(QueryCtx) -> R, R>(&self, f: F) -> R { | ||
50 | let ctx = QueryCtx { imp: &self.imp.imp.query_ctx() }; | ||
51 | f(ctx) | ||
52 | } | ||
53 | #[allow(unused)] | ||
54 | pub(crate) fn trace_query<F: FnOnce(QueryCtx) -> R, R>(&self, f: F) -> (R, Vec<&'static str>) { | ||
55 | let ctx = QueryCtx { imp: &self.imp.imp.query_ctx() }; | ||
56 | let res = f(ctx); | ||
57 | let trace = self.imp.extract_trace(ctx.imp); | ||
58 | (res, trace) | ||
59 | } | ||
60 | } | ||
61 | |||
62 | impl<'a> QueryCtx<'a> { | ||
63 | pub(crate) fn get<Q: imp::EvalQuery>(&self, q: Q, params: Q::Params) -> Arc<Q::Output> { | ||
64 | q.get(self, params) | ||
65 | } | ||
66 | } | ||
67 | |||
68 | pub(crate) fn file_text(ctx: QueryCtx, file_id: FileId) -> Arc<String> { | ||
69 | imp::file_text(ctx, file_id) | ||
70 | } | ||
71 | |||
72 | pub(crate) fn file_set(ctx: QueryCtx) -> Arc<(Vec<FileId>, FileResolverImp)> { | ||
73 | imp::file_set(ctx) | ||
74 | } | ||
75 | impl QueryRegistry { | ||
76 | fn new() -> QueryRegistry { | ||
77 | let mut reg = QueryRegistry { imp: imp::QueryRegistry::new() }; | ||
78 | ::queries::register_queries(&mut reg); | ||
79 | ::module_map::register_queries(&mut reg); | ||
80 | reg | ||
81 | } | ||
82 | pub(crate) fn add<Q: imp::EvalQuery>(&mut self, q: Q, name: &'static str) { | ||
83 | self.imp.add(q, name) | ||
84 | } | ||
85 | } | ||
diff --git a/crates/libanalysis/src/descriptors.rs b/crates/libanalysis/src/descriptors.rs new file mode 100644 index 000000000..93a4158e4 --- /dev/null +++ b/crates/libanalysis/src/descriptors.rs | |||
@@ -0,0 +1,220 @@ | |||
1 | use std::{ | ||
2 | collections::BTreeMap, | ||
3 | }; | ||
4 | use relative_path::RelativePathBuf; | ||
5 | use libsyntax2::{ | ||
6 | SmolStr, | ||
7 | ast::{self, NameOwner}, | ||
8 | }; | ||
9 | use { | ||
10 | FileId, | ||
11 | imp::FileResolverImp, | ||
12 | }; | ||
13 | |||
14 | #[derive(Debug, Hash)] | ||
15 | pub struct ModuleDescriptor { | ||
16 | pub submodules: Vec<Submodule> | ||
17 | } | ||
18 | |||
19 | impl ModuleDescriptor { | ||
20 | pub fn new(root: ast::Root) -> ModuleDescriptor { | ||
21 | let submodules = modules(root) | ||
22 | .map(|(name, _)| Submodule { name }) | ||
23 | .collect(); | ||
24 | |||
25 | ModuleDescriptor { submodules } } | ||
26 | } | ||
27 | |||
28 | fn modules<'a>(root: ast::Root<'a>) -> impl Iterator<Item=(SmolStr, ast::Module<'a>)> { | ||
29 | root | ||
30 | .modules() | ||
31 | .filter_map(|module| { | ||
32 | let name = module.name()?.text(); | ||
33 | if !module.has_semi() { | ||
34 | return None; | ||
35 | } | ||
36 | Some((name, module)) | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | #[derive(Clone, Hash, PartialEq, Eq, Debug)] | ||
41 | pub struct Submodule { | ||
42 | pub name: SmolStr, | ||
43 | } | ||
44 | |||
45 | #[derive(Hash, Debug)] | ||
46 | pub(crate) struct ModuleTreeDescriptor { | ||
47 | nodes: Vec<NodeData>, | ||
48 | links: Vec<LinkData>, | ||
49 | file_id2node: BTreeMap<FileId, Node>, | ||
50 | } | ||
51 | |||
52 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] | ||
53 | struct Node(usize); | ||
54 | #[derive(Hash, Debug)] | ||
55 | struct NodeData { | ||
56 | file_id: FileId, | ||
57 | links: Vec<Link>, | ||
58 | parents: Vec<Link> | ||
59 | } | ||
60 | |||
61 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] | ||
62 | pub(crate) struct Link(usize); | ||
63 | #[derive(Hash, Debug)] | ||
64 | struct LinkData { | ||
65 | owner: Node, | ||
66 | name: SmolStr, | ||
67 | points_to: Vec<Node>, | ||
68 | problem: Option<Problem>, | ||
69 | } | ||
70 | |||
71 | |||
72 | #[derive(Clone, Debug, Hash)] | ||
73 | pub enum Problem { | ||
74 | UnresolvedModule { | ||
75 | candidate: RelativePathBuf, | ||
76 | }, | ||
77 | NotDirOwner { | ||
78 | move_to: RelativePathBuf, | ||
79 | candidate: RelativePathBuf, | ||
80 | } | ||
81 | } | ||
82 | |||
83 | impl ModuleTreeDescriptor { | ||
84 | pub(crate) fn new<'a>( | ||
85 | files: impl Iterator<Item=(FileId, &'a ModuleDescriptor)> + Clone, | ||
86 | file_resolver: &FileResolverImp, | ||
87 | ) -> ModuleTreeDescriptor { | ||
88 | let mut file_id2node = BTreeMap::new(); | ||
89 | let mut nodes: Vec<NodeData> = files.clone().enumerate() | ||
90 | .map(|(idx, (file_id, _))| { | ||
91 | file_id2node.insert(file_id, Node(idx)); | ||
92 | NodeData { | ||
93 | file_id, | ||
94 | links: Vec::new(), | ||
95 | parents: Vec::new(), | ||
96 | } | ||
97 | }) | ||
98 | .collect(); | ||
99 | let mut links = Vec::new(); | ||
100 | |||
101 | for (idx, (file_id, descr)) in files.enumerate() { | ||
102 | let owner = Node(idx); | ||
103 | for sub in descr.submodules.iter() { | ||
104 | let link = Link(links.len()); | ||
105 | nodes[owner.0].links.push(link); | ||
106 | let (points_to, problem) = resolve_submodule(file_id, &sub.name, file_resolver); | ||
107 | let points_to = points_to | ||
108 | .into_iter() | ||
109 | .map(|file_id| { | ||
110 | let node = file_id2node[&file_id]; | ||
111 | nodes[node.0].parents.push(link); | ||
112 | node | ||
113 | }) | ||
114 | .collect(); | ||
115 | |||
116 | links.push(LinkData { | ||
117 | owner, | ||
118 | name: sub.name.clone(), | ||
119 | points_to, | ||
120 | problem, | ||
121 | }) | ||
122 | |||
123 | } | ||
124 | } | ||
125 | |||
126 | ModuleTreeDescriptor { | ||
127 | nodes, links, file_id2node | ||
128 | } | ||
129 | } | ||
130 | |||
131 | pub(crate) fn parent_modules(&self, file_id: FileId) -> Vec<Link> { | ||
132 | let node = self.file_id2node[&file_id]; | ||
133 | self.node(node) | ||
134 | .parents | ||
135 | .clone() | ||
136 | } | ||
137 | pub(crate) fn child_module_by_name(&self, file_id: FileId, name: &str) -> Vec<FileId> { | ||
138 | let node = self.file_id2node[&file_id]; | ||
139 | self.node(node) | ||
140 | .links | ||
141 | .iter() | ||
142 | .filter(|it| it.name(self) == name) | ||
143 | .flat_map(|link| link.points_to(self).iter().map(|&node| self.node(node).file_id)) | ||
144 | .collect() | ||
145 | } | ||
146 | pub(crate) fn problems<'a, 'b>(&'b self, file_id: FileId, root: ast::Root<'a>) -> Vec<(ast::Name<'a>, &'b Problem)> { | ||
147 | let node = self.file_id2node[&file_id]; | ||
148 | self.node(node) | ||
149 | .links | ||
150 | .iter() | ||
151 | .filter_map(|&link| { | ||
152 | let problem = self.link(link).problem.as_ref()?; | ||
153 | let name = link.bind_source(self, root).name()?; | ||
154 | Some((name, problem)) | ||
155 | }) | ||
156 | .collect() | ||
157 | } | ||
158 | |||
159 | fn node(&self, node: Node) -> &NodeData { | ||
160 | &self.nodes[node.0] | ||
161 | } | ||
162 | fn link(&self, link: Link) -> &LinkData { | ||
163 | &self.links[link.0] | ||
164 | } | ||
165 | } | ||
166 | |||
167 | impl Link { | ||
168 | pub(crate) fn name(self, tree: &ModuleTreeDescriptor) -> SmolStr { | ||
169 | tree.link(self).name.clone() | ||
170 | } | ||
171 | pub(crate) fn owner(self, tree: &ModuleTreeDescriptor) -> FileId { | ||
172 | let owner = tree.link(self).owner; | ||
173 | tree.node(owner).file_id | ||
174 | } | ||
175 | fn points_to(self, tree: &ModuleTreeDescriptor) -> &[Node] { | ||
176 | &tree.link(self).points_to | ||
177 | } | ||
178 | pub(crate) fn bind_source<'a>(self, tree: &ModuleTreeDescriptor, root: ast::Root<'a>) -> ast::Module<'a> { | ||
179 | modules(root) | ||
180 | .filter(|(name, _)| name == &tree.link(self).name) | ||
181 | .next() | ||
182 | .unwrap() | ||
183 | .1 | ||
184 | } | ||
185 | } | ||
186 | |||
187 | |||
188 | fn resolve_submodule( | ||
189 | file_id: FileId, | ||
190 | name: &SmolStr, | ||
191 | file_resolver: &FileResolverImp | ||
192 | ) -> (Vec<FileId>, Option<Problem>) { | ||
193 | let mod_name = file_resolver.file_stem(file_id); | ||
194 | let is_dir_owner = | ||
195 | mod_name == "mod" || mod_name == "lib" || mod_name == "main"; | ||
196 | |||
197 | let file_mod = RelativePathBuf::from(format!("../{}.rs", name)); | ||
198 | let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name)); | ||
199 | let points_to: Vec<FileId>; | ||
200 | let problem: Option<Problem>; | ||
201 | if is_dir_owner { | ||
202 | points_to = [&file_mod, &dir_mod].iter() | ||
203 | .filter_map(|path| file_resolver.resolve(file_id, path)) | ||
204 | .collect(); | ||
205 | problem = if points_to.is_empty() { | ||
206 | Some(Problem::UnresolvedModule { | ||
207 | candidate: file_mod, | ||
208 | }) | ||
209 | } else { | ||
210 | None | ||
211 | } | ||
212 | } else { | ||
213 | points_to = Vec::new(); | ||
214 | problem = Some(Problem::NotDirOwner { | ||
215 | move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)), | ||
216 | candidate: file_mod, | ||
217 | }); | ||
218 | } | ||
219 | (points_to, problem) | ||
220 | } | ||
diff --git a/crates/libanalysis/src/imp.rs b/crates/libanalysis/src/imp.rs index 3e65ee14a..6f3191fe7 100644 --- a/crates/libanalysis/src/imp.rs +++ b/crates/libanalysis/src/imp.rs | |||
@@ -5,6 +5,7 @@ use std::{ | |||
5 | }, | 5 | }, |
6 | fmt, | 6 | fmt, |
7 | collections::{HashSet, VecDeque}, | 7 | collections::{HashSet, VecDeque}, |
8 | iter, | ||
8 | }; | 9 | }; |
9 | 10 | ||
10 | use relative_path::RelativePath; | 11 | use relative_path::RelativePath; |
@@ -18,8 +19,8 @@ use libsyntax2::{ | |||
18 | use { | 19 | use { |
19 | FileId, FileResolver, Query, Diagnostic, SourceChange, SourceFileEdit, Position, FileSystemEdit, | 20 | FileId, FileResolver, Query, Diagnostic, SourceChange, SourceFileEdit, Position, FileSystemEdit, |
20 | JobToken, CrateGraph, CrateId, | 21 | JobToken, CrateGraph, CrateId, |
21 | module_map::{ModuleMap, Problem}, | ||
22 | roots::{SourceRoot, ReadonlySourceRoot, WritableSourceRoot}, | 22 | roots::{SourceRoot, ReadonlySourceRoot, WritableSourceRoot}, |
23 | descriptors::{ModuleTreeDescriptor, Problem}, | ||
23 | }; | 24 | }; |
24 | 25 | ||
25 | 26 | ||
@@ -75,14 +76,12 @@ impl AnalysisHostImpl { | |||
75 | } | 76 | } |
76 | pub fn change_files(&mut self, changes: &mut dyn Iterator<Item=(FileId, Option<String>)>) { | 77 | pub fn change_files(&mut self, changes: &mut dyn Iterator<Item=(FileId, Option<String>)>) { |
77 | let data = self.data_mut(); | 78 | let data = self.data_mut(); |
78 | for (file_id, text) in changes { | 79 | data.root = Arc::new(data.root.apply_changes(changes, None)); |
79 | data.root.update(file_id, text); | ||
80 | } | ||
81 | } | 80 | } |
82 | pub fn set_file_resolver(&mut self, resolver: FileResolverImp) { | 81 | pub fn set_file_resolver(&mut self, resolver: FileResolverImp) { |
83 | let data = self.data_mut(); | 82 | let data = self.data_mut(); |
84 | data.file_resolver = resolver.clone(); | 83 | data.file_resolver = resolver.clone(); |
85 | data.root.set_file_resolver(resolver); | 84 | data.root = Arc::new(data.root.apply_changes(&mut iter::empty(), Some(resolver))); |
86 | } | 85 | } |
87 | pub fn set_crate_graph(&mut self, graph: CrateGraph) { | 86 | pub fn set_crate_graph(&mut self, graph: CrateGraph) { |
88 | let mut visited = HashSet::new(); | 87 | let mut visited = HashSet::new(); |
@@ -124,18 +123,17 @@ impl Clone for AnalysisImpl { | |||
124 | impl AnalysisImpl { | 123 | impl AnalysisImpl { |
125 | fn root(&self, file_id: FileId) -> &SourceRoot { | 124 | fn root(&self, file_id: FileId) -> &SourceRoot { |
126 | if self.data.root.contains(file_id) { | 125 | if self.data.root.contains(file_id) { |
127 | return &self.data.root; | 126 | return &*self.data.root; |
128 | } | 127 | } |
129 | &**self.data.libs.iter().find(|it| it.contains(file_id)).unwrap() | 128 | &**self.data.libs.iter().find(|it| it.contains(file_id)).unwrap() |
130 | } | 129 | } |
131 | pub fn file_syntax(&self, file_id: FileId) -> &File { | 130 | pub fn file_syntax(&self, file_id: FileId) -> File { |
132 | self.root(file_id).syntax(file_id) | 131 | self.root(file_id).syntax(file_id) |
133 | } | 132 | } |
134 | pub fn file_line_index(&self, file_id: FileId) -> &LineIndex { | 133 | pub fn file_line_index(&self, file_id: FileId) -> Arc<LineIndex> { |
135 | self.root(file_id).lines(file_id) | 134 | self.root(file_id).lines(file_id) |
136 | } | 135 | } |
137 | pub fn world_symbols(&self, query: Query, token: &JobToken) -> Vec<(FileId, FileSymbol)> { | 136 | pub fn world_symbols(&self, query: Query, token: &JobToken) -> Vec<(FileId, FileSymbol)> { |
138 | self.reindex(); | ||
139 | let mut buf = Vec::new(); | 137 | let mut buf = Vec::new(); |
140 | if query.libs { | 138 | if query.libs { |
141 | self.data.libs.iter() | 139 | self.data.libs.iter() |
@@ -148,25 +146,24 @@ impl AnalysisImpl { | |||
148 | } | 146 | } |
149 | pub fn parent_module(&self, file_id: FileId) -> Vec<(FileId, FileSymbol)> { | 147 | pub fn parent_module(&self, file_id: FileId) -> Vec<(FileId, FileSymbol)> { |
150 | let root = self.root(file_id); | 148 | let root = self.root(file_id); |
151 | let module_map = root.module_map(); | 149 | let module_tree = root.module_tree(); |
152 | let id = module_map.file2module(file_id); | 150 | module_tree.parent_modules(file_id) |
153 | module_map | 151 | .iter() |
154 | .parent_modules(id, &|file_id| root.syntax(file_id)) | 152 | .map(|link| { |
155 | .into_iter() | 153 | let file_id = link.owner(&module_tree); |
156 | .map(|(id, name, node)| { | 154 | let syntax = root.syntax(file_id); |
157 | let id = module_map.module2file(id); | 155 | let decl = link.bind_source(&module_tree, syntax.ast()); |
158 | let sym = FileSymbol { | 156 | let sym = FileSymbol { |
159 | name, | 157 | name: link.name(&module_tree), |
160 | node_range: node.range(), | 158 | node_range: decl.syntax().range(), |
161 | kind: MODULE, | 159 | kind: MODULE, |
162 | }; | 160 | }; |
163 | (id, sym) | 161 | (file_id, sym) |
164 | }) | 162 | }) |
165 | .collect() | 163 | .collect() |
166 | } | 164 | } |
167 | |||
168 | pub fn crate_for(&self, file_id: FileId) -> Vec<CrateId> { | 165 | pub fn crate_for(&self, file_id: FileId) -> Vec<CrateId> { |
169 | let module_map = self.root(file_id).module_map(); | 166 | let module_tree = self.root(file_id).module_tree(); |
170 | let crate_graph = &self.data.crate_graph; | 167 | let crate_graph = &self.data.crate_graph; |
171 | let mut res = Vec::new(); | 168 | let mut res = Vec::new(); |
172 | let mut work = VecDeque::new(); | 169 | let mut work = VecDeque::new(); |
@@ -177,11 +174,10 @@ impl AnalysisImpl { | |||
177 | res.push(crate_id); | 174 | res.push(crate_id); |
178 | continue; | 175 | continue; |
179 | } | 176 | } |
180 | let mid = module_map.file2module(id); | 177 | let parents = module_tree |
181 | let parents = module_map | 178 | .parent_modules(id) |
182 | .parent_module_ids(mid, &|file_id| self.file_syntax(file_id)) | ||
183 | .into_iter() | 179 | .into_iter() |
184 | .map(|id| module_map.module2file(id)) | 180 | .map(|link| link.owner(&module_tree)) |
185 | .filter(|&id| visited.insert(id)); | 181 | .filter(|&id| visited.insert(id)); |
186 | work.extend(parents); | 182 | work.extend(parents); |
187 | } | 183 | } |
@@ -197,7 +193,7 @@ impl AnalysisImpl { | |||
197 | token: &JobToken, | 193 | token: &JobToken, |
198 | ) -> Vec<(FileId, FileSymbol)> { | 194 | ) -> Vec<(FileId, FileSymbol)> { |
199 | let root = self.root(file_id); | 195 | let root = self.root(file_id); |
200 | let module_map = root.module_map(); | 196 | let module_tree = root.module_tree(); |
201 | let file = root.syntax(file_id); | 197 | let file = root.syntax(file_id); |
202 | let syntax = file.syntax(); | 198 | let syntax = file.syntax(); |
203 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, offset) { | 199 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, offset) { |
@@ -206,7 +202,7 @@ impl AnalysisImpl { | |||
206 | if let Some(name) = find_node_at_offset::<ast::Name>(syntax, offset) { | 202 | if let Some(name) = find_node_at_offset::<ast::Name>(syntax, offset) { |
207 | if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) { | 203 | if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) { |
208 | if module.has_semi() { | 204 | if module.has_semi() { |
209 | let file_ids = self.resolve_module(module_map, file_id, module); | 205 | let file_ids = self.resolve_module(&*module_tree, file_id, module); |
210 | 206 | ||
211 | let res = file_ids.into_iter().map(|id| { | 207 | let res = file_ids.into_iter().map(|id| { |
212 | let name = module.name() | 208 | let name = module.name() |
@@ -229,7 +225,7 @@ impl AnalysisImpl { | |||
229 | 225 | ||
230 | pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> { | 226 | pub fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> { |
231 | let root = self.root(file_id); | 227 | let root = self.root(file_id); |
232 | let module_map = root.module_map(); | 228 | let module_tree = root.module_tree(); |
233 | let syntax = root.syntax(file_id); | 229 | let syntax = root.syntax(file_id); |
234 | 230 | ||
235 | let mut res = libeditor::diagnostics(&syntax) | 231 | let mut res = libeditor::diagnostics(&syntax) |
@@ -237,47 +233,43 @@ impl AnalysisImpl { | |||
237 | .map(|d| Diagnostic { range: d.range, message: d.msg, fix: None }) | 233 | .map(|d| Diagnostic { range: d.range, message: d.msg, fix: None }) |
238 | .collect::<Vec<_>>(); | 234 | .collect::<Vec<_>>(); |
239 | 235 | ||
240 | module_map.problems( | 236 | for (name_node, problem) in module_tree.problems(file_id, syntax.ast()) { |
241 | file_id, | 237 | let diag = match problem { |
242 | &|file_id| self.file_syntax(file_id), | 238 | Problem::UnresolvedModule { candidate } => { |
243 | |name_node, problem| { | 239 | let create_file = FileSystemEdit::CreateFile { |
244 | let diag = match problem { | 240 | anchor: file_id, |
245 | Problem::UnresolvedModule { candidate } => { | 241 | path: candidate.clone(), |
246 | let create_file = FileSystemEdit::CreateFile { | 242 | }; |
247 | anchor: file_id, | 243 | let fix = SourceChange { |
248 | path: candidate.clone(), | 244 | label: "create module".to_string(), |
249 | }; | 245 | source_file_edits: Vec::new(), |
250 | let fix = SourceChange { | 246 | file_system_edits: vec![create_file], |
251 | label: "create module".to_string(), | 247 | cursor_position: None, |
252 | source_file_edits: Vec::new(), | 248 | }; |
253 | file_system_edits: vec![create_file], | 249 | Diagnostic { |
254 | cursor_position: None, | 250 | range: name_node.syntax().range(), |
255 | }; | 251 | message: "unresolved module".to_string(), |
256 | Diagnostic { | 252 | fix: Some(fix), |
257 | range: name_node.syntax().range(), | ||
258 | message: "unresolved module".to_string(), | ||
259 | fix: Some(fix), | ||
260 | } | ||
261 | } | 253 | } |
262 | Problem::NotDirOwner { move_to, candidate } => { | 254 | } |
263 | let move_file = FileSystemEdit::MoveFile { file: file_id, path: move_to.clone() }; | 255 | Problem::NotDirOwner { move_to, candidate } => { |
264 | let create_file = FileSystemEdit::CreateFile { anchor: file_id, path: move_to.join(candidate) }; | 256 | let move_file = FileSystemEdit::MoveFile { file: file_id, path: move_to.clone() }; |
265 | let fix = SourceChange { | 257 | let create_file = FileSystemEdit::CreateFile { anchor: file_id, path: move_to.join(candidate) }; |
266 | label: "move file and create module".to_string(), | 258 | let fix = SourceChange { |
267 | source_file_edits: Vec::new(), | 259 | label: "move file and create module".to_string(), |
268 | file_system_edits: vec![move_file, create_file], | 260 | source_file_edits: Vec::new(), |
269 | cursor_position: None, | 261 | file_system_edits: vec![move_file, create_file], |
270 | }; | 262 | cursor_position: None, |
271 | Diagnostic { | 263 | }; |
272 | range: name_node.syntax().range(), | 264 | Diagnostic { |
273 | message: "can't declare module at this location".to_string(), | 265 | range: name_node.syntax().range(), |
274 | fix: Some(fix), | 266 | message: "can't declare module at this location".to_string(), |
275 | } | 267 | fix: Some(fix), |
276 | } | 268 | } |
277 | }; | 269 | } |
278 | res.push(diag) | 270 | }; |
279 | } | 271 | res.push(diag) |
280 | ); | 272 | } |
281 | res | 273 | res |
282 | } | 274 | } |
283 | 275 | ||
@@ -307,26 +299,12 @@ impl AnalysisImpl { | |||
307 | self.world_symbols(query, token) | 299 | self.world_symbols(query, token) |
308 | } | 300 | } |
309 | 301 | ||
310 | fn resolve_module(&self, module_map: &ModuleMap, file_id: FileId, module: ast::Module) -> Vec<FileId> { | 302 | fn resolve_module(&self, module_tree: &ModuleTreeDescriptor, file_id: FileId, module: ast::Module) -> Vec<FileId> { |
311 | let name = match module.name() { | 303 | let name = match module.name() { |
312 | Some(name) => name.text(), | 304 | Some(name) => name.text(), |
313 | None => return Vec::new(), | 305 | None => return Vec::new(), |
314 | }; | 306 | }; |
315 | let id = module_map.file2module(file_id); | 307 | module_tree.child_module_by_name(file_id, name.as_str()) |
316 | module_map | ||
317 | .child_module_by_name( | ||
318 | id, name.as_str(), | ||
319 | &|file_id| self.file_syntax(file_id), | ||
320 | ) | ||
321 | .into_iter() | ||
322 | .map(|id| module_map.module2file(id)) | ||
323 | .collect() | ||
324 | } | ||
325 | |||
326 | fn reindex(&self) { | ||
327 | if self.needs_reindex.compare_and_swap(true, false, SeqCst) { | ||
328 | self.data.root.reindex(); | ||
329 | } | ||
330 | } | 308 | } |
331 | } | 309 | } |
332 | 310 | ||
@@ -334,7 +312,7 @@ impl AnalysisImpl { | |||
334 | struct WorldData { | 312 | struct WorldData { |
335 | file_resolver: FileResolverImp, | 313 | file_resolver: FileResolverImp, |
336 | crate_graph: CrateGraph, | 314 | crate_graph: CrateGraph, |
337 | root: WritableSourceRoot, | 315 | root: Arc<WritableSourceRoot>, |
338 | libs: Vec<Arc<ReadonlySourceRoot>>, | 316 | libs: Vec<Arc<ReadonlySourceRoot>>, |
339 | } | 317 | } |
340 | 318 | ||
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs index 80cde079f..b4b7a6a30 100644 --- a/crates/libanalysis/src/lib.rs +++ b/crates/libanalysis/src/lib.rs | |||
@@ -9,12 +9,17 @@ 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; | ||
13 | extern crate salsa; | ||
12 | 14 | ||
13 | mod symbol_index; | 15 | mod symbol_index; |
14 | mod module_map; | 16 | mod module_map; |
15 | mod imp; | 17 | mod imp; |
16 | mod job; | 18 | mod job; |
17 | mod roots; | 19 | mod roots; |
20 | mod db; | ||
21 | mod queries; | ||
22 | mod descriptors; | ||
18 | 23 | ||
19 | use std::{ | 24 | use std::{ |
20 | sync::Arc, | 25 | sync::Arc, |
@@ -161,8 +166,8 @@ impl Analysis { | |||
161 | pub fn file_syntax(&self, file_id: FileId) -> File { | 166 | pub fn file_syntax(&self, file_id: FileId) -> File { |
162 | self.imp.file_syntax(file_id).clone() | 167 | self.imp.file_syntax(file_id).clone() |
163 | } | 168 | } |
164 | pub fn file_line_index(&self, file_id: FileId) -> LineIndex { | 169 | pub fn file_line_index(&self, file_id: FileId) -> Arc<LineIndex> { |
165 | self.imp.file_line_index(file_id).clone() | 170 | self.imp.file_line_index(file_id) |
166 | } | 171 | } |
167 | pub fn extend_selection(&self, file: &File, range: TextRange) -> TextRange { | 172 | pub fn extend_selection(&self, file: &File, range: TextRange) -> TextRange { |
168 | libeditor::extend_selection(file, range).unwrap_or(range) | 173 | libeditor::extend_selection(file, range).unwrap_or(range) |
@@ -172,19 +177,19 @@ impl Analysis { | |||
172 | } | 177 | } |
173 | pub fn syntax_tree(&self, file_id: FileId) -> String { | 178 | pub fn syntax_tree(&self, file_id: FileId) -> String { |
174 | let file = self.imp.file_syntax(file_id); | 179 | let file = self.imp.file_syntax(file_id); |
175 | libeditor::syntax_tree(file) | 180 | libeditor::syntax_tree(&file) |
176 | } | 181 | } |
177 | pub fn join_lines(&self, file_id: FileId, range: TextRange) -> SourceChange { | 182 | pub fn join_lines(&self, file_id: FileId, range: TextRange) -> SourceChange { |
178 | let file = self.imp.file_syntax(file_id); | 183 | let file = self.imp.file_syntax(file_id); |
179 | SourceChange::from_local_edit(file_id, "join lines", libeditor::join_lines(file, range)) | 184 | SourceChange::from_local_edit(file_id, "join lines", libeditor::join_lines(&file, range)) |
180 | } | 185 | } |
181 | pub fn on_eq_typed(&self, file_id: FileId, offset: TextUnit) -> Option<SourceChange> { | 186 | pub fn on_eq_typed(&self, file_id: FileId, offset: TextUnit) -> Option<SourceChange> { |
182 | let file = self.imp.file_syntax(file_id); | 187 | let file = self.imp.file_syntax(file_id); |
183 | Some(SourceChange::from_local_edit(file_id, "add semicolon", libeditor::on_eq_typed(file, offset)?)) | 188 | Some(SourceChange::from_local_edit(file_id, "add semicolon", libeditor::on_eq_typed(&file, offset)?)) |
184 | } | 189 | } |
185 | pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { | 190 | pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { |
186 | let file = self.imp.file_syntax(file_id); | 191 | let file = self.imp.file_syntax(file_id); |
187 | libeditor::file_structure(file) | 192 | libeditor::file_structure(&file) |
188 | } | 193 | } |
189 | pub fn symbol_search(&self, query: Query, token: &JobToken) -> Vec<(FileId, FileSymbol)> { | 194 | pub fn symbol_search(&self, query: Query, token: &JobToken) -> Vec<(FileId, FileSymbol)> { |
190 | self.imp.world_symbols(query, token) | 195 | self.imp.world_symbols(query, token) |
@@ -203,15 +208,15 @@ impl Analysis { | |||
203 | } | 208 | } |
204 | pub fn runnables(&self, file_id: FileId) -> Vec<Runnable> { | 209 | pub fn runnables(&self, file_id: FileId) -> Vec<Runnable> { |
205 | let file = self.imp.file_syntax(file_id); | 210 | let file = self.imp.file_syntax(file_id); |
206 | libeditor::runnables(file) | 211 | libeditor::runnables(&file) |
207 | } | 212 | } |
208 | pub fn highlight(&self, file_id: FileId) -> Vec<HighlightedRange> { | 213 | pub fn highlight(&self, file_id: FileId) -> Vec<HighlightedRange> { |
209 | let file = self.imp.file_syntax(file_id); | 214 | let file = self.imp.file_syntax(file_id); |
210 | libeditor::highlight(file) | 215 | libeditor::highlight(&file) |
211 | } | 216 | } |
212 | pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Option<Vec<CompletionItem>> { | 217 | pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Option<Vec<CompletionItem>> { |
213 | let file = self.imp.file_syntax(file_id); | 218 | let file = self.imp.file_syntax(file_id); |
214 | libeditor::scope_completion(file, offset) | 219 | libeditor::scope_completion(&file, offset) |
215 | } | 220 | } |
216 | pub fn assists(&self, file_id: FileId, range: TextRange) -> Vec<SourceChange> { | 221 | pub fn assists(&self, file_id: FileId, range: TextRange) -> Vec<SourceChange> { |
217 | self.imp.assists(file_id, range) | 222 | self.imp.assists(file_id, range) |
diff --git a/crates/libanalysis/src/module_map.rs b/crates/libanalysis/src/module_map.rs index 9acebd6e2..a21f55fff 100644 --- a/crates/libanalysis/src/module_map.rs +++ b/crates/libanalysis/src/module_map.rs | |||
@@ -1,274 +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 | 11 | ||
15 | #[derive(Debug, Default)] | 12 | pub(crate) fn register_queries(reg: &mut QueryRegistry) { |
16 | pub struct ModuleMap { | 13 | reg.add(MODULE_DESCR, "MODULE_DESCR"); |
17 | state: RwLock<State>, | 14 | reg.add(MODULE_TREE, "MODULE_TREE"); |
18 | } | 15 | } |
19 | 16 | ||
20 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 17 | pub(crate) fn module_tree(ctx: QueryCtx) -> Arc<ModuleTreeDescriptor> { |
21 | pub enum ChangeKind { | 18 | ctx.get(MODULE_TREE, ()) |
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 | } | 19 | } |
31 | 20 | ||
32 | #[derive(Clone, Debug, Default)] | 21 | const MODULE_DESCR: Query<FileId, ModuleDescriptor> = Query(30, |ctx, &file_id| { |
33 | struct State { | 22 | let file = file_syntax(ctx, file_id); |
34 | file_resolver: FileResolverImp, | 23 | ModuleDescriptor::new(file.ast()) |
35 | changes: Vec<(FileId, ChangeKind)>, | 24 | }); |
36 | links: Vec<Link>, | 25 | |
37 | } | 26 | const MODULE_TREE: Query<(), ModuleTreeDescriptor> = Query(31, |ctx, _| { |
38 | 27 | let file_set = file_set(ctx); | |
39 | #[derive(Clone, Debug)] | 28 | let mut files = Vec::new(); |
40 | struct Link { | 29 | for &file_id in file_set.0.iter() { |
41 | owner: ModuleId, | 30 | let module_descr = ctx.get(MODULE_DESCR, file_id); |
42 | syntax: SyntaxNode, | 31 | files.push((file_id, module_descr)); |
43 | points_to: Vec<ModuleId>, | 32 | } |
44 | problem: Option<Problem>, | 33 | ModuleTreeDescriptor::new(files.iter().map(|(file_id, descr)| (*file_id, &**descr)), &file_set.1) |
45 | } | 34 | }); |
46 | 35 | ||
47 | #[derive(Clone, Debug)] | 36 | #[cfg(test)] |
48 | pub enum Problem { | 37 | mod tests { |
49 | UnresolvedModule { | 38 | use std::collections::HashMap; |
50 | candidate: RelativePathBuf, | 39 | use im; |
51 | }, | 40 | use relative_path::{RelativePath, RelativePathBuf}; |
52 | NotDirOwner { | 41 | use { |
53 | move_to: RelativePathBuf, | 42 | db::{Db}, |
54 | candidate: RelativePathBuf, | 43 | imp::FileResolverImp, |
55 | } | 44 | FileId, FileResolver, |
56 | } | 45 | }; |
57 | 46 | use super::*; | |
58 | impl ModuleMap { | 47 | |
59 | pub fn new() -> ModuleMap { | 48 | #[derive(Debug)] |
60 | Default::default() | 49 | struct FileMap(im::HashMap<FileId, RelativePathBuf>); |
61 | } | 50 | |
62 | pub fn update_file(&mut self, file_id: FileId, change_kind: ChangeKind) { | 51 | impl FileResolver for FileMap { |
63 | self.state.get_mut().changes.push((file_id, change_kind)); | 52 | fn file_stem(&self, file_id: FileId) -> String { |
64 | } | 53 | self.0[&file_id].file_stem().unwrap().to_string() |
65 | pub(crate) fn set_file_resolver(&mut self, file_resolver: FileResolverImp) { | 54 | } |
66 | self.state.get_mut().file_resolver = file_resolver; | 55 | fn resolve(&self, file_id: FileId, rel: &RelativePath) -> Option<FileId> { |
67 | } | 56 | let path = self.0[&file_id].join(rel).normalize(); |
68 | pub fn module2file(&self, m: ModuleId) -> FileId { | 57 | self.0.iter() |
69 | m.0 | 58 | .filter_map(|&(id, ref p)| Some(id).filter(|_| p == &path)) |
70 | } | 59 | .next() |
71 | pub fn file2module(&self, file_id: FileId) -> ModuleId { | 60 | } |
72 | ModuleId(file_id) | ||
73 | } | ||
74 | pub fn child_module_by_name<'a>( | ||
75 | &self, | ||
76 | parent_mod: ModuleId, | ||
77 | child_mod: &str, | ||
78 | syntax_provider: &SyntaxProvider, | ||
79 | ) -> Vec<ModuleId> { | ||
80 | self.links(syntax_provider) | ||
81 | .links | ||
82 | .iter() | ||
83 | .filter(|link| link.owner == parent_mod) | ||
84 | .filter(|link| link.name() == child_mod) | ||
85 | .filter_map(|it| it.points_to.first()) | ||
86 | .map(|&it| it) | ||
87 | .collect() | ||
88 | } | ||
89 | |||
90 | pub fn parent_modules( | ||
91 | &self, | ||
92 | m: ModuleId, | ||
93 | syntax_provider: &SyntaxProvider, | ||
94 | ) -> Vec<(ModuleId, SmolStr, SyntaxNode)> { | ||
95 | let mut res = Vec::new(); | ||
96 | self.for_each_parent_link(m, syntax_provider, |link| { | ||
97 | res.push( | ||
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 | 147 | ||
248 | let mod_name = file_resolver.file_stem(self.owner.0); | 148 | f.change_file(lib, "mod bar;"); |
249 | let is_dir_owner = | 149 | f.check_parent_modules(foo, &[], &[("MODULE_DESCR", 1)]); |
250 | mod_name == "mod" || mod_name == "lib" || mod_name == "main"; | ||
251 | 150 | ||
252 | let file_mod = RelativePathBuf::from(format!("../{}.rs", self.name())); | 151 | f.change_file(lib, "mod foo;"); |
253 | let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", self.name())); | 152 | f.check_parent_modules(foo, &[lib], &[("MODULE_DESCR", 1)]); |
254 | if is_dir_owner { | 153 | |
255 | self.points_to = [&file_mod, &dir_mod].iter() | 154 | f.remove_file(lib); |
256 | .filter_map(|path| file_resolver.resolve(self.owner.0, path)) | 155 | f.check_parent_modules(foo, &[], &[("MODULE_DESCR", 0)]); |
257 | .map(ModuleId) | ||
258 | .collect(); | ||
259 | self.problem = if self.points_to.is_empty() { | ||
260 | Some(Problem::UnresolvedModule { | ||
261 | candidate: file_mod, | ||
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, | ||
271 | }); | ||
272 | } | ||
273 | } | 156 | } |
274 | } | 157 | } |
diff --git a/crates/libanalysis/src/queries.rs b/crates/libanalysis/src/queries.rs new file mode 100644 index 000000000..0b60316e6 --- /dev/null +++ b/crates/libanalysis/src/queries.rs | |||
@@ -0,0 +1,39 @@ | |||
1 | use std::sync::Arc; | ||
2 | use libsyntax2::File; | ||
3 | use libeditor::LineIndex; | ||
4 | use { | ||
5 | FileId, | ||
6 | db::{Query, QueryCtx, QueryRegistry}, | ||
7 | symbol_index::SymbolIndex, | ||
8 | }; | ||
9 | |||
10 | pub(crate) use db::{file_text, file_set}; | ||
11 | |||
12 | pub(crate) fn file_syntax(ctx: QueryCtx, file_id: FileId) -> File { | ||
13 | (&*ctx.get(FILE_SYNTAX, file_id)).clone() | ||
14 | } | ||
15 | pub(crate) fn file_lines(ctx: QueryCtx, file_id: FileId) -> Arc<LineIndex> { | ||
16 | ctx.get(FILE_LINES, file_id) | ||
17 | } | ||
18 | pub(crate) fn file_symbols(ctx: QueryCtx, file_id: FileId) -> Arc<SymbolIndex> { | ||
19 | ctx.get(FILE_SYMBOLS, file_id) | ||
20 | } | ||
21 | |||
22 | const FILE_SYNTAX: Query<FileId, File> = Query(16, |ctx, file_id: &FileId| { | ||
23 | let text = file_text(ctx, *file_id); | ||
24 | File::parse(&*text) | ||
25 | }); | ||
26 | const FILE_LINES: Query<FileId, LineIndex> = Query(17, |ctx, file_id: &FileId| { | ||
27 | let text = file_text(ctx, *file_id); | ||
28 | LineIndex::new(&*text) | ||
29 | }); | ||
30 | const FILE_SYMBOLS: Query<FileId, SymbolIndex> = Query(18, |ctx, file_id: &FileId| { | ||
31 | let syntax = file_syntax(ctx, *file_id); | ||
32 | SymbolIndex::for_file(*file_id, syntax) | ||
33 | }); | ||
34 | |||
35 | pub(crate) fn register_queries(reg: &mut QueryRegistry) { | ||
36 | reg.add(FILE_SYNTAX, "FILE_SYNTAX"); | ||
37 | reg.add(FILE_LINES, "FILE_LINES"); | ||
38 | reg.add(FILE_SYMBOLS, "FILE_SYMBOLS"); | ||
39 | } | ||
diff --git a/crates/libanalysis/src/roots.rs b/crates/libanalysis/src/roots.rs index 629a697c5..191d0d821 100644 --- a/crates/libanalysis/src/roots.rs +++ b/crates/libanalysis/src/roots.rs | |||
@@ -1,6 +1,5 @@ | |||
1 | use std::{ | 1 | use std::{ |
2 | collections::HashMap, | 2 | collections::HashMap, |
3 | time::Instant, | ||
4 | sync::Arc, | 3 | sync::Arc, |
5 | panic, | 4 | panic, |
6 | }; | 5 | }; |
@@ -13,94 +12,82 @@ use libsyntax2::File; | |||
13 | use { | 12 | use { |
14 | FileId, | 13 | FileId, |
15 | imp::FileResolverImp, | 14 | imp::FileResolverImp, |
16 | module_map::{ModuleMap, ChangeKind}, | ||
17 | symbol_index::SymbolIndex, | 15 | symbol_index::SymbolIndex, |
16 | descriptors::{ModuleDescriptor, ModuleTreeDescriptor}, | ||
17 | db::Db, | ||
18 | }; | 18 | }; |
19 | 19 | ||
20 | pub(crate) trait SourceRoot { | 20 | pub(crate) trait SourceRoot { |
21 | fn contains(&self, file_id: FileId) -> bool; | 21 | fn contains(&self, file_id: FileId) -> bool; |
22 | fn module_map(&self) -> &ModuleMap; | 22 | fn module_tree(&self) -> Arc<ModuleTreeDescriptor>; |
23 | fn lines(&self, file_id: FileId) -> &LineIndex; | 23 | fn lines(&self, file_id: FileId) -> Arc<LineIndex>; |
24 | fn syntax(&self, file_id: FileId) -> &File; | 24 | fn syntax(&self, file_id: FileId) -> File; |
25 | fn symbols<'a>(&'a self, acc: &mut Vec<&'a SymbolIndex>); | 25 | fn symbols(&self, acc: &mut Vec<Arc<SymbolIndex>>); |
26 | } | 26 | } |
27 | 27 | ||
28 | #[derive(Clone, Default, Debug)] | 28 | #[derive(Default, Debug)] |
29 | pub(crate) struct WritableSourceRoot { | 29 | pub(crate) struct WritableSourceRoot { |
30 | file_map: HashMap<FileId, Arc<(FileData, OnceCell<SymbolIndex>)>>, | 30 | db: Db, |
31 | module_map: ModuleMap, | ||
32 | } | 31 | } |
33 | 32 | ||
34 | impl WritableSourceRoot { | 33 | impl WritableSourceRoot { |
35 | pub fn update(&mut self, file_id: FileId, text: Option<String>) { | 34 | pub fn apply_changes( |
36 | let change_kind = if self.file_map.remove(&file_id).is_some() { | 35 | &self, |
37 | if text.is_some() { | 36 | changes: &mut dyn Iterator<Item=(FileId, Option<String>)>, |
38 | ChangeKind::Update | 37 | file_resolver: Option<FileResolverImp>, |
39 | } else { | 38 | ) -> WritableSourceRoot { |
40 | ChangeKind::Delete | 39 | let resolver_changed = file_resolver.is_some(); |
40 | let mut changed_files = Vec::new(); | ||
41 | let mut new_state = self.db.state().clone(); | ||
42 | |||
43 | for (file_id, text) in changes { | ||
44 | changed_files.push(file_id); | ||
45 | match text { | ||
46 | Some(text) => { | ||
47 | new_state.file_map.insert(file_id, Arc::new(text)); | ||
48 | }, | ||
49 | None => { | ||
50 | new_state.file_map.remove(&file_id); | ||
51 | } | ||
41 | } | 52 | } |
42 | } else { | ||
43 | ChangeKind::Insert | ||
44 | }; | ||
45 | self.module_map.update_file(file_id, change_kind); | ||
46 | self.file_map.remove(&file_id); | ||
47 | if let Some(text) = text { | ||
48 | let file_data = FileData::new(text); | ||
49 | self.file_map.insert(file_id, Arc::new((file_data, Default::default()))); | ||
50 | } | 53 | } |
51 | } | 54 | if let Some(file_resolver) = file_resolver { |
52 | pub fn set_file_resolver(&mut self, file_resolver: FileResolverImp) { | 55 | new_state.file_resolver = file_resolver |
53 | self.module_map.set_file_resolver(file_resolver) | 56 | } |
54 | } | 57 | WritableSourceRoot { |
55 | pub fn reindex(&self) { | 58 | db: self.db.with_changes(new_state, &changed_files, resolver_changed) |
56 | let now = Instant::now(); | ||
57 | self.file_map | ||
58 | .par_iter() | ||
59 | .for_each(|(&file_id, data)| { | ||
60 | symbols(file_id, data); | ||
61 | }); | ||
62 | info!("parallel indexing took {:?}", now.elapsed()); | ||
63 | |||
64 | } | ||
65 | fn data(&self, file_id: FileId) -> &FileData { | ||
66 | match self.file_map.get(&file_id) { | ||
67 | Some(data) => &data.0, | ||
68 | None => panic!("unknown file: {:?}", file_id), | ||
69 | } | 59 | } |
70 | } | 60 | } |
71 | } | 61 | } |
72 | 62 | ||
73 | impl SourceRoot for WritableSourceRoot { | 63 | impl SourceRoot for WritableSourceRoot { |
74 | fn contains(&self, file_id: FileId) -> bool { | 64 | fn module_tree(&self) -> Arc<ModuleTreeDescriptor> { |
75 | self.file_map.contains_key(&file_id) | 65 | self.db.make_query(::module_map::module_tree) |
76 | } | 66 | } |
77 | fn module_map(&self) -> &ModuleMap { | 67 | |
78 | &self.module_map | 68 | fn contains(&self, file_id: FileId) -> bool { |
69 | self.db.state().file_map.contains_key(&file_id) | ||
79 | } | 70 | } |
80 | fn lines(&self, file_id: FileId) -> &LineIndex { | 71 | fn lines(&self, file_id: FileId) -> Arc<LineIndex> { |
81 | self.data(file_id).lines() | 72 | self.db.make_query(|ctx| ::queries::file_lines(ctx, file_id)) |
82 | } | 73 | } |
83 | fn syntax(&self, file_id: FileId) -> &File { | 74 | fn syntax(&self, file_id: FileId) -> File { |
84 | self.data(file_id).syntax() | 75 | self.db.make_query(|ctx| ::queries::file_syntax(ctx, file_id)) |
85 | } | 76 | } |
86 | fn symbols<'a>(&'a self, acc: &mut Vec<&'a SymbolIndex>) { | 77 | fn symbols<'a>(&'a self, acc: &mut Vec<Arc<SymbolIndex>>) { |
87 | acc.extend( | 78 | self.db.make_query(|ctx| { |
88 | self.file_map | 79 | let file_set = ::queries::file_set(ctx); |
89 | .iter() | 80 | let syms = file_set.0.iter() |
90 | .map(|(&file_id, data)| symbols(file_id, data)) | 81 | .map(|file_id| ::queries::file_symbols(ctx, *file_id)); |
91 | ) | 82 | acc.extend(syms); |
83 | }); | ||
92 | } | 84 | } |
93 | } | 85 | } |
94 | 86 | ||
95 | fn symbols(file_id: FileId, (data, symbols): &(FileData, OnceCell<SymbolIndex>)) -> &SymbolIndex { | ||
96 | let syntax = data.syntax_transient(); | ||
97 | symbols.get_or_init(|| SymbolIndex::for_file(file_id, syntax)) | ||
98 | } | ||
99 | |||
100 | #[derive(Debug)] | 87 | #[derive(Debug)] |
101 | struct FileData { | 88 | struct FileData { |
102 | text: String, | 89 | text: String, |
103 | lines: OnceCell<LineIndex>, | 90 | lines: OnceCell<Arc<LineIndex>>, |
104 | syntax: OnceCell<File>, | 91 | syntax: OnceCell<File>, |
105 | } | 92 | } |
106 | 93 | ||
@@ -112,8 +99,8 @@ impl FileData { | |||
112 | lines: OnceCell::new(), | 99 | lines: OnceCell::new(), |
113 | } | 100 | } |
114 | } | 101 | } |
115 | fn lines(&self) -> &LineIndex { | 102 | fn lines(&self) -> &Arc<LineIndex> { |
116 | self.lines.get_or_init(|| LineIndex::new(&self.text)) | 103 | self.lines.get_or_init(|| Arc::new(LineIndex::new(&self.text))) |
117 | } | 104 | } |
118 | fn syntax(&self) -> &File { | 105 | fn syntax(&self) -> &File { |
119 | let text = &self.text; | 106 | let text = &self.text; |
@@ -126,40 +113,41 @@ impl FileData { | |||
126 | } | 113 | } |
127 | } | 114 | } |
128 | } | 115 | } |
129 | fn syntax_transient(&self) -> File { | ||
130 | self.syntax.get().map(|s| s.clone()) | ||
131 | .unwrap_or_else(|| File::parse(&self.text)) | ||
132 | } | ||
133 | } | 116 | } |
134 | 117 | ||
135 | #[derive(Debug)] | 118 | #[derive(Debug)] |
136 | pub(crate) struct ReadonlySourceRoot { | 119 | pub(crate) struct ReadonlySourceRoot { |
137 | symbol_index: SymbolIndex, | 120 | symbol_index: Arc<SymbolIndex>, |
138 | file_map: HashMap<FileId, FileData>, | 121 | file_map: HashMap<FileId, FileData>, |
139 | module_map: ModuleMap, | 122 | module_tree: Arc<ModuleTreeDescriptor>, |
140 | } | 123 | } |
141 | 124 | ||
142 | impl ReadonlySourceRoot { | 125 | impl ReadonlySourceRoot { |
143 | pub(crate) fn new(files: Vec<(FileId, String)>, file_resolver: FileResolverImp) -> ReadonlySourceRoot { | 126 | pub(crate) fn new(files: Vec<(FileId, String)>, file_resolver: FileResolverImp) -> ReadonlySourceRoot { |
144 | let mut module_map = ModuleMap::new(); | 127 | let modules = files.par_iter() |
145 | module_map.set_file_resolver(file_resolver); | 128 | .map(|(file_id, text)| { |
146 | let symbol_index = SymbolIndex::for_files( | 129 | let syntax = File::parse(text); |
147 | files.par_iter().map(|(file_id, text)| { | 130 | let mod_descr = ModuleDescriptor::new(syntax.ast()); |
148 | (*file_id, File::parse(text)) | 131 | (*file_id, syntax, mod_descr) |
149 | }) | 132 | }) |
133 | .collect::<Vec<_>>(); | ||
134 | let module_tree = ModuleTreeDescriptor::new( | ||
135 | modules.iter().map(|it| (it.0, &it.2)), | ||
136 | &file_resolver, | ||
137 | ); | ||
138 | |||
139 | let symbol_index = SymbolIndex::for_files( | ||
140 | modules.par_iter().map(|it| (it.0, it.1.clone())) | ||
150 | ); | 141 | ); |
151 | let file_map: HashMap<FileId, FileData> = files | 142 | let file_map: HashMap<FileId, FileData> = files |
152 | .into_iter() | 143 | .into_iter() |
153 | .map(|(id, text)| { | 144 | .map(|(id, text)| (id, FileData::new(text))) |
154 | module_map.update_file(id, ChangeKind::Insert); | ||
155 | (id, FileData::new(text)) | ||
156 | }) | ||
157 | .collect(); | 145 | .collect(); |
158 | 146 | ||
159 | ReadonlySourceRoot { | 147 | ReadonlySourceRoot { |
160 | symbol_index, | 148 | symbol_index: Arc::new(symbol_index), |
161 | file_map, | 149 | file_map, |
162 | module_map, | 150 | module_tree: Arc::new(module_tree), |
163 | } | 151 | } |
164 | } | 152 | } |
165 | 153 | ||
@@ -172,19 +160,19 @@ impl ReadonlySourceRoot { | |||
172 | } | 160 | } |
173 | 161 | ||
174 | impl SourceRoot for ReadonlySourceRoot { | 162 | impl SourceRoot for ReadonlySourceRoot { |
163 | fn module_tree(&self) -> Arc<ModuleTreeDescriptor> { | ||
164 | Arc::clone(&self.module_tree) | ||
165 | } | ||
175 | fn contains(&self, file_id: FileId) -> bool { | 166 | fn contains(&self, file_id: FileId) -> bool { |
176 | self.file_map.contains_key(&file_id) | 167 | self.file_map.contains_key(&file_id) |
177 | } | 168 | } |
178 | fn module_map(&self) -> &ModuleMap { | 169 | fn lines(&self, file_id: FileId) -> Arc<LineIndex> { |
179 | &self.module_map | 170 | Arc::clone(self.data(file_id).lines()) |
180 | } | ||
181 | fn lines(&self, file_id: FileId) -> &LineIndex { | ||
182 | self.data(file_id).lines() | ||
183 | } | 171 | } |
184 | fn syntax(&self, file_id: FileId) -> &File { | 172 | fn syntax(&self, file_id: FileId) -> File { |
185 | self.data(file_id).syntax() | 173 | self.data(file_id).syntax().clone() |
186 | } | 174 | } |
187 | fn symbols<'a>(&'a self, acc: &mut Vec<&'a SymbolIndex>) { | 175 | fn symbols(&self, acc: &mut Vec<Arc<SymbolIndex>>) { |
188 | acc.push(&self.symbol_index) | 176 | acc.push(Arc::clone(&self.symbol_index)) |
189 | } | 177 | } |
190 | } | 178 | } |
diff --git a/crates/libanalysis/src/symbol_index.rs b/crates/libanalysis/src/symbol_index.rs index 4c93761aa..d22187ac0 100644 --- a/crates/libanalysis/src/symbol_index.rs +++ b/crates/libanalysis/src/symbol_index.rs | |||
@@ -1,3 +1,7 @@ | |||
1 | use std::{ | ||
2 | sync::Arc, | ||
3 | hash::{Hash, Hasher}, | ||
4 | }; | ||
1 | use libeditor::{FileSymbol, file_symbols}; | 5 | use libeditor::{FileSymbol, file_symbols}; |
2 | use libsyntax2::{ | 6 | use libsyntax2::{ |
3 | File, | 7 | File, |
@@ -13,6 +17,12 @@ pub(crate) struct SymbolIndex { | |||
13 | map: fst::Map, | 17 | map: fst::Map, |
14 | } | 18 | } |
15 | 19 | ||
20 | impl Hash for SymbolIndex { | ||
21 | fn hash<H: Hasher>(&self, hasher: &mut H) { | ||
22 | self.symbols.hash(hasher) | ||
23 | } | ||
24 | } | ||
25 | |||
16 | impl SymbolIndex { | 26 | impl SymbolIndex { |
17 | pub(crate) fn for_files(files: impl ParallelIterator<Item=(FileId, File)>) -> SymbolIndex { | 27 | pub(crate) fn for_files(files: impl ParallelIterator<Item=(FileId, File)>) -> SymbolIndex { |
18 | let mut symbols = files | 28 | let mut symbols = files |
@@ -43,7 +53,7 @@ impl SymbolIndex { | |||
43 | impl Query { | 53 | impl Query { |
44 | pub(crate) fn search( | 54 | pub(crate) fn search( |
45 | self, | 55 | self, |
46 | indices: &[&SymbolIndex], | 56 | indices: &[Arc<SymbolIndex>], |
47 | token: &JobToken, | 57 | token: &JobToken, |
48 | ) -> Vec<(FileId, FileSymbol)> { | 58 | ) -> Vec<(FileId, FileSymbol)> { |
49 | 59 | ||
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/libeditor/src/line_index.rs b/crates/libeditor/src/line_index.rs index 801726aa5..9cd8da3a8 100644 --- a/crates/libeditor/src/line_index.rs +++ b/crates/libeditor/src/line_index.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | use superslice::Ext; | 1 | use superslice::Ext; |
2 | use ::TextUnit; | 2 | use ::TextUnit; |
3 | 3 | ||
4 | #[derive(Clone, Debug)] | 4 | #[derive(Clone, Debug, Hash)] |
5 | pub struct LineIndex { | 5 | pub struct LineIndex { |
6 | newlines: Vec<TextUnit>, | 6 | newlines: Vec<TextUnit>, |
7 | } | 7 | } |
diff --git a/crates/libeditor/src/symbols.rs b/crates/libeditor/src/symbols.rs index 28b86c004..2f9cc9233 100644 --- a/crates/libeditor/src/symbols.rs +++ b/crates/libeditor/src/symbols.rs | |||
@@ -17,7 +17,7 @@ pub struct StructureNode { | |||
17 | pub kind: SyntaxKind, | 17 | pub kind: SyntaxKind, |
18 | } | 18 | } |
19 | 19 | ||
20 | #[derive(Debug, Clone)] | 20 | #[derive(Debug, Clone, Hash)] |
21 | pub struct FileSymbol { | 21 | pub struct FileSymbol { |
22 | pub name: SmolStr, | 22 | pub name: SmolStr, |
23 | pub node_range: TextRange, | 23 | pub node_range: TextRange, |
diff --git a/crates/libsyntax2/src/lib.rs b/crates/libsyntax2/src/lib.rs index 886195660..eb271762e 100644 --- a/crates/libsyntax2/src/lib.rs +++ b/crates/libsyntax2/src/lib.rs | |||
@@ -61,7 +61,7 @@ use { | |||
61 | yellow::{GreenNode, SyntaxRoot}, | 61 | yellow::{GreenNode, SyntaxRoot}, |
62 | }; | 62 | }; |
63 | 63 | ||
64 | #[derive(Clone, Debug)] | 64 | #[derive(Clone, Debug, Hash)] |
65 | pub struct File { | 65 | pub struct File { |
66 | root: SyntaxNode | 66 | root: SyntaxNode |
67 | } | 67 | } |
diff --git a/crates/salsa/Cargo.toml b/crates/salsa/Cargo.toml new file mode 100644 index 000000000..9eb83234f --- /dev/null +++ b/crates/salsa/Cargo.toml | |||
@@ -0,0 +1,8 @@ | |||
1 | [package] | ||
2 | name = "salsa" | ||
3 | version = "0.1.0" | ||
4 | authors = ["Aleksey Kladov <[email protected]>"] | ||
5 | |||
6 | [dependencies] | ||
7 | parking_lot = "0.6.3" | ||
8 | im = "12.0.0" | ||
diff --git a/crates/salsa/src/lib.rs b/crates/salsa/src/lib.rs new file mode 100644 index 000000000..35deed374 --- /dev/null +++ b/crates/salsa/src/lib.rs | |||
@@ -0,0 +1,293 @@ | |||
1 | extern crate im; | ||
2 | extern crate parking_lot; | ||
3 | |||
4 | use std::{ | ||
5 | sync::Arc, | ||
6 | collections::{HashSet, HashMap}, | ||
7 | cell::RefCell, | ||
8 | }; | ||
9 | use parking_lot::Mutex; | ||
10 | |||
11 | pub type GroundQueryFn<T, D> = Box<Fn(&T, &D) -> (D, OutputFingerprint) + Send + Sync + 'static>; | ||
12 | pub type QueryFn<T, D> = Box<Fn(&QueryCtx<T, D>, &D) -> (D, OutputFingerprint) + Send + Sync + 'static>; | ||
13 | |||
14 | #[derive(Debug)] | ||
15 | pub struct Db<T, D> { | ||
16 | db: Arc<DbState<T, D>>, | ||
17 | query_config: Arc<QueryConfig<T, D>>, | ||
18 | } | ||
19 | |||
20 | pub struct QueryConfig<T, D> { | ||
21 | ground_fn: HashMap<QueryTypeId, GroundQueryFn<T, D>>, | ||
22 | query_fn: HashMap<QueryTypeId, QueryFn<T, D>>, | ||
23 | } | ||
24 | |||
25 | impl<T, D> ::std::fmt::Debug for QueryConfig<T, D> { | ||
26 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { | ||
27 | ::std::fmt::Display::fmt("QueryConfig { ... }", f) | ||
28 | } | ||
29 | } | ||
30 | |||
31 | #[derive(Debug)] | ||
32 | struct DbState<T, D> { | ||
33 | ground_data: T, | ||
34 | gen: Gen, | ||
35 | graph: Mutex<im::HashMap<QueryId, (Gen, Arc<QueryRecord<D>>)>>, | ||
36 | } | ||
37 | |||
38 | #[derive(Debug)] | ||
39 | struct QueryRecord<D> { | ||
40 | params: D, | ||
41 | output: D, | ||
42 | output_fingerprint: OutputFingerprint, | ||
43 | deps: Vec<(QueryId, OutputFingerprint)>, | ||
44 | } | ||
45 | |||
46 | impl<T, D> DbState<T, D> { | ||
47 | fn record( | ||
48 | &self, | ||
49 | query_id: QueryId, | ||
50 | params: D, | ||
51 | output: D, | ||
52 | output_fingerprint: OutputFingerprint, | ||
53 | deps: Vec<(QueryId, OutputFingerprint)>, | ||
54 | ) { | ||
55 | let gen = self.gen; | ||
56 | let record = QueryRecord { | ||
57 | params, | ||
58 | output, | ||
59 | output_fingerprint, | ||
60 | deps, | ||
61 | }; | ||
62 | self.graph.lock().insert(query_id, (gen, Arc::new(record))); | ||
63 | } | ||
64 | } | ||
65 | |||
66 | impl<T, D> QueryConfig<T, D> { | ||
67 | pub fn new() -> Self { | ||
68 | QueryConfig { | ||
69 | ground_fn: HashMap::new(), | ||
70 | query_fn: HashMap::new(), | ||
71 | } | ||
72 | } | ||
73 | pub fn with_ground_query( | ||
74 | mut self, | ||
75 | query_type: QueryTypeId, | ||
76 | query_fn: GroundQueryFn<T, D> | ||
77 | ) -> Self { | ||
78 | let prev = self.ground_fn.insert(query_type, query_fn); | ||
79 | assert!(prev.is_none()); | ||
80 | self | ||
81 | } | ||
82 | pub fn with_query( | ||
83 | mut self, | ||
84 | query_type: QueryTypeId, | ||
85 | query_fn: QueryFn<T, D>, | ||
86 | ) -> Self { | ||
87 | let prev = self.query_fn.insert(query_type, query_fn); | ||
88 | assert!(prev.is_none()); | ||
89 | self | ||
90 | } | ||
91 | } | ||
92 | |||
93 | pub struct QueryCtx<T, D> { | ||
94 | db: Arc<DbState<T, D>>, | ||
95 | query_config: Arc<QueryConfig<T, D>>, | ||
96 | stack: RefCell<Vec<Vec<(QueryId, OutputFingerprint)>>>, | ||
97 | executed: RefCell<Vec<QueryTypeId>>, | ||
98 | } | ||
99 | |||
100 | impl<T, D> QueryCtx<T, D> | ||
101 | where | ||
102 | D: Clone | ||
103 | { | ||
104 | fn new(db: &Db<T, D>) -> QueryCtx<T, D> { | ||
105 | QueryCtx { | ||
106 | db: Arc::clone(&db.db), | ||
107 | query_config: Arc::clone(&db.query_config), | ||
108 | stack: RefCell::new(vec![Vec::new()]), | ||
109 | executed: RefCell::new(Vec::new()), | ||
110 | } | ||
111 | } | ||
112 | pub fn get( | ||
113 | &self, | ||
114 | query_id: QueryId, | ||
115 | params: D, | ||
116 | ) -> D { | ||
117 | let (res, output_fingerprint) = self.get_inner(query_id, params); | ||
118 | self.record_dep(query_id, output_fingerprint); | ||
119 | res | ||
120 | } | ||
121 | pub fn trace(&self) -> Vec<QueryTypeId> { | ||
122 | ::std::mem::replace(&mut *self.executed.borrow_mut(), Vec::new()) | ||
123 | } | ||
124 | |||
125 | fn get_inner( | ||
126 | &self, | ||
127 | query_id: QueryId, | ||
128 | params: D, | ||
129 | ) -> (D, OutputFingerprint) { | ||
130 | let (gen, record) = { | ||
131 | let guard = self.db.graph.lock(); | ||
132 | match guard.get(&query_id).map(|it| it.clone()){ | ||
133 | None => { | ||
134 | drop(guard); | ||
135 | return self.force(query_id, params); | ||
136 | }, | ||
137 | Some(it) => it, | ||
138 | } | ||
139 | }; | ||
140 | if gen == self.db.gen { | ||
141 | return (record.output.clone(), record.output_fingerprint) | ||
142 | } | ||
143 | if self.query_config.ground_fn.contains_key(&query_id.0) { | ||
144 | let (invalidated, record) = { | ||
145 | let guard = self.db.graph.lock(); | ||
146 | let (gen, ref record) = guard[&query_id]; | ||
147 | (gen == INVALIDATED, record.clone()) | ||
148 | }; | ||
149 | if invalidated { | ||
150 | return self.force(query_id, params); | ||
151 | } else { | ||
152 | return (record.output.clone(), record.output_fingerprint); | ||
153 | } | ||
154 | } | ||
155 | for (dep_query_id, prev_fingerprint) in record.deps.iter().cloned() { | ||
156 | let dep_params: D = { | ||
157 | let guard = self.db.graph.lock(); | ||
158 | guard[&dep_query_id] | ||
159 | .1 | ||
160 | .params | ||
161 | .clone() | ||
162 | }; | ||
163 | if prev_fingerprint != self.get_inner(dep_query_id, dep_params).1 { | ||
164 | return self.force(query_id, params) | ||
165 | } | ||
166 | } | ||
167 | let gen = self.db.gen; | ||
168 | { | ||
169 | let mut guard = self.db.graph.lock(); | ||
170 | guard[&query_id].0 = gen; | ||
171 | } | ||
172 | (record.output.clone(), record.output_fingerprint) | ||
173 | } | ||
174 | fn force( | ||
175 | &self, | ||
176 | query_id: QueryId, | ||
177 | params: D, | ||
178 | ) -> (D, OutputFingerprint) { | ||
179 | self.executed.borrow_mut().push(query_id.0); | ||
180 | self.stack.borrow_mut().push(Vec::new()); | ||
181 | |||
182 | let (res, output_fingerprint) = if let Some(f) = self.query_config.ground_fn.get(&query_id.0) { | ||
183 | f(&self.db.ground_data, ¶ms) | ||
184 | } else if let Some(f) = self.query_config.query_fn.get(&query_id.0) { | ||
185 | f(self, ¶ms) | ||
186 | } else { | ||
187 | panic!("unknown query type: {:?}", query_id.0); | ||
188 | }; | ||
189 | |||
190 | let res: D = res.into(); | ||
191 | |||
192 | let deps = self.stack.borrow_mut().pop().unwrap(); | ||
193 | self.db.record(query_id, params, res.clone(), output_fingerprint, deps); | ||
194 | (res, output_fingerprint) | ||
195 | } | ||
196 | fn record_dep( | ||
197 | &self, | ||
198 | query_id: QueryId, | ||
199 | output_fingerprint: OutputFingerprint, | ||
200 | ) -> () { | ||
201 | let mut stack = self.stack.borrow_mut(); | ||
202 | let deps = stack.last_mut().unwrap(); | ||
203 | deps.push((query_id, output_fingerprint)) | ||
204 | } | ||
205 | } | ||
206 | |||
207 | pub struct Invalidations { | ||
208 | types: HashSet<QueryTypeId>, | ||
209 | ids: Vec<QueryId>, | ||
210 | } | ||
211 | |||
212 | impl Invalidations { | ||
213 | pub fn new() -> Invalidations { | ||
214 | Invalidations { | ||
215 | types: HashSet::new(), | ||
216 | ids: Vec::new(), | ||
217 | } | ||
218 | } | ||
219 | pub fn invalidate( | ||
220 | &mut self, | ||
221 | query_type: QueryTypeId, | ||
222 | params: impl Iterator<Item=InputFingerprint>, | ||
223 | ) { | ||
224 | self.types.insert(query_type); | ||
225 | self.ids.extend(params.map(|it| QueryId(query_type, it))) | ||
226 | } | ||
227 | } | ||
228 | |||
229 | impl<T, D> Db<T, D> | ||
230 | where | ||
231 | D: Clone | ||
232 | { | ||
233 | pub fn new(query_config: QueryConfig<T, D>, ground_data: T) -> Db<T, D> { | ||
234 | Db { | ||
235 | db: Arc::new(DbState { ground_data, gen: Gen(0), graph: Default::default() }), | ||
236 | query_config: Arc::new(query_config), | ||
237 | } | ||
238 | } | ||
239 | pub fn ground_data(&self) -> &T { | ||
240 | &self.db.ground_data | ||
241 | } | ||
242 | pub fn with_ground_data( | ||
243 | &self, | ||
244 | ground_data: T, | ||
245 | invalidations: Invalidations, | ||
246 | ) -> Db<T, D> { | ||
247 | for id in self.query_config.ground_fn.keys() { | ||
248 | assert!( | ||
249 | invalidations.types.contains(id), | ||
250 | "all ground queries must be invalidated" | ||
251 | ); | ||
252 | } | ||
253 | |||
254 | let gen = Gen(self.db.gen.0 + 1); | ||
255 | let mut graph = self.db.graph.lock().clone(); | ||
256 | for id in invalidations.ids { | ||
257 | if let Some((gen, _)) = graph.get_mut(&id) { | ||
258 | *gen = INVALIDATED; | ||
259 | } | ||
260 | } | ||
261 | let graph = Mutex::new(graph); | ||
262 | Db { | ||
263 | db: Arc::new(DbState { ground_data, gen, graph }), | ||
264 | query_config: Arc::clone(&self.query_config) | ||
265 | } | ||
266 | } | ||
267 | pub fn query_ctx(&self) -> QueryCtx<T, D> { | ||
268 | QueryCtx::new(self) | ||
269 | } | ||
270 | pub fn get( | ||
271 | &self, | ||
272 | query_id: QueryId, | ||
273 | params: D, | ||
274 | ) -> (D, Vec<QueryTypeId>) { | ||
275 | let ctx = self.query_ctx(); | ||
276 | let res = ctx.get(query_id, params.into()); | ||
277 | let executed = ::std::mem::replace(&mut *ctx.executed.borrow_mut(), Vec::new()); | ||
278 | (res, executed) | ||
279 | } | ||
280 | } | ||
281 | |||
282 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
283 | struct Gen(u64); | ||
284 | const INVALIDATED: Gen = Gen(!0); | ||
285 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
286 | pub struct InputFingerprint(pub u64); | ||
287 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
288 | pub struct OutputFingerprint(pub u64); | ||
289 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
290 | pub struct QueryTypeId(pub u16); | ||
291 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] | ||
292 | pub struct QueryId(pub QueryTypeId, pub InputFingerprint); | ||
293 | |||
diff --git a/crates/salsa/tests/integration.rs b/crates/salsa/tests/integration.rs new file mode 100644 index 000000000..aed9219be --- /dev/null +++ b/crates/salsa/tests/integration.rs | |||
@@ -0,0 +1,170 @@ | |||
1 | extern crate salsa; | ||
2 | use std::{ | ||
3 | iter::once, | ||
4 | sync::Arc, | ||
5 | collections::hash_map::{HashMap, DefaultHasher}, | ||
6 | any::Any, | ||
7 | hash::{Hash, Hasher}, | ||
8 | }; | ||
9 | |||
10 | type State = HashMap<u32, String>; | ||
11 | type Data = Arc<Any + Send + Sync + 'static>; | ||
12 | const GET_TEXT: salsa::QueryTypeId = salsa::QueryTypeId(1); | ||
13 | const GET_FILES: salsa::QueryTypeId = salsa::QueryTypeId(2); | ||
14 | const FILE_NEWLINES: salsa::QueryTypeId = salsa::QueryTypeId(3); | ||
15 | const TOTAL_NEWLINES: salsa::QueryTypeId = salsa::QueryTypeId(4); | ||
16 | |||
17 | fn mk_ground_query<T, R>( | ||
18 | state: &State, | ||
19 | params: &Data, | ||
20 | f: fn(&State, &T) -> R, | ||
21 | ) -> (Data, salsa::OutputFingerprint) | ||
22 | where | ||
23 | T: 'static, | ||
24 | R: Hash + Send + Sync + 'static, | ||
25 | { | ||
26 | let params = params.downcast_ref().unwrap(); | ||
27 | let result = f(state, params); | ||
28 | let fingerprint = o_print(&result); | ||
29 | (Arc::new(result), fingerprint) | ||
30 | } | ||
31 | |||
32 | fn get<T, R>(db: &salsa::Db<State, Data>, query_type: salsa::QueryTypeId, param: T) -> (Arc<R>, Vec<salsa::QueryTypeId>) | ||
33 | where | ||
34 | T: Hash + Send + Sync + 'static, | ||
35 | R: Send + Sync + 'static, | ||
36 | { | ||
37 | let i_print = i_print(¶m); | ||
38 | let param = Arc::new(param); | ||
39 | let (res, trace) = db.get(salsa::QueryId(query_type, i_print), param); | ||
40 | (res.downcast().unwrap(), trace) | ||
41 | } | ||
42 | |||
43 | struct QueryCtx<'a>(&'a salsa::QueryCtx<State, Data>); | ||
44 | |||
45 | impl<'a> QueryCtx<'a> { | ||
46 | fn get_text(&self, id: u32) -> Arc<String> { | ||
47 | let i_print = i_print(&id); | ||
48 | let text = self.0.get(salsa::QueryId(GET_TEXT, i_print), Arc::new(id)); | ||
49 | text.downcast().unwrap() | ||
50 | } | ||
51 | fn get_files(&self) -> Arc<Vec<u32>> { | ||
52 | let i_print = i_print(&()); | ||
53 | let files = self.0.get(salsa::QueryId(GET_FILES, i_print), Arc::new(())); | ||
54 | let res = files.downcast().unwrap(); | ||
55 | res | ||
56 | } | ||
57 | fn get_n_lines(&self, id: u32) -> usize { | ||
58 | let i_print = i_print(&id); | ||
59 | let n_lines = self.0.get(salsa::QueryId(FILE_NEWLINES, i_print), Arc::new(id)); | ||
60 | *n_lines.downcast().unwrap() | ||
61 | } | ||
62 | } | ||
63 | |||
64 | fn mk_query<T, R>( | ||
65 | query_ctx: &salsa::QueryCtx<State, Data>, | ||
66 | params: &Data, | ||
67 | f: fn(QueryCtx, &T) -> R, | ||
68 | ) -> (Data, salsa::OutputFingerprint) | ||
69 | where | ||
70 | T: 'static, | ||
71 | R: Hash + Send + Sync + 'static, | ||
72 | { | ||
73 | let params: &T = params.downcast_ref().unwrap(); | ||
74 | let query_ctx = QueryCtx(query_ctx); | ||
75 | let result = f(query_ctx, params); | ||
76 | let fingerprint = o_print(&result); | ||
77 | (Arc::new(result), fingerprint) | ||
78 | } | ||
79 | |||
80 | fn mk_queries() -> salsa::QueryConfig<State, Data> { | ||
81 | salsa::QueryConfig::<State, Data>::new() | ||
82 | .with_ground_query(GET_TEXT, Box::new(|state, id| { | ||
83 | mk_ground_query::<u32, String>(state, id, |state, id| state[id].clone()) | ||
84 | })) | ||
85 | .with_ground_query(GET_FILES, Box::new(|state, id| { | ||
86 | mk_ground_query::<(), Vec<u32>>(state, id, |state, &()| state.keys().cloned().collect()) | ||
87 | })) | ||
88 | .with_query(FILE_NEWLINES, Box::new(|query_ctx, id| { | ||
89 | mk_query(query_ctx, id, |query_ctx, &id| { | ||
90 | let text = query_ctx.get_text(id); | ||
91 | text.lines().count() | ||
92 | }) | ||
93 | })) | ||
94 | .with_query(TOTAL_NEWLINES, Box::new(|query_ctx, id| { | ||
95 | mk_query(query_ctx, id, |query_ctx, &()| { | ||
96 | let mut total = 0; | ||
97 | for &id in query_ctx.get_files().iter() { | ||
98 | total += query_ctx.get_n_lines(id) | ||
99 | } | ||
100 | total | ||
101 | }) | ||
102 | })) | ||
103 | } | ||
104 | |||
105 | #[test] | ||
106 | fn test_number_of_lines() { | ||
107 | let mut state = State::new(); | ||
108 | let db = salsa::Db::new(mk_queries(), state.clone()); | ||
109 | let (newlines, trace) = get::<(), usize>(&db, TOTAL_NEWLINES, ()); | ||
110 | assert_eq!(*newlines, 0); | ||
111 | assert_eq!(trace.len(), 2); | ||
112 | let (newlines, trace) = get::<(), usize>(&db, TOTAL_NEWLINES, ()); | ||
113 | assert_eq!(*newlines, 0); | ||
114 | assert_eq!(trace.len(), 0); | ||
115 | |||
116 | state.insert(1, "hello\nworld".to_string()); | ||
117 | let mut inv = salsa::Invalidations::new(); | ||
118 | inv.invalidate(GET_TEXT, once(i_print(&1u32))); | ||
119 | inv.invalidate(GET_FILES, once(i_print(&()))); | ||
120 | let db = db.with_ground_data(state.clone(), inv); | ||
121 | let (newlines, trace) = get::<(), usize>(&db, TOTAL_NEWLINES, ()); | ||
122 | assert_eq!(*newlines, 2); | ||
123 | assert_eq!(trace.len(), 4); | ||
124 | |||
125 | state.insert(2, "spam\neggs".to_string()); | ||
126 | let mut inv = salsa::Invalidations::new(); | ||
127 | inv.invalidate(GET_TEXT, once(i_print(&2u32))); | ||
128 | inv.invalidate(GET_FILES, once(i_print(&()))); | ||
129 | let db = db.with_ground_data(state.clone(), inv); | ||
130 | let (newlines, trace) = get::<(), usize>(&db, TOTAL_NEWLINES, ()); | ||
131 | assert_eq!(*newlines, 4); | ||
132 | assert_eq!(trace.len(), 4); | ||
133 | |||
134 | let mut invs = vec![]; | ||
135 | for i in 0..10 { | ||
136 | let id = i + 10; | ||
137 | invs.push(i_print(&id)); | ||
138 | state.insert(id, "spam".to_string()); | ||
139 | } | ||
140 | let mut inv = salsa::Invalidations::new(); | ||
141 | inv.invalidate(GET_TEXT, invs.into_iter()); | ||
142 | inv.invalidate(GET_FILES, once(i_print(&()))); | ||
143 | let db = db.with_ground_data(state.clone(), inv); | ||
144 | let (newlines, trace) = get::<(), usize>(&db, TOTAL_NEWLINES, ()); | ||
145 | assert_eq!(*newlines, 14); | ||
146 | assert_eq!(trace.len(), 22); | ||
147 | |||
148 | state.insert(15, String::new()); | ||
149 | let mut inv = salsa::Invalidations::new(); | ||
150 | inv.invalidate(GET_TEXT, once(i_print(&15u32))); | ||
151 | inv.invalidate(GET_FILES, once(i_print(&()))); | ||
152 | let db = db.with_ground_data(state.clone(), inv); | ||
153 | let (newlines, trace) = get::<(), usize>(&db, TOTAL_NEWLINES, ()); | ||
154 | assert_eq!(*newlines, 13); | ||
155 | assert_eq!(trace.len(), 4); | ||
156 | } | ||
157 | |||
158 | fn o_print<T: Hash>(x: &T) -> salsa::OutputFingerprint { | ||
159 | let mut hasher = DefaultHasher::new(); | ||
160 | x.hash(&mut hasher); | ||
161 | let hash = hasher.finish(); | ||
162 | salsa::OutputFingerprint(hash) | ||
163 | } | ||
164 | |||
165 | fn i_print<T: Hash>(x: &T) -> salsa::InputFingerprint { | ||
166 | let mut hasher = DefaultHasher::new(); | ||
167 | x.hash(&mut hasher); | ||
168 | let hash = hasher.finish(); | ||
169 | salsa::InputFingerprint(hash) | ||
170 | } | ||
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"] } |