diff options
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.rs | 257 |
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 @@ | |||
1 | use syntax::{ | ||
2 | ast::{ | ||
3 | self, | ||
4 | edit::{AstNodeEdit, IndentLevel}, | ||
5 | make, | ||
6 | }, | ||
7 | AstNode, | ||
8 | }; | ||
9 | |||
10 | use 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 | // ``` | ||
41 | pub(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)] | ||
85 | mod 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#" | ||
95 | impl 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#" | ||
105 | impl 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#" | ||
121 | fn foo() { | ||
122 | if <|>let VariantData::Struct(..) = a { | ||
123 | bar( | ||
124 | 123 | ||
125 | ) | ||
126 | } else { | ||
127 | false | ||
128 | } | ||
129 | } "#, | ||
130 | r#" | ||
131 | fn 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#" | ||
149 | impl 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#" | ||
171 | enum Option<T> { Some(T), None } | ||
172 | use Option::*; | ||
173 | |||
174 | fn foo(x: Option<i32>) { | ||
175 | <|>if let Some(x) = x { | ||
176 | println!("{}", x) | ||
177 | } else { | ||
178 | println!("none") | ||
179 | } | ||
180 | } | ||
181 | "#, | ||
182 | r#" | ||
183 | enum Option<T> { Some(T), None } | ||
184 | use Option::*; | ||
185 | |||
186 | fn 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#" | ||
201 | enum Result<T, E> { Ok(T), Err(E) } | ||
202 | use Result::*; | ||
203 | |||
204 | fn foo(x: Result<i32, ()>) { | ||
205 | <|>if let Ok(x) = x { | ||
206 | println!("{}", x) | ||
207 | } else { | ||
208 | println!("none") | ||
209 | } | ||
210 | } | ||
211 | "#, | ||
212 | r#" | ||
213 | enum Result<T, E> { Ok(T), Err(E) } | ||
214 | use Result::*; | ||
215 | |||
216 | fn 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#" | ||
231 | fn 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#" | ||
243 | fn 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 | } | ||