diff options
Diffstat (limited to 'crates/hir_ty/src/tests.rs')
-rw-r--r-- | crates/hir_ty/src/tests.rs | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/crates/hir_ty/src/tests.rs b/crates/hir_ty/src/tests.rs new file mode 100644 index 000000000..c953925ec --- /dev/null +++ b/crates/hir_ty/src/tests.rs | |||
@@ -0,0 +1,359 @@ | |||
1 | mod never_type; | ||
2 | mod coercion; | ||
3 | mod regression; | ||
4 | mod simple; | ||
5 | mod patterns; | ||
6 | mod traits; | ||
7 | mod method_resolution; | ||
8 | mod macros; | ||
9 | mod display_source_code; | ||
10 | |||
11 | use std::sync::Arc; | ||
12 | |||
13 | use base_db::{fixture::WithFixture, FileRange, SourceDatabase, SourceDatabaseExt}; | ||
14 | use expect::Expect; | ||
15 | use hir_def::{ | ||
16 | body::{BodySourceMap, SyntheticSyntax}, | ||
17 | child_by_source::ChildBySource, | ||
18 | db::DefDatabase, | ||
19 | item_scope::ItemScope, | ||
20 | keys, | ||
21 | nameres::CrateDefMap, | ||
22 | AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, | ||
23 | }; | ||
24 | use hir_expand::{db::AstDatabase, InFile}; | ||
25 | use stdx::format_to; | ||
26 | use syntax::{ | ||
27 | algo, | ||
28 | ast::{self, AstNode}, | ||
29 | SyntaxNode, | ||
30 | }; | ||
31 | |||
32 | use crate::{ | ||
33 | db::HirDatabase, display::HirDisplay, infer::TypeMismatch, test_db::TestDB, InferenceResult, Ty, | ||
34 | }; | ||
35 | |||
36 | // These tests compare the inference results for all expressions in a file | ||
37 | // against snapshots of the expected results using expect. Use | ||
38 | // `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots. | ||
39 | |||
40 | fn setup_tracing() -> tracing::subscriber::DefaultGuard { | ||
41 | use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; | ||
42 | use tracing_tree::HierarchicalLayer; | ||
43 | let filter = EnvFilter::from_env("CHALK_DEBUG"); | ||
44 | let layer = HierarchicalLayer::default() | ||
45 | .with_indent_lines(true) | ||
46 | .with_ansi(false) | ||
47 | .with_indent_amount(2) | ||
48 | .with_writer(std::io::stderr); | ||
49 | let subscriber = Registry::default().with(filter).with(layer); | ||
50 | tracing::subscriber::set_default(subscriber) | ||
51 | } | ||
52 | |||
53 | fn check_types(ra_fixture: &str) { | ||
54 | check_types_impl(ra_fixture, false) | ||
55 | } | ||
56 | |||
57 | fn check_types_source_code(ra_fixture: &str) { | ||
58 | check_types_impl(ra_fixture, true) | ||
59 | } | ||
60 | |||
61 | fn check_types_impl(ra_fixture: &str, display_source: bool) { | ||
62 | let _tracing = setup_tracing(); | ||
63 | let db = TestDB::with_files(ra_fixture); | ||
64 | let mut checked_one = false; | ||
65 | for (file_id, annotations) in db.extract_annotations() { | ||
66 | for (range, expected) in annotations { | ||
67 | let ty = type_at_range(&db, FileRange { file_id, range }); | ||
68 | let actual = if display_source { | ||
69 | let module = db.module_for_file(file_id); | ||
70 | ty.display_source_code(&db, module).unwrap() | ||
71 | } else { | ||
72 | ty.display(&db).to_string() | ||
73 | }; | ||
74 | assert_eq!(expected, actual); | ||
75 | checked_one = true; | ||
76 | } | ||
77 | } | ||
78 | assert!(checked_one, "no `//^` annotations found"); | ||
79 | } | ||
80 | |||
81 | fn type_at_range(db: &TestDB, pos: FileRange) -> Ty { | ||
82 | let file = db.parse(pos.file_id).ok().unwrap(); | ||
83 | let expr = algo::find_node_at_range::<ast::Expr>(file.syntax(), pos.range).unwrap(); | ||
84 | let fn_def = expr.syntax().ancestors().find_map(ast::Fn::cast).unwrap(); | ||
85 | let module = db.module_for_file(pos.file_id); | ||
86 | let func = *module.child_by_source(db)[keys::FUNCTION] | ||
87 | .get(&InFile::new(pos.file_id.into(), fn_def)) | ||
88 | .unwrap(); | ||
89 | |||
90 | let (_body, source_map) = db.body_with_source_map(func.into()); | ||
91 | if let Some(expr_id) = source_map.node_expr(InFile::new(pos.file_id.into(), &expr)) { | ||
92 | let infer = db.infer(func.into()); | ||
93 | return infer[expr_id].clone(); | ||
94 | } | ||
95 | panic!("Can't find expression") | ||
96 | } | ||
97 | |||
98 | fn infer(ra_fixture: &str) -> String { | ||
99 | infer_with_mismatches(ra_fixture, false) | ||
100 | } | ||
101 | |||
102 | fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String { | ||
103 | let _tracing = setup_tracing(); | ||
104 | let (db, file_id) = TestDB::with_single_file(content); | ||
105 | |||
106 | let mut buf = String::new(); | ||
107 | |||
108 | let mut infer_def = |inference_result: Arc<InferenceResult>, | ||
109 | body_source_map: Arc<BodySourceMap>| { | ||
110 | let mut types: Vec<(InFile<SyntaxNode>, &Ty)> = Vec::new(); | ||
111 | let mut mismatches: Vec<(InFile<SyntaxNode>, &TypeMismatch)> = Vec::new(); | ||
112 | |||
113 | for (pat, ty) in inference_result.type_of_pat.iter() { | ||
114 | let syntax_ptr = match body_source_map.pat_syntax(pat) { | ||
115 | Ok(sp) => { | ||
116 | let root = db.parse_or_expand(sp.file_id).unwrap(); | ||
117 | sp.map(|ptr| { | ||
118 | ptr.either( | ||
119 | |it| it.to_node(&root).syntax().clone(), | ||
120 | |it| it.to_node(&root).syntax().clone(), | ||
121 | ) | ||
122 | }) | ||
123 | } | ||
124 | Err(SyntheticSyntax) => continue, | ||
125 | }; | ||
126 | types.push((syntax_ptr, ty)); | ||
127 | } | ||
128 | |||
129 | for (expr, ty) in inference_result.type_of_expr.iter() { | ||
130 | let node = match body_source_map.expr_syntax(expr) { | ||
131 | Ok(sp) => { | ||
132 | let root = db.parse_or_expand(sp.file_id).unwrap(); | ||
133 | sp.map(|ptr| ptr.to_node(&root).syntax().clone()) | ||
134 | } | ||
135 | Err(SyntheticSyntax) => continue, | ||
136 | }; | ||
137 | types.push((node.clone(), ty)); | ||
138 | if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr) { | ||
139 | mismatches.push((node, mismatch)); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | // sort ranges for consistency | ||
144 | types.sort_by_key(|(node, _)| { | ||
145 | let range = node.value.text_range(); | ||
146 | (range.start(), range.end()) | ||
147 | }); | ||
148 | for (node, ty) in &types { | ||
149 | let (range, text) = if let Some(self_param) = ast::SelfParam::cast(node.value.clone()) { | ||
150 | (self_param.self_token().unwrap().text_range(), "self".to_string()) | ||
151 | } else { | ||
152 | (node.value.text_range(), node.value.text().to_string().replace("\n", " ")) | ||
153 | }; | ||
154 | let macro_prefix = if node.file_id != file_id.into() { "!" } else { "" }; | ||
155 | format_to!( | ||
156 | buf, | ||
157 | "{}{:?} '{}': {}\n", | ||
158 | macro_prefix, | ||
159 | range, | ||
160 | ellipsize(text, 15), | ||
161 | ty.display(&db) | ||
162 | ); | ||
163 | } | ||
164 | if include_mismatches { | ||
165 | mismatches.sort_by_key(|(node, _)| { | ||
166 | let range = node.value.text_range(); | ||
167 | (range.start(), range.end()) | ||
168 | }); | ||
169 | for (src_ptr, mismatch) in &mismatches { | ||
170 | let range = src_ptr.value.text_range(); | ||
171 | let macro_prefix = if src_ptr.file_id != file_id.into() { "!" } else { "" }; | ||
172 | format_to!( | ||
173 | buf, | ||
174 | "{}{:?}: expected {}, got {}\n", | ||
175 | macro_prefix, | ||
176 | range, | ||
177 | mismatch.expected.display(&db), | ||
178 | mismatch.actual.display(&db), | ||
179 | ); | ||
180 | } | ||
181 | } | ||
182 | }; | ||
183 | |||
184 | let module = db.module_for_file(file_id); | ||
185 | let crate_def_map = db.crate_def_map(module.krate); | ||
186 | |||
187 | let mut defs: Vec<DefWithBodyId> = Vec::new(); | ||
188 | visit_module(&db, &crate_def_map, module.local_id, &mut |it| defs.push(it)); | ||
189 | defs.sort_by_key(|def| match def { | ||
190 | DefWithBodyId::FunctionId(it) => { | ||
191 | let loc = it.lookup(&db); | ||
192 | let tree = db.item_tree(loc.id.file_id); | ||
193 | tree.source(&db, loc.id).syntax().text_range().start() | ||
194 | } | ||
195 | DefWithBodyId::ConstId(it) => { | ||
196 | let loc = it.lookup(&db); | ||
197 | let tree = db.item_tree(loc.id.file_id); | ||
198 | tree.source(&db, loc.id).syntax().text_range().start() | ||
199 | } | ||
200 | DefWithBodyId::StaticId(it) => { | ||
201 | let loc = it.lookup(&db); | ||
202 | let tree = db.item_tree(loc.id.file_id); | ||
203 | tree.source(&db, loc.id).syntax().text_range().start() | ||
204 | } | ||
205 | }); | ||
206 | for def in defs { | ||
207 | let (_body, source_map) = db.body_with_source_map(def); | ||
208 | let infer = db.infer(def); | ||
209 | infer_def(infer, source_map); | ||
210 | } | ||
211 | |||
212 | buf.truncate(buf.trim_end().len()); | ||
213 | buf | ||
214 | } | ||
215 | |||
216 | fn visit_module( | ||
217 | db: &TestDB, | ||
218 | crate_def_map: &CrateDefMap, | ||
219 | module_id: LocalModuleId, | ||
220 | cb: &mut dyn FnMut(DefWithBodyId), | ||
221 | ) { | ||
222 | visit_scope(db, crate_def_map, &crate_def_map[module_id].scope, cb); | ||
223 | for impl_id in crate_def_map[module_id].scope.impls() { | ||
224 | let impl_data = db.impl_data(impl_id); | ||
225 | for &item in impl_data.items.iter() { | ||
226 | match item { | ||
227 | AssocItemId::FunctionId(it) => { | ||
228 | let def = it.into(); | ||
229 | cb(def); | ||
230 | let body = db.body(def); | ||
231 | visit_scope(db, crate_def_map, &body.item_scope, cb); | ||
232 | } | ||
233 | AssocItemId::ConstId(it) => { | ||
234 | let def = it.into(); | ||
235 | cb(def); | ||
236 | let body = db.body(def); | ||
237 | visit_scope(db, crate_def_map, &body.item_scope, cb); | ||
238 | } | ||
239 | AssocItemId::TypeAliasId(_) => (), | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | |||
244 | fn visit_scope( | ||
245 | db: &TestDB, | ||
246 | crate_def_map: &CrateDefMap, | ||
247 | scope: &ItemScope, | ||
248 | cb: &mut dyn FnMut(DefWithBodyId), | ||
249 | ) { | ||
250 | for decl in scope.declarations() { | ||
251 | match decl { | ||
252 | ModuleDefId::FunctionId(it) => { | ||
253 | let def = it.into(); | ||
254 | cb(def); | ||
255 | let body = db.body(def); | ||
256 | visit_scope(db, crate_def_map, &body.item_scope, cb); | ||
257 | } | ||
258 | ModuleDefId::ConstId(it) => { | ||
259 | let def = it.into(); | ||
260 | cb(def); | ||
261 | let body = db.body(def); | ||
262 | visit_scope(db, crate_def_map, &body.item_scope, cb); | ||
263 | } | ||
264 | ModuleDefId::StaticId(it) => { | ||
265 | let def = it.into(); | ||
266 | cb(def); | ||
267 | let body = db.body(def); | ||
268 | visit_scope(db, crate_def_map, &body.item_scope, cb); | ||
269 | } | ||
270 | ModuleDefId::TraitId(it) => { | ||
271 | let trait_data = db.trait_data(it); | ||
272 | for &(_, item) in trait_data.items.iter() { | ||
273 | match item { | ||
274 | AssocItemId::FunctionId(it) => cb(it.into()), | ||
275 | AssocItemId::ConstId(it) => cb(it.into()), | ||
276 | AssocItemId::TypeAliasId(_) => (), | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.local_id, cb), | ||
281 | _ => (), | ||
282 | } | ||
283 | } | ||
284 | } | ||
285 | } | ||
286 | |||
287 | fn ellipsize(mut text: String, max_len: usize) -> String { | ||
288 | if text.len() <= max_len { | ||
289 | return text; | ||
290 | } | ||
291 | let ellipsis = "..."; | ||
292 | let e_len = ellipsis.len(); | ||
293 | let mut prefix_len = (max_len - e_len) / 2; | ||
294 | while !text.is_char_boundary(prefix_len) { | ||
295 | prefix_len += 1; | ||
296 | } | ||
297 | let mut suffix_len = max_len - e_len - prefix_len; | ||
298 | while !text.is_char_boundary(text.len() - suffix_len) { | ||
299 | suffix_len += 1; | ||
300 | } | ||
301 | text.replace_range(prefix_len..text.len() - suffix_len, ellipsis); | ||
302 | text | ||
303 | } | ||
304 | |||
305 | #[test] | ||
306 | fn typing_whitespace_inside_a_function_should_not_invalidate_types() { | ||
307 | let (mut db, pos) = TestDB::with_position( | ||
308 | " | ||
309 | //- /lib.rs | ||
310 | fn foo() -> i32 { | ||
311 | <|>1 + 1 | ||
312 | } | ||
313 | ", | ||
314 | ); | ||
315 | { | ||
316 | let events = db.log_executed(|| { | ||
317 | let module = db.module_for_file(pos.file_id); | ||
318 | let crate_def_map = db.crate_def_map(module.krate); | ||
319 | visit_module(&db, &crate_def_map, module.local_id, &mut |def| { | ||
320 | db.infer(def); | ||
321 | }); | ||
322 | }); | ||
323 | assert!(format!("{:?}", events).contains("infer")) | ||
324 | } | ||
325 | |||
326 | let new_text = " | ||
327 | fn foo() -> i32 { | ||
328 | 1 | ||
329 | + | ||
330 | 1 | ||
331 | } | ||
332 | " | ||
333 | .to_string(); | ||
334 | |||
335 | db.set_file_text(pos.file_id, Arc::new(new_text)); | ||
336 | |||
337 | { | ||
338 | let events = db.log_executed(|| { | ||
339 | let module = db.module_for_file(pos.file_id); | ||
340 | let crate_def_map = db.crate_def_map(module.krate); | ||
341 | visit_module(&db, &crate_def_map, module.local_id, &mut |def| { | ||
342 | db.infer(def); | ||
343 | }); | ||
344 | }); | ||
345 | assert!(!format!("{:?}", events).contains("infer"), "{:#?}", events) | ||
346 | } | ||
347 | } | ||
348 | |||
349 | fn check_infer(ra_fixture: &str, expect: Expect) { | ||
350 | let mut actual = infer(ra_fixture); | ||
351 | actual.push('\n'); | ||
352 | expect.assert_eq(&actual); | ||
353 | } | ||
354 | |||
355 | fn check_infer_with_mismatches(ra_fixture: &str, expect: Expect) { | ||
356 | let mut actual = infer_with_mismatches(ra_fixture, true); | ||
357 | actual.push('\n'); | ||
358 | expect.assert_eq(&actual); | ||
359 | } | ||