]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide-diagnostics / src / handlers / type_mismatch.rs
CommitLineData
064997fb
FG
1use hir::{db::AstDatabase, HirDisplay, Type};
2use ide_db::{famous_defs::FamousDefs, source_change::SourceChange};
3use syntax::{
4 ast::{self, BlockExpr, ExprStmt},
5 AstNode,
6};
7use text_edit::TextEdit;
8
9use 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.
15pub(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
42fn 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
57fn 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
79fn 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
118fn 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
145fn 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)]
172mod tests {
173 use crate::tests::{check_diagnostics, check_fix, check_no_fix};
174
175 #[test]
176 fn missing_reference() {
177 check_diagnostics(
178 r#"
179fn main() {
180 test(123);
181 //^^^ 💡 error: expected &i32, found i32
182}
183fn test(arg: &i32) {}
184"#,
185 );
186 }
187
188 #[test]
189 fn test_add_reference_to_int() {
190 check_fix(
191 r#"
192fn main() {
193 test(123$0);
194}
195fn test(arg: &i32) {}
196 "#,
197 r#"
198fn main() {
199 test(&123);
200}
201fn test(arg: &i32) {}
202 "#,
203 );
204 }
205
206 #[test]
207 fn test_add_mutable_reference_to_int() {
208 check_fix(
209 r#"
210fn main() {
211 test($0123);
212}
213fn test(arg: &mut i32) {}
214 "#,
215 r#"
216fn main() {
217 test(&mut 123);
218}
219fn 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
229fn main() {
230 test($0[1, 2, 3]);
231}
232fn test(arg: &[i32]) {}
233 "#,
234 r#"
235fn main() {
236 test(&[1, 2, 3]);
237}
238fn 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
248struct Foo;
249struct Bar;
250impl core::ops::Deref for Foo {
251 type Target = Bar;
252}
253
254fn main() {
255 test($0Foo);
256}
257fn test(arg: &Bar) {}
258 "#,
259 r#"
260struct Foo;
261struct Bar;
262impl core::ops::Deref for Foo {
263 type Target = Bar;
264}
265
266fn main() {
267 test(&Foo);
268}
269fn test(arg: &Bar) {}
270 "#,
271 );
272 }
273
274 #[test]
275 fn test_add_reference_to_method_call() {
276 check_fix(
277 r#"
278fn main() {
279 Test.call_by_ref($0123);
280}
281struct Test;
282impl Test {
283 fn call_by_ref(&self, arg: &i32) {}
284}
285 "#,
286 r#"
287fn main() {
288 Test.call_by_ref(&123);
289}
290struct Test;
291impl 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#"
302fn main() {
303 let test: &i32 = $0123;
304}
305 "#,
306 r#"
307fn 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#"
318macro_rules! thousand {
319 () => {
320 1000_u64
321 };
322}
323fn test(foo: &u64) {}
324fn main() {
325 test($0thousand!());
326}
327 "#,
328 r#"
329macro_rules! thousand {
330 () => {
331 1000_u64
332 };
333}
334fn test(foo: &u64) {}
335fn 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#"
346fn main() {
347 let test: &mut i32 = $0123;
348}
349 "#,
350 r#"
351fn 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
363fn div(x: i32, y: i32) -> Option<i32> {
364 if y == 0 {
365 return None;
366 }
367 x / y$0
368}
369"#,
370 r#"
371fn 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
418fn 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#"
429fn 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
447fn div(x: i32, y: i32) -> Result<i32, ()> {
448 if y == 0 {
449 return Err(());
450 }
451 x / y$0
452}
453"#,
454 r#"
455fn 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
470fn div<T>(x: T) -> Result<T, i32> {
471 if x == 0 {
472 return Err(7);
473 }
474 $0x
475}
476"#,
477 r#"
478fn 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
493type MyResult<T> = Result<T, ()>;
494
495fn div(x: i32, y: i32) -> MyResult<i32> {
496 if y == 0 {
497 return Err(());
498 }
499 x $0/ y
500}
501"#,
502 r#"
503type MyResult<T> = Result<T, ()>;
504
505fn 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
520static A: Option<()> = {($0)};
521 "#,
522 r#"
523static A: Option<()> = {Some(())};
524 "#,
525 );
526 check_fix(
527 r#"
528//- minicore: option, result
529const _: Option<()> = {($0)};
530 "#,
531 r#"
532const _: 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
542fn 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
552enum SomeOtherEnum { Ok(i32), Err(String) }
553
554fn 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#"
568struct String;
569
570fn test() -> String {
571 "a"$0
572}
573 "#,
574 r#"
575struct String;
576
577fn 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#"
589fn f() -> i32 {
590 let x = 1;
591 let y = 2;
592 let _ = x + y;
593 }
594//^ error: expected i32, found ()
595"#,
596 );
597 }
598}