use either::Either;
use hir::{CallableKind, ClosureStyle, HirDisplay, InFile};
use ide_db::{
    famous_defs::FamousDefs,
    source_change::{SourceChange, SourceChangeBuilder},
    text_edit::TextEdit,
};
use syntax::{
    AstNode, AstPtr, TextSize,
    ast::{
        self, BlockExpr, Expr, ExprStmt, HasArgList, RefExpr,
        edit::{AstNodeEdit, IndentLevel},
    },
};

use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_display_range, fix};

// 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<'_>,
) -> Option<Diagnostic> {
    if d.expected.is_unknown() || d.actual.is_unknown() {
        return None;
    }

    let display_range = adjusted_display_range(ctx, d.expr_or_pat, &|node| {
        let Either::Left(expr) = node else { return None };
        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)
    });

    let expected = d
        .expected
        .display(ctx.db(), ctx.display_target)
        .with_closure_style(ClosureStyle::ClosureWithId)
        .to_string();
    let actual = d
        .actual
        .display(ctx.db(), ctx.display_target)
        .with_closure_style(ClosureStyle::ClosureWithId)
        .to_string();

    // The types differ (that's why we're here), yet they render the same, e.g. `foo::S` and
    // `bar::S` both render as `S`. Retry with qualified paths so the message isn't a useless
    // "expected S, found S".
    let (expected, actual) = if expected == actual {
        qualified_display(ctx, d).unwrap_or((expected, actual))
    } else {
        (expected, actual)
    };

    Some(
        Diagnostic::new(
            DiagnosticCode::RustcHardError("E0308"),
            format!("expected {expected}, found {actual}"),
            display_range,
        )
        .stable()
        .with_fixes(fixes(ctx, d)),
    )
}

/// Renders both sides of the mismatch with qualified paths, for when their plain names collide.
/// Returns `None` if either side has no renderable path, in which case both keep their plain
/// names — mixing a qualified and an unqualified name would be more confusing, not less.
fn qualified_display(
    ctx: &DiagnosticsContext<'_, '_>,
    d: &hir::TypeMismatch<'_>,
) -> Option<(String, String)> {
    let root = d.expr_or_pat.file_id.parse_or_expand(ctx.db());
    let module = ctx.sema.scope(d.expr_or_pat.value.to_node(&root).syntax())?.module();
    let expected = d.expected.display_source_code(ctx.db(), module.into(), true).ok()?;
    let actual = d.actual.display_source_code(ctx.db(), module.into(), true).ok()?;
    Some((expected, actual))
}

fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &hir::TypeMismatch<'_>) -> Option<Vec<Assist>> {
    let mut fixes = Vec::new();

    if let Some(expr_ptr) = d.expr_or_pat.value.cast::<ast::Expr>() {
        let expr_ptr = &InFile { file_id: d.expr_or_pat.file_id, value: expr_ptr };
        add_or_fix_reference(ctx, d, expr_ptr, &mut fixes);
        add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes);
        remove_unnecessary_wrapper(ctx, d, expr_ptr, &mut fixes);
        remove_semicolon(ctx, d, expr_ptr, &mut fixes);
        str_ref_to_owned(ctx, d, expr_ptr, &mut fixes);
        add_await(ctx, d, expr_ptr, &mut fixes);
        array_length(ctx, d, expr_ptr, &mut fixes);
    }

    if fixes.is_empty() { None } else { Some(fixes) }
}

