]> git.lizzy.rs Git - rust.git/blob - crates/hir_ty/src/tests.rs
Merge #10420
[rust.git] / crates / hir_ty / src / tests.rs
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 mod incremental;
11
12 use std::{collections::HashMap, env, sync::Arc};
13
14 use base_db::{fixture::WithFixture, FileRange, SourceDatabaseExt};
15 use expect_test::Expect;
16 use hir_def::{
17     body::{Body, BodySourceMap, SyntheticSyntax},
18     db::DefDatabase,
19     expr::{ExprId, PatId},
20     item_scope::ItemScope,
21     nameres::DefMap,
22     src::HasSource,
23     AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId,
24 };
25 use hir_expand::{db::AstDatabase, InFile};
26 use once_cell::race::OnceBool;
27 use stdx::format_to;
28 use syntax::{
29     ast::{self, AstNode, HasName},
30     SyntaxNode,
31 };
32 use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
33 use tracing_tree::HierarchicalLayer;
34
35 use crate::{
36     db::HirDatabase,
37     display::HirDisplay,
38     infer::{Adjustment, TypeMismatch},
39     test_db::TestDB,
40     InferenceResult, Ty,
41 };
42
43 // These tests compare the inference results for all expressions in a file
44 // against snapshots of the expected results using expect. Use
45 // `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots.
46
47 fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
48     static ENABLE: OnceBool = OnceBool::new();
49     if !ENABLE.get_or_init(|| env::var("CHALK_DEBUG").is_ok()) {
50         return None;
51     }
52
53     let filter = EnvFilter::from_env("CHALK_DEBUG");
54     let layer = HierarchicalLayer::default()
55         .with_indent_lines(true)
56         .with_ansi(false)
57         .with_indent_amount(2)
58         .with_writer(std::io::stderr);
59     let subscriber = Registry::default().with(filter).with(layer);
60     Some(tracing::subscriber::set_default(subscriber))
61 }
62
63 fn check_types(ra_fixture: &str) {
64     check_impl(ra_fixture, false, true, false)
65 }
66
67 fn check_types_source_code(ra_fixture: &str) {
68     check_impl(ra_fixture, false, true, true)
69 }
70
71 fn check_no_mismatches(ra_fixture: &str) {
72     check_impl(ra_fixture, true, false, false)
73 }
74
75 fn check(ra_fixture: &str) {
76     check_impl(ra_fixture, false, false, false)
77 }
78
79 fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_source: bool) {
80     let _tracing = setup_tracing();
81     let (db, files) = TestDB::with_many_files(ra_fixture);
82
83     let mut had_annotations = false;
84     let mut mismatches = HashMap::new();
85     let mut types = HashMap::new();
86     let mut adjustments = HashMap::<_, Vec<_>>::new();
87     for (file_id, annotations) in db.extract_annotations() {
88         for (range, expected) in annotations {
89             let file_range = FileRange { file_id, range };
90             if only_types {
91                 types.insert(file_range, expected);
92             } else if expected.starts_with("type: ") {
93                 types.insert(file_range, expected.trim_start_matches("type: ").to_string());
94             } else if expected.starts_with("expected") {
95                 mismatches.insert(file_range, expected);
96             } else if expected.starts_with("adjustments: ") {
97                 adjustments.insert(
98                     file_range,
99                     expected
100                         .trim_start_matches("adjustments: ")
101                         .split(',')
102                         .map(|it| it.trim().to_string())
103                         .collect(),
104                 );
105             } else {
106                 panic!("unexpected annotation: {}", expected);
107             }
108             had_annotations = true;
109         }
110     }
111     assert!(had_annotations || allow_none, "no `//^` annotations found");
112
113     let mut defs: Vec<DefWithBodyId> = Vec::new();
114     for file_id in files {
115         let module = db.module_for_file_opt(file_id);
116         let module = match module {
117             Some(m) => m,
118             None => continue,
119         };
120         let def_map = module.def_map(&db);
121         visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
122     }
123     defs.sort_by_key(|def| match def {
124         DefWithBodyId::FunctionId(it) => {
125             let loc = it.lookup(&db);
126             loc.source(&db).value.syntax().text_range().start()
127         }
128         DefWithBodyId::ConstId(it) => {
129             let loc = it.lookup(&db);
130             loc.source(&db).value.syntax().text_range().start()
131         }
132         DefWithBodyId::StaticId(it) => {
133             let loc = it.lookup(&db);
134             loc.source(&db).value.syntax().text_range().start()
135         }
136     });
137     let mut unexpected_type_mismatches = String::new();
138     for def in defs {
139         let (_body, body_source_map) = db.body_with_source_map(def);
140         let inference_result = db.infer(def);
141
142         for (pat, ty) in inference_result.type_of_pat.iter() {
143             let node = match pat_node(&body_source_map, pat, &db) {
144                 Some(value) => value,
145                 None => continue,
146             };
147             let range = node.as_ref().original_file_range(&db);
148             if let Some(expected) = types.remove(&range) {
149                 let actual = if display_source {
150                     ty.display_source_code(&db, def.module(&db)).unwrap()
151                 } else {
152                     ty.display_test(&db).to_string()
153                 };
154                 assert_eq!(actual, expected);
155             }
156         }
157
158         for (expr, ty) in inference_result.type_of_expr.iter() {
159             let node = match expr_node(&body_source_map, expr, &db) {
160                 Some(value) => value,
161                 None => continue,
162             };
163             let range = node.as_ref().original_file_range(&db);
164             if let Some(expected) = types.remove(&range) {
165                 let actual = if display_source {
166                     ty.display_source_code(&db, def.module(&db)).unwrap()
167                 } else {
168                     ty.display_test(&db).to_string()
169                 };
170                 assert_eq!(actual, expected);
171             }
172             if let Some(expected) = adjustments.remove(&range) {
173                 if let Some(adjustments) = inference_result.expr_adjustments.get(&expr) {
174                     assert_eq!(
175                         expected,
176                         adjustments
177                             .iter()
178                             .map(|Adjustment { kind, .. }| format!("{:?}", kind))
179                             .collect::<Vec<_>>()
180                     );
181                 } else {
182                     panic!("expected {:?} adjustments, found none", expected);
183                 }
184             }
185         }
186
187         for (pat, mismatch) in inference_result.pat_type_mismatches() {
188             let node = match pat_node(&body_source_map, pat, &db) {
189                 Some(value) => value,
190                 None => continue,
191             };
192             let range = node.as_ref().original_file_range(&db);
193             let actual = format!(
194                 "expected {}, got {}",
195                 mismatch.expected.display_test(&db),
196                 mismatch.actual.display_test(&db)
197             );
198             match mismatches.remove(&range) {
199                 Some(annotation) => assert_eq!(actual, annotation),
200                 None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
201             }
202         }
203         for (expr, mismatch) in inference_result.expr_type_mismatches() {
204             let node = match body_source_map.expr_syntax(expr) {
205                 Ok(sp) => {
206                     let root = db.parse_or_expand(sp.file_id).unwrap();
207                     sp.map(|ptr| ptr.to_node(&root).syntax().clone())
208                 }
209                 Err(SyntheticSyntax) => continue,
210             };
211             let range = node.as_ref().original_file_range(&db);
212             let actual = format!(
213                 "expected {}, got {}",
214                 mismatch.expected.display_test(&db),
215                 mismatch.actual.display_test(&db)
216             );
217             match mismatches.remove(&range) {
218                 Some(annotation) => assert_eq!(actual, annotation),
219                 None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
220             }
221         }
222     }
223
224     let mut buf = String::new();
225     if !unexpected_type_mismatches.is_empty() {
226         format_to!(buf, "Unexpected type mismatches:\n{}", unexpected_type_mismatches);
227     }
228     if !mismatches.is_empty() {
229         format_to!(buf, "Unchecked mismatch annotations:\n");
230         for m in mismatches {
231             format_to!(buf, "{:?}: {}\n", m.0.range, m.1);
232         }
233     }
234     if !types.is_empty() {
235         format_to!(buf, "Unchecked type annotations:\n");
236         for t in types {
237             format_to!(buf, "{:?}: type {}\n", t.0.range, t.1);
238         }
239     }
240     if !adjustments.is_empty() {
241         format_to!(buf, "Unchecked adjustments annotations:\n");
242         for t in adjustments {
243             format_to!(buf, "{:?}: type {:?}\n", t.0.range, t.1);
244         }
245     }
246     assert!(buf.is_empty(), "{}", buf);
247 }
248
249 fn expr_node(
250     body_source_map: &BodySourceMap,
251     expr: ExprId,
252     db: &TestDB,
253 ) -> Option<InFile<SyntaxNode>> {
254     Some(match body_source_map.expr_syntax(expr) {
255         Ok(sp) => {
256             let root = db.parse_or_expand(sp.file_id).unwrap();
257             sp.map(|ptr| ptr.to_node(&root).syntax().clone())
258         }
259         Err(SyntheticSyntax) => return None,
260     })
261 }
262
263 fn pat_node(
264     body_source_map: &BodySourceMap,
265     pat: PatId,
266     db: &TestDB,
267 ) -> Option<InFile<SyntaxNode>> {
268     Some(match body_source_map.pat_syntax(pat) {
269         Ok(sp) => {
270             let root = db.parse_or_expand(sp.file_id).unwrap();
271             sp.map(|ptr| {
272                 ptr.either(
273                     |it| it.to_node(&root).syntax().clone(),
274                     |it| it.to_node(&root).syntax().clone(),
275                 )
276             })
277         }
278         Err(SyntheticSyntax) => return None,
279     })
280 }
281
282 fn infer(ra_fixture: &str) -> String {
283     infer_with_mismatches(ra_fixture, false)
284 }
285
286 fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
287     let _tracing = setup_tracing();
288     let (db, file_id) = TestDB::with_single_file(content);
289
290     let mut buf = String::new();
291
292     let mut infer_def = |inference_result: Arc<InferenceResult>,
293                          body_source_map: Arc<BodySourceMap>| {
294         let mut types: Vec<(InFile<SyntaxNode>, &Ty)> = Vec::new();
295         let mut mismatches: Vec<(InFile<SyntaxNode>, &TypeMismatch)> = Vec::new();
296
297         for (pat, ty) in inference_result.type_of_pat.iter() {
298             let syntax_ptr = match body_source_map.pat_syntax(pat) {
299                 Ok(sp) => {
300                     let root = db.parse_or_expand(sp.file_id).unwrap();
301                     sp.map(|ptr| {
302                         ptr.either(
303                             |it| it.to_node(&root).syntax().clone(),
304                             |it| it.to_node(&root).syntax().clone(),
305                         )
306                     })
307                 }
308                 Err(SyntheticSyntax) => continue,
309             };
310             types.push((syntax_ptr.clone(), ty));
311             if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat) {
312                 mismatches.push((syntax_ptr, mismatch));
313             }
314         }
315
316         for (expr, ty) in inference_result.type_of_expr.iter() {
317             let node = match body_source_map.expr_syntax(expr) {
318                 Ok(sp) => {
319                     let root = db.parse_or_expand(sp.file_id).unwrap();
320                     sp.map(|ptr| ptr.to_node(&root).syntax().clone())
321                 }
322                 Err(SyntheticSyntax) => continue,
323             };
324             types.push((node.clone(), ty));
325             if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr) {
326                 mismatches.push((node, mismatch));
327             }
328         }
329
330         // sort ranges for consistency
331         types.sort_by_key(|(node, _)| {
332             let range = node.value.text_range();
333             (range.start(), range.end())
334         });
335         for (node, ty) in &types {
336             let (range, text) = if let Some(self_param) = ast::SelfParam::cast(node.value.clone()) {
337                 (self_param.name().unwrap().syntax().text_range(), "self".to_string())
338             } else {
339                 (node.value.text_range(), node.value.text().to_string().replace("\n", " "))
340             };
341             let macro_prefix = if node.file_id != file_id.into() { "!" } else { "" };
342             format_to!(
343                 buf,
344                 "{}{:?} '{}': {}\n",
345                 macro_prefix,
346                 range,
347                 ellipsize(text, 15),
348                 ty.display_test(&db)
349             );
350         }
351         if include_mismatches {
352             mismatches.sort_by_key(|(node, _)| {
353                 let range = node.value.text_range();
354                 (range.start(), range.end())
355             });
356             for (src_ptr, mismatch) in &mismatches {
357                 let range = src_ptr.value.text_range();
358                 let macro_prefix = if src_ptr.file_id != file_id.into() { "!" } else { "" };
359                 format_to!(
360                     buf,
361                     "{}{:?}: expected {}, got {}\n",
362                     macro_prefix,
363                     range,
364                     mismatch.expected.display_test(&db),
365                     mismatch.actual.display_test(&db),
366                 );
367             }
368         }
369     };
370
371     let module = db.module_for_file(file_id);
372     let def_map = module.def_map(&db);
373
374     let mut defs: Vec<DefWithBodyId> = Vec::new();
375     visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
376     defs.sort_by_key(|def| match def {
377         DefWithBodyId::FunctionId(it) => {
378             let loc = it.lookup(&db);
379             loc.source(&db).value.syntax().text_range().start()
380         }
381         DefWithBodyId::ConstId(it) => {
382             let loc = it.lookup(&db);
383             loc.source(&db).value.syntax().text_range().start()
384         }
385         DefWithBodyId::StaticId(it) => {
386             let loc = it.lookup(&db);
387             loc.source(&db).value.syntax().text_range().start()
388         }
389     });
390     for def in defs {
391         let (_body, source_map) = db.body_with_source_map(def);
392         let infer = db.infer(def);
393         infer_def(infer, source_map);
394     }
395
396     buf.truncate(buf.trim_end().len());
397     buf
398 }
399
400 fn visit_module(
401     db: &TestDB,
402     crate_def_map: &DefMap,
403     module_id: LocalModuleId,
404     cb: &mut dyn FnMut(DefWithBodyId),
405 ) {
406     visit_scope(db, crate_def_map, &crate_def_map[module_id].scope, cb);
407     for impl_id in crate_def_map[module_id].scope.impls() {
408         let impl_data = db.impl_data(impl_id);
409         for &item in impl_data.items.iter() {
410             match item {
411                 AssocItemId::FunctionId(it) => {
412                     let def = it.into();
413                     cb(def);
414                     let body = db.body(def);
415                     visit_body(db, &body, cb);
416                 }
417                 AssocItemId::ConstId(it) => {
418                     let def = it.into();
419                     cb(def);
420                     let body = db.body(def);
421                     visit_body(db, &body, cb);
422                 }
423                 AssocItemId::TypeAliasId(_) => (),
424             }
425         }
426     }
427
428     fn visit_scope(
429         db: &TestDB,
430         crate_def_map: &DefMap,
431         scope: &ItemScope,
432         cb: &mut dyn FnMut(DefWithBodyId),
433     ) {
434         for decl in scope.declarations() {
435             match decl {
436                 ModuleDefId::FunctionId(it) => {
437                     let def = it.into();
438                     cb(def);
439                     let body = db.body(def);
440                     visit_body(db, &body, cb);
441                 }
442                 ModuleDefId::ConstId(it) => {
443                     let def = it.into();
444                     cb(def);
445                     let body = db.body(def);
446                     visit_body(db, &body, cb);
447                 }
448                 ModuleDefId::StaticId(it) => {
449                     let def = it.into();
450                     cb(def);
451                     let body = db.body(def);
452                     visit_body(db, &body, cb);
453                 }
454                 ModuleDefId::TraitId(it) => {
455                     let trait_data = db.trait_data(it);
456                     for &(_, item) in trait_data.items.iter() {
457                         match item {
458                             AssocItemId::FunctionId(it) => cb(it.into()),
459                             AssocItemId::ConstId(it) => cb(it.into()),
460                             AssocItemId::TypeAliasId(_) => (),
461                         }
462                     }
463                 }
464                 ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.local_id, cb),
465                 _ => (),
466             }
467         }
468     }
469
470     fn visit_body(db: &TestDB, body: &Body, cb: &mut dyn FnMut(DefWithBodyId)) {
471         for (_, def_map) in body.blocks(db) {
472             for (mod_id, _) in def_map.modules() {
473                 visit_module(db, &def_map, mod_id, cb);
474             }
475         }
476     }
477 }
478
479 fn ellipsize(mut text: String, max_len: usize) -> String {
480     if text.len() <= max_len {
481         return text;
482     }
483     let ellipsis = "...";
484     let e_len = ellipsis.len();
485     let mut prefix_len = (max_len - e_len) / 2;
486     while !text.is_char_boundary(prefix_len) {
487         prefix_len += 1;
488     }
489     let mut suffix_len = max_len - e_len - prefix_len;
490     while !text.is_char_boundary(text.len() - suffix_len) {
491         suffix_len += 1;
492     }
493     text.replace_range(prefix_len..text.len() - suffix_len, ellipsis);
494     text
495 }
496
497 fn check_infer(ra_fixture: &str, expect: Expect) {
498     let mut actual = infer(ra_fixture);
499     actual.push('\n');
500     expect.assert_eq(&actual);
501 }
502
503 fn check_infer_with_mismatches(ra_fixture: &str, expect: Expect) {
504     let mut actual = infer_with_mismatches(ra_fixture, true);
505     actual.push('\n');
506     expect.assert_eq(&actual);
507 }
508
509 #[test]
510 fn salsa_bug() {
511     let (mut db, pos) = TestDB::with_position(
512         "
513         //- /lib.rs
514         trait Index {
515             type Output;
516         }
517
518         type Key<S: UnificationStoreBase> = <S as UnificationStoreBase>::Key;
519
520         pub trait UnificationStoreBase: Index<Output = Key<Self>> {
521             type Key;
522
523             fn len(&self) -> usize;
524         }
525
526         pub trait UnificationStoreMut: UnificationStoreBase {
527             fn push(&mut self, value: Self::Key);
528         }
529
530         fn main() {
531             let x = 1;
532             x.push(1);$0
533         }
534     ",
535     );
536
537     let module = db.module_for_file(pos.file_id);
538     let crate_def_map = module.def_map(&db);
539     visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
540         db.infer(def);
541     });
542
543     let new_text = "
544         //- /lib.rs
545         trait Index {
546             type Output;
547         }
548
549         type Key<S: UnificationStoreBase> = <S as UnificationStoreBase>::Key;
550
551         pub trait UnificationStoreBase: Index<Output = Key<Self>> {
552             type Key;
553
554             fn len(&self) -> usize;
555         }
556
557         pub trait UnificationStoreMut: UnificationStoreBase {
558             fn push(&mut self, value: Self::Key);
559         }
560
561         fn main() {
562
563             let x = 1;
564             x.push(1);
565         }
566     "
567     .to_string();
568
569     db.set_file_text(pos.file_id, Arc::new(new_text));
570
571     let module = db.module_for_file(pos.file_id);
572     let crate_def_map = module.def_map(&db);
573     visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
574         db.infer(def);
575     });
576 }