use itertools::Itertools;
use std::iter::successors;

use ide_db::{RootDatabase, defs::NameClass, ty_filter::TryEnum};
use syntax::{
    AstNode, SyntaxKind, T, TextRange,
    ast::{
        self, HasName,
        edit::{AstNodeEdit, IndentLevel},
        syntax_factory::SyntaxFactory,
    },
    syntax_editor::SyntaxEditor,
};

use crate::{
    AssistContext, AssistId, Assists,
    utils::{
        does_pat_match_variant, does_pat_variant_nested_or_literal, unwrap_trivial_block,
        wrap_paren_in_guard_chain,
    },
};

// Assist: replace_if_let_with_match
//
// Replaces a `if let` expression with a `match` expression.
//
// ```
// enum Action { Move { distance: u32 }, Stop }
//
// fn handle(action: Action) {
//     $0if let Action::Move { distance } = action {
//         foo(distance)
//     } else {
//         bar()
//     }
// }
// ```
// ->
// ```
// enum Action { Move { distance: u32 }, Stop }
//
// fn handle(action: Action) {
//     match action {
//         Action::Move { distance } => foo(distance),
//         _ => bar(),
//     }
// }
// ```
pub(crate) fn replace_if_let_with_match(
    acc: &mut Assists,
    ctx: &AssistContext<'_, '_>,
) -> Option<()> {
    let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
    let available_range = TextRange::new(
        if_expr.syntax().text_range().start(),
        if_expr.then_branch()?.syntax().text_range().start(),
    );
    let cursor_in_range = available_range.contains_range(ctx.selection_trimmed());
    if !cursor_in_range {
        return None;
    }
    let mut else_block = None;
    let indent = if_expr.indent_level();
    let if_exprs = successors(Some(if_expr.clone()), |expr| match expr.else_branch()? {
        ast::ElseBranch::IfExpr(expr) => Some(expr),
        ast::ElseBranch::Block(block) => {
            let block = unwrap_trivial_block(block);
            else_block = Some(block.reset_indent().indent(IndentLevel(1)));
            None
        }
    });
    let scrutinee_to_be_expr = if_expr.condition()?;
    let scrutinee_to_be_expr = match let_and_guard(&scrutinee_to_be_expr, ctx)? {
        (Some((_, expr)), _) => expr,
        (None, cond) => cond?,
    };

    let mut pat_seen = false;
    let mut cond_bodies = Vec::new();
    for if_expr in if_exprs {
        let cond = if_expr.condition()?;
        let (cond, guard) = match let_and_guard(&cond, ctx)? {
            (None, guard) => (None, Some(guard?)),
            (Some((pat, expr)), guard) => {
                if scrutinee_to_be_expr.syntax().text() != expr.syntax().text() {
                    // Only if all condition expressions are equal we can merge them into a match
                    return None;
                }
                pat_seen = true;
                (Some(pat), guard)
            }
        };
        let guard = if let Some(guard) = &guard {
            Some(guard.dedent(indent).indent(IndentLevel(1)))
        } else {
            guard
        };

        let body = if_expr.then_branch()?.indent(IndentLevel(1));
        cond_bodies.push((cond, guard, body));
    }

    if !pat_seen && cond_bodies.len() != 1 {
        // Don't offer turning an if (chain) without patterns into a match,
        // unless it's a simple `if cond { .. } (else { .. })`
        return None;
    }

    let let_ = if pat_seen { " let" } else { "" };

    acc.add(
        AssistId::refactor_rewrite("replace_if_let_with_match"),
        format!("Replace if{let_} with match"),
        available_range,
        move |builder| {
            let editor = builder.make_editor(if_expr.syntax());
            let make = editor.make();
            let match_expr: ast::Expr = {
                let else_arm = make_else_arm(ctx, make, else_block, &cond_bodies);
                let make_match_arm =
                    |(pat, guard, body): (_, Option<ast::Expr>, ast::BlockExpr)| {
                        // Dedent from original position, then indent for match arm
                        let body = body.dedent(indent);
                        let body = unwrap_trivial_block(body);
                        let pat = pretty_pat_inside_macro(pat, &ctx.sema);
                        match (pat, guard.map(|it| make.match_guard(it))) {
                            (Some(pat), guard) => make.match_arm(pat, guard, body),
                            (None, _) if !pat_seen => {
                                make.match_arm(make.literal_pat("true").into(), None, body)
                            }
                            (None, guard) => {
                                make.match_arm(make.wildcard_pat().into(), guard, body)
                            }
                        }
                    };
                let arms = cond_bodies.into_iter().map(make_match_arm).chain([else_arm]);
                let expr = scrutinee_to_be_expr.reset_indent();
                let expr = if match_scrutinee_needs_paren(&expr) {
                    make.expr_paren(expr).into()
                } else {
                    expr
                };
                let match_expr = make.expr_match(expr, make.match_arm_list(arms)).indent(indent);
                match_expr.into()
            };

            let has_preceding_if_expr =
                if_expr.syntax().parent().is_some_and(|it| ast::IfExpr::can_cast(it.kind()));
            let expr = if has_preceding_if_expr {
                // make sure we replace the `else if let ...` with a block so we don't end up with `else expr`
                let block_expr = make
                    .block_expr([], Some(match_expr.dedent(indent).indent(IndentLevel(1))))
                    .indent(indent);
                block_expr.into()
            } else {
                match_expr
            };
            editor.replace(if_expr.syntax(), expr.syntax());
            builder.add_file_edits(ctx.vfs_file_id(), editor);
        },
    )
}

fn make_else_arm(
    ctx: &AssistContext<'_, '_>,
    make: &SyntaxFactory,
    else_expr: Option<ast::Expr>,
    conditionals: &[(Option<ast::Pat>, Option<ast::Expr>, ast::BlockExpr)],
) -> ast::MatchArm {
    let (pattern, expr) = if let Some(else_expr) = else_expr {
        let pattern = match conditionals {
            [(None, Some(_), _)] => make.literal_pat("false").into(),
            [(Some(pat), None, _)] => match ctx
                .sema
                .type_of_pat(pat)
                .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
            {
                Some(it) => {
                    if does_pat_match_variant(pat, &it.sad_pattern()) {
                        it.happy_pattern_wildcard()
                    } else if does_pat_variant_nested_or_literal(ctx, pat) {
                        make.wildcard_pat().into()
                    } else {
                        it.sad_pattern()
                    }
                }
                None => make.wildcard_pat().into(),
            },
            _ => make.wildcard_pat().into(),
        };
        (pattern, else_expr)
    } else {
        let pattern = match conditionals {
            [(None, Some(_), _)] => make.literal_pat("false").into(),
            _ => make.wildcard_pat().into(),
        };
        (pattern, make.expr_unit())
    };
    make.match_arm(pattern, None, expr)
}