fn add_or_fix_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).map(|it| it.into()));

    let (expected_with_ref_removed, expected_mutability) = d.expected.as_reference()?;

    if let Some((actual_with_ref_removed, hir::Mutability::Shared)) = d.actual.as_reference()
        && expected_mutability == hir::Mutability::Mut
        && actual_with_ref_removed.could_coerce_to(ctx.db(), &expected_with_ref_removed)
    {
        // The actual type is `&T`, and the expected type is `&mut T`, (or `U` that `T` can be coerced to).
        // It's likely that, instead of adding a reference, we should just change the mutability of
        // the existing one.

        let expr = expr_ptr.to_node(ctx.db());
        // If the node comes from a macro expansion, then we shouldn't assist,
        // as the suggestion would overwrite the macro _definition_ position
        let expr = ctx.sema.original_ast_node(expr)?;
        let expr_without_ref = RefExpr::cast(expr.syntax().clone())?.expr()?;

        let pos = expr_without_ref.syntax().text_range().start();
        let edit = TextEdit::insert(pos, expected_mutability.as_keyword_for_ref().to_owned());
        let source_change = SourceChange::from_text_edit(range.file_id, edit);
        acc.push(fix(
            "make_reference_mutable",
            "Make reference mutable",
            source_change,
            range.range,
        ));
        return Some(());
    }

    let actual_with_ref = d.actual.add_reference(ctx.db(), expected_mutability);
    if !actual_with_ref.could_coerce_to(ctx.db(), &d.expected) {
        return None;
    }

    let expr = expr_ptr.to_node(ctx.db());
    let assign = expr
        .syntax()
        .parent()
        .and_then(ast::BinExpr::cast)
        .filter(|it| it.op_kind() == Some(ast::BinaryOp::Assignment { op: None }));
    if let Some(assign) = assign
        && expected_mutability.is_mut()
        && let Some(range) = ctx.sema.original_range_opt(assign.syntax())
    {
        let edit = TextEdit::insert(range.range.start(), "*".to_owned());
        let source_change = SourceChange::from_text_edit(range.file_id.file_id(ctx.db()), edit);
        acc.push(fix("add_deref_here", "Add deref here", source_change, range.range));
        return Some(());
    }

    let ampersands = format!("&{}", expected_mutability.as_keyword_for_ref());

    let edit = insert_prefix(&expr, range.range, &ampersands);
    let source_change = SourceChange::from_text_edit(range.file_id, edit);
    acc.push(fix("add_reference_here", "Add reference here", source_change, range.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 = expr_ptr.file_id.parse_or_expand(ctx.db());
    let expr = expr_ptr.value.to_node(&root);
    let hir::FileRange { file_id, range: expr_range } =
        ctx.sema.original_range_opt(expr.syntax())?;
    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(ctx.db()).instantiate([d.actual.clone()]);

    if !d.expected.could_unify_with(ctx.db(), &wrapped_actual_ty) {
        return None;
    }

    let file_id = file_id.file_id(ctx.db());

    if d.actual.is_unit() {
        if let Expr::BlockExpr(block) = &expr {
            if block.tail_expr().is_none() {
                // Fix for forms like `fn foo() -> Result<(), String> {}`
                let mut builder = TextEdit::builder();
                let block_indent = block.indent_level();

                if block.statements().count() == 0 {
                    // Empty block
                    let indent = block_indent + 1;
                    builder.insert(
                        expr_range.start() + TextSize::from(1),
                        format!("\n{indent}{variant_name}(())\n{block_indent}"),
                    );
                } else {
                    let indent = IndentLevel::from(1);
                    builder.insert(
                        expr_range.end() - TextSize::from(1),
                        format!("{indent}{variant_name}(())\n{block_indent}"),
                    );
                }

                let source_change = SourceChange::from_text_edit(file_id, builder.finish());
                let name = format!("Insert {variant_name}(()) as the tail of this block");
                acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range));
            }
            return Some(());
        } else if let Expr::ReturnExpr(ret_expr) = &expr {
            // Fix for forms like `fn foo() -> Result<(), String> { return; }`
            if ret_expr.expr().is_none() {
                let mut builder = TextEdit::builder();
                builder.insert(expr_range.end(), format!(" {variant_name}(())"));
                let source_change = SourceChange::from_text_edit(file_id, builder.finish());
                let name = format!("Insert {variant_name}(()) as the return value");
                acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range));
            }
            return Some(());
        } else if expr.is_block_like()
            && expr.syntax().parent().and_then(ast::StmtList::cast).is_some()
        {
            // Fix for forms like `fn foo() -> Result<(), String> { for _ in 0..8 {} }`
            let mut builder = TextEdit::builder();
            let indent = expr.indent_level();
            builder.insert(expr_range.end(), format!("\n{indent}{variant_name}(())"));

            let source_change = SourceChange::from_text_edit(file_id, builder.finish());
            let name = format!("Insert {variant_name}(()) as the tail of this block");
            acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range));
            return Some(());
        }
    }

    let mut builder = TextEdit::builder();
    builder.insert(expr_range.start(), format!("{variant_name}("));
    builder.insert(expr_range.end(), ")".to_owned());
    let source_change = SourceChange::from_text_edit(file_id, builder.finish());
    let name = format!("Wrap in {variant_name}");
    acc.push(fix("wrap_in_constructor", &name, source_change, expr_range));
    Some(())
}

