| use either::Either; |
| use hir::{db::ExpandDatabase, ClosureStyle, HirDisplay, InFile, Type}; |
| use ide_db::{famous_defs::FamousDefs, source_change::SourceChange}; |
| use syntax::{ |
| ast::{self, BlockExpr, ExprStmt}, |
| AstNode, AstPtr, |
| }; |
| use text_edit::TextEdit; |
| |
| use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext}; |
| |
| // Diagnostic: type-mismatch |
| // |
| // This diagnostic is triggered when the type of an expression or pattern does not match |
| // the expected type. |
| pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic { |
| let display_range = match &d.expr_or_pat { |
| Either::Left(expr) => { |
| adjusted_display_range::<ast::Expr>(ctx, expr.clone().map(|it| it.into()), &|expr| { |
| let salient_token_range = match expr { |
| ast::Expr::IfExpr(it) => it.if_token()?.text_range(), |
| ast::Expr::LoopExpr(it) => it.loop_token()?.text_range(), |
| ast::Expr::ForExpr(it) => it.for_token()?.text_range(), |
| ast::Expr::WhileExpr(it) => it.while_token()?.text_range(), |
| ast::Expr::BlockExpr(it) => it.stmt_list()?.r_curly_token()?.text_range(), |
| ast::Expr::MatchExpr(it) => it.match_token()?.text_range(), |
| ast::Expr::MethodCallExpr(it) => it.name_ref()?.ident_token()?.text_range(), |
| ast::Expr::FieldExpr(it) => it.name_ref()?.ident_token()?.text_range(), |
| ast::Expr::AwaitExpr(it) => it.await_token()?.text_range(), |
| _ => return None, |
| }; |
| |
| cov_mark::hit!(type_mismatch_range_adjustment); |
| Some(salient_token_range) |
| }) |
| } |
| Either::Right(pat) => { |
| ctx.sema.diagnostics_display_range(pat.clone().map(|it| it.into())).range |
| } |
| }; |
| let mut diag = Diagnostic::new( |
| DiagnosticCode::RustcHardError("E0308"), |
| format!( |
| "expected {}, found {}", |
| d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), |
| d.actual.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), |
| ), |
| display_range, |
| ) |
| .with_fixes(fixes(ctx, d)); |
| if diag.fixes.is_none() { |
| diag.experimental = true; |
| } |
| diag |
| } |
| |
| fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assist>> { |
| let mut fixes = Vec::new(); |
| |
| match &d.expr_or_pat { |
| Either::Left(expr_ptr) => { |
| add_reference(ctx, d, expr_ptr, &mut fixes); |
| add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes); |
| remove_semicolon(ctx, d, expr_ptr, &mut fixes); |
| str_ref_to_owned(ctx, d, expr_ptr, &mut fixes); |
| } |
| Either::Right(_pat_ptr) => {} |
| } |
| |
| if fixes.is_empty() { |
| None |
| } else { |
| Some(fixes) |
| } |
| } |
| |
| fn add_reference( |
| ctx: &DiagnosticsContext<'_>, |
| d: &hir::TypeMismatch, |
| expr_ptr: &InFile<AstPtr<ast::Expr>>, |
| acc: &mut Vec<Assist>, |
| ) -> Option<()> { |
| let range = ctx.sema.diagnostics_display_range(expr_ptr.clone().map(|it| it.into())).range; |
| |
| let (_, mutability) = d.expected.as_reference()?; |
| let actual_with_ref = Type::reference(&d.actual, mutability); |
| if !actual_with_ref.could_coerce_to(ctx.sema.db, &d.expected) { |
| return None; |
| } |
| |
| let ampersands = format!("&{}", mutability.as_keyword_for_ref()); |
| |
| let edit = TextEdit::insert(range.start(), ampersands); |
| let source_change = |
| SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); |
| acc.push(fix("add_reference_here", "Add reference here", source_change, range)); |
| Some(()) |
| } |
| |
| fn add_missing_ok_or_some( |
| ctx: &DiagnosticsContext<'_>, |
| d: &hir::TypeMismatch, |
| expr_ptr: &InFile<AstPtr<ast::Expr>>, |
| acc: &mut Vec<Assist>, |
| ) -> Option<()> { |
| let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id); |
| let expr = expr_ptr.value.to_node(&root); |
| let expr_range = expr.syntax().text_range(); |
| let scope = ctx.sema.scope(expr.syntax())?; |
| |
| let expected_adt = d.expected.as_adt()?; |
| let expected_enum = expected_adt.as_enum()?; |
| |
| let famous_defs = FamousDefs(&ctx.sema, scope.krate()); |
| let core_result = famous_defs.core_result_Result(); |
| let core_option = famous_defs.core_option_Option(); |
| |
| if Some(expected_enum) != core_result && Some(expected_enum) != core_option { |
| return None; |
| } |
| |
| let variant_name = if Some(expected_enum) == core_result { "Ok" } else { "Some" }; |
| |
| let wrapped_actual_ty = expected_adt.ty_with_args(ctx.sema.db, &[d.actual.clone()]); |
| |
| if !d.expected.could_unify_with(ctx.sema.db, &wrapped_actual_ty) { |
| return None; |
| } |
| |
| let mut builder = TextEdit::builder(); |
| builder.insert(expr.syntax().text_range().start(), format!("{variant_name}(")); |
| builder.insert(expr.syntax().text_range().end(), ")".to_string()); |
| let source_change = |
| SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), builder.finish()); |
| let name = format!("Wrap in {variant_name}"); |
| acc.push(fix("wrap_in_constructor", &name, source_change, expr_range)); |
| Some(()) |
| } |
| |
| fn remove_semicolon( |
| ctx: &DiagnosticsContext<'_>, |
| d: &hir::TypeMismatch, |
| expr_ptr: &InFile<AstPtr<ast::Expr>>, |
| acc: &mut Vec<Assist>, |
| ) -> Option<()> { |
| let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id); |
| let expr = expr_ptr.value.to_node(&root); |
| if !d.actual.is_unit() { |
| return None; |
| } |
| let block = BlockExpr::cast(expr.syntax().clone())?; |
| let expr_before_semi = |
| block.statements().last().and_then(|s| ExprStmt::cast(s.syntax().clone()))?; |
| let type_before_semi = ctx.sema.type_of_expr(&expr_before_semi.expr()?)?.original(); |
| if !type_before_semi.could_coerce_to(ctx.sema.db, &d.expected) { |
| return None; |
| } |
| let semicolon_range = expr_before_semi.semicolon_token()?.text_range(); |
| |
| let edit = TextEdit::delete(semicolon_range); |
| let source_change = |
| SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); |
| |
| acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range)); |
| Some(()) |
| } |
| |
| fn str_ref_to_owned( |
| ctx: &DiagnosticsContext<'_>, |
| d: &hir::TypeMismatch, |
| expr_ptr: &InFile<AstPtr<ast::Expr>>, |
| acc: &mut Vec<Assist>, |
| ) -> Option<()> { |
| let expected = d.expected.display(ctx.sema.db); |
| let actual = d.actual.display(ctx.sema.db); |
| |
| // FIXME do this properly |
| if expected.to_string() != "String" || actual.to_string() != "&str" { |
| return None; |
| } |
| |
| let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id); |
| let expr = expr_ptr.value.to_node(&root); |
| let expr_range = expr.syntax().text_range(); |
| |
| let to_owned = format!(".to_owned()"); |
| |
| let edit = TextEdit::insert(expr.syntax().text_range().end(), to_owned); |
| let source_change = |
| SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); |
| acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, expr_range)); |
| |
| Some(()) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::tests::{check_diagnostics, check_fix, check_no_fix}; |
| |
| #[test] |
| fn missing_reference() { |
| check_diagnostics( |
| r#" |
| fn main() { |
| test(123); |
| //^^^ 💡 error: expected &i32, found i32 |
| } |
| fn test(arg: &i32) {} |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_add_reference_to_int() { |
| check_fix( |
| r#" |
| fn main() { |
| test(123$0); |
| } |
| fn test(arg: &i32) {} |
| "#, |
| r#" |
| fn main() { |
| test(&123); |
| } |
| fn test(arg: &i32) {} |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_add_mutable_reference_to_int() { |
| check_fix( |
| r#" |
| fn main() { |
| test($0123); |
| } |
| fn test(arg: &mut i32) {} |
| "#, |
| r#" |
| fn main() { |
| test(&mut 123); |
| } |
| fn test(arg: &mut i32) {} |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_add_reference_to_array() { |
| check_fix( |
| r#" |
| //- minicore: coerce_unsized |
| fn main() { |
| test($0[1, 2, 3]); |
| } |
| fn test(arg: &[i32]) {} |
| "#, |
| r#" |
| fn main() { |
| test(&[1, 2, 3]); |
| } |
| fn test(arg: &[i32]) {} |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_add_reference_with_autoderef() { |
| check_fix( |
| r#" |
| //- minicore: coerce_unsized, deref |
| struct Foo; |
| struct Bar; |
| impl core::ops::Deref for Foo { |
| type Target = Bar; |
| } |
| |
| fn main() { |
| test($0Foo); |
| } |
| fn test(arg: &Bar) {} |
| "#, |
| r#" |
| struct Foo; |
| struct Bar; |
| impl core::ops::Deref for Foo { |
| type Target = Bar; |
| } |
| |
| fn main() { |
| test(&Foo); |
| } |
| fn test(arg: &Bar) {} |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_add_reference_to_method_call() { |
| check_fix( |
| r#" |
| fn main() { |
| Test.call_by_ref($0123); |
| } |
| struct Test; |
| impl Test { |
| fn call_by_ref(&self, arg: &i32) {} |
| } |
| "#, |
| r#" |
| fn main() { |
| Test.call_by_ref(&123); |
| } |
| struct Test; |
| impl Test { |
| fn call_by_ref(&self, arg: &i32) {} |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_add_reference_to_let_stmt() { |
| check_fix( |
| r#" |
| fn main() { |
| let test: &i32 = $0123; |
| } |
| "#, |
| r#" |
| fn main() { |
| let test: &i32 = &123; |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_add_reference_to_macro_call() { |
| check_fix( |
| r#" |
| macro_rules! thousand { |
| () => { |
| 1000_u64 |
| }; |
| } |
| fn test(foo: &u64) {} |
| fn main() { |
| test($0thousand!()); |
| } |
| "#, |
| r#" |
| macro_rules! thousand { |
| () => { |
| 1000_u64 |
| }; |
| } |
| fn test(foo: &u64) {} |
| fn main() { |
| test(&thousand!()); |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_add_mutable_reference_to_let_stmt() { |
| check_fix( |
| r#" |
| fn main() { |
| let test: &mut i32 = $0123; |
| } |
| "#, |
| r#" |
| fn main() { |
| let test: &mut i32 = &mut 123; |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_wrap_return_type_option() { |
| check_fix( |
| r#" |
| //- minicore: option, result |
| fn div(x: i32, y: i32) -> Option<i32> { |
| if y == 0 { |
| return None; |
| } |
| x / y$0 |
| } |
| "#, |
| r#" |
| fn div(x: i32, y: i32) -> Option<i32> { |
| if y == 0 { |
| return None; |
| } |
| Some(x / y) |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn const_generic_type_mismatch() { |
| check_diagnostics( |
| r#" |
| pub struct Rate<const N: u32>; |
| fn f<const N: u64>() -> Rate<N> { // FIXME: add some error |
| loop {} |
| } |
| fn run(t: Rate<5>) { |
| } |
| fn main() { |
| run(f()) // FIXME: remove this error |
| //^^^ error: expected Rate<5>, found Rate<_> |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn const_generic_unknown() { |
| check_diagnostics( |
| r#" |
| pub struct Rate<T, const NOM: u32, const DENOM: u32>(T); |
| fn run(t: Rate<u32, 1, 1>) { |
| } |
| fn main() { |
| run(Rate::<_, _, _>(5)); |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_wrap_return_type_option_tails() { |
| check_fix( |
| r#" |
| //- minicore: option, result |
| fn div(x: i32, y: i32) -> Option<i32> { |
| if y == 0 { |
| Some(0) |
| } else if true { |
| 100$0 |
| } else { |
| None |
| } |
| } |
| "#, |
| r#" |
| fn div(x: i32, y: i32) -> Option<i32> { |
| if y == 0 { |
| Some(0) |
| } else if true { |
| Some(100) |
| } else { |
| None |
| } |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_wrap_return_type() { |
| check_fix( |
| r#" |
| //- minicore: option, result |
| fn div(x: i32, y: i32) -> Result<i32, ()> { |
| if y == 0 { |
| return Err(()); |
| } |
| x / y$0 |
| } |
| "#, |
| r#" |
| fn div(x: i32, y: i32) -> Result<i32, ()> { |
| if y == 0 { |
| return Err(()); |
| } |
| Ok(x / y) |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_wrap_return_type_handles_generic_functions() { |
| check_fix( |
| r#" |
| //- minicore: option, result |
| fn div<T>(x: T) -> Result<T, i32> { |
| if x == 0 { |
| return Err(7); |
| } |
| $0x |
| } |
| "#, |
| r#" |
| fn div<T>(x: T) -> Result<T, i32> { |
| if x == 0 { |
| return Err(7); |
| } |
| Ok(x) |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_wrap_return_type_handles_type_aliases() { |
| check_fix( |
| r#" |
| //- minicore: option, result |
| type MyResult<T> = Result<T, ()>; |
| |
| fn div(x: i32, y: i32) -> MyResult<i32> { |
| if y == 0 { |
| return Err(()); |
| } |
| x $0/ y |
| } |
| "#, |
| r#" |
| type MyResult<T> = Result<T, ()>; |
| |
| fn div(x: i32, y: i32) -> MyResult<i32> { |
| if y == 0 { |
| return Err(()); |
| } |
| Ok(x / y) |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_in_const_and_static() { |
| check_fix( |
| r#" |
| //- minicore: option, result |
| static A: Option<()> = {($0)}; |
| "#, |
| r#" |
| static A: Option<()> = {Some(())}; |
| "#, |
| ); |
| check_fix( |
| r#" |
| //- minicore: option, result |
| const _: Option<()> = {($0)}; |
| "#, |
| r#" |
| const _: Option<()> = {Some(())}; |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { |
| check_no_fix( |
| r#" |
| //- minicore: option, result |
| fn foo() -> Result<(), i32> { 0$0 } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn test_wrap_return_type_not_applicable_when_return_type_is_not_result_or_option() { |
| check_no_fix( |
| r#" |
| //- minicore: option, result |
| enum SomeOtherEnum { Ok(i32), Err(String) } |
| |
| fn foo() -> SomeOtherEnum { 0$0 } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn remove_semicolon() { |
| check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); |
| } |
| |
| #[test] |
| fn str_ref_to_owned() { |
| check_fix( |
| r#" |
| struct String; |
| |
| fn test() -> String { |
| "a"$0 |
| } |
| "#, |
| r#" |
| struct String; |
| |
| fn test() -> String { |
| "a".to_owned() |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn closure_mismatch_show_different_type() { |
| check_diagnostics( |
| r#" |
| fn f() { |
| let mut x = (|| 1, 2); |
| x = (|| 3, 4); |
| //^^^^ error: expected {closure#0}, found {closure#1} |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn type_mismatch_range_adjustment() { |
| cov_mark::check!(type_mismatch_range_adjustment); |
| check_diagnostics( |
| r#" |
| fn f() -> i32 { |
| let x = 1; |
| let y = 2; |
| let _ = x + y; |
| } |
| //^ error: expected i32, found () |
| |
| fn g() -> i32 { |
| while true {} |
| } //^^^^^ error: expected i32, found () |
| |
| struct S; |
| impl S { fn foo(&self) -> &S { self } } |
| fn h() { |
| let _: i32 = S.foo().foo().foo(); |
| } //^^^ error: expected i32, found &S |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn unknown_type_in_function_signature() { |
| check_diagnostics( |
| r#" |
| struct X<T>(T); |
| |
| fn foo(x: X<Unknown>) {} |
| fn test1() { |
| // Unknown might be `i32`, so we should not emit type mismatch here. |
| foo(X(42)); |
| } |
| fn test2() { |
| foo(42); |
| //^^ error: expected X<{unknown}>, found i32 |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn evaluate_const_generics_in_types() { |
| check_diagnostics( |
| r#" |
| pub const ONE: usize = 1; |
| |
| pub struct Inner<const P: usize>(); |
| |
| pub struct Outer { |
| pub inner: Inner<ONE>, |
| } |
| |
| fn main() { |
| _ = Outer { |
| inner: Inner::<2>(), |
| //^^^^^^^^^^^^ error: expected Inner<1>, found Inner<2> |
| }; |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn type_mismatch_pat_smoke_test() { |
| check_diagnostics( |
| r#" |
| fn f() { |
| let &() = &mut (); |
| //^^^ error: expected &mut (), found &() |
| match &() { |
| // FIXME: we should only show the deep one. |
| &9 => () |
| //^^ error: expected &(), found &i32 |
| //^ error: expected (), found i32 |
| } |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn regression_14768() { |
| check_diagnostics( |
| r#" |
| //- minicore: derive, fmt, slice, coerce_unsized, builtin_impls |
| use core::fmt::Debug; |
| |
| #[derive(Debug)] |
| struct Foo(u8, u16, [u8]); |
| |
| #[derive(Debug)] |
| struct Bar { |
| f1: u8, |
| f2: &[u16], |
| f3: dyn Debug, |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn return_no_value() { |
| check_diagnostics( |
| r#" |
| fn f() -> i32 { |
| return; |
| // ^^^^^^ error: expected i32, found () |
| 0 |
| } |
| fn g() { return; } |
| "#, |
| ); |
| } |
| } |