// Assist: replace_match_with_if_let
//
// Replaces a binary `match` with a wildcard pattern with an `if let` expression.
//
// ```
// enum Action { Move { distance: u32 }, Stop }
//
// fn handle(action: Action) {
//     $0match action {
//         Action::Move { distance } => foo(distance),
//         _ => bar(),
//     }
// }
// ```
// ->
// ```
// enum Action { Move { distance: u32 }, Stop }
//
// fn handle(action: Action) {
//     if let Action::Move { distance } = action {
//         foo(distance)
//     } else {
//         bar()
//     }
// }
// ```
pub(crate) fn replace_match_with_if_let(
    acc: &mut Assists,
    ctx: &AssistContext<'_, '_>,
) -> Option<()> {
    let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
    let match_arm_list = match_expr.match_arm_list()?;
    let available_range = TextRange::new(
        match_expr.syntax().text_range().start(),
        match_arm_list.syntax().text_range().start(),
    );
    let cursor_in_range = available_range.contains_range(ctx.selection_trimmed());
    if !cursor_in_range {
        return None;
    }

    let arms = match_arm_list
        .arms()
        .map(|arm| Some((arm.pat()?, arm.expr()?, arm.guard())))
        .collect::<Option<Vec<_>>>()?;

    if arms.len() < 2 {
        return None;
    }

    let (if_exprs, else_expr) = pick_pattern_and_expr_order(&ctx.sema, arms)?;
    let scrutinee = match_expr.expr()?.reset_indent();

    let let_ = match &if_exprs.first()?.0 {
        ast::Pat::LiteralPat(p)
            if p.literal()
                .map(|it| it.token().kind())
                .is_some_and(|it| it == T![true] || it == T![false]) =>
        {
            ""
        }
        _ => " let",
    };
    acc.add(
        AssistId::refactor_rewrite("replace_match_with_if_let"),
        format!("Replace match with if{let_}"),
        match_expr.syntax().text_range(),
        move |builder| {
            let editor = builder.make_editor(match_expr.syntax());
            let make = editor.make();
            let make_block_expr = |expr: ast::Expr| {
                // Blocks with modifiers (unsafe, async, etc.) are parsed as BlockExpr, but are
                // formatted without enclosing braces. If we encounter such block exprs,
                // wrap them in another BlockExpr.
                match expr {
                    ast::Expr::BlockExpr(block) if block.modifier().is_none() => block,
                    expr => make.block_expr([], Some(expr.indent(IndentLevel(1)))),
                }
            };

            let if_branches = if_exprs.into_iter().map(|(pat, then_expr, guard)| {
                let guard = guard.and_then(|it| it.condition());

                let condition = match pat {
                    ast::Pat::LiteralPat(p)
                        if p.literal().is_some_and(|it| it.token().kind() == T![true]) =>
                    {
                        scrutinee.clone()
                    }
                    ast::Pat::LiteralPat(p)
                        if p.literal().is_some_and(|it| it.token().kind() == T![false]) =>
                    {
                        make.expr_prefix(T![!], scrutinee.clone()).into()
                    }
                    ast::Pat::WildcardPat(_) => make.expr_literal("true").into(),
                    _ => make.expr_let(pat, scrutinee.clone()).into(),
                };
                let condition = if condition.syntax().text() == "true"
                    && let Some(guard) = guard
                {
                    guard
                } else if let Some(guard) = guard {
                    let guard = wrap_paren_in_guard_chain(guard, make);
                    make.expr_bin(condition, ast::BinaryOp::LogicOp(ast::LogicOp::And), guard)
                        .into()
                } else {
                    condition
                };

                let then_expr = then_expr.reset_indent();
                let then_block = make_block_expr(then_expr);

                (condition, then_block)
            });

            let else_expr = else_expr.reset_indent();
            let else_expr = if is_empty_expr(&else_expr) { None } else { Some(else_expr) };
            let else_branch = else_expr.map(make_block_expr).map(ast::ElseBranch::Block);

            let if_let_expr =
                if_branches.rfold(else_branch, |else_branch, (condition, then_branch)| {
                    Some(make.expr_if(condition, then_branch, else_branch).into())
                });
            let Some(ast::ElseBranch::IfExpr(if_let_expr)) = if_let_expr else { unreachable!() };
            let if_let_expr = if_let_expr.indent(match_expr.indent_level());

            editor.replace(match_expr.syntax(), if_let_expr.syntax());
            builder.add_file_edits(ctx.vfs_file_id(), editor);
        },
    )
}

/// Pick the pattern for the if let condition and return the expressions for the `then` body and `else` body in that order.
fn pick_pattern_and_expr_order(
    sema: &hir::Semantics<'_, RootDatabase>,
    mut arms: Vec<(ast::Pat, ast::Expr, Option<ast::MatchGuard>)>,
) -> Option<(Vec<(ast::Pat, ast::Expr, Option<ast::MatchGuard>)>, ast::Expr)> {
    if arms.last()?.2.is_some() {
        return None;
    }

    if matches!(arms.last()?.0, ast::Pat::WildcardPat(_)) {
        let (_, last, _) = arms.pop()?;
        return Some((arms, last));
    }

    if let Some(sad_arm) = arms.iter().rposition(|arm| {
        matches!(arm, (_, expr, None) if is_empty_expr(expr)) || is_sad_pat(sema, &arm.0)
    }) {
        let (_, sad_arm, _) = arms.remove(sad_arm);
        return Some((arms, sad_arm));
    }

    // This pattern triggers an invalid transformation.
    // See issues #11373, #19443
    // XXX: Perhaps we can extract a let statement to improve
    if !binds_name(sema, &arms.last()?.0) {
        let (_, last, _) = arms.pop()?;
        return Some((arms, last));
    }

    None
}