fn remove_unnecessary_wrapper(
    ctx: &DiagnosticsContext<'_, '_>,
    d: &hir::TypeMismatch<'_>,
    expr_ptr: &InFile<AstPtr<ast::Expr>>,
    acc: &mut Vec<Assist>,
) -> Option<()> {
    let db = ctx.db();
    let root = expr_ptr.file_id.parse_or_expand(db);
    let expr = expr_ptr.value.to_node(&root);
    // FIXME: support inside MacroCall?
    let expr = ctx.sema.original_ast_node(expr)?;

    let Expr::CallExpr(call_expr) = expr else {
        return None;
    };

    let callable = ctx.sema.resolve_expr_as_callable(&call_expr.expr()?)?;
    let CallableKind::TupleEnumVariant(variant) = callable.kind() else {
        return None;
    };

    let actual_enum = d.actual.as_adt()?.as_enum()?;
    let famous_defs = FamousDefs(&ctx.sema, ctx.sema.scope(call_expr.syntax())?.krate());
    let core_option = famous_defs.core_option_Option();
    let core_result = famous_defs.core_result_Result();
    if Some(actual_enum) != core_option && Some(actual_enum) != core_result {
        return None;
    }

    let inner_type = variant.fields(db).first()?.ty(db).instantiate(d.actual.type_arguments());
    if !d.expected.could_unify_with(db, &inner_type) {
        return None;
    }

    let inner_arg = call_expr.arg_list()?.args().next()?;

    let file_id = expr_ptr.file_id.original_file(db);
    let mut builder = SourceChangeBuilder::new(file_id.file_id(ctx.db()));
    let editor;
    match inner_arg {
        // We're returning `()`
        Expr::TupleExpr(tup) if tup.fields().next().is_none() => {
            let parent = call_expr
                .syntax()
                .parent()
                .and_then(Either::<ast::ReturnExpr, ast::StmtList>::cast)?;

            editor = builder.make_editor(parent.syntax());
            let make = editor.make();

            match parent {
                Either::Left(ret_expr) => {
                    editor.replace(ret_expr.syntax(), make.expr_return(None).syntax());
                }
                Either::Right(stmt_list) => {
                    let new_block = if stmt_list.statements().next().is_none() {
                        make.expr_empty_block()
                    } else {
                        make.block_expr(stmt_list.statements(), None)
                    };

                    editor.replace(stmt_list.syntax().parent()?, new_block.syntax());
                }
            }
        }
        _ => {
            editor = builder.make_editor(call_expr.syntax());
            editor.replace(call_expr.syntax(), inner_arg.syntax());
        }
    }

    builder.add_file_edits(file_id.file_id(ctx.db()), editor);
    let name = format!("Remove unnecessary {}() wrapper", variant.name(db).as_str());
    acc.push(fix(
        "remove_unnecessary_wrapper",
        &name,
        builder.finish(),
        call_expr.syntax().text_range(),
    ));
    Some(())
}

fn remove_semicolon(
    ctx: &DiagnosticsContext<'_, '_>,
    d: &hir::TypeMismatch<'_>,
    expr_ptr: &InFile<AstPtr<ast::Expr>>,
    acc: &mut Vec<Assist>,
) -> Option<()> {
    let root = expr_ptr.file_id.parse_or_expand(ctx.db());
    let expr = expr_ptr.value.to_node(&root);
    if !d.actual.is_unit() {
        return None;
    }
    let block = BlockExpr::cast(expr.syntax().clone())?;
    // FIXME: support inside MacroCall?
    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.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.db()).file_id(ctx.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.db(), ctx.display_target);
    // FIXME do this properly
    let is_applicable = d.actual.strip_reference().is_str() && expected.to_string() == "String";
    if !is_applicable {
        return None;
    }

    let root = expr_ptr.file_id.parse_or_expand(ctx.db());
    let expr = expr_ptr.value.to_node(&root);
    let hir::FileRange { file_id, range } = ctx.sema.original_range_opt(expr.syntax())?;

    let edit = insert_postfix(&expr, range, ".to_owned()");
    let source_change = SourceChange::from_text_edit(file_id.file_id(ctx.db()), edit);
    acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, range));

    Some(())
}

fn add_await(
    ctx: &DiagnosticsContext<'_, '_>,
    d: &hir::TypeMismatch<'_>,
    expr_ptr: &InFile<AstPtr<ast::Expr>>,
    acc: &mut Vec<Assist>,
) -> Option<()> {
    let output = d.actual.clone().future_output(ctx.db())?;
    // XXX: maybe should check if ctx is async
    let is_applicable = output.could_coerce_to(ctx.db(), &d.expected);
    if !is_applicable {
        return None;
    }

    let root = expr_ptr.file_id.parse_or_expand(ctx.db());
    let expr = expr_ptr.value.to_node(&root);
    let hir::FileRange { file_id, range } = ctx.sema.original_range_opt(expr.syntax())?;

    let edit = insert_postfix(&expr, range, ".await");
    let source_change = SourceChange::from_text_edit(file_id.file_id(ctx.db()), edit);
    acc.push(fix("add_await", "Add .await here", source_change, range));

    Some(())
}

