ide_assists/handlers/
replace_let_with_if_let.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use ide_db::ty_filter::TryEnum;
use syntax::{
    AstNode, T,
    ast::{self, edit::IndentLevel, edit_in_place::Indent, syntax_factory::SyntaxFactory},
};

use crate::{AssistContext, AssistId, Assists};

// Assist: replace_let_with_if_let
//
// Replaces `let` with an `if let`.
//
// ```
// # enum Option<T> { Some(T), None }
//
// fn main(action: Action) {
//     $0let x = compute();
// }
//
// fn compute() -> Option<i32> { None }
// ```
// ->
// ```
// # enum Option<T> { Some(T), None }
//
// fn main(action: Action) {
//     if let Some(x) = compute() {
//     }
// }
//
// fn compute() -> Option<i32> { None }
// ```
pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
    let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
    let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?;
    let init = let_stmt.initializer()?;
    let original_pat = let_stmt.pat()?;

    let target = let_kw.text_range();
    acc.add(
        AssistId::refactor_rewrite("replace_let_with_if_let"),
        "Replace let with if let",
        target,
        |builder| {
            let mut editor = builder.make_editor(let_stmt.syntax());
            let make = SyntaxFactory::new();
            let ty = ctx.sema.type_of_expr(&init);
            let pat = if let_stmt.let_else().is_some() {
                // Do not add the wrapper type that implements `Try`,
                // since the statement already wraps the pattern.
                original_pat
            } else {
                let happy_variant = ty
                    .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
                    .map(|it| it.happy_case());
                match happy_variant {
                    None => original_pat,
                    Some(var_name) => {
                        make.tuple_struct_pat(make.ident_path(var_name), [original_pat]).into()
                    }
                }
            };

            let block = make.block_expr([], None);
            block.indent(IndentLevel::from_node(let_stmt.syntax()));
            let if_expr = make.expr_if(
                make.expr_let(pat, init).into(),
                block,
                let_stmt
                    .let_else()
                    .and_then(|let_else| let_else.block_expr().map(ast::ElseBranch::from)),
            );
            let if_stmt = make.expr_stmt(if_expr.into());

            editor.replace(let_stmt.syntax(), if_stmt.syntax());
            editor.add_mappings(make.finish_with_mappings());
            builder.add_file_edits(ctx.file_id(), editor);
        },
    )
}

#[cfg(test)]
mod tests {
    use crate::tests::check_assist;

    use super::*;

    #[test]
    fn replace_let_unknown_enum() {
        check_assist(
            replace_let_with_if_let,
            r"
enum E<T> { X(T), Y(T) }

fn main() {
    $0let x = E::X(92);
}
            ",
            r"
enum E<T> { X(T), Y(T) }

fn main() {
    if let x = E::X(92) {
    }
}
            ",
        )
    }

    #[test]
    fn replace_let_else() {
        check_assist(
            replace_let_with_if_let,
            r"
//- minicore: option
fn main() {
    let a = Some(1);
    $0let Some(_) = a else { unreachable!() };
}
            ",
            r"
fn main() {
    let a = Some(1);
    if let Some(_) = a {
    } else { unreachable!() }
}
            ",
        )
    }
}