fn is_empty_expr(expr: &ast::Expr) -> bool {
    match expr {
        ast::Expr::BlockExpr(expr) => match expr.stmt_list() {
            Some(it) => it.statements().next().is_none() && it.tail_expr().is_none(),
            None => true,
        },
        ast::Expr::TupleExpr(expr) => expr.fields().next().is_none(),
        _ => false,
    }
}

fn binds_name(sema: &hir::Semantics<'_, RootDatabase>, pat: &ast::Pat) -> bool {
    let binds_name_v = |pat| binds_name(sema, &pat);
    match pat {
        ast::Pat::IdentPat(pat) => !matches!(
            pat.name().and_then(|name| NameClass::classify(sema, &name)),
            Some(NameClass::ConstReference(_))
        ),
        ast::Pat::MacroPat(_) => true,
        ast::Pat::OrPat(pat) => pat.pats().any(binds_name_v),
        ast::Pat::SlicePat(pat) => pat.pats().any(binds_name_v),
        ast::Pat::TuplePat(it) => it.fields().any(binds_name_v),
        ast::Pat::TupleStructPat(it) => it.fields().any(binds_name_v),
        ast::Pat::RecordPat(it) => it
            .record_pat_field_list()
            .is_some_and(|rpfl| rpfl.fields().flat_map(|rpf| rpf.pat()).any(binds_name_v)),
        ast::Pat::RefPat(pat) => pat.pat().is_some_and(binds_name_v),
        ast::Pat::BoxPat(pat) => pat.pat().is_some_and(binds_name_v),
        ast::Pat::ParenPat(pat) => pat.pat().is_some_and(binds_name_v),
        _ => false,
    }
}

fn is_sad_pat(sema: &hir::Semantics<'_, RootDatabase>, pat: &ast::Pat) -> bool {
    sema.type_of_pat(pat)
        .and_then(|ty| TryEnum::from_ty(sema, &ty.adjusted()))
        .is_some_and(|it| does_pat_match_variant(pat, &it.sad_pattern()))
}

fn let_and_guard(
    cond: &ast::Expr,
    ctx: &AssistContext<'_, '_>,
) -> Option<(Option<(ast::Pat, ast::Expr)>, Option<ast::Expr>)> {
    if let ast::Expr::ParenExpr(expr) = cond
        && let Some(sub_expr) = expr.expr()
    {
        let_and_guard(&sub_expr, ctx)?
    } else if let ast::Expr::LetExpr(let_expr) = cond {
        (Some((let_expr.pat()?, let_expr.expr()?)), None)
    } else if let Some((pat, expr, guard)) = parse_matches_macro(cond, ctx) {
        (Some((pat, expr)), guard)
    } else if let ast::Expr::BinExpr(bin_expr) = cond
        && let Some(ast::Expr::LetExpr(let_expr)) = and_bin_expr_left(bin_expr).lhs()
    {
        let (editor, new_expr) = SyntaxEditor::with_ast_node(bin_expr);
        let left_bin = and_bin_expr_left(&new_expr);
        if let Some(rhs) = left_bin.rhs() {
            editor.replace(left_bin.syntax(), rhs.syntax());
        } else {
            if let Some(next) = left_bin.syntax().next_sibling_or_token()
                && next.kind() == SyntaxKind::WHITESPACE
            {
                editor.delete(next);
            }
            editor.delete(left_bin.syntax());
        }

        let new_expr = editor.finish().new_root().clone();
        (Some((let_expr.pat()?, let_expr.expr()?)), ast::Expr::cast(new_expr))
    } else {
        (None, Some(cond.clone()))
    }
    .into()
}

fn match_scrutinee_needs_paren(expr: &ast::Expr) -> bool {
    let make = SyntaxFactory::without_mappings();
    let fake_scrutinee = make.expr_unit();
    let fake_match = make.expr_match(fake_scrutinee, make.match_arm_list(std::iter::empty()));
    let Some(fake_expr) = fake_match.expr() else {
        stdx::never!();
        return false;
    };
    expr.needs_parens_in_place_of(fake_match.syntax(), fake_expr.syntax())
}

fn and_bin_expr_left(expr: &ast::BinExpr) -> ast::BinExpr {
    if expr.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And))
        && let Some(ast::Expr::BinExpr(left)) = expr.lhs()
    {
        and_bin_expr_left(&left)
    } else {
        expr.clone()
    }
}

fn parse_matches_macro(
    expr: &ast::Expr,
    ctx: &AssistContext<'_, '_>,
) -> Option<(ast::Pat, ast::Expr, Option<ast::Expr>)> {
    let ast::Expr::MacroExpr(macro_expr) = expr else { return None };
    let macro_call = macro_expr.macro_call()?;
    let tt = macro_call.token_tree()?;
    let r_delim = syntax::NodeOrToken::Token(tt.right_delimiter_token()?);

    if macro_call.path()?.segment()?.name_ref()?.text() != "matches" {
        return None;
    }

    let parse = |src: String| syntax::hacks::parse_expr_from_str(&src, ctx.edition());
    let input = tt.syntax().children_with_tokens().skip(1).take_while(|it| *it != r_delim);
    // Only supports single top-level comma case
    let [comma] = input.clone().filter(|it| it.kind() == T![,]).collect_array()?;
    let input_rest = input.clone().skip_while(|it| *it != comma).skip(1);
    let if_kwd = input_rest.clone().find(|it| it.kind() == T![if]);
    let input_expr = input.clone().take_while(|it| *it != comma).join("");
    let input_pat = input_rest.clone().take_while(|it| Some(it) != if_kwd.as_ref());
    let input_guard =
        if_kwd.as_ref().map(|if_kwd| { input_rest }.skip_while(|it| it != if_kwd).skip(1).join(""));

    let pat_token = match { input_pat }.find(|it| !it.kind().is_trivia())? {
        syntax::NodeOrToken::Node(node) => node.first_token()?,
        syntax::NodeOrToken::Token(t) => t,
    };
    // XXX: Use descend pat for sema analysis
    let descend_pat = ctx.sema.descend_into_macros(pat_token).into_iter().find_map(|token| {
        token
            .parent_ancestors()
            .take_while(|it| !matches!(it.kind(), SyntaxKind::MATCH_ARM | SyntaxKind::ITEM_LIST))
            .filter_map(ast::Pat::cast)
            .last()
    })?;

    Some((descend_pat, parse(input_expr)?, input_guard.and_then(parse)))
}

