Skip to content

Commit 3898387

Browse files
Merge #9346
9346: Refactor / clean up hir_ty tests r=flodiebold a=flodiebold Notable changes: - unify `check_types` and `check_mismatches` into `check`, which supports both kinds of annotations (`check_types` still exists because I didn't want to change all the annotations, but uses the same implementation) - because of that, `check_types` now fails on any type mismatches; also annotations always have to hit the exact range - there's also `check_no_mismatches` for when we explicitly just want to check that there are no type mismatches without giving any annotations (`check` will fail without annotations) - test annotations can now be overlapping (they point to the nearest line that has actual code in that range): ``` // ^^^^ annotation // ^^^^^^^^^ another annotation ``` Co-authored-by: Florian Diebold <[email protected]>
2 parents 8cc2b71 + a1120b6 commit 3898387

File tree

12 files changed

+557
-996
lines changed

12 files changed

+557
-996
lines changed

crates/hir_ty/src/chalk_db.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! about the code that Chalk needs.
33
use std::sync::Arc;
44

5+
use cov_mark::hit;
56
use log::debug;
67

78
use chalk_ir::{cast::Cast, fold::shift::Shift, CanonicalVarKinds};
@@ -106,7 +107,9 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
106107
};
107108

108109
fn local_impls(db: &dyn HirDatabase, module: ModuleId) -> Option<Arc<TraitImpls>> {
109-
db.trait_impls_in_block(module.containing_block()?)
110+
let block = module.containing_block()?;
111+
hit!(block_local_impls);
112+
db.trait_impls_in_block(block)
110113
}
111114

112115
// Note: Since we're using impls_for_trait, only impls where the trait

crates/hir_ty/src/test_db.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,20 @@ impl FileLoader for TestDB {
8686
}
8787

