ide_assists/handlers/
replace_let_with_if_let.rs

1use ide_db::ty_filter::TryEnum;
2use syntax::{
3    AstNode, T,
4    ast::{self, edit::IndentLevel, edit_in_place::Indent, syntax_factory::SyntaxFactory},
5};
6
7use crate::{AssistContext, AssistId, Assists};
8
9// Assist: replace_let_with_if_let
10//
11// Replaces `let` with an `if let`.
12//
13// ```
14// # enum Option<T> { Some(T), None }
15//
16// fn main(action: Action) {
17//     $0let x = compute();
18// }
19//
20// fn compute() -> Option<i32> { None }
21// ```
22// ->
23// ```
24// # enum Option<T> { Some(T), None }
25//
26// fn main(action: Action) {
27//     if let Some(x) = compute() {
28//     }
29// }
30//
31// fn compute() -> Option<i32> { None }
32// ```
33pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
34    let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
35    let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?;
36    let init = let_stmt.initializer()?;
37    let original_pat = let_stmt.pat()?;
38
39    let target = let_kw.text_range();
40    acc.add(
41        AssistId::refactor_rewrite("replace_let_with_if_let"),
42        "Replace let with if let",
43        target,
44        |builder| {
45            let mut editor = builder.make_editor(let_stmt.syntax());
46            let make = SyntaxFactory::with_mappings();
47            let ty = ctx.sema.type_of_expr(&init);
48            let pat = if let_stmt.let_else().is_some() {
49                // Do not add the wrapper type that implements `Try`,
50                // since the statement already wraps the pattern.
51                original_pat
52            } else {
53                let happy_variant = ty
54                    .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
55                    .map(|it| it.happy_case());
56                match happy_variant {
57                    None => original_pat,
58                    Some(var_name) => {
59                        make.tuple_struct_pat(make.ident_path(var_name), [original_pat]).into()
60                    }
61                }
62            };
63            let init_expr =
64                if let_expr_needs_paren(&init) { make.expr_paren(init).into() } else { init };
65
66            let block = make.block_expr([], None);
67            block.indent(IndentLevel::from_node(let_stmt.syntax()));
68            let if_expr = make.expr_if(
69                make.expr_let(pat, init_expr).into(),
70                block,
71                let_stmt
72                    .let_else()
73                    .and_then(|let_else| let_else.block_expr().map(ast::ElseBranch::from)),
74            );
75            let if_stmt = make.expr_stmt(if_expr.into());
76
77            editor.replace(let_stmt.syntax(), if_stmt.syntax());
78            editor.add_mappings(make.finish_with_mappings());
79            builder.add_file_edits(ctx.vfs_file_id(), editor);
80        },
81    )
82}
83
84fn let_expr_needs_paren(expr: &ast::Expr) -> bool {
85    let fake_expr_let =
86        ast::make::expr_let(ast::make::tuple_pat(None).into(), ast::make::ext::expr_unit());
87    let Some(fake_expr) = fake_expr_let.expr() else {
88        stdx::never!();
89        return false;
90    };
91    expr.needs_parens_in_place_of(fake_expr_let.syntax(), fake_expr.syntax())
92}
93
94#[cfg(test)]
95mod tests {
96    use crate::tests::check_assist;
97
98    use super::*;
99
100    #[test]
101    fn replace_let_unknown_enum() {
102        check_assist(
103            replace_let_with_if_let,
104            r"
105enum E<T> { X(T), Y(T) }
106
107fn main() {
108    $0let x = E::X(92);
109}
110            ",
111            r"
112enum E<T> { X(T), Y(T) }
113
114fn main() {
115    if let x = E::X(92) {
116    }
117}
118            ",
119        )
120    }
121
122    #[test]
123    fn replace_let_logic_and() {
124        check_assist(
125            replace_let_with_if_let,
126            r"
127fn main() {
128    $0let x = true && false;
129}
130            ",
131            r"
132fn main() {
133    if let x = (true && false) {
134    }
135}
136            ",
137        )
138    }
139
140    #[test]
141    fn replace_let_logic_or() {
142        check_assist(
143            replace_let_with_if_let,
144            r"
145fn main() {
146    $0let x = true || false;
147}
148            ",
149            r"
150fn main() {
151    if let x = (true || false) {
152    }
153}
154            ",
155        )
156    }
157
158    #[test]
159    fn replace_let_else() {
160        check_assist(
161            replace_let_with_if_let,
162            r"
163//- minicore: option
164fn main() {
165    let a = Some(1);
166    $0let Some(_) = a else { unreachable!() };
167}
168            ",
169            r"
170fn main() {
171    let a = Some(1);
172    if let Some(_) = a {
173    } else { unreachable!() }
174}
175            ",
176        )
177    }
178}