]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/hir-ty/src/tests.rs
Auto merge of #102717 - beetrees:repr128-c-style-debuginfo, r=nagisa
[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, InternDatabase},
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         DefWithBodyId::VariantId(it) => {
139             let loc = db.lookup_intern_enum(it.parent);
140             loc.source(&db).value.syntax().text_range().start()
141         }
142     });
143     let mut unexpected_type_mismatches = String::new();
144     for def in defs {
145         let (_body, body_source_map) = db.body_with_source_map(def);
146         let inference_result = db.infer(def);
147
148         for (pat, ty) in inference_result.type_of_pat.iter() {
149             let node = match pat_node(&body_source_map, pat, &db) {
150                 Some(value) => value,
151                 None => continue,
152             };
153             let range = node.as_ref().original_file_range(&db);
154             if let Some(expected) = types.remove(&range) {
155                 let actual = if display_source {
156                     ty.display_source_code(&db, def.module(&db)).unwrap()
157                 } else {
158                     ty.display_test(&db).to_string()
159                 };
160                 assert_eq!(actual, expected);
161             }
162         }
163
164         for (expr, ty) in inference_result.type_of_expr.iter() {
165             let node = match expr_node(&body_source_map, expr, &db) {
166                 Some(value) => value,
167                 None => continue,
168             };
169             let range = node.as_ref().original_file_range(&db);
170             if let Some(expected) = types.remove(&range) {
171                 let actual = if display_source {
172                     ty.display_source_code(&db, def.module(&db)).unwrap()
173                 } else {
174                     ty.display_test(&db).to_string()
175                 };
176                 assert_eq!(actual, expected);
177             }
178             if let Some(expected) = adjustments.remove(&range) {
179                 if let Some(adjustments) = inference_result.expr_adjustments.get(&expr) {
180                     assert_eq!(
181                         expected,
182                         adjustments
183                             .iter()
184                             .map(|Adjustment { kind, .. }| format!("{:?}", kind))
185                             .collect::<Vec<_>>()
186                     );
187                 } else {
188                     panic!("expected {:?} adjustments, found none", expected);
189                 }
190             }
191         }
192
193         for (pat, mismatch) in inference_result.pat_type_mismatches() {
194             let node = match pat_node(&body_source_map, pat, &db) {
195                 Some(value) => value,
196                 None => continue,
197             };
198             let range = node.as_ref().original_file_range(&db);
199             let actual = format!(
200                 "expected {}, got {}",
201                 mismatch.expected.display_test(&db),
202                 mismatch.actual.display_test(&db)
203             );
204             match mismatches.remove(&range) {
205                 Some(annotation) => assert_eq!(actual, annotation),
206                 None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
207             }
208         }
209         for (expr, mismatch) in inference_result.expr_type_mismatches() {
210             let node = match body_source_map.expr_syntax(expr) {
211                 Ok(sp) => {
212                     let root = db.parse_or_expand(sp.file_id).unwrap();
213                     sp.map(|ptr| ptr.to_node(&root).syntax().clone())
214                 }
215                 Err(SyntheticSyntax) => continue,
216             };
217             let range = node.as_ref().original_file_range(&db);
218             let actual = format!(
219                 "expected {}, got {}",
220                 mismatch.expected.display_test(&db),
221                 mismatch.actual.display_test(&db)
222             );
223             match mismatches.remove(&range) {
224                 Some(annotation) => assert_eq!(actual, annotation),
225                 None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
226             }
227         }
228     }
229
230     let mut buf = String::new();
231     if !unexpected_type_mismatches.is_empty() {
232         format_to!(buf, "Unexpected type mismatches:\n{}", unexpected_type_mismatches);
233     }
234     if !mismatches.is_empty() {
235         format_to!(buf, "Unchecked mismatch annotations:\n");
236         for m in mismatches {
237             format_to!(buf, "{:?}: {}\n", m.0.range, m.1);
238         }
239     }
240     if !types.is_empty() {
241         format_to!(buf, "Unchecked type annotations:\n");
242         for t in types {
243             format_to!(buf, "{:?}: type {}\n", t.0.range, t.1);
244         }
245     }
246     if !adjustments.is_empty() {
247         format_to!(buf, "Unchecked adjustments annotations:\n");
248         for t in adjustments {
249             format_to!(buf, "{:?}: type {:?}\n", t.0.range, t.1);
250         }
251     }
252     assert!(buf.is_empty(), "{}", buf);
253 }
254
255 fn expr_node(
256     body_source_map: &BodySourceMap,
257     expr: ExprId,
258     db: &TestDB,
259 ) -> Option<InFile<SyntaxNode>> {
260     Some(match body_source_map.expr_syntax(expr) {
261         Ok(sp) => {
262             let root = db.parse_or_expand(sp.file_id).unwrap();
263             sp.map(|ptr| ptr.to_node(&root).syntax().clone())
264         }
265         Err(SyntheticSyntax) => return None,
266     })
267 }
268
269 fn pat_node(
270     body_source_map: &BodySourceMap,
271     pat: PatId,
272     db: &TestDB,
273 ) -> Option<InFile<SyntaxNode>> {
274     Some(match body_source_map.pat_syntax(pat) {
275         Ok(sp) => {
276             let root = db.parse_or_expand(sp.file_id).unwrap();
277             sp.map(|ptr| {
278                 ptr.either(
279                     |it| it.to_node(&root).syntax().clone(),
280                     |it| it.to_node(&root).syntax().clone(),
281                 )
282             })
283         }
284         Err(SyntheticSyntax) => return None,
285     })
286 }
287
288 fn infer(ra_fixture: &str) -> String {
289     infer_with_mismatches(ra_fixture, false)
290 }
291
292 fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
293     let _tracing = setup_tracing();
294     let (db, file_id) = TestDB::with_single_file(content);
295
296     let mut buf = String::new();
297
298     let mut infer_def = |inference_result: Arc<InferenceResult>,
299                          body_source_map: Arc<BodySourceMap>| {
300         let mut types: Vec<(InFile<SyntaxNode>, &Ty)> = Vec::new();
301         let mut mismatches: Vec<(InFile<SyntaxNode>, &TypeMismatch)> = Vec::new();
302
303         for (pat, ty) in inference_result.type_of_pat.iter() {
304             let syntax_ptr = match body_source_map.pat_syntax(pat) {
305                 Ok(sp) => {
306                     let root = db.parse_or_expand(sp.file_id).unwrap();
307                     sp.map(|ptr| {
308                         ptr.either(
309                             |it| it.to_node(&root).syntax().clone(),
310                             |it| it.to_node(&root).syntax().clone(),
311                         )
312                     })
313                 }
314                 Err(SyntheticSyntax) => continue,
315             };
316             types.push((syntax_ptr.clone(), ty));
317             if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat) {
318                 mismatches.push((syntax_ptr, mismatch));
319             }
320         }
321
322         for (expr, ty) in inference_result.type_of_expr.iter() {
323             let node = match body_source_map.expr_syntax(expr) {
324                 Ok(sp) => {
325                     let root = db.parse_or_expand(sp.file_id).unwrap();
326                     sp.map(|ptr| ptr.to_node(&root).syntax().clone())
327                 }
328                 Err(SyntheticSyntax) => continue,
329             };
330             types.push((node.clone(), ty));
331             if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr) {
332                 mismatches.push((node, mismatch));
333             }
334         }
335
336         // sort ranges for consistency
337         types.sort_by_key(|(node, _)| {
338             let range = node.value.text_range();
339             (range.start(), range.end())
340         });
341         for (node, ty) in &types {
342             let (range, text) = if let Some(self_param) = ast::SelfParam::cast(node.value.clone()) {
343                 (self_param.name().unwrap().syntax().text_range(), "self".to_string())
344             } else {
345                 (node.value.text_range(), node.value.text().to_string().replace('\n', " "))
346             };
347             let macro_prefix = if node.file_id != file_id.into() { "!" } else { "" };
348             format_to!(
349                 buf,
350                 "{}{:?} '{}': {}\n",
351                 macro_prefix,
352                 range,
353                 ellipsize(text, 15),
354                 ty.display_test(&db)
355             );
356         }
357         if include_mismatches {
358             mismatches.sort_by_key(|(node, _)| {
359                 let range = node.value.text_range();
360                 (range.start(), range.end())
361             });
362             for (src_ptr, mismatch) in &mismatches {
363                 let range = src_ptr.value.text_range();
364                 let macro_prefix = if src_ptr.file_id != file_id.into() { "!" } else { "" };
365                 format_to!(
366                     buf,
367                     "{}{:?}: expected {}, got {}\n",
368                     macro_prefix,
369                     range,
370                     mismatch.expected.display_test(&db),
371                     mismatch.actual.display_test(&db),
372                 );
373             }
374         }
375     };
376
377     let module = db.module_for_file(file_id);
378     let def_map = module.def_map(&db);
379
380     let mut defs: Vec<DefWithBodyId> = Vec::new();
381     visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
382     defs.sort_by_key(|def| match def {
383         DefWithBodyId::FunctionId(it) => {
384             let loc = it.lookup(&db);
385             loc.source(&db).value.syntax().text_range().start()
386         }
387         DefWithBodyId::ConstId(it) => {
388             let loc = it.lookup(&db);
389             loc.source(&db).value.syntax().text_range().start()
390         }
391         DefWithBodyId::StaticId(it) => {
392             let loc = it.lookup(&db);
393             loc.source(&db).value.syntax().text_range().start()
394         }
395         DefWithBodyId::VariantId(it) => {
396             let loc = db.lookup_intern_enum(it.parent);
397             loc.source(&db).value.syntax().text_range().start()
398         }
399     });
400     for def in defs {
401         let (_body, source_map) = db.body_with_source_map(def);
402         let infer = db.infer(def);
403         infer_def(infer, source_map);
404     }
405
406     buf.truncate(buf.trim_end().len());
407     buf
408 }
409
410 fn visit_module(
411     db: &TestDB,
412     crate_def_map: &DefMap,
413     module_id: LocalModuleId,
414     cb: &mut dyn FnMut(DefWithBodyId),
415 ) {
416     visit_scope(db, crate_def_map, &crate_def_map[module_id].scope, cb);
417     for impl_id in crate_def_map[module_id].scope.impls() {
418         let impl_data = db.impl_data(impl_id);
419         for &item in impl_data.items.iter() {
420             match item {
421                 AssocItemId::FunctionId(it) => {
422                     let def = it.into();
423                     cb(def);
424                     let body = db.body(def);
425                     visit_body(db, &body, cb);
426                 }
427                 AssocItemId::ConstId(it) => {
428                     let def = it.into();
429                     cb(def);
430                     let body = db.body(def);
431                     visit_body(db, &body, cb);
432                 }
433                 AssocItemId::TypeAliasId(_) => (),
434             }
435         }
436     }
437
438     fn visit_scope(
439         db: &TestDB,
440         crate_def_map: &DefMap,
441         scope: &ItemScope,
442         cb: &mut dyn FnMut(DefWithBodyId),
443     ) {
444         for decl in scope.declarations() {
445             match decl {
446                 ModuleDefId::FunctionId(it) => {
447                     let def = it.into();
448                     cb(def);
449                     let body = db.body(def);
450                     visit_body(db, &body, cb);
451                 }
452                 ModuleDefId::ConstId(it) => {
453                     let def = it.into();
454                     cb(def);
455                     let body = db.body(def);
456                     visit_body(db, &body, cb);
457                 }
458                 ModuleDefId::StaticId(it) => {
459                     let def = it.into();
460                     cb(def);
461                     let body = db.body(def);
462                     visit_body(db, &body, cb);
463                 }
464                 ModuleDefId::AdtId(hir_def::AdtId::EnumId(it)) => {
465                     db.enum_data(it)
466                         .variants
467                         .iter()
468                         .map(|(id, _)| hir_def::EnumVariantId { parent: it, local_id: id })
469                         .for_each(|it| {
470                             let def = it.into();
471                             cb(def);
472                             let body = db.body(def);
473                             visit_body(db, &body, cb);
474                         });
475                 }
476                 ModuleDefId::TraitId(it) => {
477                     let trait_data = db.trait_data(it);
478                     for &(_, item) in trait_data.items.iter() {
479                         match item {
480                             AssocItemId::FunctionId(it) => cb(it.into()),
481                             AssocItemId::ConstId(it) => cb(it.into()),
482                             AssocItemId::TypeAliasId(_) => (),
483                         }
484                     }
485                 }
486                 ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.local_id, cb),
487                 _ => (),
488             }
489         }
490     }
491
492     fn visit_body(db: &TestDB, body: &Body, cb: &mut dyn FnMut(DefWithBodyId)) {
493         for (_, def_map) in body.blocks(db) {
494             for (mod_id, _) in def_map.modules() {
495                 visit_module(db, &def_map, mod_id, cb);
496             }
497         }
498     }
499 }
500
501 fn ellipsize(mut text: String, max_len: usize) -> String {
502     if text.len() <= max_len {
503         return text;
504     }
505     let ellipsis = "...";
506     let e_len = ellipsis.len();
507     let mut prefix_len = (max_len - e_len) / 2;
508     while !text.is_char_boundary(prefix_len) {
509         prefix_len += 1;
510     }
511     let mut suffix_len = max_len - e_len - prefix_len;
512     while !text.is_char_boundary(text.len() - suffix_len) {
513         suffix_len += 1;
514     }
515     text.replace_range(prefix_len..text.len() - suffix_len, ellipsis);
516     text
517 }
518
519 fn check_infer(ra_fixture: &str, expect: Expect) {
520     let mut actual = infer(ra_fixture);
521     actual.push('\n');
522     expect.assert_eq(&actual);
523 }
524
525 fn check_infer_with_mismatches(ra_fixture: &str, expect: Expect) {
526     let mut actual = infer_with_mismatches(ra_fixture, true);
527     actual.push('\n');
528     expect.assert_eq(&actual);
529 }
530
531 #[test]
532 fn salsa_bug() {
533     let (mut db, pos) = TestDB::with_position(
534         "
535         //- /lib.rs
536         trait Index {
537             type Output;
538         }
539
540         type Key<S: UnificationStoreBase> = <S as UnificationStoreBase>::Key;
541
542         pub trait UnificationStoreBase: Index<Output = Key<Self>> {
543             type Key;
544
545             fn len(&self) -> usize;
546         }
547
548         pub trait UnificationStoreMut: UnificationStoreBase {
549             fn push(&mut self, value: Self::Key);
550         }
551
552         fn main() {
553             let x = 1;
554             x.push(1);$0
555         }
556     ",
557     );
558
559     let module = db.module_for_file(pos.file_id);
560     let crate_def_map = module.def_map(&db);
561     visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
562         db.infer(def);
563     });
564
565     let new_text = "
566         //- /lib.rs
567         trait Index {
568             type Output;
569         }
570
571         type Key<S: UnificationStoreBase> = <S as UnificationStoreBase>::Key;
572
573         pub trait UnificationStoreBase: Index<Output = Key<Self>> {
574             type Key;
575
576             fn len(&self) -> usize;
577         }
578
579         pub trait UnificationStoreMut: UnificationStoreBase {
580             fn push(&mut self, value: Self::Key);
581         }
582
583         fn main() {
584
585             let x = 1;
586             x.push(1);
587         }
588     "
589     .to_string();
590
591     db.set_file_text(pos.file_id, Arc::new(new_text));
592
593     let module = db.module_for_file(pos.file_id);
594     let crate_def_map = module.def_map(&db);
595     visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
596         db.infer(def);
597     });
598 }