aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/replace_if_let_with_match.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers/replace_if_let_with_match.rs')
-rw-r--r--crates/assists/src/handlers/replace_if_let_with_match.rs257
1 files changed, 257 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/replace_if_let_with_match.rs b/crates/assists/src/handlers/replace_if_let_with_match.rs
new file mode 100644
index 000000000..79097621e
--- /dev/null
+++ b/crates/assists/src/handlers/replace_if_let_with_match.rs
@@ -0,0 +1,257 @@
1use syntax::{
2 ast::{
3 self,
4 edit::{AstNodeEdit, IndentLevel},
5 make,
6 },
7 AstNode,
8};
9
10use crate::{
11 utils::{unwrap_trivial_block, TryEnum},
12 AssistContext, AssistId, AssistKind, Assists,
13};
14
15// Assist: replace_if_let_with_match
16//
17// Replaces `if let` with an else branch with a `match` expression.
18//
19// ```
20// enum Action { Move { distance: u32 }, Stop }
21//
22// fn handle(action: Action) {
23// <|>if let Action::Move { distance } = action {
24// foo(distance)
25// } else {
26// bar()
27// }
28// }
29// ```
30// ->
31// ```
32// enum Action { Move { distance: u32 }, Stop }
33//
34// fn handle(action: Action) {
35// match action {
36// Action::Move { distance } => foo(distance),
37// _ => bar(),
38// }
39// }
40// ```
41pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
43 let cond = if_expr.condition()?;
44 let pat = cond.pat()?;
45 let expr = cond.expr()?;
46 let then_block = if_expr.then_branch()?;
47 let else_block = match if_expr.else_branch()? {
48 ast::ElseBranch::Block(it) => it,
49 ast::ElseBranch::IfExpr(_) => return None,
50 };
51
52 let target = if_expr.syntax().text_range();
53 acc.add(
54 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
55 "Replace with match",
56 target,
57 move |edit| {
58 let match_expr = {
59 let then_arm = {
60 let then_block = then_block.reset_indent().indent(IndentLevel(1));
61 let then_expr = unwrap_trivial_block(then_block);
62 make::match_arm(vec![pat.clone()], then_expr)
63 };
64 let else_arm = {
65 let pattern = ctx
66 .sema
67 .type_of_pat(&pat)
68 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
69 .map(|it| it.sad_pattern())
70 .unwrap_or_else(|| make::wildcard_pat().into());
71 let else_expr = unwrap_trivial_block(else_block);
72 make::match_arm(vec![pattern], else_expr)
73 };
74 let match_expr =
75 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
76 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
77 };
78
79 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
80 },
81 )
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 use crate::tests::{check_assist, check_assist_target};
89
90 #[test]
91 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
92 check_assist(
93 replace_if_let_with_match,
94 r#"
95impl VariantData {
96 pub fn is_struct(&self) -> bool {
97 if <|>let VariantData::Struct(..) = *self {
98 true
99 } else {
100 false
101 }
102 }
103} "#,
104 r#"
105impl VariantData {
106 pub fn is_struct(&self) -> bool {
107 match *self {
108 VariantData::Struct(..) => true,
109 _ => false,
110 }
111 }
112} "#,
113 )
114 }
115
116 #[test]
117 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
118 check_assist(
119 replace_if_let_with_match,
120 r#"
121fn foo() {
122 if <|>let VariantData::Struct(..) = a {
123 bar(
124 123
125 )
126 } else {
127 false
128 }
129} "#,
130 r#"
131fn foo() {
132 match a {
133 VariantData::Struct(..) => {
134 bar(
135 123
136 )
137 }
138 _ => false,
139 }
140} "#,
141 )
142 }
143
144 #[test]
145 fn replace_if_let_with_match_target() {
146 check_assist_target(
147 replace_if_let_with_match,
148 r#"
149impl VariantData {
150 pub fn is_struct(&self) -> bool {
151 if <|>let VariantData::Struct(..) = *self {
152 true
153 } else {
154 false
155 }
156 }
157} "#,
158 "if let VariantData::Struct(..) = *self {
159 true
160 } else {
161 false
162 }",
163 );
164 }
165
166 #[test]
167 fn special_case_option() {
168 check_assist(
169 replace_if_let_with_match,
170 r#"
171enum Option<T> { Some(T), None }
172use Option::*;
173
174fn foo(x: Option<i32>) {
175 <|>if let Some(x) = x {
176 println!("{}", x)
177 } else {
178 println!("none")
179 }
180}
181 "#,
182 r#"
183enum Option<T> { Some(T), None }
184use Option::*;
185
186fn foo(x: Option<i32>) {
187 match x {
188 Some(x) => println!("{}", x),
189 None => println!("none"),
190 }
191}
192 "#,
193 );
194 }
195
196 #[test]
197 fn special_case_result() {
198 check_assist(
199 replace_if_let_with_match,
200 r#"
201enum Result<T, E> { Ok(T), Err(E) }
202use Result::*;
203
204fn foo(x: Result<i32, ()>) {
205 <|>if let Ok(x) = x {
206 println!("{}", x)
207 } else {
208 println!("none")
209 }
210}
211 "#,
212 r#"
213enum Result<T, E> { Ok(T), Err(E) }
214use Result::*;
215
216fn foo(x: Result<i32, ()>) {
217 match x {
218 Ok(x) => println!("{}", x),
219 Err(_) => println!("none"),
220 }
221}
222 "#,
223 );
224 }
225
226 #[test]
227 fn nested_indent() {
228 check_assist(
229 replace_if_let_with_match,
230 r#"
231fn main() {
232 if true {
233 <|>if let Ok(rel_path) = path.strip_prefix(root_path) {
234 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
235 Some((*id, rel_path))
236 } else {
237 None
238 }
239 }
240}
241"#,
242 r#"
243fn main() {
244 if true {
245 match path.strip_prefix(root_path) {
246 Ok(rel_path) => {
247 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
248 Some((*id, rel_path))
249 }
250 _ => None,
251 }
252 }
253}
254"#,
255 )
256 }
257}