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