fn array_length(
    ctx: &DiagnosticsContext<'_, '_>,
    d: &hir::TypeMismatch<'_>,
    expr_ptr: &InFile<AstPtr<ast::Expr>>,
    acc: &mut Vec<Assist>,
) -> Option<()> {
    let (ty1, expected) = d.expected.as_array(ctx.db())?;
    let (ty2, actual) = d.actual.as_array(ctx.db())?;

    if !ty1.could_unify_with(ctx.db(), &ty2) || expected == actual {
        return None;
    }

    let root = expr_ptr.file_id.parse_or_expand(ctx.db());
    let expr = expr_ptr.value.to_node(&root);
    let container = skip_transparent(expr).syntax().parent()?;
    let ty = syntax::match_ast! {
        match container {
            ast::LetStmt(it) => it.ty()?,
            ast::Static(it) => it.ty()?,
            ast::Const(it) => it.ty()?,
            _ => return None,
        }
    };
    let ast::Type::ArrayType(ty) = ty else { return None };
    let len = ty.const_arg()?.expr()?;
    let hir::FileRange { range: len_range, .. } = ctx.sema.original_range_opt(len.syntax())?;
    let hir::FileRange { range, file_id } = ctx.sema.original_range_opt(&container)?;

    let edit = TextEdit::replace(len_range, actual.to_string());
    let source_change = SourceChange::from_text_edit(file_id.file_id(ctx.db()), edit);
    let label = &format!("Change array length to {actual}");
    acc.push(fix("array_length", label, source_change, range));

    fn skip_transparent(mut expr: Expr) -> Expr {
        while let Some(parent) = expr.syntax().parent() {
            if let Some(it) = ast::StmtList::cast(parent.clone())
                && it.statements().next().is_none()
                && let Some(parent) = it.syntax().parent().and_then(Expr::cast)
            {
                expr = parent;
            } else if let Some(parent) = ast::ParenExpr::cast(parent) {
                expr = parent.into();
            } else {
                break;
            }
        }
        expr
    }

    Some(())
}

fn insert_prefix(expr: &Expr, range: syntax::TextRange, text: &str) -> TextEdit {
    if expr.precedence().needs_parentheses_in(ast::prec::ExprPrecedence::Prefix) {
        let mut builder = TextEdit::builder();
        builder.insert(range.start(), format!("{text}("));
        builder.insert(range.end(), ")".to_owned());
        builder.finish()
    } else {
        TextEdit::insert(range.start(), text.to_owned())
    }
}

fn insert_postfix(expr: &Expr, range: syntax::TextRange, text: &str) -> TextEdit {
    if expr.precedence().needs_parentheses_in(ast::prec::ExprPrecedence::Postfix) {
        let mut builder = TextEdit::builder();
        builder.insert(range.start(), "(".to_owned());
        builder.insert(range.end(), format!("){text}"));
        builder.finish()
    } else {
        TextEdit::insert(range.end(), text.to_owned())
    }
}

