aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
diff options
context:
space:
mode:
authorChetan Khilosiya <[email protected]>2021-02-22 18:47:48 +0000
committerChetan Khilosiya <[email protected]>2021-02-22 19:29:16 +0000
commite4756cb4f6e66097638b9d101589358976be2ba8 (patch)
treeb6ca0ae6b45b57834476ae0f9985cec3a6bd9090 /crates/ide_assists/src/handlers/replace_if_let_with_match.rs
parent8687053b118f47ce1a4962d0baa19b22d40d2758 (diff)
7526: Rename crate assists to ide_assists.
Diffstat (limited to 'crates/ide_assists/src/handlers/replace_if_let_with_match.rs')
-rw-r--r--crates/ide_assists/src/handlers/replace_if_let_with_match.rs599
1 files changed, 599 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/replace_if_let_with_match.rs b/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
new file mode 100644
index 000000000..aee880625
--- /dev/null
+++ b/crates/ide_assists/src/handlers/replace_if_let_with_match.rs
@@ -0,0 +1,599 @@
1use std::iter;
2
3use ide_db::{ty_filter::TryEnum, RootDatabase};
4use syntax::{
5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make,
9 },
10 AstNode,
11};
12
13use crate::{
14 utils::{does_pat_match_variant, unwrap_trivial_block},
15 AssistContext, AssistId, AssistKind, Assists,
16};
17
18// Assist: replace_if_let_with_match
19//
20// Replaces `if let` with an else branch with a `match` expression.
21//
22// ```
23// enum Action { Move { distance: u32 }, Stop }
24//
25// fn handle(action: Action) {
26// $0if let Action::Move { distance } = action {
27// foo(distance)
28// } else {
29// bar()
30// }
31// }
32// ```
33// ->
34// ```
35// enum Action { Move { distance: u32 }, Stop }
36//
37// fn handle(action: Action) {
38// match action {
39// Action::Move { distance } => foo(distance),
40// _ => bar(),
41// }
42// }
43// ```
44pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
45 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
46 let cond = if_expr.condition()?;
47 let pat = cond.pat()?;
48 let expr = cond.expr()?;
49 let then_block = if_expr.then_branch()?;
50 let else_block = match if_expr.else_branch()? {
51 ast::ElseBranch::Block(it) => it,
52 ast::ElseBranch::IfExpr(_) => return None,
53 };
54
55 let target = if_expr.syntax().text_range();
56 acc.add(
57 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
58 "Replace with match",
59 target,
60 move |edit| {
61 let match_expr = {
62 let then_arm = {
63 let then_block = then_block.reset_indent().indent(IndentLevel(1));
64 let then_expr = unwrap_trivial_block(then_block);
65 make::match_arm(vec![pat.clone()], then_expr)
66 };
67 let else_arm = {
68 let pattern = ctx
69 .sema
70 .type_of_pat(&pat)
71 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
72 .map(|it| {
73 if does_pat_match_variant(&pat, &it.sad_pattern()) {
74 it.happy_pattern()
75 } else {
76 it.sad_pattern()
77 }
78 })
79 .unwrap_or_else(|| make::wildcard_pat().into());
80 let else_expr = unwrap_trivial_block(else_block);
81 make::match_arm(vec![pattern], else_expr)
82 };
83 let match_expr =
84 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
85 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
86 };
87
88 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
89 },
90 )
91}
92
93// Assist: replace_match_with_if_let
94//
95// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
96//
97// ```
98// enum Action { Move { distance: u32 }, Stop }
99//
100// fn handle(action: Action) {
101// $0match action {
102// Action::Move { distance } => foo(distance),
103// _ => bar(),
104// }
105// }
106// ```
107// ->
108// ```
109// enum Action { Move { distance: u32 }, Stop }
110//
111// fn handle(action: Action) {
112// if let Action::Move { distance } = action {
113// foo(distance)
114// } else {
115// bar()
116// }
117// }
118// ```
119pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
120 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
121 let mut arms = match_expr.match_arm_list()?.arms();
122 let first_arm = arms.next()?;
123 let second_arm = arms.next()?;
124 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
125 return None;
126 }
127 let condition_expr = match_expr.expr()?;
128 let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
129 {
130 (second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
131 } else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
132 (first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
133 } else {
134 return None;
135 };
136
137 let target = match_expr.syntax().text_range();
138 acc.add(
139 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
140 "Replace with if let",
141 target,
142 move |edit| {
143 let condition = make::condition(condition_expr, Some(if_let_pat));
144 let then_block = match then_expr.reset_indent() {
145 ast::Expr::BlockExpr(block) => block,
146 expr => make::block_expr(iter::empty(), Some(expr)),
147 };
148 let else_expr = match else_expr {
149 ast::Expr::BlockExpr(block)
150 if block.statements().count() == 0 && block.tail_expr().is_none() =>
151 {
152 None
153 }
154 ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
155 expr => Some(expr),
156 };
157 let if_let_expr = make::expr_if(
158 condition,
159 then_block,
160 else_expr.map(|else_expr| {
161 ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
162 }),
163 )
164 .indent(IndentLevel::from_node(match_expr.syntax()));
165
166 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
167 },
168 )
169}
170
171fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
172 sema.type_of_pat(&pat)
173 .and_then(|ty| TryEnum::from_ty(sema, &ty))
174 .map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
175 .unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 use crate::tests::{check_assist, check_assist_target};
183
184 #[test]
185 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
186 check_assist(
187 replace_if_let_with_match,
188 r#"
189impl VariantData {
190 pub fn is_struct(&self) -> bool {
191 if $0let VariantData::Struct(..) = *self {
192 true
193 } else {
194 false
195 }
196 }
197} "#,
198 r#"
199impl VariantData {
200 pub fn is_struct(&self) -> bool {
201 match *self {
202 VariantData::Struct(..) => true,
203 _ => false,
204 }
205 }
206} "#,
207 )
208 }
209
210 #[test]
211 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
212 check_assist(
213 replace_if_let_with_match,
214 r#"
215fn foo() {
216 if $0let VariantData::Struct(..) = a {
217 bar(
218 123
219 )
220 } else {
221 false
222 }
223} "#,
224 r#"
225fn foo() {
226 match a {
227 VariantData::Struct(..) => {
228 bar(
229 123
230 )
231 }
232 _ => false,
233 }
234} "#,
235 )
236 }
237
238 #[test]
239 fn replace_if_let_with_match_target() {
240 check_assist_target(
241 replace_if_let_with_match,
242 r#"
243impl VariantData {
244 pub fn is_struct(&self) -> bool {
245 if $0let VariantData::Struct(..) = *self {
246 true
247 } else {
248 false
249 }
250 }
251} "#,
252 "if let VariantData::Struct(..) = *self {
253 true
254 } else {
255 false
256 }",
257 );
258 }
259
260 #[test]
261 fn special_case_option() {
262 check_assist(
263 replace_if_let_with_match,
264 r#"
265enum Option<T> { Some(T), None }
266use Option::*;
267
268fn foo(x: Option<i32>) {
269 $0if let Some(x) = x {
270 println!("{}", x)
271 } else {
272 println!("none")
273 }
274}
275 "#,
276 r#"
277enum Option<T> { Some(T), None }
278use Option::*;
279
280fn foo(x: Option<i32>) {
281 match x {
282 Some(x) => println!("{}", x),
283 None => println!("none"),
284 }
285}
286 "#,
287 );
288 }
289
290 #[test]
291 fn special_case_inverted_option() {
292 check_assist(
293 replace_if_let_with_match,
294 r#"
295enum Option<T> { Some(T), None }
296use Option::*;
297
298fn foo(x: Option<i32>) {
299 $0if let None = x {
300 println!("none")
301 } else {
302 println!("some")
303 }
304}
305 "#,
306 r#"
307enum Option<T> { Some(T), None }
308use Option::*;
309
310fn foo(x: Option<i32>) {
311 match x {
312 None => println!("none"),
313 Some(_) => println!("some"),
314 }
315}
316 "#,
317 );
318 }
319
320 #[test]
321 fn special_case_result() {
322 check_assist(
323 replace_if_let_with_match,
324 r#"
325enum Result<T, E> { Ok(T), Err(E) }
326use Result::*;
327
328fn foo(x: Result<i32, ()>) {
329 $0if let Ok(x) = x {
330 println!("{}", x)
331 } else {
332 println!("none")
333 }
334}
335 "#,
336 r#"
337enum Result<T, E> { Ok(T), Err(E) }
338use Result::*;
339
340fn foo(x: Result<i32, ()>) {
341 match x {
342 Ok(x) => println!("{}", x),
343 Err(_) => println!("none"),
344 }
345}
346 "#,
347 );
348 }
349
350 #[test]
351 fn special_case_inverted_result() {
352 check_assist(
353 replace_if_let_with_match,
354 r#"
355enum Result<T, E> { Ok(T), Err(E) }
356use Result::*;
357
358fn foo(x: Result<i32, ()>) {
359 $0if let Err(x) = x {
360 println!("{}", x)
361 } else {
362 println!("ok")
363 }
364}
365 "#,
366 r#"
367enum Result<T, E> { Ok(T), Err(E) }
368use Result::*;
369
370fn foo(x: Result<i32, ()>) {
371 match x {
372 Err(x) => println!("{}", x),
373 Ok(_) => println!("ok"),
374 }
375}
376 "#,
377 );
378 }
379
380 #[test]
381 fn nested_indent() {
382 check_assist(
383 replace_if_let_with_match,
384 r#"
385fn main() {
386 if true {
387 $0if let Ok(rel_path) = path.strip_prefix(root_path) {
388 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
389 Some((*id, rel_path))
390 } else {
391 None
392 }
393 }
394}
395"#,
396 r#"
397fn main() {
398 if true {
399 match path.strip_prefix(root_path) {
400 Ok(rel_path) => {
401 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
402 Some((*id, rel_path))
403 }
404 _ => None,
405 }
406 }
407}
408"#,
409 )
410 }
411
412 #[test]
413 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
414 check_assist(
415 replace_match_with_if_let,
416 r#"
417impl VariantData {
418 pub fn is_struct(&self) -> bool {
419 $0match *self {
420 VariantData::Struct(..) => true,
421 _ => false,
422 }
423 }
424} "#,
425 r#"
426impl VariantData {
427 pub fn is_struct(&self) -> bool {
428 if let VariantData::Struct(..) = *self {
429 true
430 } else {
431 false
432 }
433 }
434} "#,
435 )
436 }
437
438 #[test]
439 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
440 check_assist(
441 replace_match_with_if_let,
442 r#"
443fn foo() {
444 $0match a {
445 VariantData::Struct(..) => {
446 bar(
447 123
448 )
449 }
450 _ => false,
451 }
452} "#,
453 r#"
454fn foo() {
455 if let VariantData::Struct(..) = a {
456 bar(
457 123
458 )
459 } else {
460 false
461 }
462} "#,
463 )
464 }
465
466 #[test]
467 fn replace_match_with_if_let_target() {
468 check_assist_target(
469 replace_match_with_if_let,
470 r#"
471impl VariantData {
472 pub fn is_struct(&self) -> bool {
473 $0match *self {
474 VariantData::Struct(..) => true,
475 _ => false,
476 }
477 }
478} "#,
479 r#"match *self {
480 VariantData::Struct(..) => true,
481 _ => false,
482 }"#,
483 );
484 }
485
486 #[test]
487 fn special_case_option_match_to_if_let() {
488 check_assist(
489 replace_match_with_if_let,
490 r#"
491enum Option<T> { Some(T), None }
492use Option::*;
493
494fn foo(x: Option<i32>) {
495 $0match x {
496 Some(x) => println!("{}", x),
497 None => println!("none"),
498 }
499}
500 "#,
501 r#"
502enum Option<T> { Some(T), None }
503use Option::*;
504
505fn foo(x: Option<i32>) {
506 if let Some(x) = x {
507 println!("{}", x)
508 } else {
509 println!("none")
510 }
511}
512 "#,
513 );
514 }
515
516 #[test]
517 fn special_case_result_match_to_if_let() {
518 check_assist(
519 replace_match_with_if_let,
520 r#"
521enum Result<T, E> { Ok(T), Err(E) }
522use Result::*;
523
524fn foo(x: Result<i32, ()>) {
525 $0match x {
526 Ok(x) => println!("{}", x),
527 Err(_) => println!("none"),
528 }
529}
530 "#,
531 r#"
532enum Result<T, E> { Ok(T), Err(E) }
533use Result::*;
534
535fn foo(x: Result<i32, ()>) {
536 if let Ok(x) = x {
537 println!("{}", x)
538 } else {
539 println!("none")
540 }
541}
542 "#,
543 );
544 }
545
546 #[test]
547 fn nested_indent_match_to_if_let() {
548 check_assist(
549 replace_match_with_if_let,
550 r#"
551fn main() {
552 if true {
553 $0match path.strip_prefix(root_path) {
554 Ok(rel_path) => {
555 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
556 Some((*id, rel_path))
557 }
558 _ => None,
559 }
560 }
561}
562"#,
563 r#"
564fn main() {
565 if true {
566 if let Ok(rel_path) = path.strip_prefix(root_path) {
567 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
568 Some((*id, rel_path))
569 } else {
570 None
571 }
572 }
573}
574"#,
575 )
576 }
577
578 #[test]
579 fn replace_match_with_if_let_empty_wildcard_expr() {
580 check_assist(
581 replace_match_with_if_let,
582 r#"
583fn main() {
584 $0match path.strip_prefix(root_path) {
585 Ok(rel_path) => println!("{}", rel_path),
586 _ => (),
587 }
588}
589"#,
590 r#"
591fn main() {
592 if let Ok(rel_path) = path.strip_prefix(root_path) {
593 println!("{}", rel_path)
594 }
595}
596"#,
597 )
598 }
599}