]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | use hir::{db::AstDatabase, HirDisplay, Type}; |
2 | use ide_db::{famous_defs::FamousDefs, source_change::SourceChange}; | |
3 | use syntax::{ | |
4 | ast::{self, BlockExpr, ExprStmt}, | |
5 | AstNode, | |
6 | }; | |
7 | use text_edit::TextEdit; | |
8 | ||
9 | use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticsContext}; | |
10 | ||
11 | // Diagnostic: type-mismatch | |
12 | // | |
13 | // This diagnostic is triggered when the type of an expression does not match | |
14 | // the expected type. | |
15 | pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic { | |
16 | let display_range = adjusted_display_range::<ast::BlockExpr>( | |
17 | ctx, | |
18 | d.expr.clone().map(|it| it.into()), | |
19 | &|block| { | |
20 | let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range(); | |
21 | cov_mark::hit!(type_mismatch_on_block); | |
22 | Some(r_curly_range) | |
23 | }, | |
24 | ); | |
25 | ||
26 | let mut diag = Diagnostic::new( | |
27 | "type-mismatch", | |
28 | format!( | |
29 | "expected {}, found {}", | |
30 | d.expected.display(ctx.sema.db), | |
31 | d.actual.display(ctx.sema.db) | |
32 | ), | |
33 | display_range, | |
34 | ) | |
35 | .with_fixes(fixes(ctx, d)); | |
36 | if diag.fixes.is_none() { | |
37 | diag.experimental = true; | |
38 | } | |
39 | diag | |
40 | } | |
41 | ||
42 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assist>> { | |
43 | let mut fixes = Vec::new(); | |
44 | ||
45 | add_reference(ctx, d, &mut fixes); | |
46 | add_missing_ok_or_some(ctx, d, &mut fixes); | |
47 | remove_semicolon(ctx, d, &mut fixes); | |
48 | str_ref_to_owned(ctx, d, &mut fixes); | |
49 | ||
50 | if fixes.is_empty() { | |
51 | None | |
52 | } else { | |
53 | Some(fixes) | |
54 | } | |
55 | } | |
56 | ||
57 | fn add_reference( | |
58 | ctx: &DiagnosticsContext<'_>, | |
59 | d: &hir::TypeMismatch, | |
60 | acc: &mut Vec<Assist>, | |
61 | ) -> Option<()> { | |
064997fb FG |
62 | let range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range; |
63 | ||
64 | let (_, mutability) = d.expected.as_reference()?; | |
65 | let actual_with_ref = Type::reference(&d.actual, mutability); | |
66 | if !actual_with_ref.could_coerce_to(ctx.sema.db, &d.expected) { | |
67 | return None; | |
68 | } | |
69 | ||
70 | let ampersands = format!("&{}", mutability.as_keyword_for_ref()); | |
71 | ||
2b03887a | 72 | let edit = TextEdit::insert(range.start(), ampersands); |
064997fb FG |
73 | let source_change = |
74 | SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); | |
75 | acc.push(fix("add_reference_here", "Add reference here", source_change, range)); | |
76 | Some(()) | |
77 | } | |
78 | ||
79 | fn add_missing_ok_or_some( | |
80 | ctx: &DiagnosticsContext<'_>, | |
81 | d: &hir::TypeMismatch, | |
82 | acc: &mut Vec<Assist>, | |
83 | ) -> Option<()> { | |
84 | let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; | |
85 | let expr = d.expr.value.to_node(&root); | |
86 | let expr_range = expr.syntax().text_range(); | |
87 | let scope = ctx.sema.scope(expr.syntax())?; | |
88 | ||
89 | let expected_adt = d.expected.as_adt()?; | |
90 | let expected_enum = expected_adt.as_enum()?; | |
91 | ||
92 | let famous_defs = FamousDefs(&ctx.sema, scope.krate()); | |
93 | let core_result = famous_defs.core_result_Result(); | |
94 | let core_option = famous_defs.core_option_Option(); | |
95 | ||
96 | if Some(expected_enum) != core_result && Some(expected_enum) != core_option { | |
97 | return None; | |
98 | } | |
99 | ||
100 | let variant_name = if Some(expected_enum) == core_result { "Ok" } else { "Some" }; | |
101 | ||
102 | let wrapped_actual_ty = expected_adt.ty_with_args(ctx.sema.db, &[d.actual.clone()]); | |
103 | ||
104 | if !d.expected.could_unify_with(ctx.sema.db, &wrapped_actual_ty) { | |
105 | return None; | |
106 | } | |
107 | ||
108 | let mut builder = TextEdit::builder(); | |
9c376795 | 109 | builder.insert(expr.syntax().text_range().start(), format!("{variant_name}(")); |
064997fb FG |
110 | builder.insert(expr.syntax().text_range().end(), ")".to_string()); |
111 | let source_change = | |
112 | SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish()); | |
9c376795 | 113 | let name = format!("Wrap in {variant_name}"); |
064997fb FG |
114 | acc.push(fix("wrap_in_constructor", &name, source_change, expr_range)); |
115 | Some(()) | |
116 | } | |
117 | ||
118 | fn remove_semicolon( | |
119 | ctx: &DiagnosticsContext<'_>, | |
120 | d: &hir::TypeMismatch, | |
121 | acc: &mut Vec<Assist>, | |
122 | ) -> Option<()> { | |
123 | let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; | |
124 | let expr = d.expr.value.to_node(&root); | |
125 | if !d.actual.is_unit() { | |
126 | return None; | |
127 | } | |
128 | let block = BlockExpr::cast(expr.syntax().clone())?; | |
129 | let expr_before_semi = | |
130 | block.statements().last().and_then(|s| ExprStmt::cast(s.syntax().clone()))?; | |
131 | let type_before_semi = ctx.sema.type_of_expr(&expr_before_semi.expr()?)?.original(); | |
132 | if !type_before_semi.could_coerce_to(ctx.sema.db, &d.expected) { | |
133 | return None; | |
134 | } | |
135 | let semicolon_range = expr_before_semi.semicolon_token()?.text_range(); | |
136 | ||
137 | let edit = TextEdit::delete(semicolon_range); | |
138 | let source_change = | |
139 | SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); | |
140 | ||
141 | acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range)); | |
142 | Some(()) | |
143 | } | |
144 | ||
145 | fn str_ref_to_owned( | |
146 | ctx: &DiagnosticsContext<'_>, | |
147 | d: &hir::TypeMismatch, | |
148 | acc: &mut Vec<Assist>, | |
149 | ) -> Option<()> { | |
150 | let expected = d.expected.display(ctx.sema.db); | |
151 | let actual = d.actual.display(ctx.sema.db); | |
152 | ||
153 | if expected.to_string() != "String" || actual.to_string() != "&str" { | |
154 | return None; | |
155 | } | |
156 | ||
157 | let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; | |
158 | let expr = d.expr.value.to_node(&root); | |
159 | let expr_range = expr.syntax().text_range(); | |
160 | ||
161 | let to_owned = format!(".to_owned()"); | |
162 | ||
163 | let edit = TextEdit::insert(expr.syntax().text_range().end(), to_owned); | |
164 | let source_change = | |
165 | SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); | |
166 | acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, expr_range)); | |
167 | ||
168 | Some(()) | |
169 | } | |
170 | ||
171 | #[cfg(test)] | |
172 | mod tests { | |
173 | use crate::tests::{check_diagnostics, check_fix, check_no_fix}; | |
174 | ||
175 | #[test] | |
176 | fn missing_reference() { | |
177 | check_diagnostics( | |
178 | r#" | |
179 | fn main() { | |
180 | test(123); | |
181 | //^^^ 💡 error: expected &i32, found i32 | |
182 | } | |
183 | fn test(arg: &i32) {} | |
184 | "#, | |
185 | ); | |
186 | } | |
187 | ||
188 | #[test] | |
189 | fn test_add_reference_to_int() { | |
190 | check_fix( | |
191 | r#" | |
192 | fn main() { | |
193 | test(123$0); | |
194 | } | |
195 | fn test(arg: &i32) {} | |
196 | "#, | |
197 | r#" | |
198 | fn main() { | |
199 | test(&123); | |
200 | } | |
201 | fn test(arg: &i32) {} | |
202 | "#, | |
203 | ); | |
204 | } | |
205 | ||
206 | #[test] | |
207 | fn test_add_mutable_reference_to_int() { | |
208 | check_fix( | |
209 | r#" | |
210 | fn main() { | |
211 | test($0123); | |
212 | } | |
213 | fn test(arg: &mut i32) {} | |
214 | "#, | |
215 | r#" | |
216 | fn main() { | |
217 | test(&mut 123); | |
218 | } | |
219 | fn test(arg: &mut i32) {} | |
220 | "#, | |
221 | ); | |
222 | } | |
223 | ||
224 | #[test] | |
225 | fn test_add_reference_to_array() { | |
226 | check_fix( | |
227 | r#" | |
228 | //- minicore: coerce_unsized | |
229 | fn main() { | |
230 | test($0[1, 2, 3]); | |
231 | } | |
232 | fn test(arg: &[i32]) {} | |
233 | "#, | |
234 | r#" | |
235 | fn main() { | |
236 | test(&[1, 2, 3]); | |
237 | } | |
238 | fn test(arg: &[i32]) {} | |
239 | "#, | |
240 | ); | |
241 | } | |
242 | ||
243 | #[test] | |
244 | fn test_add_reference_with_autoderef() { | |
245 | check_fix( | |
246 | r#" | |
247 | //- minicore: coerce_unsized, deref | |
248 | struct Foo; | |
249 | struct Bar; | |
250 | impl core::ops::Deref for Foo { | |
251 | type Target = Bar; | |
252 | } | |
253 | ||
254 | fn main() { | |
255 | test($0Foo); | |
256 | } | |
257 | fn test(arg: &Bar) {} | |
258 | "#, | |
259 | r#" | |
260 | struct Foo; | |
261 | struct Bar; | |
262 | impl core::ops::Deref for Foo { | |
263 | type Target = Bar; | |
264 | } | |
265 | ||
266 | fn main() { | |
267 | test(&Foo); | |
268 | } | |
269 | fn test(arg: &Bar) {} | |
270 | "#, | |
271 | ); | |
272 | } | |
273 | ||
274 | #[test] | |
275 | fn test_add_reference_to_method_call() { | |
276 | check_fix( | |
277 | r#" | |
278 | fn main() { | |
279 | Test.call_by_ref($0123); | |
280 | } | |
281 | struct Test; | |
282 | impl Test { | |
283 | fn call_by_ref(&self, arg: &i32) {} | |
284 | } | |
285 | "#, | |
286 | r#" | |
287 | fn main() { | |
288 | Test.call_by_ref(&123); | |
289 | } | |
290 | struct Test; | |
291 | impl Test { | |
292 | fn call_by_ref(&self, arg: &i32) {} | |
293 | } | |
294 | "#, | |
295 | ); | |
296 | } | |
297 | ||
298 | #[test] | |
299 | fn test_add_reference_to_let_stmt() { | |
300 | check_fix( | |
301 | r#" | |
302 | fn main() { | |
303 | let test: &i32 = $0123; | |
304 | } | |
305 | "#, | |
306 | r#" | |
307 | fn main() { | |
308 | let test: &i32 = &123; | |
309 | } | |
310 | "#, | |
311 | ); | |
312 | } | |
313 | ||
2b03887a FG |
314 | #[test] |
315 | fn test_add_reference_to_macro_call() { | |
316 | check_fix( | |
317 | r#" | |
318 | macro_rules! thousand { | |
319 | () => { | |
320 | 1000_u64 | |
321 | }; | |
322 | } | |
323 | fn test(foo: &u64) {} | |
324 | fn main() { | |
325 | test($0thousand!()); | |
326 | } | |
327 | "#, | |
328 | r#" | |
329 | macro_rules! thousand { | |
330 | () => { | |
331 | 1000_u64 | |
332 | }; | |
333 | } | |
334 | fn test(foo: &u64) {} | |
335 | fn main() { | |
336 | test(&thousand!()); | |
337 | } | |
338 | "#, | |
339 | ); | |
340 | } | |
341 | ||
064997fb FG |
342 | #[test] |
343 | fn test_add_mutable_reference_to_let_stmt() { | |
344 | check_fix( | |
345 | r#" | |
346 | fn main() { | |
347 | let test: &mut i32 = $0123; | |
348 | } | |
349 | "#, | |
350 | r#" | |
351 | fn main() { | |
352 | let test: &mut i32 = &mut 123; | |
353 | } | |
354 | "#, | |
355 | ); | |
356 | } | |
357 | ||
358 | #[test] | |
359 | fn test_wrap_return_type_option() { | |
360 | check_fix( | |
361 | r#" | |
362 | //- minicore: option, result | |
363 | fn div(x: i32, y: i32) -> Option<i32> { | |
364 | if y == 0 { | |
365 | return None; | |
366 | } | |
367 | x / y$0 | |
368 | } | |
369 | "#, | |
370 | r#" | |
371 | fn div(x: i32, y: i32) -> Option<i32> { | |
372 | if y == 0 { | |
373 | return None; | |
374 | } | |
375 | Some(x / y) | |
376 | } | |
377 | "#, | |
378 | ); | |
379 | } | |
380 | ||
381 | #[test] | |
382 | fn const_generic_type_mismatch() { | |
383 | check_diagnostics( | |
384 | r#" | |
385 | pub struct Rate<const N: u32>; | |
386 | fn f<const N: u64>() -> Rate<N> { // FIXME: add some error | |
387 | loop {} | |
388 | } | |
389 | fn run(t: Rate<5>) { | |
390 | } | |
391 | fn main() { | |
392 | run(f()) // FIXME: remove this error | |
393 | //^^^ error: expected Rate<5>, found Rate<_> | |
394 | } | |
395 | "#, | |
396 | ); | |
397 | } | |
398 | ||
399 | #[test] | |
400 | fn const_generic_unknown() { | |
401 | check_diagnostics( | |
402 | r#" | |
403 | pub struct Rate<T, const NOM: u32, const DENOM: u32>(T); | |
404 | fn run(t: Rate<u32, 1, 1>) { | |
405 | } | |
406 | fn main() { | |
407 | run(Rate::<_, _, _>(5)); | |
408 | } | |
409 | "#, | |
410 | ); | |
411 | } | |
412 | ||
413 | #[test] | |
414 | fn test_wrap_return_type_option_tails() { | |
415 | check_fix( | |
416 | r#" | |
417 | //- minicore: option, result | |
418 | fn div(x: i32, y: i32) -> Option<i32> { | |
419 | if y == 0 { | |
420 | Some(0) | |
421 | } else if true { | |
422 | 100$0 | |
423 | } else { | |
424 | None | |
425 | } | |
426 | } | |
427 | "#, | |
428 | r#" | |
429 | fn div(x: i32, y: i32) -> Option<i32> { | |
430 | if y == 0 { | |
431 | Some(0) | |
432 | } else if true { | |
433 | Some(100) | |
434 | } else { | |
435 | None | |
436 | } | |
437 | } | |
438 | "#, | |
439 | ); | |
440 | } | |
441 | ||
442 | #[test] | |
443 | fn test_wrap_return_type() { | |
444 | check_fix( | |
445 | r#" | |
446 | //- minicore: option, result | |
447 | fn div(x: i32, y: i32) -> Result<i32, ()> { | |
448 | if y == 0 { | |
449 | return Err(()); | |
450 | } | |
451 | x / y$0 | |
452 | } | |
453 | "#, | |
454 | r#" | |
455 | fn div(x: i32, y: i32) -> Result<i32, ()> { | |
456 | if y == 0 { | |
457 | return Err(()); | |
458 | } | |
459 | Ok(x / y) | |
460 | } | |
461 | "#, | |
462 | ); | |
463 | } | |
464 | ||
465 | #[test] | |
466 | fn test_wrap_return_type_handles_generic_functions() { | |
467 | check_fix( | |
468 | r#" | |
469 | //- minicore: option, result | |
470 | fn div<T>(x: T) -> Result<T, i32> { | |
471 | if x == 0 { | |
472 | return Err(7); | |
473 | } | |
474 | $0x | |
475 | } | |
476 | "#, | |
477 | r#" | |
478 | fn div<T>(x: T) -> Result<T, i32> { | |
479 | if x == 0 { | |
480 | return Err(7); | |
481 | } | |
482 | Ok(x) | |
483 | } | |
484 | "#, | |
485 | ); | |
486 | } | |
487 | ||
488 | #[test] | |
489 | fn test_wrap_return_type_handles_type_aliases() { | |
490 | check_fix( | |
491 | r#" | |
492 | //- minicore: option, result | |
493 | type MyResult<T> = Result<T, ()>; | |
494 | ||
495 | fn div(x: i32, y: i32) -> MyResult<i32> { | |
496 | if y == 0 { | |
497 | return Err(()); | |
498 | } | |
499 | x $0/ y | |
500 | } | |
501 | "#, | |
502 | r#" | |
503 | type MyResult<T> = Result<T, ()>; | |
504 | ||
505 | fn div(x: i32, y: i32) -> MyResult<i32> { | |
506 | if y == 0 { | |
507 | return Err(()); | |
508 | } | |
509 | Ok(x / y) | |
510 | } | |
511 | "#, | |
512 | ); | |
513 | } | |
514 | ||
515 | #[test] | |
516 | fn test_in_const_and_static() { | |
517 | check_fix( | |
518 | r#" | |
519 | //- minicore: option, result | |
520 | static A: Option<()> = {($0)}; | |
521 | "#, | |
522 | r#" | |
523 | static A: Option<()> = {Some(())}; | |
524 | "#, | |
525 | ); | |
526 | check_fix( | |
527 | r#" | |
528 | //- minicore: option, result | |
529 | const _: Option<()> = {($0)}; | |
530 | "#, | |
531 | r#" | |
532 | const _: Option<()> = {Some(())}; | |
533 | "#, | |
534 | ); | |
535 | } | |
536 | ||
537 | #[test] | |
538 | fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { | |
539 | check_no_fix( | |
540 | r#" | |
541 | //- minicore: option, result | |
542 | fn foo() -> Result<(), i32> { 0$0 } | |
543 | "#, | |
544 | ); | |
545 | } | |
546 | ||
547 | #[test] | |
548 | fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() { | |
549 | check_no_fix( | |
550 | r#" | |
551 | //- minicore: option, result | |
552 | enum SomeOtherEnum { Ok(i32), Err(String) } | |
553 | ||
554 | fn foo() -> SomeOtherEnum { 0$0 } | |
555 | "#, | |
556 | ); | |
557 | } | |
558 | ||
559 | #[test] | |
560 | fn remove_semicolon() { | |
561 | check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); | |
562 | } | |
563 | ||
564 | #[test] | |
565 | fn str_ref_to_owned() { | |
566 | check_fix( | |
567 | r#" | |
568 | struct String; | |
569 | ||
570 | fn test() -> String { | |
571 | "a"$0 | |
572 | } | |
573 | "#, | |
574 | r#" | |
575 | struct String; | |
576 | ||
577 | fn test() -> String { | |
578 | "a".to_owned() | |
579 | } | |
580 | "#, | |
581 | ); | |
582 | } | |
583 | ||
584 | #[test] | |
585 | fn type_mismatch_on_block() { | |
586 | cov_mark::check!(type_mismatch_on_block); | |
587 | check_diagnostics( | |
588 | r#" | |
589 | fn f() -> i32 { | |
590 | let x = 1; | |
591 | let y = 2; | |
592 | let _ = x + y; | |
593 | } | |
594 | //^ error: expected i32, found () | |
595 | "#, | |
596 | ); | |
597 | } | |
598 | } |