ide_assists/handlers/
replace_let_with_if_let.rs1use 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
9pub(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 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}