ide_assists/handlers/
move_guard.rs

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