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