ide_assists/handlers/
replace_let_with_if_let.rs1use ide_db::ty_filter::TryEnum;
2use syntax::{
3 AstNode, T,
4 ast::{
5 self,
6 edit::{AstNodeEdit, IndentLevel},
7 syntax_factory::SyntaxFactory,
8 },
9};
10
11use crate::{AssistContext, AssistId, Assists};
12
13pub(crate) fn replace_let_with_if_let(
38 acc: &mut Assists,
39 ctx: &AssistContext<'_, '_>,
40) -> Option<()> {
41 let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
42 let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?;
43 let init = let_stmt.initializer()?;
44 let original_pat = let_stmt.pat()?;
45
46 let target = let_kw.text_range();
47 acc.add(
48 AssistId::refactor_rewrite("replace_let_with_if_let"),
49 "Replace let with if let",
50 target,
51 |builder| {
52 let editor = builder.make_editor(let_stmt.syntax());
53 let make = editor.make();
54 let ty = ctx.sema.type_of_expr(&init);
55 let pat = if let_stmt.let_else().is_some() {
56 original_pat
59 } else {
60 let happy_variant = ty
61 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))
62 .map(|it| it.happy_case());
63 match happy_variant {
64 None => original_pat,
65 Some(var_name) => {
66 make.tuple_struct_pat(make.ident_path(var_name), [original_pat]).into()
67 }
68 }
69 };
70 let init_expr =
71 if let_expr_needs_paren(&init) { make.expr_paren(init).into() } else { init };
72
73 let block = make.block_expr([], None);
74 let block = block.indent(IndentLevel::from_node(let_stmt.syntax()));
75 let if_expr = make.expr_if(
76 make.expr_let(pat, init_expr).into(),
77 block,
78 let_stmt
79 .let_else()
80 .and_then(|let_else| let_else.block_expr().map(ast::ElseBranch::from)),
81 );
82 let if_stmt = make.expr_stmt(if_expr.into());
83
84 editor.replace(let_stmt.syntax(), if_stmt.syntax());
85 builder.add_file_edits(ctx.vfs_file_id(), editor);
86 },
87 )
88}
89
90fn let_expr_needs_paren(expr: &ast::Expr) -> bool {
91 let make = SyntaxFactory::without_mappings();
92 let fake_expr_let = make.expr_let(make.tuple_pat(None).into(), make.expr_unit());
93 let fake_if = make.expr_if(fake_expr_let.into(), make.expr_empty_block(), None);
94 let Some(ast::Expr::LetExpr(fake_expr_let)) = fake_if.condition() else {
95 stdx::never!();
96 return false;
97 };
98 let Some(fake_expr) = fake_expr_let.expr() else {
99 stdx::never!();
100 return false;
101 };
102 expr.needs_parens_in_place_of(fake_expr_let.syntax(), fake_expr.syntax())
103}
104
105#[cfg(test)]
106mod tests {
107 use crate::tests::check_assist;
108
109 use super::*;
110
111 #[test]
112 fn replace_let_try_enum_ref() {
113 check_assist(
114 replace_let_with_if_let,
115 r"
116//- minicore: option
117fn main(action: Action) {
118 $0let x = compute();
119}
120
121fn compute() -> &'static Option<i32> { &None }
122 ",
123 r"
124fn main(action: Action) {
125 if let Some(x) = compute() {
126 }
127}
128
129fn compute() -> &'static Option<i32> { &None }
130 ",
131 )
132 }
133
134 #[test]
135 fn replace_let_unknown_enum() {
136 check_assist(
137 replace_let_with_if_let,
138 r"
139enum E<T> { X(T), Y(T) }
140
141fn main() {
142 $0let x = E::X(92);
143}
144 ",
145 r"
146enum E<T> { X(T), Y(T) }
147
148fn main() {
149 if let x = E::X(92) {
150 }
151}
152 ",
153 )
154 }
155
156 #[test]
157 fn replace_let_logic_and() {
158 check_assist(
159 replace_let_with_if_let,
160 r"
161fn main() {
162 $0let x = true && false;
163}
164 ",
165 r"
166fn main() {
167 if let x = (true && false) {
168 }
169}
170 ",
171 )
172 }
173
174 #[test]
175 fn replace_let_logic_or() {
176 check_assist(
177 replace_let_with_if_let,
178 r"
179fn main() {
180 $0let x = true || false;
181}
182 ",
183 r"
184fn main() {
185 if let x = (true || false) {
186 }
187}
188 ",
189 )
190 }
191
192 #[test]
193 fn replace_let_record_expr() {
194 check_assist(
195 replace_let_with_if_let,
196 r"
197fn main() {
198 $0let x = Foo { x };
199}
200 ",
201 r"
202fn main() {
203 if let x = (Foo { x }) {
204 }
205}
206 ",
207 )
208 }
209
210 #[test]
211 fn replace_let_else() {
212 check_assist(
213 replace_let_with_if_let,
214 r"
215//- minicore: option
216fn main() {
217 let a = Some(1);
218 $0let Some(_) = a else { unreachable!() };
219}
220 ",
221 r"
222fn main() {
223 let a = Some(1);
224 if let Some(_) = a {
225 } else { unreachable!() }
226}
227 ",
228 )
229 }
230}