1use either::Either;
2use hir::{CallableKind, ClosureStyle, HirDisplay, InFile, db::ExpandDatabase};
3use ide_db::{
4 famous_defs::FamousDefs,
5 source_change::{SourceChange, SourceChangeBuilder},
6 text_edit::TextEdit,
7};
8use syntax::{
9 AstNode, AstPtr, TextSize,
10 ast::{
11 self, BlockExpr, Expr, ExprStmt, HasArgList,
12 edit::{AstNodeEdit, IndentLevel},
13 },
14};
15
16use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_display_range, fix};
17
18pub(crate) fn type_mismatch(
23 ctx: &DiagnosticsContext<'_, '_>,
24 d: &hir::TypeMismatch<'_>,
25) -> Option<Diagnostic> {
26 if d.expected.is_unknown() || d.actual.is_unknown() {
27 return None;
28 }
29
30 let display_range = adjusted_display_range(ctx, d.expr_or_pat, &|node| {
31 let Either::Left(expr) = node else { return None };
32 let salient_token_range = match expr {
33 ast::Expr::IfExpr(it) => it.if_token()?.text_range(),
34 ast::Expr::LoopExpr(it) => it.loop_token()?.text_range(),
35 ast::Expr::ForExpr(it) => it.for_token()?.text_range(),
36 ast::Expr::WhileExpr(it) => it.while_token()?.text_range(),
37 ast::Expr::BlockExpr(it) => it.stmt_list()?.r_curly_token()?.text_range(),
38 ast::Expr::MatchExpr(it) => it.match_token()?.text_range(),
39 ast::Expr::MethodCallExpr(it) => it.name_ref()?.ident_token()?.text_range(),
40 ast::Expr::FieldExpr(it) => it.name_ref()?.ident_token()?.text_range(),
41 ast::Expr::AwaitExpr(it) => it.await_token()?.text_range(),
42 _ => return None,
43 };
44
45 cov_mark::hit!(type_mismatch_range_adjustment);
46 Some(salient_token_range)
47 });
48 Some(
49 Diagnostic::new(
50 DiagnosticCode::RustcHardError("E0308"),
51 format!(
52 "expected {}, found {}",
53 d.expected
54 .display(ctx.sema.db, ctx.display_target)
55 .with_closure_style(ClosureStyle::ClosureWithId),
56 d.actual
57 .display(ctx.sema.db, ctx.display_target)
58 .with_closure_style(ClosureStyle::ClosureWithId),
59 ),
60 display_range,
61 )
62 .stable()
63 .with_fixes(fixes(ctx, d)),
64 )
65}
66
67fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &hir::TypeMismatch<'_>) -> Option<Vec<Assist>> {
68 let mut fixes = Vec::new();
69
70 if let Some(expr_ptr) = d.expr_or_pat.value.cast::<ast::Expr>() {
71 let expr_ptr = &InFile { file_id: d.expr_or_pat.file_id, value: expr_ptr };
72 add_reference(ctx, d, expr_ptr, &mut fixes);
73 add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes);
74 remove_unnecessary_wrapper(ctx, d, expr_ptr, &mut fixes);
75 remove_semicolon(ctx, d, expr_ptr, &mut fixes);
76 str_ref_to_owned(ctx, d, expr_ptr, &mut fixes);
77 }
78
79 if fixes.is_empty() { None } else { Some(fixes) }
80}
81
82fn add_reference(
83 ctx: &DiagnosticsContext<'_, '_>,
84 d: &hir::TypeMismatch<'_>,
85 expr_ptr: &InFile<AstPtr<ast::Expr>>,
86 acc: &mut Vec<Assist>,
87) -> Option<()> {
88 let range = ctx.sema.diagnostics_display_range((*expr_ptr).map(|it| it.into()));
89
90 let (_, mutability) = d.expected.as_reference()?;
91 let actual_with_ref = d.actual.add_reference(ctx.db(), mutability);
92 if !actual_with_ref.could_coerce_to(ctx.sema.db, &d.expected) {
93 return None;
94 }
95
96 let ampersands = format!("&{}", mutability.as_keyword_for_ref());
97
98 let edit = TextEdit::insert(range.range.start(), ampersands);
99 let source_change = SourceChange::from_text_edit(range.file_id, edit);
100 acc.push(fix("add_reference_here", "Add reference here", source_change, range.range));
101 Some(())
102}
103
104fn add_missing_ok_or_some(
105 ctx: &DiagnosticsContext<'_, '_>,
106 d: &hir::TypeMismatch<'_>,
107 expr_ptr: &InFile<AstPtr<ast::Expr>>,
108 acc: &mut Vec<Assist>,
109) -> Option<()> {
110 let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
111 let expr = expr_ptr.value.to_node(&root);
112 let hir::FileRange { file_id, range: expr_range } =
113 ctx.sema.original_range_opt(expr.syntax())?;
114 let scope = ctx.sema.scope(expr.syntax())?;
115
116 let expected_adt = d.expected.as_adt()?;
117 let expected_enum = expected_adt.as_enum()?;
118
119 let famous_defs = FamousDefs(&ctx.sema, scope.krate());
120 let core_result = famous_defs.core_result_Result();
121 let core_option = famous_defs.core_option_Option();
122
123 if Some(expected_enum) != core_result && Some(expected_enum) != core_option {
124 return None;
125 }
126
127 let variant_name = if Some(expected_enum) == core_result { "Ok" } else { "Some" };
128
129 let wrapped_actual_ty =
130 expected_adt.ty_with_args(ctx.sema.db, std::iter::once(d.actual.clone()));
131
132 if !d.expected.could_unify_with(ctx.sema.db, &wrapped_actual_ty) {
133 return None;
134 }
135
136 let file_id = file_id.file_id(ctx.sema.db);
137
138 if d.actual.is_unit() {
139 if let Expr::BlockExpr(block) = &expr {
140 if block.tail_expr().is_none() {
141 let mut builder = TextEdit::builder();
143 let block_indent = block.indent_level();
144
145 if block.statements().count() == 0 {
146 let indent = block_indent + 1;
148 builder.insert(
149 expr_range.start() + TextSize::from(1),
150 format!("\n{indent}{variant_name}(())\n{block_indent}"),
151 );
152 } else {
153 let indent = IndentLevel::from(1);
154 builder.insert(
155 expr_range.end() - TextSize::from(1),
156 format!("{indent}{variant_name}(())\n{block_indent}"),
157 );
158 }
159
160 let source_change = SourceChange::from_text_edit(file_id, builder.finish());
161 let name = format!("Insert {variant_name}(()) as the tail of this block");
162 acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range));
163 }
164 return Some(());
165 } else if let Expr::ReturnExpr(ret_expr) = &expr {
166 if ret_expr.expr().is_none() {
168 let mut builder = TextEdit::builder();
169 builder.insert(expr_range.end(), format!(" {variant_name}(())"));
170 let source_change = SourceChange::from_text_edit(file_id, builder.finish());
171 let name = format!("Insert {variant_name}(()) as the return value");
172 acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range));
173 }
174 return Some(());
175 } else if expr.is_block_like()
176 && expr.syntax().parent().and_then(ast::StmtList::cast).is_some()
177 {
178 let mut builder = TextEdit::builder();
180 let indent = expr.indent_level();
181 builder.insert(expr_range.end(), format!("\n{indent}{variant_name}(())"));
182
183 let source_change = SourceChange::from_text_edit(file_id, builder.finish());
184 let name = format!("Insert {variant_name}(()) as the tail of this block");
185 acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range));
186 return Some(());
187 }
188 }
189
190 let mut builder = TextEdit::builder();
191 builder.insert(expr_range.start(), format!("{variant_name}("));
192 builder.insert(expr_range.end(), ")".to_owned());
193 let source_change = SourceChange::from_text_edit(file_id, builder.finish());
194 let name = format!("Wrap in {variant_name}");
195 acc.push(fix("wrap_in_constructor", &name, source_change, expr_range));
196 Some(())
197}
198
199fn remove_unnecessary_wrapper(
200 ctx: &DiagnosticsContext<'_, '_>,
201 d: &hir::TypeMismatch<'_>,
202 expr_ptr: &InFile<AstPtr<ast::Expr>>,
203 acc: &mut Vec<Assist>,
204) -> Option<()> {
205 let db = ctx.sema.db;
206 let root = db.parse_or_expand(expr_ptr.file_id);
207 let expr = expr_ptr.value.to_node(&root);
208 let expr = ctx.sema.original_ast_node(expr)?;
210
211 let Expr::CallExpr(call_expr) = expr else {
212 return None;
213 };
214
215 let callable = ctx.sema.resolve_expr_as_callable(&call_expr.expr()?)?;
216 let CallableKind::TupleEnumVariant(variant) = callable.kind() else {
217 return None;
218 };
219
220 let actual_enum = d.actual.as_adt()?.as_enum()?;
221 let famous_defs = FamousDefs(&ctx.sema, ctx.sema.scope(call_expr.syntax())?.krate());
222 let core_option = famous_defs.core_option_Option();
223 let core_result = famous_defs.core_result_Result();
224 if Some(actual_enum) != core_option && Some(actual_enum) != core_result {
225 return None;
226 }
227
228 let inner_type = variant.fields(db).first()?.ty_with_args(db, d.actual.type_arguments());
229 if !d.expected.could_unify_with(db, &inner_type) {
230 return None;
231 }
232
233 let inner_arg = call_expr.arg_list()?.args().next()?;
234
235 let file_id = expr_ptr.file_id.original_file(db);
236 let mut builder = SourceChangeBuilder::new(file_id.file_id(ctx.sema.db));
237 let editor;
238 match inner_arg {
239 Expr::TupleExpr(tup) if tup.fields().next().is_none() => {
241 let parent = call_expr
242 .syntax()
243 .parent()
244 .and_then(Either::<ast::ReturnExpr, ast::StmtList>::cast)?;
245
246 editor = builder.make_editor(parent.syntax());
247 let make = editor.make();
248
249 match parent {
250 Either::Left(ret_expr) => {
251 editor.replace(ret_expr.syntax(), make.expr_return(None).syntax());
252 }
253 Either::Right(stmt_list) => {
254 let new_block = if stmt_list.statements().next().is_none() {
255 make.expr_empty_block()
256 } else {
257 make.block_expr(stmt_list.statements(), None)
258 };
259
260 editor.replace(stmt_list.syntax().parent()?, new_block.syntax());
261 }
262 }
263 }
264 _ => {
265 editor = builder.make_editor(call_expr.syntax());
266 editor.replace(call_expr.syntax(), inner_arg.syntax());
267 }
268 }
269
270 builder.add_file_edits(file_id.file_id(ctx.sema.db), editor);
271 let name = format!("Remove unnecessary {}() wrapper", variant.name(db).as_str());
272 acc.push(fix(
273 "remove_unnecessary_wrapper",
274 &name,
275 builder.finish(),
276 call_expr.syntax().text_range(),
277 ));
278 Some(())
279}
280
281fn remove_semicolon(
282 ctx: &DiagnosticsContext<'_, '_>,
283 d: &hir::TypeMismatch<'_>,
284 expr_ptr: &InFile<AstPtr<ast::Expr>>,
285 acc: &mut Vec<Assist>,
286) -> Option<()> {
287 let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
288 let expr = expr_ptr.value.to_node(&root);
289 if !d.actual.is_unit() {
290 return None;
291 }
292 let block = BlockExpr::cast(expr.syntax().clone())?;
293 let expr_before_semi =
295 block.statements().last().and_then(|s| ExprStmt::cast(s.syntax().clone()))?;
296 let type_before_semi = ctx.sema.type_of_expr(&expr_before_semi.expr()?)?.original();
297 if !type_before_semi.could_coerce_to(ctx.sema.db, &d.expected) {
298 return None;
299 }
300 let semicolon_range = expr_before_semi.semicolon_token()?.text_range();
301
302 let edit = TextEdit::delete(semicolon_range);
303 let source_change = SourceChange::from_text_edit(
304 expr_ptr.file_id.original_file(ctx.sema.db).file_id(ctx.sema.db),
305 edit,
306 );
307
308 acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range));
309 Some(())
310}
311
312fn str_ref_to_owned(
313 ctx: &DiagnosticsContext<'_, '_>,
314 d: &hir::TypeMismatch<'_>,
315 expr_ptr: &InFile<AstPtr<ast::Expr>>,
316 acc: &mut Vec<Assist>,
317) -> Option<()> {
318 let expected = d.expected.display(ctx.sema.db, ctx.display_target);
319 let is_applicable = d.actual.strip_reference().is_str() && expected.to_string() == "String";
321 if !is_applicable {
322 return None;
323 }
324
325 let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id);
326 let expr = expr_ptr.value.to_node(&root);
327 let hir::FileRange { file_id, range } = ctx.sema.original_range_opt(expr.syntax())?;
328
329 let to_owned = ".to_owned()".to_owned();
330
331 let edit = TextEdit::insert(range.end(), to_owned);
332 let source_change = SourceChange::from_text_edit(file_id.file_id(ctx.sema.db), edit);
333 acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, range));
334
335 Some(())
336}
337
338#[cfg(test)]
339mod tests {
340 use crate::tests::{
341 check_diagnostics, check_diagnostics_with_disabled, check_fix, check_fix_with_disabled,
342 check_has_fix, check_no_fix,
343 };
344
345 #[test]
346 fn missing_reference() {
347 check_diagnostics(
348 r#"
349fn main() {
350 test(123);
351 //^^^ 💡 error: expected &i32, found i32
352}
353fn test(_arg: &i32) {}
354"#,
355 );
356 }
357
358 #[test]
359 fn add_reference_to_int() {
360 check_fix(
361 r#"
362fn main() {
363 test(123$0);
364}
365fn test(_arg: &i32) {}
366 "#,
367 r#"
368fn main() {
369 test(&123);
370}
371fn test(_arg: &i32) {}
372 "#,
373 );
374 }
375
376 #[test]
377 fn add_mutable_reference_to_int() {
378 check_fix(
379 r#"
380fn main() {
381 test($0123);
382}
383fn test(_arg: &mut i32) {}
384 "#,
385 r#"
386fn main() {
387 test(&mut 123);
388}
389fn test(_arg: &mut i32) {}
390 "#,
391 );
392 }
393
394 #[test]
395 fn add_reference_to_array() {
396 check_fix(
397 r#"
398//- minicore: coerce_unsized
399fn main() {
400 test($0[1, 2, 3]);
401}
402fn test(_arg: &[i32]) {}
403 "#,
404 r#"
405fn main() {
406 test(&[1, 2, 3]);
407}
408fn test(_arg: &[i32]) {}
409 "#,
410 );
411 }
412
413 #[test]
414 fn add_reference_with_autoderef() {
415 check_fix(
416 r#"
417//- minicore: coerce_unsized, deref
418struct Foo;
419struct Bar;
420impl core::ops::Deref for Foo {
421 type Target = Bar;
422 fn deref(&self) -> &Self::Target { loop {} }
423}
424
425fn main() {
426 test($0Foo);
427}
428fn test(_arg: &Bar) {}
429 "#,
430 r#"
431struct Foo;
432struct Bar;
433impl core::ops::Deref for Foo {
434 type Target = Bar;
435 fn deref(&self) -> &Self::Target { loop {} }
436}
437
438fn main() {
439 test(&Foo);
440}
441fn test(_arg: &Bar) {}
442 "#,
443 );
444 }
445
446 #[test]
447 fn add_reference_to_method_call() {
448 check_fix(
449 r#"
450fn main() {
451 Test.call_by_ref($0123);
452}
453struct Test;
454impl Test {
455 fn call_by_ref(&self, _arg: &i32) {}
456}
457 "#,
458 r#"
459fn main() {
460 Test.call_by_ref(&123);
461}
462struct Test;
463impl Test {
464 fn call_by_ref(&self, _arg: &i32) {}
465}
466 "#,
467 );
468 }
469
470 #[test]
471 fn add_reference_to_let_stmt() {
472 check_fix(
473 r#"
474fn main() {
475 let test: &i32 = $0123;
476}
477 "#,
478 r#"
479fn main() {
480 let test: &i32 = &123;
481}
482 "#,
483 );
484 }
485
486 #[test]
487 fn add_reference_to_macro_call() {
488 check_fix(
489 r#"
490macro_rules! thousand {
491 () => {
492 1000_u64
493 };
494}
495fn test(_foo: &u64) {}
496fn main() {
497 test($0thousand!());
498}
499 "#,
500 r#"
501macro_rules! thousand {
502 () => {
503 1000_u64
504 };
505}
506fn test(_foo: &u64) {}
507fn main() {
508 test(&thousand!());
509}
510 "#,
511 );
512 }
513
514 #[test]
515 fn add_mutable_reference_to_let_stmt() {
516 check_fix(
517 r#"
518fn main() {
519 let _test: &mut i32 = $0123;
520}
521 "#,
522 r#"
523fn main() {
524 let _test: &mut i32 = &mut 123;
525}
526 "#,
527 );
528 }
529
530 #[test]
531 fn const_generic_type_mismatch() {
532 check_diagnostics(
533 r#"
534 pub struct Rate<const N: u32>;
535 fn f<const N: u64>() -> Rate<N> { // FIXME: add some error
536 loop {}
537 }
538 fn run(_t: Rate<5>) {
539 }
540 fn main() {
541 run(f())
542 }
543"#,
544 );
545 }
546
547 #[test]
548 fn const_generic_unknown() {
549 check_diagnostics(
550 r#"
551 pub struct Rate<T, const NOM: u32, const DENOM: u32>(T);
552 fn run(_t: Rate<u32, 1, 1>) {
553 }
554 fn main() {
555 run(Rate::<_, _, _>(5));
556 }
557"#,
558 );
559 }
560
561 #[test]
562 fn wrap_return_type() {
563 check_fix(
564 r#"
565//- minicore: option, result
566fn div(x: i32, y: i32) -> Result<i32, ()> {
567 if y == 0 {
568 return Err(());
569 }
570 x / y$0
571}
572"#,
573 r#"
574fn div(x: i32, y: i32) -> Result<i32, ()> {
575 if y == 0 {
576 return Err(());
577 }
578 Ok(x / y)
579}
580"#,
581 );
582
583 check_fix(
584 r#"
585//- minicore: option, result
586macro_rules! identity { ($($t:tt)*) => ($($t)*) }
587identity! {
588 fn div(x: i32, y: i32) -> Result<i32, ()> {
589 if y == 0 {
590 return Err(());
591 }
592 x / y$0
593 }
594}
595"#,
596 r#"
597macro_rules! identity { ($($t:tt)*) => ($($t)*) }
598identity! {
599 fn div(x: i32, y: i32) -> Result<i32, ()> {
600 if y == 0 {
601 return Err(());
602 }
603 Ok(x / y)
604 }
605}
606"#,
607 );
608 }
609
610 #[test]
611 fn wrap_return_type_option() {
612 check_fix(
613 r#"
614//- minicore: option, result
615fn div(x: i32, y: i32) -> Option<i32> {
616 if y == 0 {
617 return None;
618 }
619 x / y$0
620}
621"#,
622 r#"
623fn div(x: i32, y: i32) -> Option<i32> {
624 if y == 0 {
625 return None;
626 }
627 Some(x / y)
628}
629"#,
630 );
631 }
632
633 #[test]
634 fn wrap_return_type_option_tails() {
635 check_fix(
636 r#"
637//- minicore: option, result
638fn div(x: i32, y: i32) -> Option<i32> {
639 if y == 0 {
640 Some(0)
641 } else if true {
642 100$0
643 } else {
644 None
645 }
646}
647"#,
648 r#"
649fn div(x: i32, y: i32) -> Option<i32> {
650 if y == 0 {
651 Some(0)
652 } else if true {
653 Some(100)
654 } else {
655 None
656 }
657}
658"#,
659 );
660 }
661
662 #[test]
663 fn wrap_return_type_handles_generic_functions() {
664 check_fix(
665 r#"
666//- minicore: option, result
667fn div<T>(x: T) -> Result<T, i32> {
668 if x == 0 {
669 return Err(7);
670 }
671 $0x
672}
673"#,
674 r#"
675fn div<T>(x: T) -> Result<T, i32> {
676 if x == 0 {
677 return Err(7);
678 }
679 Ok(x)
680}
681"#,
682 );
683 }
684
685 #[test]
686 fn wrap_return_type_handles_type_aliases() {
687 check_fix(
688 r#"
689//- minicore: option, result
690type MyResult<T> = Result<T, ()>;
691
692fn div(x: i32, y: i32) -> MyResult<i32> {
693 if y == 0 {
694 return Err(());
695 }
696 x $0/ y
697}
698"#,
699 r#"
700type MyResult<T> = Result<T, ()>;
701
702fn div(x: i32, y: i32) -> MyResult<i32> {
703 if y == 0 {
704 return Err(());
705 }
706 Ok(x / y)
707}
708"#,
709 );
710 }
711
712 #[test]
713 fn wrapped_unit_as_block_tail_expr() {
714 check_fix(
715 r#"
716//- minicore: result
717fn foo() -> Result<(), ()> {
718 foo();
719}$0
720 "#,
721 r#"
722fn foo() -> Result<(), ()> {
723 foo();
724 Ok(())
725}
726 "#,
727 );
728
729 check_fix(
730 r#"
731//- minicore: result
732fn foo() -> Result<(), ()> {}$0
733 "#,
734 r#"
735fn foo() -> Result<(), ()> {
736 Ok(())
737}
738 "#,
739 );
740
741 check_fix(
742 r#"
743//- minicore: result, iterator
744fn foo() -> Result<(), ()> {
745 for _ in 0..5 {}$0
746}
747 "#,
748 r#"
749fn foo() -> Result<(), ()> {
750 for _ in 0..5 {}
751 Ok(())
752}
753 "#,
754 );
755 }
756
757 #[test]
758 fn wrapped_unit_as_return_expr() {
759 check_fix_with_disabled(
760 r#"
761//- minicore: result
762fn foo(b: bool) -> Result<(), String> {
763 if b {
764 return$0;
765 }
766
767 Err("oh dear".to_owned())
768}"#,
769 r#"
770fn foo(b: bool) -> Result<(), String> {
771 if b {
772 return Ok(());
773 }
774
775 Err("oh dear".to_owned())
776}"#,
777 &["E0599"],
778 );
779 }
780
781 #[test]
782 fn wrap_in_const_and_static() {
783 check_fix(
784 r#"
785//- minicore: option, result
786static A: Option<()> = {($0)};
787 "#,
788 r#"
789static A: Option<()> = {Some(())};
790 "#,
791 );
792 check_fix(
793 r#"
794//- minicore: option, result
795const _: Option<()> = {($0)};
796 "#,
797 r#"
798const _: Option<()> = {Some(())};
799 "#,
800 );
801 }
802
803 #[test]
804 fn wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
805 check_no_fix(
806 r#"
807//- minicore: option, result
808fn foo() -> Result<(), i32> { 0$0 }
809"#,
810 );
811 }
812
813 #[test]
814 fn wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() {
815 check_no_fix(
816 r#"
817//- minicore: option, result
818enum SomeOtherEnum { Ok(i32), Err(String) }
819
820fn foo() -> SomeOtherEnum { 0$0 }
821"#,
822 );
823 }
824
825 #[test]
826 fn unwrap_return_type() {
827 check_fix_with_disabled(
828 r#"
829//- minicore: option, result
830fn div(x: i32, y: i32) -> i32 {
831 if y == 0 {
832 panic!();
833 }
834 Ok(x / y)$0
835}
836"#,
837 r#"
838fn div(x: i32, y: i32) -> i32 {
839 if y == 0 {
840 panic!();
841 }
842 x / y
843}
844"#,
845 &["E0282"],
846 );
847 }
848
849 #[test]
850 fn unwrap_return_type_option() {
851 check_fix(
852 r#"
853//- minicore: option, result
854fn div(x: i32, y: i32) -> i32 {
855 if y == 0 {
856 panic!();
857 }
858 Some(x / y)$0
859}
860"#,
861 r#"
862fn div(x: i32, y: i32) -> i32 {
863 if y == 0 {
864 panic!();
865 }
866 x / y
867}
868"#,
869 );
870 }
871
872 #[test]
873 fn unwrap_return_type_option_tails() {
874 check_fix(
875 r#"
876//- minicore: option, result
877fn div(x: i32, y: i32) -> i32 {
878 if y == 0 {
879 42
880 } else if true {
881 Some(100)$0
882 } else {
883 0
884 }
885}
886"#,
887 r#"
888fn div(x: i32, y: i32) -> i32 {
889 if y == 0 {
890 42
891 } else if true {
892 100
893 } else {
894 0
895 }
896}
897"#,
898 );
899 }
900
901 #[test]
902 fn unwrap_return_type_option_tail_unit() {
903 check_fix_with_disabled(
904 r#"
905//- minicore: option, result
906fn div(x: i32, y: i32) {
907 if y == 0 {
908 panic!();
909 }
910
911 Ok(())$0
912}
913"#,
914 r#"
915fn div(x: i32, y: i32) {
916 if y == 0 {
917 panic!();
918 }
919}
920"#,
921 &["E0282"],
922 );
923 }
924
925 #[test]
926 fn unwrap_return_type_handles_generic_functions() {
927 check_fix_with_disabled(
928 r#"
929//- minicore: option, result
930fn div<T>(x: T) -> T {
931 if x == 0 {
932 panic!();
933 }
934 $0Ok(x)
935}
936"#,
937 r#"
938fn div<T>(x: T) -> T {
939 if x == 0 {
940 panic!();
941 }
942 x
943}
944"#,
945 &["E0282"],
946 );
947 }
948
949 #[test]
950 fn unwrap_return_type_handles_type_aliases() {
951 check_fix_with_disabled(
952 r#"
953//- minicore: option, result
954type MyResult<T> = T;
955
956fn div(x: i32, y: i32) -> MyResult<i32> {
957 if y == 0 {
958 panic!();
959 }
960 Ok(x $0/ y)
961}
962"#,
963 r#"
964type MyResult<T> = T;
965
966fn div(x: i32, y: i32) -> MyResult<i32> {
967 if y == 0 {
968 panic!();
969 }
970 x / y
971}
972"#,
973 &["E0282"],
974 );
975 }
976
977 #[test]
978 fn unwrap_tail_expr() {
979 check_fix_with_disabled(
980 r#"
981//- minicore: result
982fn foo() -> () {
983 println!("Hello, world!");
984 Ok(())$0
985}
986 "#,
987 r#"
988fn foo() -> () {
989 println!("Hello, world!");
990}
991 "#,
992 &["E0282"],
993 );
994 }
995
996 #[test]
997 fn unwrap_to_empty_block() {
998 check_fix_with_disabled(
999 r#"
1000//- minicore: result
1001fn foo() -> () {
1002 Ok(())$0
1003}
1004 "#,
1005 r#"
1006fn foo() -> () {}
1007 "#,
1008 &["E0282"],
1009 );
1010 }
1011
1012 #[test]
1013 fn unwrap_to_return_expr() {
1014 check_has_fix(
1015 r#"
1016//- minicore: result
1017fn foo(b: bool) -> () {
1018 if b {
1019 return $0Ok(());
1020 }
1021
1022 panic!("oh dear");
1023}"#,
1024 r#"
1025fn foo(b: bool) -> () {
1026 if b {
1027 return;
1028 }
1029
1030 panic!("oh dear");
1031}"#,
1032 );
1033 }
1034
1035 #[test]
1036 fn unwrap_in_const_and_static() {
1037 check_fix(
1038 r#"
1039//- minicore: option, result
1040static A: () = {Some(($0))};
1041 "#,
1042 r#"
1043static A: () = {};
1044 "#,
1045 );
1046 check_fix(
1047 r#"
1048//- minicore: option, result
1049const _: () = {Some(($0))};
1050 "#,
1051 r#"
1052const _: () = {};
1053 "#,
1054 );
1055 }
1056
1057 #[test]
1058 fn unwrap_return_type_not_applicable_when_inner_type_does_not_match_return_type() {
1059 check_no_fix(
1060 r#"
1061//- minicore: result
1062fn foo() -> i32 { $0Ok(()) }
1063"#,
1064 );
1065 }
1066
1067 #[test]
1068 fn unwrap_return_type_not_applicable_when_wrapper_type_is_not_result_or_option() {
1069 check_no_fix(
1070 r#"
1071//- minicore: option, result
1072enum SomeOtherEnum { Ok(i32), Err(String) }
1073
1074fn foo() -> i32 { SomeOtherEnum::Ok($042) }
1075"#,
1076 );
1077 }
1078
1079 #[test]
1080 fn remove_semicolon() {
1081 check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#);
1082 }
1083
1084 #[test]
1085 fn str_ref_to_owned() {
1086 check_fix(
1087 r#"
1088struct String;
1089
1090fn test() -> String {
1091 "a"$0
1092}
1093 "#,
1094 r#"
1095struct String;
1096
1097fn test() -> String {
1098 "a".to_owned()
1099}
1100 "#,
1101 );
1102
1103 check_fix(
1104 r#"
1105macro_rules! identity { ($($t:tt)*) => ($($t)*) }
1106struct String;
1107
1108identity! {
1109 fn test() -> String {
1110 "a"$0
1111 }
1112}
1113 "#,
1114 r#"
1115macro_rules! identity { ($($t:tt)*) => ($($t)*) }
1116struct String;
1117
1118identity! {
1119 fn test() -> String {
1120 "a".to_owned()
1121 }
1122}
1123 "#,
1124 );
1125 }
1126
1127 #[test]
1128 fn type_mismatch_range_adjustment() {
1129 cov_mark::check!(type_mismatch_range_adjustment);
1130 check_diagnostics(
1131 r#"
1132fn f() -> i32 {
1133 let x = 1;
1134 let y = 2;
1135 let _ = x + y;
1136 }
1137//^ error: expected i32, found ()
1138
1139fn g() -> i32 {
1140 while true {}
1141} //^^^^^ error: expected i32, found ()
1142
1143struct S;
1144impl S { fn foo(&self) -> &S { self } }
1145fn h() {
1146 let _: i32 = S.foo().foo().foo();
1147} //^^^ error: expected i32, found &S
1148"#,
1149 );
1150 }
1151
1152 #[test]
1153 fn unknown_type_in_function_signature() {
1154 check_diagnostics(
1155 r#"
1156struct X<T>(T);
1157
1158fn foo(_x: X<Unknown>) {}
1159fn test1() {
1160 // Unknown might be `i32`, so we should not emit type mismatch here.
1161 foo(X(42));
1162}
1163fn test2() {
1164 foo(42);
1165 //^^ error: expected X<{unknown}>, found i32
1166}
1167"#,
1168 );
1169 }
1170
1171 #[test]
1172 fn evaluate_const_generics_in_types() {
1173 check_diagnostics(
1174 r#"
1175pub const ONE: usize = 1;
1176
1177pub struct Inner<const P: usize>();
1178
1179pub struct Outer {
1180 pub inner: Inner<ONE>,
1181}
1182
1183fn main() {
1184 _ = Outer {
1185 inner: Inner::<2>(),
1186 //^^^^^^^^^^^^ error: expected Inner<1>, found Inner<2>
1187 };
1188}
1189"#,
1190 );
1191 }
1192
1193 #[test]
1194 fn type_mismatch_pat_smoke_test() {
1195 check_diagnostics(
1196 r#"
1197fn f() {
1198 let &() = &mut ();
1199 //^^^ error: expected &mut (), found &()
1200 match &() {
1201 &9 => ()
1202 //^ error: expected (), found i32
1203 }
1204}
1205"#,
1206 );
1207 }
1208
1209 #[test]
1210 fn regression_14768() {
1211 check_diagnostics(
1212 r#"
1213//- minicore: derive, fmt, slice, coerce_unsized, builtin_impls
1214use core::fmt::Debug;
1215
1216#[derive(Debug)]
1217struct Foo(u8, u16, [u8]);
1218
1219#[derive(Debug)]
1220struct Bar {
1221 f1: u8,
1222 f2: &[u16],
1223 f3: dyn Debug,
1224}
1225"#,
1226 );
1227 }
1228
1229 #[test]
1230 fn trait_upcast_ok() {
1231 check_diagnostics(
1232 r#"
1233//- minicore: coerce_unsized
1234trait A: B {}
1235trait B {}
1236
1237fn test(a: &dyn A) -> &dyn B {
1238 a
1239}
1240"#,
1241 );
1242 }
1243
1244 #[test]
1245 fn trait_upcast_err() {
1246 check_diagnostics(
1247 r#"
1248//- minicore: coerce_unsized
1249trait A {} // `A` does not have `B` as a supertrait, so no upcast :c
1250trait B {}
1251
1252fn test(a: &dyn A) -> &dyn B {
1253 a
1254 //^💡 error: expected &(dyn B + 'static), found &(dyn A + 'static)
1255}
1256"#,
1257 );
1258 }
1259
1260 #[test]
1261 fn return_no_value() {
1262 check_diagnostics_with_disabled(
1263 r#"
1264fn f() -> i32 {
1265 return;
1266 // ^^^^^^ error: expected i32, found ()
1267 0
1268}
1269fn g() { return; }
1270"#,
1271 &["needless_return"],
1272 );
1273 }
1274
1275 #[test]
1276 fn smoke_test_inner_items() {
1277 check_diagnostics(
1278 r#"
1279fn f() {
1280 fn inner() -> i32 {
1281 return;
1282 // ^^^^^^ error: expected i32, found ()
1283 0
1284 }
1285}
1286"#,
1287 );
1288 }
1289
1290 #[test]
1291 fn regression_17585() {
1292 check_diagnostics(
1293 r#"
1294fn f() {
1295 let (_, _, _, ..) = (true, 42);
1296 // ^^^^^^^^^^^^^ error: expected (bool, i32), found (bool, i32, {unknown})
1297}
1298"#,
1299 );
1300 }
1301
1302 #[test]
1303 fn complex_enum_variant_non_ref_pat() {
1304 check_diagnostics(
1305 r#"
1306enum Enum { Variant }
1307
1308trait Trait {
1309 type Assoc;
1310}
1311impl Trait for () {
1312 type Assoc = Enum;
1313}
1314
1315fn foo(v: &Enum) {
1316 let <Enum>::Variant = v;
1317 let <() as Trait>::Assoc::Variant = v;
1318}
1319 "#,
1320 );
1321 }
1322
1323 #[test]
1324 fn regression_19844() {
1325 check_diagnostics(
1326 r#"
1327fn main() {
1328 struct S {}
1329 enum E { V() }
1330 let E::V() = &S {};
1331 // ^^^^^^ error: expected S, found E
1332}
1333"#,
1334 );
1335 }
1336
1337 #[test]
1338 fn test_ignore_unknown_mismatch() {
1339 check_diagnostics(
1340 r#"
1341pub trait Foo {
1342 type Out;
1343}
1344impl Foo for [i32; 1] {
1345 type Out = ();
1346}
1347pub fn foo<T: Foo>(_: T) -> (T::Out,) { loop { } }
1348
1349fn main() {
1350 let _x = foo(2);
1351 // ^^ error: type annotations needed
1352 // ^^^ error: the trait bound `i32: Foo` is not satisfied
1353}
1354"#,
1355 );
1356 }
1357}