#[cfg(test)]
mod tests {
    use crate::tests::{
        check_diagnostics, check_diagnostics_with_disabled, check_fix, check_fix_with_disabled,
        check_has_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 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 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 fix_reference_to_int() {
        check_fix(
            r#"
fn main() {
    test($0&123);
}
fn test(_arg: &mut i32) {}
            "#,
            r#"
fn main() {
    test(&mut 123);
}
fn test(_arg: &mut i32) {}
            "#,
        );
    }

    #[test]
    fn add_reference_to_parenthesized_int() {
        check_fix(
            r#"
fn main() {
    test(($0123));
}
fn test(_arg: &i32) {}
            "#,
            r#"
fn main() {
    test((&123));
}
fn test(_arg: &i32) {}
            "#,
        );
    }

    #[test]
    fn add_mutable_reference_to_parenthesized_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 fix_reference_to_parenthesized_int_paren_inside_ref() {
        check_fix(
            r#"
fn main() {
    test(&$0(123));
}
fn test(_arg: &mut i32) {}
            "#,
            r#"
fn main() {
    test(&mut (123));
}
fn test(_arg: &mut i32) {}
            "#,
        );
    }

    #[test]
    fn fix_reference_to_parenthesized_int_ref_inside_paren() {
        check_fix(
            r#"
fn main() {
    test(($0&123));
}
fn test(_arg: &mut i32) {}
            "#,
            r#"
fn main() {
    test((&mut 123));
}
fn test(_arg: &mut i32) {}
            "#,
        );
    }

    #[test]
    fn add_deref_in_assign() {
        check_fix(
            r#"
fn test(arg: &mut i32) {
    arg = $02;
}
            "#,
            r#"
fn test(arg: &mut i32) {
    *arg = 2;
}
            "#,
        );
    }

    #[test]
    fn 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 fix_reference_to_array() {
        check_no_fix(
            r#"
//- minicore: coerce_unsized
fn main() {
    test($0&[1, 2, 3]);
}
fn test(_arg: &mut [i32]) {}
            "#,
        );
    }

    #[test]
    fn 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 deref(&self) -> &Self::Target { loop {} }
}

fn main() {
    test($0Foo);
}
fn test(_arg: &Bar) {}
            "#,
            r#"
struct Foo;
struct Bar;
impl core::ops::Deref for Foo {
    type Target = Bar;
    fn deref(&self) -> &Self::Target { loop {} }
}

fn main() {
    test(&Foo);
}
fn test(_arg: &Bar) {}
            "#,
        );
    }

    #[test]
    fn add_reference_needs_parentheses() {
        check_fix(
            r#"
//- minicore: add
struct Foo;
impl core::ops::Add<i32> for Foo {
    type Output = Foo;
    fn add(self, rhs: i32) -> Self::Output { loop {} }
}

fn main() {
    test($0Foo + 2);
}
fn test(_arg: &Foo) {}
            "#,
            r#"
struct Foo;
impl core::ops::Add<i32> for Foo {
    type Output = Foo;
    fn add(self, rhs: i32) -> Self::Output { loop {} }
}

fn main() {
    test(&(Foo + 2));
}
fn test(_arg: &Foo) {}
            "#,
        );
    }

    #[test]
    // FIXME: this should suggest making the reference mutable instead: `&Foo -> &mut Foo`.
    // Currently it doesn't, as the logic for that assist strips away references, and thus checks
    // whether `Foo` can be coerced to `Bar` (which it can't), instead of checking `&mut Foo` to
    // `&mut Bar` (which it can)
    fn fix_reference_with_autoderef() {
        check_fix(
            r#"
//- minicore: coerce_unsized, deref_mut
struct Foo;
struct Bar;
impl core::ops::Deref for Foo {
    type Target = Bar;
    fn deref(&self) -> &Self::Target { loop {} }
}
impl core::ops::DerefMut for Foo {
    fn deref_mut(&mut self) -> &mut Self::Target { loop {} }
}

fn main() {
    test($0&Foo);
}
fn test(_arg: &mut Bar) {}
            "#,
            r#"
struct Foo;
struct Bar;
impl core::ops::Deref for Foo {
    type Target = Bar;
    fn deref(&self) -> &Self::Target { loop {} }
}
impl core::ops::DerefMut for Foo {
    fn deref_mut(&mut self) -> &mut Self::Target { loop {} }
}

fn main() {
    test(&mut &Foo);
}
fn test(_arg: &mut Bar) {}
            "#,
        );
    }

    #[test]
    fn 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 add_reference_in_let_stmt() {
        check_fix(
            r#"
fn main() {
    let test: &i32 = $0123;
}
            "#,
            r#"
fn main() {
    let test: &i32 = &123;
}
            "#,
        );
    }

    #[test]
    fn add_mutable_reference_in_let_stmt() {
        check_fix(
            r#"
fn main() {
    let _test: &mut i32 = $0123;
}
            "#,
            r#"
fn main() {
    let _test: &mut i32 = &mut 123;
}
            "#,
        );
    }

    #[test]
    fn fix_reference_in_let_stmt() {
        check_fix(
            r#"
fn main() {
    let _test: &mut i32 = $0&123;
}
            "#,
            r#"
fn main() {
    let _test: &mut i32 = &mut 123;
}
            "#,
        );
    }

    #[test]
    fn 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 fix_reference_to_macro_call() {
        check_fix(
            r#"
macro_rules! thousand {
    () => {
        1000_u64
    };
}

fn test(_foo: &mut u64) {}
fn main() {
    test($0&thousand!());
}
            "#,
            r#"
macro_rules! thousand {
    () => {
        1000_u64
    };
}

fn test(_foo: &mut u64) {}
fn main() {
    test(&mut thousand!());
}
            "#,
        );
    }

    #[test]
    // If the immutable reference comes from a macro expansion,
    // we can't do anything to change it to a mutable one.
    fn dont_fix_reference_inside_macro_call() {
        check_no_fix(
            r#"
macro_rules! thousand {
    () => {
        &1000_u64
    };
}

fn test(_foo: &mut u64) {}
fn main() {
    test($0thousand!());
}
            "#,
        );
    }

    #[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())
            }