fn pretty_pat_inside_macro(
    pat: Option<ast::Pat>,
    sema: &hir::Semantics<'_, RootDatabase>,
) -> Option<ast::Pat> {
    let pretty = |pat| {
        let db = sema.db;
        let scope = sema.scope(&pat)?;
        let file_id = scope.file_id().macro_file()?;
        // Don't call `prettify_macro_expansion()` outside the actual assist action; see inline_macro assist
        let pretty_node = hir::prettify_macro_expansion(
            db,
            pat,
            file_id.expansion_span_map(db),
            scope.module().krate(db).into(),
        );
        ast::Pat::cast(pretty_node)
    };
    pat.map(|pat| pretty(pat.syntax().clone()).unwrap_or(pat))
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};

    #[test]
    fn test_if_let_with_match_inapplicable_for_simple_ifs() {
        check_assist_not_applicable(
            replace_if_let_with_match,
            r#"
fn main() {
    if $0true {} else if false {} else {}
}
"#,
        )
    }

    #[test]
    fn test_if_with_match_paren_jump_scrutinee() {
        check_assist(
            replace_if_let_with_match,
            r#"
fn f() {
    if $0(return) {}
}
"#,
            r#"
fn f() {
    match (return) {
        true => {}
        false => (),
    }
}
"#,
        )
    }

    #[test]
    fn test_if_with_match_no_else() {
        check_assist(
            replace_if_let_with_match,
            r#"
pub fn foo(foo: bool) {
    if foo$0 {
        self.foo();
    }
}
"#,
            r#"
pub fn foo(foo: bool) {
    match foo {
        true => {
            self.foo();
        }
        false => (),
    }
}
"#,
        )
    }

    #[test]
    fn test_if_with_match_with_else() {
        check_assist(
            replace_if_let_with_match,
            r#"
pub fn foo(foo: bool) {
    if foo$0 {
        self.foo();
    } else {
        self.bar();
    }
}
"#,
            r#"
pub fn foo(foo: bool) {
    match foo {
        true => {
            self.foo();
        }
        false => {
            self.bar();
        }
    }
}
"#,
        )
    }

    #[test]
    fn test_if_with_match_comments() {
        check_assist(
            replace_if_let_with_match,
            r#"
pub fn foo(foo: i32) {
    $0if let 1 = foo {
        // some comment
        self.foo();
    } else if let 2 = foo {
        // some comment 2
        self.bar()
    } else {
        // some comment 3
        self.baz();
    }
}
"#,
            r#"
pub fn foo(foo: i32) {
    match foo {
        1 => {
            // some comment
            self.foo();
        }
        2 => {
            // some comment 2
            self.bar()
        }
        _ => {
            // some comment 3
            self.baz();
        }
    }
}
"#,
        )
    }

    #[test]
    fn test_if_let_with_match_no_else() {
        check_assist(
            replace_if_let_with_match,
            r#"
impl VariantData {
    pub fn foo(&self) {
        if $0let VariantData::Struct(..) = *self {
            self.foo();
        }
    }
}
"#,
            r#"
impl VariantData {
    pub fn foo(&self) {
        match *self {
            VariantData::Struct(..) => {
                self.foo();
            }
            _ => (),
        }
    }
}
"#,
        )
    }

    #[test]
    fn test_if_let_with_match_available_range_left() {
        check_assist_not_applicable(
            replace_if_let_with_match,
            r#"
impl VariantData {
    pub fn foo(&self) {
        $0 if let VariantData::Struct(..) = *self {
            self.foo();
        }
    }
}
"#,
        )
    }

    #[test]
    fn test_if_let_with_match_available_range_right() {
        check_assist_not_applicable(
            replace_if_let_with_match,
            r#"
impl VariantData {
    pub fn foo(&self) {
        if let VariantData::Struct(..) = *self {$0
            self.foo();
        }
    }
}
"#,
        )
    }

    #[test]
    fn test_if_let_with_match_let_chain() {
        check_assist(
            replace_if_let_with_match,
            r#"
fn main() {
    if $0let true = true && let Some(1) = None {} else { other() }
}
"#,
            r#"
fn main() {
    match true {
        true if let Some(1) = None => {}
        _ => other(),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
fn main() {
    if true {
        $0if let ParenExpr(expr) = cond
            && let Some(sub_expr) = expr.expr()
        {
            branch1(
                "..."
            )
        } else if let LetExpr(let_expr) = cond {
            branch2(
                "..."
            )
        } else if let BinExpr(bin_expr) = cond
            && let Some(kind) = bin_expr.op_kind()
            && let Some(LetExpr(let_expr)) = foo(bin_expr)
        {
            branch3()
        } else {
            branch4(
                "..."
            )
        }
    }
}
"#,
            r#"
fn main() {
    if true {
        match cond {
            ParenExpr(expr) if let Some(sub_expr) = expr.expr() => {
                branch1(
                    "..."
                )
            }
            LetExpr(let_expr) => {
                branch2(
                    "..."
                )
            }
            BinExpr(bin_expr) if let Some(kind) = bin_expr.op_kind()
                && let Some(LetExpr(let_expr)) = foo(bin_expr) => branch3(),
            _ => {
                branch4(
                    "..."
                )
            }
        }
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
fn main() {
    if $0let true = true
        && true
        && false
    {
        code()
    } else {
        other()
    }
}
"#,
            r#"
fn main() {
    match true {
        true if true
            && false => code(),
        _ => other(),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_let_chain_no_else() {
        check_assist(
            replace_if_let_with_match,
            r#"
fn main() {
    if $0let true = true && let Some(1) = None {}
}
"#,
            r#"
fn main() {
    match true {
        true if let Some(1) = None => {}
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
fn main() {
    if $0let true = true
        && true
        && false
    {
        code()
    }
}
"#,
            r#"
fn main() {
    match true {
        true if true
            && false => code(),
        _ => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_basic() {
        check_assist(
            replace_if_let_with_match,
            r#"
impl VariantData {
    pub fn is_struct(&self) -> bool {
        if $0let VariantData::Struct(..) = *self {
            true
        } else if let VariantData::Tuple(..) = *self {
            false
        } else if cond() {
            true
        } else {
            bar(
                123
            )
        }
    }
}
"#,
            r#"
impl VariantData {
    pub fn is_struct(&self) -> bool {
        match *self {
            VariantData::Struct(..) => true,
            VariantData::Tuple(..) => false,
            _ if cond() => true,
            _ => {
                bar(
                    123
                )
            }
        }
    }
}
"#,
        )
    }

    #[test]
    fn test_if_let_with_match_on_tail_if_let() {
        check_assist(
            replace_if_let_with_match,
            r#"
impl VariantData {
    pub fn is_struct(&self) -> bool {
        if let VariantData::Struct(..) = *self {
            true
        } else if let$0 VariantData::Tuple(..) = *self {
            false
        } else {
            false
        }
    }
}
"#,
            r#"
impl VariantData {
    pub fn is_struct(&self) -> bool {
        if let VariantData::Struct(..) = *self {
            true
        } else {
            match *self {
                VariantData::Tuple(..) => false,
                _ => false,
            }
        }
    }
}
"#,
        )
    }

    #[test]
    fn special_case_option() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: option
fn foo(x: Option<i32>) {
    $0if let Some(x) = x {
        println!("{}", x)
    } else {
        println!("none")
    }
}
"#,
            r#"
fn foo(x: Option<i32>) {
    match x {
        Some(x) => println!("{}", x),
        None => println!("none"),
    }
}
"#,
        );
    }

    #[test]
    fn special_case_option_with_guard() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: option
fn foo(x: Option<i32>) {
    $0if let Some(x) = x && x != 4 {
        println!("{}", x)
    } else {
        println!("none")
    }
}
"#,
            r#"
fn foo(x: Option<i32>) {
    match x {
        Some(x) if x != 4 => println!("{}", x),
        _ => println!("none"),
    }
}
"#,
        );
    }

    #[test]
    fn special_case_option_ref() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: option
fn foo(x: &Option<i32>) {
    $0if let Some(x) = x {
        println!("{}", x)
    } else {
        println!("none")
    }
}
"#,
            r#"
fn foo(x: &Option<i32>) {
    match x {
        Some(x) => println!("{}", x),
        None => println!("none"),
    }
}
"#,
        );
    }

    #[test]
    fn special_case_inverted_option() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: option
fn foo(x: Option<i32>) {
    $0if let None = x {
        println!("none")
    } else {
        println!("some")
    }
}
"#,
            r#"
fn foo(x: Option<i32>) {
    match x {
        None => println!("none"),
        Some(_) => println!("some"),
    }
}
"#,
        );
    }

    #[test]
    fn special_case_inverted_option_with_guard() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: option
fn foo(x: Option<i32>) {
    $0if let None = x && other_cond {
        println!("none")
    } else {
        println!("some")
    }
}
"#,
            r#"
fn foo(x: Option<i32>) {
    match x {
        None if other_cond => println!("none"),
        _ => println!("some"),
    }
}
"#,
        );
    }

    #[test]
    fn special_case_result() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
    $0if let Ok(x) = x {
        println!("{}", x)
    } else {
        println!("none")
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    match x {
        Ok(x) => println!("{}", x),
        Err(_) => println!("none"),
    }
}
"#,
        );
    }

    #[test]
    fn special_case_inverted_result() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
    $0if let Err(x) = x {
        println!("{}", x)
    } else {
        println!("ok")
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    match x {
        Err(x) => println!("{}", x),
        Ok(_) => println!("ok"),
    }
}
"#,
        );
    }

    #[test]
    fn nested_indent() {
        check_assist(
            replace_if_let_with_match,
            r#"
fn main() {
    if true {
        $0if let Ok(rel_path) = path.strip_prefix(root_path)
            .and(x)
        {
            let rel_path = RelativePathBuf::from_path(rel_path)
                .ok()?;
            Some((*id, rel_path))
        } else {
            let _ = some_code()
                .clone();
            None
        }
    }
}
"#,
            r#"
fn main() {
    if true {
        match path.strip_prefix(root_path)
            .and(x)
        {
            Ok(rel_path) => {
                let rel_path = RelativePathBuf::from_path(rel_path)
                    .ok()?;
                Some((*id, rel_path))
            }
            _ => {
                let _ = some_code()
                    .clone();
                None
            }
        }
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
fn main() {
    if true {
        $0if let Ok(rel_path) = path.strip_prefix(root_path)
            .and(x)
        {
            Foo {
                x: 1
            }
        } else {
            Foo {
                x: 2
            }
        }
    }
}
"#,
            r#"
fn main() {
    if true {
        match path.strip_prefix(root_path)
            .and(x)
        {
            Ok(rel_path) => {
                Foo {
                    x: 1
                }
            }
            _ => {
                Foo {
                    x: 2
                }
            }
        }
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
fn main() {
    if true {
        $0if true
            && false
        {
            foo()
        }
    }
}
"#,
            r#"
fn main() {
    if true {
        match true
            && false
        {
            true => foo(),
            false => (),
        }
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_tuple_struct() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result, option
fn foo(x: Result<i32, ()>) {
    let bar: Result<_, ()> = Ok(Some(1));
    $0if let Ok(Some(_)) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    let bar: Result<_, ()> = Ok(Some(1));
    match bar {
        Ok(Some(_)) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
struct MyStruct(i32, i32);
fn foo(x: Result<MyStruct, ()>) {
    let bar: Result<MyStruct, ()> = Ok(MyStruct(1, 2));
    $0if let Ok(MyStruct(a, b)) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
struct MyStruct(i32, i32);
fn foo(x: Result<MyStruct, ()>) {
    let bar: Result<MyStruct, ()> = Ok(MyStruct(1, 2));
    match bar {
        Ok(MyStruct(a, b)) => (),
        Err(_) => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_slice() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<&[i32], ()>) {
    let foo: Result<&[_], ()> = Ok(&[0, 1, 2]);
    $0if let Ok([]) = foo {
        ()
    } else {
        ()
    }
}
        "#,
            r#"
fn foo(x: Result<&[i32], ()>) {
    let foo: Result<&[_], ()> = Ok(&[0, 1, 2]);
    match foo {
        Ok([]) => (),
        _ => (),
    }
}
        "#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<[&'static str; 2], ()>) {
    let foobar: Result<_, ()> = Ok(["foo", "bar"]);
    $0if let Ok([_, "bar"]) = foobar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<[&'static str; 2], ()>) {
    let foobar: Result<_, ()> = Ok(["foo", "bar"]);
    match foobar {
        Ok([_, "bar"]) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<[&'static str; 2], ()>) {
    let foobar: Result<_, ()> = Ok(["foo", "bar"]);
    $0if let Ok([..]) = foobar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<[&'static str; 2], ()>) {
    let foobar: Result<_, ()> = Ok(["foo", "bar"]);
    match foobar {
        Ok([..]) => (),
        Err(_) => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<&[&'static str], ()>) {
    let foobar: Result<&[&'static str], ()> = Ok(&["foo", "bar"]);
    $0if let Ok([a, ..]) = foobar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<&[&'static str], ()>) {
    let foobar: Result<&[&'static str], ()> = Ok(&["foo", "bar"]);
    match foobar {
        Ok([a, ..]) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<&[&'static str], ()>) {
    let foobar: Result<&[&'static str], ()> = Ok(&["foo", "bar"]);
    $0if let Ok([a, .., b, c]) = foobar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<&[&'static str], ()>) {
    let foobar: Result<&[&'static str], ()> = Ok(&["foo", "bar"]);
    match foobar {
        Ok([a, .., b, c]) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<Option<[&'static str; 2]>, ()>) {
    let foobar: Result<_, ()> = Ok(Some(["foo", "bar"]));
    $0if let Ok(Some([_, "bar"])) = foobar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<Option<[&'static str; 2]>, ()>) {
    let foobar: Result<_, ()> = Ok(Some(["foo", "bar"]));
    match foobar {
        Ok(Some([_, "bar"])) => (),
        _ => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_literal() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<&'static str, ()>) {
    let bar: Result<&_, ()> = Ok("bar");
    $0if let Ok("foo") = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<&'static str, ()>) {
    let bar: Result<&_, ()> = Ok("bar");
    match bar {
        Ok("foo") => (),
        _ => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_tuple() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<(i32, i32, i32), ()>) {
    let bar: Result<(i32, i32, i32), ()> = Ok((1, 2, 3));
    $0if let Ok((1, second, third)) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<(i32, i32, i32), ()>) {
    let bar: Result<(i32, i32, i32), ()> = Ok((1, 2, 3));
    match bar {
        Ok((1, second, third)) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<(i32, i32, i32), ()>) {
    let bar: Result<(i32, i32, i32), ()> = Ok((1, 2, 3));
    $0if let Ok((first, second, third)) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<(i32, i32, i32), ()>) {
    let bar: Result<(i32, i32, i32), ()> = Ok((1, 2, 3));
    match bar {
        Ok((first, second, third)) => (),
        Err(_) => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_or() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    $0if let Ok(1 | 2) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    match bar {
        Ok(1 | 2) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<(i32, i32), ()>) {
    let bar: Result<(i32, i32), ()> = Ok((1, 2));
    $0if let Ok((b, a) | (a, b)) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<(i32, i32), ()>) {
    let bar: Result<(i32, i32), ()> = Ok((1, 2));
    match bar {
        Ok((b, a) | (a, b)) => (),
        Err(_) => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<(i32, i32), ()>) {
    let bar: Result<(i32, i32), ()> = Ok((1, 2));
    $0if let Ok((1, a) | (a, 2)) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<(i32, i32), ()>) {
    let bar: Result<(i32, i32), ()> = Ok((1, 2));
    match bar {
        Ok((1, a) | (a, 2)) => (),
        _ => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_range() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    $0if let Ok(1..2) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    match bar {
        Ok(1..2) => (),
        _ => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_paren() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<(i32, i32), ()>) {
    let bar: Result<(i32, i32), ()> = Ok((1, 1));
    $0if let Ok(((1, 2))) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<(i32, i32), ()>) {
    let bar: Result<(i32, i32), ()> = Ok((1, 1));
    match bar {
        Ok(((1, 2))) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<(i32, i32), ()>) {
    let bar: Result<(i32, i32), ()> = Ok((1, 1));
    $0if let Ok(((a, b))) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<(i32, i32), ()>) {
    let bar: Result<(i32, i32), ()> = Ok((1, 1));
    match bar {
        Ok(((a, b))) => (),
        Err(_) => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_macro() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
    macro_rules! is_42 {
        () => {
            42
        };
    }

    let bar: Result<i32, ()> = Ok(1);
    $0if let Ok(is_42!()) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    macro_rules! is_42 {
        () => {
            42
        };
    }

    let bar: Result<i32, ()> = Ok(1);
    match bar {
        Ok(is_42!()) => (),
        _ => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_path() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
enum MyEnum {
    Foo,
    Bar,
}

fn foo(x: Result<MyEnum, ()>) {
    let bar: Result<MyEnum, ()> = Ok(MyEnum::Foo);
    $0if let Ok(MyEnum::Foo) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
enum MyEnum {
    Foo,
    Bar,
}

fn foo(x: Result<MyEnum, ()>) {
    let bar: Result<MyEnum, ()> = Ok(MyEnum::Foo);
    match bar {
        Ok(MyEnum::Foo) => (),
        _ => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_record() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
struct MyStruct {
    foo: i32,
    bar: i32,
}

fn foo(x: Result<MyStruct, ()>) {
    let bar: Result<MyStruct, ()> = Ok(MyStruct { foo: 1, bar: 2 });
    $0if let Ok(MyStruct { foo, bar }) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
struct MyStruct {
    foo: i32,
    bar: i32,
}

fn foo(x: Result<MyStruct, ()>) {
    let bar: Result<MyStruct, ()> = Ok(MyStruct { foo: 1, bar: 2 });
    match bar {
        Ok(MyStruct { foo, bar }) => (),
        Err(_) => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
struct MyStruct {
    foo: i32,
    bar: i32,
}

fn foo(x: Result<MyStruct, ()>) {
    let bar: Result<MyStruct, ()> = Ok(MyStruct { foo: 1, bar: 2 });
    $0if let Ok(MyStruct { foo, bar: 12 }) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
struct MyStruct {
    foo: i32,
    bar: i32,
}

fn foo(x: Result<MyStruct, ()>) {
    let bar: Result<MyStruct, ()> = Ok(MyStruct { foo: 1, bar: 2 });
    match bar {
        Ok(MyStruct { foo, bar: 12 }) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
struct MyStruct {
    foo: i32,
    bar: i32,
}

fn foo(x: Result<MyStruct, ()>) {
    let bar: Result<MyStruct, ()> = Ok(MyStruct { foo: 1, bar: 2 });
    $0if let Ok(MyStruct { foo, .. }) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
struct MyStruct {
    foo: i32,
    bar: i32,
}

fn foo(x: Result<MyStruct, ()>) {
    let bar: Result<MyStruct, ()> = Ok(MyStruct { foo: 1, bar: 2 });
    match bar {
        Ok(MyStruct { foo, .. }) => (),
        Err(_) => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
enum MyEnum {
    Foo(i32, i32),
    Bar { a: i32, b: i32 },
}

fn foo(x: Result<MyEnum, ()>) {
    let bar: Result<MyEnum, ()> = Ok(MyEnum::Foo(1, 2));
    $0if let Ok(MyEnum::Bar { a, b }) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
enum MyEnum {
    Foo(i32, i32),
    Bar { a: i32, b: i32 },
}

fn foo(x: Result<MyEnum, ()>) {
    let bar: Result<MyEnum, ()> = Ok(MyEnum::Foo(1, 2));
    match bar {
        Ok(MyEnum::Bar { a, b }) => (),
        _ => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_let_with_match_nested_ident() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    $0if let Ok(a @ 1..2) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    match bar {
        Ok(a @ 1..2) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    $0if let Ok(a) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    match bar {
        Ok(a) => (),
        Err(_) => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    $0if let Ok(a @ b @ c @ d) = bar {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    let bar: Result<i32, ()> = Ok(1);
    match bar {
        Ok(a @ b @ c @ d) => (),
        Err(_) => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_if_matches_with_match() {
        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result, matches
fn foo(x: Result<i32, ()>) {
    $0if matches!(x, Ok(a @ 1..2)) {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    match x {
        Ok(a@1..2) => (),
        _ => (),
    }
}
"#,
        );

        check_assist(
            replace_if_let_with_match,
            r#"
//- minicore: result, matches
fn foo(x: Result<i32, ()>) {
    $0if matches!(x, Ok(ref a)) {
        ()
    } else {
        ()
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    match x {
        Ok(ref a) => (),
        Err(_) => (),
    }
}
"#,
        );
    }

    #[test]
    fn test_replace_match_with_if_let_unwraps_simple_expressions() {
        check_assist(
            replace_match_with_if_let,
            r#"
impl VariantData {
    pub fn is_struct(&self) -> bool {
        $0match *self {
            VariantData::Struct(..) => true,
            _ => false,
        }
    }
}           "#,
            r#"
impl VariantData {
    pub fn is_struct(&self) -> bool {
        if let VariantData::Struct(..) = *self {
            true
        } else {
            false
        }
    }
}           "#,
        )
    }

    #[test]
    fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn foo() {
    $0match a {
        VariantData::Struct(..) => {
            bar(
                123
            )
        }
        _ => false,
    }
}           "#,
            r#"
fn foo() {
    if let VariantData::Struct(..) = a {
        bar(
            123
        )
    } else {
        false
    }
}           "#,
        )
    }

    #[test]
    fn replace_match_with_if_let_target() {
        check_assist_target(
            replace_match_with_if_let,
            r#"
impl VariantData {
    pub fn is_struct(&self) -> bool {
        $0match *self {
            VariantData::Struct(..) => true,
            _ => false,
        }
    }
}           "#,
            r#"match *self {
            VariantData::Struct(..) => true,
            _ => false,
        }"#,
        );
    }

    #[test]
    fn special_case_option_match_to_if_let() {
        check_assist(
            replace_match_with_if_let,
            r#"
//- minicore: option
fn foo(x: Option<i32>) {
    $0match x {
        Some(x) => println!("{}", x),
        None => println!("none"),
    }
}
"#,
            r#"
fn foo(x: Option<i32>) {
    if let Some(x) = x {
        println!("{}", x)
    } else {
        println!("none")
    }
}
"#,
        );
    }

    #[test]
    fn special_case_result_match_to_if_let() {
        check_assist(
            replace_match_with_if_let,
            r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
    $0match x {
        Ok(x) => println!("{}", x),
        Err(_) => println!("none"),
    }
}
"#,
            r#"
fn foo(x: Result<i32, ()>) {
    if let Ok(x) = x {
        println!("{}", x)
    } else {
        println!("none")
    }
}
"#,
        );
    }

    #[test]
    fn nested_indent_match_to_if_let() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    if true {
        $0match path.strip_prefix(root_path)
            .and(x)
        {
            Ok(rel_path) => Foo {
                x: 2
            }
            _ => Foo {
                x: 3
            },
        }
    }
}
"#,
            r#"
fn main() {
    if true {
        if let Ok(rel_path) = path.strip_prefix(root_path)
            .and(x)
        {
            Foo {
                x: 2
            }
        } else {
            Foo {
                x: 3
            }
        }
    }
}
"#,
        );

        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    if true {
        $0match path.strip_prefix(root_path)
            .and(x)
        {
            Ok(rel_path) => {
                let rel_path = RelativePathBuf::from_path(rel_path)
                    .ok()?;
                Some((*id, rel_path))
            }
            _ => {
                let _ = some_code()
                    .clone();
                None
            },
        }
    }
}
"#,
            r#"
fn main() {
    if true {
        if let Ok(rel_path) = path.strip_prefix(root_path)
            .and(x)
        {
            let rel_path = RelativePathBuf::from_path(rel_path)
                .ok()?;
            Some((*id, rel_path))
        } else {
            let _ = some_code()
                .clone();
            None
        }
    }
}
"#,
        );
    }

    #[test]
    fn replace_match_with_if_let_empty_wildcard_expr() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    $0match path.strip_prefix(root_path) {
        Ok(rel_path) => println!("{}", rel_path),
        _ => (),
    }
}
"#,
            r#"
fn main() {
    if let Ok(rel_path) = path.strip_prefix(root_path) {
        println!("{}", rel_path)
    }
}
"#,
        )
    }

    #[test]
    fn replace_match_with_if_let_number_body() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    $0match Ok(()) {
        Ok(()) => {},
        Err(_) => 0,
    }
}
"#,
            r#"
fn main() {
    if let Err(_) = Ok(()) {
        0
    }
}
"#,
        )
    }

    #[test]
    fn replace_match_with_if_let_exhaustive() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn print_source(def_source: ModuleSource) {
    match def_so$0urce {
        ModuleSource::SourceFile(..) => { println!("source file"); }
        ModuleSource::Module(..) => { println!("module"); }
    }
}
"#,
            r#"
fn print_source(def_source: ModuleSource) {
    if let ModuleSource::SourceFile(..) = def_source { println!("source file"); } else { println!("module"); }
}
"#,
        )
    }

    #[test]
    fn replace_match_with_if_let_prefer_name_bind() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn foo() {
    match $0Foo(0) {
        Foo(_) => (),
        Bar(bar) => println!("bar {}", bar),
    }
}
"#,
            r#"
fn foo() {
    if let Bar(bar) = Foo(0) {
        println!("bar {}", bar)
    }
}
"#,
        );
        check_assist(
            replace_match_with_if_let,
            r#"
fn foo() {
    match $0Foo(0) {
        Bar(bar) => println!("bar {}", bar),
        Foo(_) => (),
    }
}
"#,
            r#"
fn foo() {
    if let Bar(bar) = Foo(0) {
        println!("bar {}", bar)
    }
}
"#,
        );
    }

    #[test]
    fn replace_match_with_if_let_prefer_nonempty_body() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn foo() {
    match $0Ok(0) {
        Ok(value) => {},
        Err(err) => eprintln!("{}", err),
    }
}
"#,
            r#"
fn foo() {
    if let Err(err) = Ok(0) {
        eprintln!("{}", err)
    }
}
"#,
        );
        check_assist(
            replace_match_with_if_let,
            r#"
fn foo() {
    match $0Ok(0) {
        Err(err) => eprintln!("{}", err),
        Ok(value) => {},
    }
}
"#,
            r#"
fn foo() {
    if let Err(err) = Ok(0) {
        eprintln!("{}", err)
    }
}
"#,
        );
    }

    #[test]
    fn replace_match_with_if_let_rejects_double_name_bindings() {
        check_assist_not_applicable(
            replace_match_with_if_let,
            r#"
fn foo() {
    match $0Foo(0) {
        Foo(foo) => println!("bar {}", foo),
        Bar(bar) => println!("bar {}", bar),
    }
}
"#,
        );
    }

    #[test]
    fn test_replace_match_with_if_let_keeps_unsafe_block() {
        check_assist(
            replace_match_with_if_let,
            r#"
impl VariantData {
    pub fn is_struct(&self) -> bool {
        $0match *self {
            VariantData::Struct(..) => true,
            _ => unsafe { unreachable_unchecked() },
        }
    }
}           "#,
            r#"
impl VariantData {
    pub fn is_struct(&self) -> bool {
        if let VariantData::Struct(..) = *self {
            true
        } else {
            unsafe { unreachable_unchecked() }
        }
    }
}           "#,
        )
    }

    #[test]
    fn test_replace_match_with_if_let_forces_else() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 0 {
        0 => (),
        _ => code(),
    }
}
"#,
            r#"
fn main() {
    if let 0 = 0 {
        ()
    } else {
        code()
    }
}
"#,
        )
    }

    #[test]
    fn test_replace_match_with_if_bool() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 b {
        true => (),
        _ => code(),
    }
}
"#,
            r#"
fn main() {
    if b {
        ()
    } else {
        code()
    }
}
"#,
        );
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 b {
        false => code(),
        true => (),
    }
}
"#,
            r#"
fn main() {
    if !b {
        code()
    }
}
"#,
        );
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 b {
        false => (),
        true => code(),
    }
}
"#,
            r#"
fn main() {
    if b {
        code()
    }
}
"#,
        )
    }

    #[test]
    fn test_replace_match_with_if_let_with_simple_guard() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 Some(0) {
        Some(n) if n % 2 == 0 && n != 6 => (),
        _ => code(),
    }
}
"#,
            r#"
fn main() {
    if let Some(n) = Some(0) && (n % 2 == 0 && n != 6) {
        ()
    } else {
        code()
    }
}
"#,
        );

        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 Some(0) {
        Some(n) if n % 2 == 0 || n == 7 => (),
        _ => code(),
    }
}
"#,
            r#"
fn main() {
    if let Some(n) = Some(0) && (n % 2 == 0 || n == 7) {
        ()
    } else {
        code()
    }
}
"#,
        );
    }

    #[test]
    fn test_replace_match_with_if_let_with_if_let_guard() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 Some(0) {
        Some(n) if let Some(m) = n.checked_add(1) => (),
        _ => code(),
    }
}
"#,
            r#"
fn main() {
    if let Some(n) = Some(0) && let Some(m) = n.checked_add(1) {
        ()
    } else {
        code()
    }
}
"#,
        );

        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 Some(0) {
        Some(n) if let Some(m) = n.checked_add(1) && m > 5 => (),
        _ => code(),
    }
}
        "#,
            r#"
fn main() {
    if let Some(n) = Some(0) && let Some(m) = n.checked_add(1) && m > 5 {
        ()
    } else {
        code()
    }
}
        "#,
        );

        // what if the `let` expr is not the first one in the guard?
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 Some(0) {
        Some(n) if n > 5 && let Some(m) = n.checked_add(1) => (),
        _ => code(),
    }
}
        "#,
            r#"
fn main() {
    if let Some(n) = Some(0) && n > 5 && let Some(m) = n.checked_add(1) {
        ()
    } else {
        code()
    }
}
        "#,
        );
    }

    #[test]
    fn test_replace_match_multi_arms_with_if_let() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 Some(0) {
        Some(n) if n % 2 == 0 && n != 6 => (),
        Some(n) if valided(n) => foo(),
        _ => code(),
    }
}
"#,
            r#"
fn main() {
    if let Some(n) = Some(0) && (n % 2 == 0 && n != 6) {
        ()
    } else if let Some(n) = Some(0) && valided(n) {
        foo()
    } else {
        code()
    }
}
"#,
        );
    }

    #[test]
    fn test_replace_match_with_if_let_other_guards() {
        check_assist(
            replace_match_with_if_let,
            r#"
fn main() {
    match$0 Some(0) {
        Some(n) if n % 2 == 0 && n != 6 => (),
        _ if cond => foo(),
        _ => code(),
    }
}
"#,
            r#"
fn main() {
    if let Some(n) = Some(0) && (n % 2 == 0 && n != 6) {
        ()
    } else if cond {
        foo()
    } else {
        code()
    }
}
"#,
        );
    }

    #[test]
    fn test_replace_match_with_if_let_not_applicable_pat2_is_ident_pat() {
        check_assist_not_applicable(
            replace_match_with_if_let,
            r"
fn test(a: i32) {
    match$0 a {
        1 => code(),
        other => code(other),
    }
}
",
        )
    }
}
