aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty/src/tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_ty/src/tests.rs')
-rw-r--r--crates/hir_ty/src/tests.rs359
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 @@
1mod never_type;
2mod coercion;
3mod regression;
4mod simple;
5mod patterns;
6mod traits;
7mod method_resolution;
8mod macros;
9mod display_source_code;
10
11use std::sync::Arc;
12
13use base_db::{fixture::WithFixture, FileRange, SourceDatabase, SourceDatabaseExt};
14use expect::Expect;
15use 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};
24use hir_expand::{db::AstDatabase, InFile};
25use stdx::format_to;
26use syntax::{
27 algo,
28 ast::{self, AstNode},
29 SyntaxNode,
30};
31
32use 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
40fn 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
53fn check_types(ra_fixture: &str) {
54 check_types_impl(ra_fixture, false)
55}
56
57fn check_types_source_code(ra_fixture: &str) {
58 check_types_impl(ra_fixture, true)
59}
60
61fn 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
81fn 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
98fn infer(ra_fixture: &str) -> String {
99 infer_with_mismatches(ra_fixture, false)
100}
101
102fn 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
216fn 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
287fn 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]
306fn 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
349fn 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
355fn 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}