"#,
        );
    }

    #[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 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)
}
"#,
        );

        check_fix(
            r#"
//- minicore: option, result
macro_rules! identity { ($($t:tt)*) => ($($t)*) }
identity! {
    fn div(x: i32, y: i32) -> Result<i32, ()> {
        if y == 0 {
            return Err(());
        }
        x / y$0
    }
}
"#,
            r#"
macro_rules! identity { ($($t:tt)*) => ($($t)*) }
identity! {
    fn div(x: i32, y: i32) -> Result<i32, ()> {
        if y == 0 {
            return Err(());
        }
        Ok(x / y)
    }
}
"#,
        );
    }

    #[test]
    fn 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 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 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 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 wrapped_unit_as_block_tail_expr() {
        check_fix(
            r#"
//- minicore: result
fn foo() -> Result<(), ()> {
    foo();
}$0
            "#,
            r#"
fn foo() -> Result<(), ()> {
    foo();
    Ok(())
}
            "#,
        );

        check_fix(
            r#"
//- minicore: result
fn foo() -> Result<(), ()> {}$0
            "#,
            r#"
fn foo() -> Result<(), ()> {
    Ok(())
}
            "#,
        );

        check_fix(
            r#"
//- minicore: result, iterator
fn foo() -> Result<(), ()> {
    for _ in 0..5 {}$0
}
            "#,
            r#"
fn foo() -> Result<(), ()> {
    for _ in 0..5 {}
    Ok(())
}
            "#,
        );
    }

    #[test]
    fn wrapped_unit_as_return_expr() {
        check_fix_with_disabled(
            r#"
//- minicore: result
fn foo(b: bool) -> Result<(), String> {
    if b {
        return$0;
    }

    Err("oh dear".to_owned())
}"#,
            r#"
fn foo(b: bool) -> Result<(), String> {
    if b {
        return Ok(());
    }

    Err("oh dear".to_owned())
}"#,
            &["E0599"],
        );
    }

    #[test]
    fn wrap_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 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 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 unwrap_return_type() {
        check_fix_with_disabled(
            r#"
//- minicore: option, result
fn div(x: i32, y: i32) -> i32 {
    if y == 0 {
        panic!();
    }
    Ok(x / y)$0
}
"#,
            r#"
fn div(x: i32, y: i32) -> i32 {
    if y == 0 {
        panic!();
    }
    x / y
}
"#,
            &["E0282"],
        );
    }

    #[test]
    fn unwrap_return_type_option() {
        check_fix(
            r#"
//- minicore: option, result
fn div(x: i32, y: i32) -> i32 {
    if y == 0 {
        panic!();
    }
    Some(x / y)$0
}
"#,
            r#"
fn div(x: i32, y: i32) -> i32 {
    if y == 0 {
        panic!();
    }
    x / y
}
"#,
        );
    }

    #[test]
    fn unwrap_return_type_option_tails() {
        check_fix(
            r#"
//- minicore: option, result
fn div(x: i32, y: i32) -> i32 {
    if y == 0 {
        42
    } else if true {
        Some(100)$0
    } else {
        0
    }
}
"#,
            r#"
fn div(x: i32, y: i32) -> i32 {
    if y == 0 {
        42
    } else if true {
        100
    } else {
        0
    }
}
"#,
        );
    }

    #[test]
    fn unwrap_return_type_option_tail_unit() {
        check_fix_with_disabled(
            r#"
//- minicore: option, result
fn div(x: i32, y: i32) {
    if y == 0 {
        panic!();
    }

    Ok(())$0
}
"#,
            r#"
fn div(x: i32, y: i32) {
    if y == 0 {
        panic!();
    }
}
"#,
            &["E0282"],
        );
    }

    #[test]
    fn unwrap_return_type_handles_generic_functions() {
        check_fix_with_disabled(
            r#"
//- minicore: option, result
fn div<T>(x: T) -> T {
    if x == 0 {
        panic!();
    }
    $0Ok(x)
}
"#,
            r#"
fn div<T>(x: T) -> T {
    if x == 0 {
        panic!();
    }
    x
}
"#,
            &["E0282"],
        );
    }

    #[test]
    fn unwrap_return_type_handles_type_aliases() {
        check_fix_with_disabled(
            r#"
//- minicore: option, result
type MyResult<T> = T;

fn div(x: i32, y: i32) -> MyResult<i32> {
    if y == 0 {
        panic!();
    }
    Ok(x $0/ y)
}
"#,
            r#"
type MyResult<T> = T;

fn div(x: i32, y: i32) -> MyResult<i32> {
    if y == 0 {
        panic!();
    }
    x / y
}
"#,
            &["E0282"],
        );
    }

    #[test]
    fn unwrap_tail_expr() {
        check_fix_with_disabled(
            r#"
//- minicore: result
fn foo() -> () {
    println!("Hello, world!");
    Ok(())$0
}
            "#,
            r#"
fn foo() -> () {
    println!("Hello, world!");
}
            "#,
            &["E0282"],
        );
    }

    #[test]
    fn unwrap_to_empty_block() {
        check_fix_with_disabled(
            r#"
//- minicore: result
fn foo() -> () {
    Ok(())$0
}
            "#,
            r#"
fn foo() -> () {}
            "#,
            &["E0282"],
        );
    }

    #[test]
    fn unwrap_to_return_expr() {
        check_has_fix(
            r#"
//- minicore: result
fn foo(b: bool) -> () {
    if b {
        return $0Ok(());
    }

    panic!("oh dear");
}"#,
            r#"
fn foo(b: bool) -> () {
    if b {
        return;
    }

    panic!("oh dear");
}"#,
        );
    }

    #[test]
    fn unwrap_in_const_and_static() {
        check_fix(
            r#"
//- minicore: option, result
static A: () = {Some(($0))};
            "#,
            r#"
static A: () = {};
            "#,
        );
        check_fix(
            r#"
//- minicore: option, result
const _: () = {Some(($0))};
            "#,
            r#"
const _: () = {};
            "#,
        );
    }

    #[test]
    fn unwrap_return_type_not_applicable_when_inner_type_does_not_match_return_type() {
        check_no_fix(
            r#"
//- minicore: result
fn foo() -> i32 { $0Ok(()) }
"#,
        );
    }

    #[test]
    fn unwrap_return_type_not_applicable_when_wrapper_type_is_not_result_or_option() {
        check_no_fix(
            r#"
//- minicore: option, result
enum SomeOtherEnum { Ok(i32), Err(String) }

fn foo() -> i32 { SomeOtherEnum::Ok($042) }
"#,
        );
    }

    #[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()
}
            "#,
        );

        check_fix(
            r#"
struct String;

fn test() -> String {
    let s = &"a";
    *s$0
}
            "#,
            r#"
struct String;

fn test() -> String {
    let s = &"a";
    (*s).to_owned()
}
            "#,
        );

        check_fix(
            r#"
macro_rules! identity { ($($t:tt)*) => ($($t)*) }
struct String;

identity! {
    fn test() -> String {
        "a"$0
    }
}
            "#,
            r#"
macro_rules! identity { ($($t:tt)*) => ($($t)*) }
struct String;

identity! {
    fn test() -> String {
        "a".to_owned()
    }
}
            "#,
        );
    }

    #[test]
    fn add_await() {
        check_fix(
            r#"
//- minicore: future
async fn foo() -> u32 { 2 }
async fn test() {
    let x: u32 = foo()$0;
}
            "#,
            r#"
async fn foo() -> u32 { 2 }
async fn test() {
    let x: u32 = foo().await;
}
            "#,
        );

        check_fix(
            r#"
//- minicore: future
macro_rules! identity { ($($t:tt)*) => ($($t)*) }
async fn foo() -> u32 { 2 }
identity! {
    async fn test() {
        let x: u32 = foo()$0;
    }
}
            "#,
            r#"
macro_rules! identity { ($($t:tt)*) => ($($t)*) }
async fn foo() -> u32 { 2 }
identity! {
    async fn test() {
        let x: u32 = foo().await;
    }
}
            "#,
        );
    }

    #[test]
    fn array_length() {
        check_fix(
            r#"
const VALS: [i32; 2$0] = [1, 2, 3];
            "#,
            r#"
const VALS: [i32; 3] = [1, 2, 3];
            "#,
        );

        check_fix(
            r#"
static VALS: [i32; 2$0] = [1, 2, 3];
            "#,
            r#"
static VALS: [i32; 3] = [1, 2, 3];
            "#,
        );

        check_fix(
            r#"
static VALS: [i32; 2$0] = {[1, 2, 3]};
            "#,
            r#"
static VALS: [i32; 3] = {[1, 2, 3]};
            "#,
        );

        // Convenient trigger range
        check_fix(
            r#"
static VALS: [i32; 2] = [$01, 2, 3];
            "#,
            r#"
static VALS: [i32; 3] = [1, 2, 3];
            "#,
        );

        check_fix(
            r#"
macro_rules! identity { ($($t:tt)*) => ($($t)*) }
identity! {
    const VALS: [i32; 2$0] = [1, 2, 3];
}
            "#,
            r#"
macro_rules! identity { ($($t:tt)*) => ($($t)*) }
identity! {
    const VALS: [i32; 3] = [1, 2, 3];
}
            "#,
        );
    }

    #[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 &() {
        &9 => ()
       //^ error: expected (), found i32
    }
}
"#,
        );
    }

    #[test]
    fn type_mismatch_in_condition() {
        check_diagnostics(
            r#"
fn f() {
    if 1 {}
     //^ error: expected bool, found i32
    match () { _ if 1 => (), _ => () }
                  //^ error: expected bool, 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 trait_upcast_ok() {
        check_diagnostics(
            r#"
//- minicore: coerce_unsized
trait A: B {}
trait B {}

fn test(a: &dyn A) -> &dyn B {
    a
}
"#,
        );
    }

    #[test]
    fn trait_upcast_err() {
        check_diagnostics(
            r#"
//- minicore: coerce_unsized
trait A {} // `A` does not have `B` as a supertrait, so no upcast :c
trait B {}

fn test(a: &dyn A) -> &dyn B {
    a
  //^💡 error: expected &(dyn B + 'static), found &(dyn A + 'static)
}
"#,
        );
    }

    #[test]
    fn return_no_value() {
        check_diagnostics_with_disabled(
            r#"
fn f() -> i32 {
    return;
 // ^^^^^^ error: expected i32, found ()
    0
}
fn g() { return; }
"#,
            &["needless_return"],
        );
    }

    #[test]
    fn smoke_test_inner_items() {
        check_diagnostics(
            r#"
fn f() {
    fn inner() -> i32 {
        return;
     // ^^^^^^ error: expected i32, found ()
        0
    }
}
"#,
        );
    }

    #[test]
    fn regression_17585() {
        check_diagnostics(
            r#"
fn f() {
    let (_, _, _, ..) = (true, 42);
     // ^^^^^^^^^^^^^ error: expected (bool, i32), found (bool, i32, {unknown})
}
"#,
        );
    }

    #[test]
    fn complex_enum_variant_non_ref_pat() {
        check_diagnostics(
            r#"
enum Enum { Variant }

trait Trait {
    type Assoc;
}
impl Trait for () {
    type Assoc = Enum;
}

fn foo(v: &Enum) {
    let <Enum>::Variant = v;
    let <() as Trait>::Assoc::Variant = v;
}
    "#,
        );
    }

    #[test]
    fn regression_19844() {
        check_diagnostics(
            r#"
fn main() {
    struct S {}
    enum E { V() }
    let E::V() = &S {};
     // ^^^^^^ error: expected S, found E
}
"#,
        );
    }

    #[test]
    fn test_ignore_unknown_mismatch() {
        check_diagnostics(
            r#"
pub trait Foo {
    type Out;
}
impl Foo for [i32; 1] {
    type Out = ();
}
pub fn foo<T: Foo>(_: T) -> (T::Out,) { loop { } }

fn main() {
    let _x = foo(2);
     // ^^ error: type annotations needed
          // ^^^ error: the trait bound `i32: Foo` is not satisfied
}
"#,
        );
    }

    #[test]
    fn regression_21668() {
        check_diagnostics(
            r#"
mod std {
    pub enum Ordering { Less }
}
pub use std::*;

pub mod evil {
    pub struct Ordering(pub i32);
}

pub mod oblivious {
    use crate::Ordering;

    pub fn what() -> Ordering {
        Ordering(2)
    }
}
pub use evil::Ordering;
"#,
        );
    }

    // Tests for qualified paths on name collision (issue #22331)

    #[test]
    fn type_mismatch_collision_basic_structs() {
        check_diagnostics(
            r#"
mod foo {
    pub struct S;
}
mod bar {
    pub struct S;
}
fn test(_: foo::S) {
    test(bar::S);
       //^^^^^^ error: expected foo::S, found bar::S
}
"#,
        );
    }

    #[test]
    fn type_mismatch_collision_in_generic() {
        check_diagnostics(
            r#"
//- minicore: option
mod foo {
    pub struct S;
}
mod bar {
    pub struct S;
}
fn make() -> Option<bar::S> { loop {} }
fn test(_: Option<foo::S>) {
    test(make());
       //^^^^^^ error: expected Option<foo::S>, found Option<bar::S>
}
"#,
        );
    }

    #[test]
    fn type_mismatch_no_collision_unchanged() {
        check_diagnostics(
            r#"
mod foo {
    pub struct S;
}
mod bar {
    pub struct T;
}
fn test(_: foo::S) {
    test(bar::T);
       //^^^^^^ error: expected S, found T
}
"#,
        );
    }

    #[test]
    fn type_mismatch_multiple_collisions() {
        check_diagnostics(
            r#"
//- minicore: result
mod foo {
    pub struct T;
}
mod bar {
    pub struct T;
}
mod baz {
    pub struct E;
}
mod qux {
    pub struct E;
}
fn make() -> Result<bar::T, qux::E> { loop {} }
fn test(_: Result<foo::T, baz::E>) {
    test(make());
       //^^^^^^ error: expected Result<foo::T, baz::E>, found Result<bar::T, qux::E>
}
"#,
        );
    }
}
