]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/move_guard.rs
Don't add pattern if there is a catch all afterwards
[rust.git] / crates / ide_assists / src / handlers / move_guard.rs
1 use syntax::{
2     ast::{
3         edit::AstNodeEdit, make, AstNode, BlockExpr, Condition, ElseBranch, Expr, IfExpr, MatchArm,
4         Pat,
5     },
6     SyntaxKind::WHITESPACE,
7 };
8
9 use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11 // Assist: move_guard_to_arm_body
12 //
13 // Moves match guard into match arm body.
14 //
15 // ```
16 // enum Action { Move { distance: u32 }, Stop }
17 //
18 // fn handle(action: Action) {
19 //     match action {
20 //         Action::Move { distance } $0if distance > 10 => foo(),
21 //         _ => (),
22 //     }
23 // }
24 // ```
25 // ->
26 // ```
27 // enum Action { Move { distance: u32 }, Stop }
28 //
29 // fn handle(action: Action) {
30 //     match action {
31 //         Action::Move { distance } => if distance > 10 {
32 //             foo()
33 //         },
34 //         _ => (),
35 //     }
36 // }
37 // ```
38 pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39     let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
40     let guard = match_arm.guard()?;
41     if ctx.offset() > guard.syntax().text_range().end() {
42         cov_mark::hit!(move_guard_unapplicable_in_arm_body);
43         return None;
44     }
45     let space_before_guard = guard.syntax().prev_sibling_or_token();
46
47     // FIXME: support `if let` guards too
48     if guard.let_token().is_some() {
49         return None;
50     }
51     let guard_condition = guard.expr()?;
52     let arm_expr = match_arm.expr()?;
53     let if_expr = make::expr_if(
54         make::condition(guard_condition, None),
55         make::block_expr(None, Some(arm_expr.clone())),
56         None,
57     )
58     .indent(arm_expr.indent_level());
59
60     let target = guard.syntax().text_range();
61     acc.add(
62         AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
63         "Move guard to arm body",
64         target,
65         |edit| {
66             match space_before_guard {
67                 Some(element) if element.kind() == WHITESPACE => {
68                     edit.delete(element.text_range());
69                 }
70                 _ => (),
71             };
72
73             edit.delete(guard.syntax().text_range());
74             edit.replace_ast(arm_expr, if_expr);
75         },
76     )
77 }
78
79 // Assist: move_arm_cond_to_match_guard
80 //
81 // Moves if expression from match arm body into a guard.
82 //
83 // ```
84 // enum Action { Move { distance: u32 }, Stop }
85 //
86 // fn handle(action: Action) {
87 //     match action {
88 //         Action::Move { distance } => $0if distance > 10 { foo() },
89 //         _ => (),
90 //     }
91 // }
92 // ```
93 // ->
94 // ```
95 // enum Action { Move { distance: u32 }, Stop }
96 //
97 // fn handle(action: Action) {
98 //     match action {
99 //         Action::Move { distance } if distance > 10 => foo(),
100 //         _ => (),
101 //     }
102 // }
103 // ```
104 pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
105     let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
106     let match_pat = match_arm.pat()?;
107     let arm_body = match_arm.expr()?;
108
109     let mut replace_node = None;
110     let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone()).or_else(|| {
111         let block_expr = BlockExpr::cast(arm_body.syntax().clone())?;
112         if let Expr::IfExpr(e) = block_expr.tail_expr()? {
113             replace_node = Some(block_expr.syntax().clone());
114             Some(e)
115         } else {
116             None
117         }
118     })?;
119     let replace_node = replace_node.unwrap_or_else(|| if_expr.syntax().clone());
120     let needs_dedent = replace_node != *if_expr.syntax();
121     let (conds_blocks, tail) = parse_if_chain(if_expr)?;
122
123     acc.add(
124         AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
125         "Move condition to match guard",
126         replace_node.text_range(),
127         |edit| {
128             edit.delete(match_arm.syntax().text_range());
129             // Dedent if if_expr is in a BlockExpr
130             let dedent = if needs_dedent {
131                 cov_mark::hit!(move_guard_ifelse_in_block);
132                 1
133             } else {
134                 cov_mark::hit!(move_guard_ifelse_else_block);
135                 0
136             };
137             let then_arm_end = match_arm.syntax().text_range().end();
138             let indent_level = match_arm.indent_level();
139             let spaces = "    ".repeat(indent_level.0 as _);
140
141             let mut first = true;
142             for (cond, block) in conds_blocks {
143                 if !first {
144                     edit.insert(then_arm_end, format!("\n{}", spaces));
145                 } else {
146                     first = false;
147                 }
148                 let guard = format!("{} if {} => ", match_pat, cond.syntax().text());
149                 edit.insert(then_arm_end, guard);
150                 let only_expr = block.statements().next().is_none();
151                 match &block.tail_expr() {
152                     Some(then_expr) if only_expr => {
153                         edit.insert(then_arm_end, then_expr.syntax().text());
154                         edit.insert(then_arm_end, ",");
155                     }
156                     _ => {
157                         let to_insert = block.dedent(dedent.into()).syntax().text();
158                         edit.insert(then_arm_end, to_insert)
159                     }
160                 }
161             }
162             if let Some(e) = tail {
163                 cov_mark::hit!(move_guard_ifelse_else_tail);
164                 let guard = format!("\n{}{} => ", spaces, match_pat);
165                 edit.insert(then_arm_end, guard);
166                 let only_expr = e.statements().next().is_none();
167                 match &e.tail_expr() {
168                     Some(expr) if only_expr => {
169                         cov_mark::hit!(move_guard_ifelse_expr_only);
170                         edit.insert(then_arm_end, expr.syntax().text());
171                         edit.insert(then_arm_end, ",");
172                     }
173                     _ => {
174                         let to_insert = e.dedent(dedent.into()).syntax().text();
175                         edit.insert(then_arm_end, to_insert)
176                     }
177                 }
178             } else {
179                 // There's no else branch. Add a pattern without guard, unless the following match
180                 // arm is `_ => ...`
181                 cov_mark::hit!(move_guard_ifelse_notail);
182                 match match_arm.syntax().next_sibling().and_then(MatchArm::cast) {
183                     Some(next_arm)
184                         if matches!(next_arm.pat(), Some(Pat::WildcardPat(_)))
185                             && next_arm.guard().is_none() =>
186                     {
187                         cov_mark::hit!(move_guard_ifelse_has_wildcard);
188                     }
189                     _ => edit.insert(then_arm_end, format!("\n{}{} => {{}}", spaces, match_pat)),
190                 }
191             }
192         },
193     )
194 }
195
196 // Parses an if-else-if chain to get the conditons and the then branches until we encounter an else
197 // branch or the end.
198 fn parse_if_chain(if_expr: IfExpr) -> Option<(Vec<(Condition, BlockExpr)>, Option<BlockExpr>)> {
199     let mut conds_blocks = Vec::new();
200     let mut curr_if = if_expr;
201     let tail = loop {
202         let cond = curr_if.condition()?;
203         // Not support moving if let to arm guard
204         if cond.is_pattern_cond() {
205             return None;
206         }
207         conds_blocks.push((cond, curr_if.then_branch()?));
208         match curr_if.else_branch() {
209             Some(ElseBranch::IfExpr(e)) => {
210                 curr_if = e;
211             }
212             Some(ElseBranch::Block(b)) => {
213                 break Some(b);
214             }
215             None => break None,
216         }
217     };
218     Some((conds_blocks, tail))
219 }
220
221 #[cfg(test)]
222 mod tests {
223     use super::*;
224
225     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
226
227     #[test]
228     fn move_guard_to_arm_body_range() {
229         cov_mark::check!(move_guard_unapplicable_in_arm_body);
230         check_assist_not_applicable(
231             move_guard_to_arm_body,
232             r#"
233 fn main() {
234     match 92 {
235         x if x > 10 => $0false,
236         _ => true
237     }
238 }
239 "#,
240         );
241     }
242     #[test]
243     fn move_guard_to_arm_body_target() {
244         check_assist_target(
245             move_guard_to_arm_body,
246             r#"
247 fn main() {
248     match 92 {
249         x $0if x > 10 => false,
250         _ => true
251     }
252 }
253 "#,
254             r#"if x > 10"#,
255         );
256     }
257
258     #[test]
259     fn move_guard_to_arm_body_works() {
260         check_assist(
261             move_guard_to_arm_body,
262             r#"
263 fn main() {
264     match 92 {
265         x $0if x > 10 => false,
266         _ => true
267     }
268 }
269 "#,
270             r#"
271 fn main() {
272     match 92 {
273         x => if x > 10 {
274             false
275         },
276         _ => true
277     }
278 }
279 "#,
280         );
281     }
282
283     #[test]
284     fn move_guard_to_arm_body_works_complex_match() {
285         check_assist(
286             move_guard_to_arm_body,
287             r#"
288 fn main() {
289     match 92 {
290         $0x @ 4 | x @ 5    if x > 5 => true,
291         _ => false
292     }
293 }
294 "#,
295             r#"
296 fn main() {
297     match 92 {
298         x @ 4 | x @ 5 => if x > 5 {
299             true
300         },
301         _ => false
302     }
303 }
304 "#,
305         );
306     }
307
308     #[test]
309     fn move_arm_cond_to_match_guard_works() {
310         check_assist(
311             move_arm_cond_to_match_guard,
312             r#"
313 fn main() {
314     match 92 {
315         x => if x > 10 { $0false },
316         _ => true
317     }
318 }
319 "#,
320             r#"
321 fn main() {
322     match 92 {
323         x if x > 10 => false,
324         _ => true
325     }
326 }
327 "#,
328         );
329     }
330
331     #[test]
332     fn move_arm_cond_in_block_to_match_guard_works() {
333         cov_mark::check!(move_guard_ifelse_has_wildcard);
334         check_assist(
335             move_arm_cond_to_match_guard,
336             r#"
337 fn main() {
338     match 92 {
339         x => {
340             $0if x > 10 {
341                 false
342             }
343         },
344         _ => true
345     }
346 }
347 "#,
348             r#"
349 fn main() {
350     match 92 {
351         x if x > 10 => false,
352         _ => true
353     }
354 }
355 "#,
356         );
357     }
358
359     #[test]
360     fn move_arm_cond_in_block_to_match_guard_no_wildcard_works() {
361         cov_mark::check_count!(move_guard_ifelse_has_wildcard, 0);
362         check_assist(
363             move_arm_cond_to_match_guard,
364             r#"
365 fn main() {
366     match 92 {
367         x => {
368             $0if x > 10 {
369                 false
370             }
371         }
372     }
373 }
374 "#,
375             r#"
376 fn main() {
377     match 92 {
378         x if x > 10 => false,
379         x => {}
380     }
381 }
382 "#,
383         );
384     }
385
386     #[test]
387     fn move_arm_cond_in_block_to_match_guard_wildcard_guard_works() {
388         cov_mark::check_count!(move_guard_ifelse_has_wildcard, 0);
389         check_assist(
390             move_arm_cond_to_match_guard,
391             r#"
392 fn main() {
393     match 92 {
394         x => {
395             $0if x > 10 {
396                 false
397             }
398         }
399         _ if x > 10 => true,
400     }
401 }
402 "#,
403             r#"
404 fn main() {
405     match 92 {
406         x if x > 10 => false,
407         x => {}
408         _ if x > 10 => true,
409     }
410 }
411 "#,
412         );
413     }
414
415     #[test]
416     fn move_arm_cond_in_block_to_match_guard_add_comma_works() {
417         check_assist(
418             move_arm_cond_to_match_guard,
419             r#"
420 fn main() {
421     match 92 {
422         x => {
423             $0if x > 10 {
424                 false
425             }
426         }
427         _ => true
428     }
429 }
430 "#,
431             r#"
432 fn main() {
433     match 92 {
434         x if x > 10 => false,
435         _ => true
436     }
437 }
438 "#,
439         );
440     }
441
442     #[test]
443     fn move_arm_cond_to_match_guard_if_let_not_works() {
444         check_assist_not_applicable(
445             move_arm_cond_to_match_guard,
446             r#"
447 fn main() {
448     match 92 {
449         x => if let 62 = x { $0false },
450         _ => true
451     }
452 }
453 "#,
454         );
455     }
456
457     #[test]
458     fn move_arm_cond_to_match_guard_if_empty_body_works() {
459         check_assist(
460             move_arm_cond_to_match_guard,
461             r#"
462 fn main() {
463     match 92 {
464         x => if x > 10 { $0 },
465         _ => true
466     }
467 }
468 "#,
469             r#"
470 fn main() {
471     match 92 {
472         x if x > 10 => {  }
473         _ => true
474     }
475 }
476 "#,
477         );
478     }
479
480     #[test]
481     fn move_arm_cond_to_match_guard_if_multiline_body_works() {
482         check_assist(
483             move_arm_cond_to_match_guard,
484             r#"
485 fn main() {
486     match 92 {
487         x => if x > 10 {
488             92;$0
489             false
490         },
491         _ => true
492     }
493 }
494 "#,
495             r#"
496 fn main() {
497     match 92 {
498         x if x > 10 => {
499             92;
500             false
501         }
502         _ => true
503     }
504 }
505 "#,
506         );
507     }
508
509     #[test]
510     fn move_arm_cond_in_block_to_match_guard_if_multiline_body_works() {
511         check_assist(
512             move_arm_cond_to_match_guard,
513             r#"
514 fn main() {
515     match 92 {
516         x => {
517             if x > 10 {
518                 92;$0
519                 false
520             }
521         }
522         _ => true
523     }
524 }
525 "#,
526             r#"
527 fn main() {
528     match 92 {
529         x if x > 10 => {
530             92;
531             false
532         }
533         _ => true
534     }
535 }
536 "#,
537         )
538     }
539
540     #[test]
541     fn move_arm_cond_to_match_guard_with_else_works() {
542         check_assist(
543             move_arm_cond_to_match_guard,
544             r#"
545 fn main() {
546     match 92 {
547         x => if x > 10 {$0
548             false
549         } else {
550             true
551         }
552         _ => true,
553     }
554 }
555 "#,
556             r#"
557 fn main() {
558     match 92 {
559         x if x > 10 => false,
560         x => true,
561         _ => true,
562     }
563 }
564 "#,
565         )
566     }
567
568     #[test]
569     fn move_arm_cond_to_match_guard_with_else_block_works() {
570         cov_mark::check!(move_guard_ifelse_expr_only);
571         check_assist(
572             move_arm_cond_to_match_guard,
573             r#"
574 fn main() {
575     match 92 {
576         x => {
577             if x > 10 {$0
578                 false
579             } else {
580                 true
581             }
582         }
583         _ => true
584     }
585 }
586 "#,
587             r#"
588 fn main() {
589     match 92 {
590         x if x > 10 => false,
591         x => true,
592         _ => true
593     }
594 }
595 "#,
596         )
597     }
598
599     #[test]
600     fn move_arm_cond_to_match_guard_else_if_empty_body_works() {
601         check_assist(
602             move_arm_cond_to_match_guard,
603             r#"
604 fn main() {
605     match 92 {
606         x => if x > 10 { $0 } else { },
607         _ => true
608     }
609 }
610 "#,
611             r#"
612 fn main() {
613     match 92 {
614         x if x > 10 => {  }
615         x => { }
616         _ => true
617     }
618 }
619 "#,
620         );
621     }
622
623     #[test]
624     fn move_arm_cond_to_match_guard_with_else_multiline_works() {
625         check_assist(
626             move_arm_cond_to_match_guard,
627             r#"
628 fn main() {
629     match 92 {
630         x => if x > 10 {
631             92;$0
632             false
633         } else {
634             true
635         }
636         _ => true
637     }
638 }
639 "#,
640             r#"
641 fn main() {
642     match 92 {
643         x if x > 10 => {
644             92;
645             false
646         }
647         x => true,
648         _ => true
649     }
650 }
651 "#,
652         )
653     }
654
655     #[test]
656     fn move_arm_cond_to_match_guard_with_else_multiline_else_works() {
657         cov_mark::check!(move_guard_ifelse_else_block);
658         check_assist(
659             move_arm_cond_to_match_guard,
660             r#"
661 fn main() {
662     match 92 {
663         x => if x > 10 {$0
664             false
665         } else {
666             42;
667             true
668         }
669         _ => true
670     }
671 }
672 "#,
673             r#"
674 fn main() {
675     match 92 {
676         x if x > 10 => false,
677         x => {
678             42;
679             true
680         }
681         _ => true
682     }
683 }
684 "#,
685         )
686     }
687
688     #[test]
689     fn move_arm_cond_to_match_guard_with_else_multiline_else_block_works() {
690         cov_mark::check!(move_guard_ifelse_in_block);
691         check_assist(
692             move_arm_cond_to_match_guard,
693             r#"
694 fn main() {
695     match 92 {
696         x => {
697             if x > 10 {$0
698                 false
699             } else {
700                 42;
701                 true
702             }
703         }
704         _ => true
705     }
706 }
707 "#,
708             r#"
709 fn main() {
710     match 92 {
711         x if x > 10 => false,
712         x => {
713             42;
714             true
715         }
716         _ => true
717     }
718 }
719 "#,
720         )
721     }
722
723     #[test]
724     fn move_arm_cond_to_match_guard_with_else_last_arm_works() {
725         check_assist(
726             move_arm_cond_to_match_guard,
727             r#"
728 fn main() {
729     match 92 {
730         3 => true,
731         x => {
732             if x > 10 {$0
733                 false
734             } else {
735                 92;
736                 true
737             }
738         }
739     }
740 }
741 "#,
742             r#"
743 fn main() {
744     match 92 {
745         3 => true,
746         x if x > 10 => false,
747         x => {
748             92;
749             true
750         }
751     }
752 }
753 "#,
754         )
755     }
756
757     #[test]
758     fn move_arm_cond_to_match_guard_with_else_comma_works() {
759         check_assist(
760             move_arm_cond_to_match_guard,
761             r#"
762 fn main() {
763     match 92 {
764         3 => true,
765         x => if x > 10 {$0
766             false
767         } else {
768             92;
769             true
770         },
771     }
772 }
773 "#,
774             r#"
775 fn main() {
776     match 92 {
777         3 => true,
778         x if x > 10 => false,
779         x => {
780             92;
781             true
782         }
783     }
784 }
785 "#,
786         )
787     }
788
789     #[test]
790     fn move_arm_cond_to_match_guard_elseif() {
791         check_assist(
792             move_arm_cond_to_match_guard,
793             r#"
794 fn main() {
795     match 92 {
796         3 => true,
797         x => if x > 10 {$0
798             false
799         } else if x > 5 {
800             true
801         } else if x > 4 {
802             false
803         } else {
804             true
805         },
806     }
807 }
808 "#,
809             r#"
810 fn main() {
811     match 92 {
812         3 => true,
813         x if x > 10 => false,
814         x if x > 5 => true,
815         x if x > 4 => false,
816         x => true,
817     }
818 }
819 "#,
820         )
821     }
822
823     #[test]
824     fn move_arm_cond_to_match_guard_elseif_in_block() {
825         cov_mark::check!(move_guard_ifelse_in_block);
826         check_assist(
827             move_arm_cond_to_match_guard,
828             r#"
829 fn main() {
830     match 92 {
831         3 => true,
832         x => {
833             if x > 10 {$0
834                 false
835             } else if x > 5 {
836                 true
837             } else if x > 4 {
838                 false
839             } else {
840                 true
841             }
842         }
843     }
844 }
845 "#,
846             r#"
847 fn main() {
848     match 92 {
849         3 => true,
850         x if x > 10 => false,
851         x if x > 5 => true,
852         x if x > 4 => false,
853         x => true,
854     }
855 }
856 "#,
857         )
858     }
859
860     #[test]
861     fn move_arm_cond_to_match_guard_elseif_chain() {
862         cov_mark::check!(move_guard_ifelse_else_tail);
863         check_assist(
864             move_arm_cond_to_match_guard,
865             r#"
866 fn main() {
867     match 92 {
868         3 => 0,
869         x => if x > 10 {$0
870             1
871         } else if x > 5 {
872             2
873         } else if x > 3 {
874             42;
875             3
876         } else {
877             4
878         },
879     }
880 }
881 "#,
882             r#"
883 fn main() {
884     match 92 {
885         3 => 0,
886         x if x > 10 => 1,
887         x if x > 5 => 2,
888         x if x > 3 => {
889             42;
890             3
891         }
892         x => 4,
893     }
894 }
895 "#,
896         )
897     }
898
899     #[test]
900     fn move_arm_cond_to_match_guard_elseif_iflet() {
901         check_assist_not_applicable(
902             move_arm_cond_to_match_guard,
903             r#"
904 fn main() {
905     match 92 {
906         3 => 0,
907         x => if x > 10 {$0
908             1
909         } else if x > 5 {
910             2
911         } else if let 4 = 4 {
912             42;
913             3
914         } else {
915             4
916         },
917     }
918 }
919 "#,
920         )
921     }
922
923     #[test]
924     fn move_arm_cond_to_match_guard_elseif_notail() {
925         cov_mark::check!(move_guard_ifelse_notail);
926         check_assist(
927             move_arm_cond_to_match_guard,
928             r#"
929 fn main() {
930     match 92 {
931         3 => 0,
932         x => if x > 10 {$0
933             1
934         } else if x > 5 {
935             2
936         } else if x > 4 {
937             42;
938             3
939         },
940     }
941 }
942 "#,
943             r#"
944 fn main() {
945     match 92 {
946         3 => 0,
947         x if x > 10 => 1,
948         x if x > 5 => 2,
949         x if x > 4 => {
950             42;
951             3
952         }
953         x => {}
954     }
955 }
956 "#,
957         )
958     }
959 }