8888
impl TestDB {
89-
pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId {
89+
pub(crate) fn module_for_file_opt(&self, file_id: FileId) -> Option<ModuleId> {
9090
for &krate in self.relevant_crates(file_id).iter() {
9191
let crate_def_map = self.crate_def_map(krate);
9292
for (local_id, data) in crate_def_map.modules() {
9393
if data.origin.file_id() == Some(file_id) {
94-
return crate_def_map.module_id(local_id);
94+
return Some(crate_def_map.module_id(local_id));
9595
}
9696
}
9797
}
98-
panic!("Can't find module for file")
98+
None
99+
}
100+
101+
pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId {
102+
self.module_for_file_opt(file_id).unwrap()
99103
}
100104

101105
pub(crate) fn extract_annotations(&self) -> FxHashMap<FileId, Vec<(TextRange, String)>> {

crates/hir_ty/src/tests.rs

+142-93
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,21 @@ mod incremental;
1111

1212
use std::{collections::HashMap, env, sync::Arc};
1313

14-
use base_db::{fixture::WithFixture, FileRange, SourceDatabase, SourceDatabaseExt};
14+
use base_db::{fixture::WithFixture, FileRange, SourceDatabaseExt};
1515
use expect_test::Expect;
1616
use hir_def::{
1717
body::{Body, BodySourceMap, SyntheticSyntax},
18-
child_by_source::ChildBySource,
1918
db::DefDatabase,
19+
expr::{ExprId, PatId},
2020
item_scope::ItemScope,
21-
keys,
2221
nameres::DefMap,
2322
src::HasSource,
24-
AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId,
23+
AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId,
2524
};
2625
use hir_expand::{db::AstDatabase, InFile};
2726
use once_cell::race::OnceBool;
2827
use stdx::format_to;
2928
use syntax::{
30-
algo,
3129
ast::{self, AstNode, NameOwner},
3230
SyntaxNode,
3331
};
@@ -59,51 +57,55 @@ fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
5957
}
6058

6159
fn check_types(ra_fixture: &str) {
62-
check_types_impl(ra_fixture, false)
60+
check_impl(ra_fixture, false, true, false)
6361
}
6462

6563
fn check_types_source_code(ra_fixture: &str) {
66-
check_types_impl(ra_fixture, true)
67-
}
68-
69-
fn check_types_impl(ra_fixture: &str, display_source: bool) {
70-
let _tracing = setup_tracing();
71-
let db = TestDB::with_files(ra_fixture);
72-
let mut checked_one = false;
73-
for (file_id, annotations) in db.extract_annotations() {
74-
for (range, expected) in annotations {
75-
let ty = type_at_range(&db, FileRange { file_id, range });
76-
let actual = if display_source {
77-
let module = db.module_for_file(file_id);
78-
ty.display_source_code(&db, module).unwrap()
79-
} else {
80-
ty.display_test(&db).to_string()
81-
};
82-
assert_eq!(expected, actual);
83-
checked_one = true;
84-
}
85-
}
86-
87-
assert!(checked_one, "no `//^` annotations found");
64+
check_impl(ra_fixture, false, true, true)
8865
}
8966

9067
fn check_no_mismatches(ra_fixture: &str) {
91-
check_mismatches_impl(ra_fixture, true)
68+
check_impl(ra_fixture, true, false, false)
9269
}
9370

94-
#[allow(unused)]
95-
fn check_mismatches(ra_fixture: &str) {
96-
check_mismatches_impl(ra_fixture, false)
71+
fn check(ra_fixture: &str) {
72+
check_impl(ra_fixture, false, false, false)
9773
}
9874

99-
fn check_mismatches_impl(ra_fixture: &str, allow_none: bool) {
75+
fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_source: bool) {
10076
let _tracing = setup_tracing();
101-
let (db, file_id) = TestDB::with_single_file(ra_fixture);
102-
let module = db.module_for_file(file_id);
103-
let def_map = module.def_map(&db);
77+
let (db, files) = TestDB::with_many_files(ra_fixture);
78+
79+
let mut had_annotations = false;
80+
let mut mismatches = HashMap::new();
81+
let mut types = HashMap::new();
82+
for (file_id, annotations) in db.extract_annotations() {
83+
for (range, expected) in annotations {
84+
let file_range = FileRange { file_id, range };
85+
if only_types {
86+
types.insert(file_range, expected);
87+
} else if expected.starts_with("type: ") {
88+
types.insert(file_range, expected.trim_start_matches("type: ").to_string());
89+
} else if expected.starts_with("expected") {
90+
mismatches.insert(file_range, expected);
91+
} else {
92+
panic!("unexpected annotation: {}", expected);
93+
}
94+
had_annotations = true;
95+
}
96+
}
97+
assert!(had_annotations || allow_none, "no `//^` annotations found");
10498

10599
let mut defs: Vec<DefWithBodyId> = Vec::new();
106-
visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
100+
for file_id in files {
101+
let module = db.module_for_file_opt(file_id);
102+
let module = match module {
103+
Some(m) => m,
104+
None => continue,
105+
};
106+
let def_map = module.def_map(&db);
107+
visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
108+
}
107109
defs.sort_by_key(|def| match def {
108110
DefWithBodyId::FunctionId(it) => {
109111
let loc = it.lookup(&db);
@@ -118,37 +120,59 @@ fn check_mismatches_impl(ra_fixture: &str, allow_none: bool) {
118120
loc.source(&db).value.syntax().text_range().start()
119121
}
120122
});
121-
let mut mismatches = HashMap::new();
122-
let mut push_mismatch = |src_ptr: InFile<SyntaxNode>, mismatch: TypeMismatch| {
123-
let range = src_ptr.value.text_range();
124-
if src_ptr.file_id.call_node(&db).is_some() {
125-
panic!("type mismatch in macro expansion");
126-
}
127-
let file_range = FileRange { file_id: src_ptr.file_id.original_file(&db), range };
128-
let actual = format!(
129-
"expected {}, got {}",
130-
mismatch.expected.display_test(&db),
131-
mismatch.actual.display_test(&db)
132-
);
133-
mismatches.insert(file_range, actual);
134-
};
123+
let mut unexpected_type_mismatches = String::new();
135124
for def in defs {
136125
let (_body, body_source_map) = db.body_with_source_map(def);
137126
let inference_result = db.infer(def);
127+
128+
for (pat, ty) in inference_result.type_of_pat.iter() {
129+
let node = match pat_node(&body_source_map, pat, &db) {
130+
Some(value) => value,
131+
None => continue,
132+
};
133+
let range = node.as_ref().original_file_range(&db);
134+
if let Some(expected) = types.remove(&range) {
135+
let actual = if display_source {
136+
ty.display_source_code(&db, def.module(&db)).unwrap()
137+
} else {
138+
ty.display_test(&db).to_string()
139+
};
140+
assert_eq!(actual, expected);
141+
}
142+
}
143+
144+
for (expr, ty) in inference_result.type_of_expr.iter() {
145+
let node = match expr_node(&body_source_map, expr, &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+
138160
for (pat, mismatch) in inference_result.pat_type_mismatches() {
139-
let syntax_ptr = match body_source_map.pat_syntax(pat) {
140-
Ok(sp) => {
141-
let root = db.parse_or_expand(sp.file_id).unwrap();
142-
sp.map(|ptr| {
143-
ptr.either(
144-
|it| it.to_node(&root).syntax().clone(),
145-
|it| it.to_node(&root).syntax().clone(),
146-
)
147-
})
148-
}
149-
Err(SyntheticSyntax) => continue,
161+
let node = match pat_node(&body_source_map, pat, &db) {
162+
Some(value) => value,
163+
None => continue,
150164
};
151-
push_mismatch(syntax_ptr, mismatch.clone());
165+
let range = node.as_ref().original_file_range(&db);
166+
let actual = format!(
167+
"expected {}, got {}",
168+
mismatch.expected.display_test(&db),
169+
mismatch.actual.display_test(&db)
170+
);
171+
if let Some(annotation) = mismatches.remove(&range) {
172+
assert_eq!(actual, annotation);
173+
} else {
174+
format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual);
175+
}
152176
}
153177
for (expr, mismatch) in inference_result.expr_type_mismatches() {
154178
let node = match body_source_map.expr_syntax(expr) {
@@ -158,45 +182,70 @@ fn check_mismatches_impl(ra_fixture: &str, allow_none: bool) {
158182
}
159183
Err(SyntheticSyntax) => continue,
160184
};
161-
push_mismatch(node, mismatch.clone());
162-
}
163-
}
164-
let mut checked_one = false;
165-
for (file_id, annotations) in db.extract_annotations() {
166-
for (range, expected) in annotations {
167-
let file_range = FileRange { file_id, range };
168-
if let Some(mismatch) = mismatches.remove(&file_range) {
169-
assert_eq!(mismatch, expected);
185+
let range = node.as_ref().original_file_range(&db);
186+
let actual = format!(
187+
"expected {}, got {}",
188+
mismatch.expected.display_test(&db),
189+
mismatch.actual.display_test(&db)
190+
);
191+
if let Some(annotation) = mismatches.remove(&range) {
192+
assert_eq!(actual, annotation);
170193
} else {
171-
assert!(false, "Expected mismatch not encountered: {}\n", expected);
194+
format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual);
172195
}
173-
checked_one = true;
174196
}
175197
}
198+
176199
let mut buf = String::new();
177-
for (range, mismatch) in mismatches {
178-
format_to!(buf, "{:?}: {}\n", range.range, mismatch,);
200+
if !unexpected_type_mismatches.is_empty() {
201+
format_to!(buf, "Unexpected type mismatches:\n{}", unexpected_type_mismatches);
202+
}
203+
if !mismatches.is_empty() {
204+
format_to!(buf, "Unchecked mismatch annotations:\n");
205+
for m in mismatches {
206+
format_to!(buf, "{:?}: {}\n", m.0.range, m.1);
207+
}
179208
}
180-
assert!(buf.is_empty(), "Unexpected type mismatches:\n{}", buf);
209+
if !types.is_empty() {
210+
format_to!(buf, "Unchecked type annotations:\n");
211+
for t in types {
212+
format_to!(buf, "{:?}: type {}\n", t.0.range, t.1);
213+
}
214+
}
215+
assert!(buf.is_empty(), "{}", buf);
216+
}
181217

182-
assert!(checked_one || allow_none, "no `//^` annotations found");
218+
fn expr_node(
219+
body_source_map: &BodySourceMap,
220+
expr: ExprId,
221+
db: &TestDB,
222+
) -> Option<InFile<SyntaxNode>> {
223+
Some(match body_source_map.expr_syntax(expr) {
224+
Ok(sp) => {
225+
let root = db.parse_or_expand(sp.file_id).unwrap();
226+
sp.map(|ptr| ptr.to_node(&root).syntax().clone())
227+
}
228+
Err(SyntheticSyntax) => return None,
229+
})
183230
}
184231

185-
fn type_at_range(db: &TestDB, pos: FileRange) -> Ty {
186-
let file = db.parse(pos.file_id).ok().unwrap();
187-
let expr = algo::find_node_at_range::<ast::Expr>(file.syntax(), pos.range).unwrap();
188-
let fn_def = expr.syntax().ancestors().find_map(ast::Fn::cast).unwrap();
189-
let module = db.module_for_file(pos.file_id);
190-
let func = *module.child_by_source(db)[keys::FUNCTION]
191-
.get(&InFile::new(pos.file_id.into(), fn_def))
192-
.unwrap();
193-
194-
let (_body, source_map) = db.body_with_source_map(func.into());
195-
if let Some(expr_id) = source_map.node_expr(InFile::new(pos.file_id.into(), &expr)) {
196-
let infer = db.infer(func.into());
197-
return infer[expr_id].clone();
198-
}
199-
panic!("Can't find expression")
232+
fn pat_node(
233+
body_source_map: &BodySourceMap,
234+
pat: PatId,
235+
db: &TestDB,
236+
) -> Option<InFile<SyntaxNode>> {
237+
Some(match body_source_map.pat_syntax(pat) {
238+
Ok(sp) => {
239+
let root = db.parse_or_expand(sp.file_id).unwrap();
240+
sp.map(|ptr| {
241+
ptr.either(
242+
|it| it.to_node(&root).syntax().clone(),
243+
|it| it.to_node(&root).syntax().clone(),
244+
)
245+
})
246+
}
247+
Err(SyntheticSyntax) => return None,
248+
})
200249
}
201250

202251
fn infer(ra_fixture: &str) -> String {

0 commit comments

Comments
 (0)