diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-06 14:51:10 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-06 14:51:10 +0000 |
commit | cf0ce14351af03c620aca784ee2c03aad86b866e (patch) | |
tree | bffd84981df9cca1143807796dc6772ddcfe8e0b /crates/ra_hir/src/nameres | |
parent | eaf553dade9a28b41631387d7c88b09fd0ba64e2 (diff) | |
parent | 733383446fc229a35d4432d14c295c5a01e5a87f (diff) |
Merge #429
429: Reorganize hir public API in terms of code model r=matklad a=matklad
Recently, I've been thinking about introducing "object orient code model" API for rust: a set of APIs with types like `Function`, `Module`, etc, with methods like `get_containing_declaration()`, `get_type()`, etc.
Here's how a similar API might look like in .Net land:
https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.semanticmodel?view=roslyn-dotnet
https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.imethodsymbol?view=roslyn-dotnet
The main feature of such API is that it can be powered by different backends. For example, one can imagine a backend based on salsa, and a backend which reads all the data from a specially prepared JSON file. The "OO" bit is interesting mostly in this "can swap implementations via dynamic dispatch" aspect, the actual API could have a more database/ECS flavored feeling.
It's not clear at this moment how exactly should we implement such a dynamically (or if we even need dynamism in the first pace) swapable API in Rust, but I'd love to experiment with this a bit.
For starters, I propose creating a `code_model_api` which contains various definition types and their public methods (mandatory implemented as one-liners, so that the API has a header-file feel). Specifically, I propose that each type is a wrapper around some integer ID, and that all methods of it accept a `&db` argument. The actual impl goes elsewhere: into the db queries or, absent a better place, into the `code_model_api_impl`. In the first commit, I've moved the simplest type, `Crate`, over to this pattern.
I *think* that we, at least initially, will be used types from `code_model_api` *inside* `hir` as well, but this is not required: we might pick a different implementation down the line, while preserving the API.
Long term I'd love to replace the `db: &impl HirDatabase` argument by a `mp: &dyn ModelProvider`, implement `ModelProvider` for `T: HirDatabase`, and move `code_model_api` into the separate crate, which does not depend on `hir`.
@flodiebold you've recently done some `Def`s work, would do you think of this plan? Could it become a good API in the future, or is it just a useless boilerplate duplicating method signatures between `code_model_api` and `code_model_impl`?
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_hir/src/nameres')
-rw-r--r-- | crates/ra_hir/src/nameres/tests.rs | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir/src/nameres/tests.rs new file mode 100644 index 000000000..dcbe65aec --- /dev/null +++ b/crates/ra_hir/src/nameres/tests.rs | |||
@@ -0,0 +1,273 @@ | |||
1 | use std::sync::Arc; | ||
2 | |||
3 | use salsa::Database; | ||
4 | use ra_db::{FilesDatabase, CrateGraph}; | ||
5 | use relative_path::RelativePath; | ||
6 | use test_utils::assert_eq_text; | ||
7 | |||
8 | use crate::{ | ||
9 | self as hir, | ||
10 | db::HirDatabase, | ||
11 | mock::MockDatabase, | ||
12 | }; | ||
13 | |||
14 | fn item_map(fixture: &str) -> (Arc<hir::ItemMap>, hir::ModuleId) { | ||
15 | let (db, pos) = MockDatabase::with_position(fixture); | ||
16 | let source_root = db.file_source_root(pos.file_id); | ||
17 | let module = hir::source_binder::module_from_position(&db, pos) | ||
18 | .unwrap() | ||
19 | .unwrap(); | ||
20 | let module_id = module.def_id.loc(&db).module_id; | ||
21 | (db.item_map(source_root).unwrap(), module_id) | ||
22 | } | ||
23 | |||
24 | fn check_module_item_map(map: &hir::ItemMap, module_id: hir::ModuleId, expected: &str) { | ||
25 | let mut lines = map.per_module[&module_id] | ||
26 | .items | ||
27 | .iter() | ||
28 | .map(|(name, res)| format!("{}: {}", name, dump_resolution(res))) | ||
29 | .collect::<Vec<_>>(); | ||
30 | lines.sort(); | ||
31 | let actual = lines.join("\n"); | ||
32 | let expected = expected | ||
33 | .trim() | ||
34 | .lines() | ||
35 | .map(|it| it.trim()) | ||
36 | .collect::<Vec<_>>() | ||
37 | .join("\n"); | ||
38 | assert_eq_text!(&actual, &expected); | ||
39 | |||
40 | fn dump_resolution(resolution: &hir::Resolution) -> &'static str { | ||
41 | match ( | ||
42 | resolution.def_id.types.is_some(), | ||
43 | resolution.def_id.values.is_some(), | ||
44 | ) { | ||
45 | (true, true) => "t v", | ||
46 | (true, false) => "t", | ||
47 | (false, true) => "v", | ||
48 | (false, false) => "_", | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | |||
53 | #[test] | ||
54 | fn item_map_smoke_test() { | ||
55 | let (item_map, module_id) = item_map( | ||
56 | " | ||
57 | //- /lib.rs | ||
58 | mod foo; | ||
59 | |||
60 | use crate::foo::bar::Baz; | ||
61 | <|> | ||
62 | |||
63 | //- /foo/mod.rs | ||
64 | pub mod bar; | ||
65 | |||
66 | //- /foo/bar.rs | ||
67 | pub struct Baz; | ||
68 | ", | ||
69 | ); | ||
70 | check_module_item_map( | ||
71 | &item_map, | ||
72 | module_id, | ||
73 | " | ||
74 | Baz: t v | ||
75 | foo: t | ||
76 | ", | ||
77 | ); | ||
78 | } | ||
79 | |||
80 | #[test] | ||
81 | fn item_map_contains_items_from_expansions() { | ||
82 | let (item_map, module_id) = item_map( | ||
83 | " | ||
84 | //- /lib.rs | ||
85 | mod foo; | ||
86 | |||
87 | use crate::foo::bar::Baz; | ||
88 | <|> | ||
89 | |||
90 | //- /foo/mod.rs | ||
91 | pub mod bar; | ||
92 | |||
93 | //- /foo/bar.rs | ||
94 | salsa::query_group! { | ||
95 | trait Baz {} | ||
96 | } | ||
97 | ", | ||
98 | ); | ||
99 | check_module_item_map( | ||
100 | &item_map, | ||
101 | module_id, | ||
102 | " | ||
103 | Baz: t | ||
104 | foo: t | ||
105 | ", | ||
106 | ); | ||
107 | } | ||
108 | |||
109 | #[test] | ||
110 | fn item_map_using_self() { | ||
111 | let (item_map, module_id) = item_map( | ||
112 | " | ||
113 | //- /lib.rs | ||
114 | mod foo; | ||
115 | use crate::foo::bar::Baz::{self}; | ||
116 | <|> | ||
117 | //- /foo/mod.rs | ||
118 | pub mod bar; | ||
119 | //- /foo/bar.rs | ||
120 | pub struct Baz; | ||
121 | ", | ||
122 | ); | ||
123 | check_module_item_map( | ||
124 | &item_map, | ||
125 | module_id, | ||
126 | " | ||
127 | Baz: t v | ||
128 | foo: t | ||
129 | ", | ||
130 | ); | ||
131 | } | ||
132 | |||
133 | #[test] | ||
134 | fn item_map_across_crates() { | ||
135 | let (mut db, sr) = MockDatabase::with_files( | ||
136 | " | ||
137 | //- /main.rs | ||
138 | use test_crate::Baz; | ||
139 | |||
140 | //- /lib.rs | ||
141 | pub struct Baz; | ||
142 | ", | ||
143 | ); | ||
144 | let main_id = sr.files[RelativePath::new("/main.rs")]; | ||
145 | let lib_id = sr.files[RelativePath::new("/lib.rs")]; | ||
146 | |||
147 | let mut crate_graph = CrateGraph::default(); | ||
148 | let main_crate = crate_graph.add_crate_root(main_id); | ||
149 | let lib_crate = crate_graph.add_crate_root(lib_id); | ||
150 | crate_graph.add_dep(main_crate, "test_crate".into(), lib_crate); | ||
151 | |||
152 | db.set_crate_graph(crate_graph); | ||
153 | |||
154 | let source_root = db.file_source_root(main_id); | ||
155 | let module = hir::source_binder::module_from_file_id(&db, main_id) | ||
156 | .unwrap() | ||
157 | .unwrap(); | ||
158 | let module_id = module.def_id.loc(&db).module_id; | ||
159 | let item_map = db.item_map(source_root).unwrap(); | ||
160 | |||
161 | check_module_item_map( | ||
162 | &item_map, | ||
163 | module_id, | ||
164 | " | ||
165 | Baz: t v | ||
166 | test_crate: t | ||
167 | ", | ||
168 | ); | ||
169 | } | ||
170 | |||
171 | #[test] | ||
172 | fn typing_inside_a_function_should_not_invalidate_item_map() { | ||
173 | let (mut db, pos) = MockDatabase::with_position( | ||
174 | " | ||
175 | //- /lib.rs | ||
176 | mod foo; | ||
177 | |||
178 | use crate::foo::bar::Baz; | ||
179 | |||
180 | //- /foo/mod.rs | ||
181 | pub mod bar; | ||
182 | |||
183 | //- /foo/bar.rs | ||
184 | <|> | ||
185 | salsa::query_group! { | ||
186 | trait Baz { | ||
187 | fn foo() -> i32 { 1 + 1 } | ||
188 | } | ||
189 | } | ||
190 | ", | ||
191 | ); | ||
192 | let source_root = db.file_source_root(pos.file_id); | ||
193 | { | ||
194 | let events = db.log_executed(|| { | ||
195 | db.item_map(source_root).unwrap(); | ||
196 | }); | ||
197 | assert!(format!("{:?}", events).contains("item_map")) | ||
198 | } | ||
199 | |||
200 | let new_text = " | ||
201 | salsa::query_group! { | ||
202 | trait Baz { | ||
203 | fn foo() -> i32 { 92 } | ||
204 | } | ||
205 | } | ||
206 | " | ||
207 | .to_string(); | ||
208 | |||
209 | db.query_mut(ra_db::FileTextQuery) | ||
210 | .set(pos.file_id, Arc::new(new_text)); | ||
211 | |||
212 | { | ||
213 | let events = db.log_executed(|| { | ||
214 | db.item_map(source_root).unwrap(); | ||
215 | }); | ||
216 | assert!( | ||
217 | !format!("{:?}", events).contains("item_map"), | ||
218 | "{:#?}", | ||
219 | events | ||
220 | ) | ||
221 | } | ||
222 | } | ||
223 | |||
224 | #[test] | ||
225 | fn typing_inside_a_function_inside_a_macro_should_not_invalidate_item_map() { | ||
226 | let (mut db, pos) = MockDatabase::with_position( | ||
227 | " | ||
228 | //- /lib.rs | ||
229 | mod foo;<|> | ||
230 | |||
231 | use crate::foo::bar::Baz; | ||
232 | |||
233 | fn foo() -> i32 { | ||
234 | 1 + 1 | ||
235 | } | ||
236 | //- /foo/mod.rs | ||
237 | pub mod bar; | ||
238 | |||
239 | //- /foo/bar.rs | ||
240 | pub struct Baz; | ||
241 | ", | ||
242 | ); | ||
243 | let source_root = db.file_source_root(pos.file_id); | ||
244 | { | ||
245 | let events = db.log_executed(|| { | ||
246 | db.item_map(source_root).unwrap(); | ||
247 | }); | ||
248 | assert!(format!("{:?}", events).contains("item_map")) | ||
249 | } | ||
250 | |||
251 | let new_text = " | ||
252 | mod foo; | ||
253 | |||
254 | use crate::foo::bar::Baz; | ||
255 | |||
256 | fn foo() -> i32 { 92 } | ||
257 | " | ||
258 | .to_string(); | ||
259 | |||
260 | db.query_mut(ra_db::FileTextQuery) | ||
261 | .set(pos.file_id, Arc::new(new_text)); | ||
262 | |||
263 | { | ||
264 | let events = db.log_executed(|| { | ||
265 | db.item_map(source_root).unwrap(); | ||
266 | }); | ||
267 | assert!( | ||
268 | !format!("{:?}", events).contains("item_map"), | ||
269 | "{:#?}", | ||
270 | events | ||
271 | ) | ||
272 | } | ||
273 | } | ||