1use hir::Type;
2use ide_db::FxHashMap;
3use std::iter::successors;
4use syntax::{
5 Direction,
6 algo::neighbor,
7 ast::{self, AstNode, HasName},
8};
9
10use crate::{AssistContext, AssistId, Assists, TextRange};
11
12pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
37 let current_arm = ctx.find_node_at_trimmed_offset::<ast::MatchArm>()?;
38 if current_arm.guard().is_some() {
40 return None;
41 }
42 let current_expr = current_arm.expr()?;
43 let current_text_range = current_arm.syntax().text_range();
44 let current_arm_types = get_arm_types(ctx, ¤t_arm);
45 let multi_arm_selection = !ctx.has_empty_selection()
46 && ctx.selection_trimmed().end() > current_arm.syntax().text_range().end();
47
48 let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
51 .take_while(|arm| match arm.expr() {
52 Some(expr) if arm.guard().is_none() => {
53 if multi_arm_selection
55 && arm.syntax().text_range().start() >= ctx.selection_trimmed().end()
56 {
57 return false;
58 }
59
60 let same_text = expr.syntax().text() == current_expr.syntax().text();
61 if !same_text {
62 return false;
63 }
64
65 are_same_types(¤t_arm_types, arm, ctx)
66 }
67 _ => false,
68 })
69 .collect::<Vec<_>>();
70
71 if arms_to_merge.len() <= 1 {
72 return None;
73 }
74
75 acc.add(
76 AssistId::refactor_rewrite("merge_match_arms"),
77 "Merge match arms",
78 current_text_range,
79 |edit| {
80 let pats = if arms_to_merge.iter().any(contains_placeholder) {
81 "_".into()
82 } else {
83 arms_to_merge
84 .iter()
85 .filter_map(ast::MatchArm::pat)
86 .map(|x| x.syntax().to_string())
87 .collect::<Vec<String>>()
88 .join(" | ")
89 };
90
91 let arm = format!("{pats} => {current_expr},");
92
93 if let [first, .., last] = &*arms_to_merge {
94 let start = first.syntax().text_range().start();
95 let end = last.syntax().text_range().end();
96
97 edit.replace(TextRange::new(start, end), arm);
98 }
99 },
100 )
101}
102
103fn contains_placeholder(a: &ast::MatchArm) -> bool {
104 matches!(a.pat(), Some(ast::Pat::WildcardPat(..)))
105}
106
107fn are_same_types(
108 current_arm_types: &FxHashMap<String, Option<Type<'_>>>,
109 arm: &ast::MatchArm,
110 ctx: &AssistContext<'_, '_>,
111) -> bool {
112 let arm_types = get_arm_types(ctx, arm);
113 for (other_arm_type_name, other_arm_type) in arm_types {
114 match (current_arm_types.get(&other_arm_type_name), other_arm_type) {
115 (Some(Some(current_arm_type)), Some(other_arm_type))
116 if other_arm_type == *current_arm_type => {}
117 _ => return false,
118 }
119 }
120
121 true
122}
123
124fn get_arm_types<'db>(
125 context: &AssistContext<'_, 'db>,
126 arm: &ast::MatchArm,
127) -> FxHashMap<String, Option<Type<'db>>> {
128 let mut mapping: FxHashMap<String, Option<Type<'db>>> = FxHashMap::default();
129
130 fn recurse<'db>(
131 map: &mut FxHashMap<String, Option<Type<'db>>>,
132 ctx: &AssistContext<'_, 'db>,
133 pat: &Option<ast::Pat>,
134 ) {
135 if let Some(local_pat) = pat {
136 match local_pat {
137 ast::Pat::TupleStructPat(tuple) => {
138 for field in tuple.fields() {
139 recurse(map, ctx, &Some(field));
140 }
141 }
142 ast::Pat::TuplePat(tuple) => {
143 for field in tuple.fields() {
144 recurse(map, ctx, &Some(field));
145 }
146 }
147 ast::Pat::RecordPat(record) => {
148 if let Some(field_list) = record.record_pat_field_list() {
149 for field in field_list.fields() {
150 recurse(map, ctx, &field.pat());
151 }
152 }
153 }
154 ast::Pat::ParenPat(parentheses) => {
155 recurse(map, ctx, &parentheses.pat());
156 }
157 ast::Pat::SlicePat(slice) => {
158 for slice_pat in slice.pats() {
159 recurse(map, ctx, &Some(slice_pat));
160 }
161 }
162 ast::Pat::IdentPat(ident_pat) => {
163 if let Some(name) = ident_pat.name()
164 && ctx.sema.to_def(ident_pat).is_some()
165 {
166 let pat_type = ctx.sema.type_of_binding_in_pat(ident_pat);
167
168 map.insert(name.text().to_string(), pat_type);
169 }
170 }
171 _ => (),
172 }
173 }
174 }
175
176 recurse(&mut mapping, context, &arm.pat());
177 mapping
178}
179
180#[cfg(test)]
181mod tests {
182 use crate::tests::{check_assist, check_assist_not_applicable};
183
184 use super::*;
185
186 #[test]
187 fn merge_match_arms_single_patterns() {
188 check_assist(
189 merge_match_arms,
190 r#"
191#[derive(Debug)]
192enum X { A, B, C }
193
194fn main() {
195 let x = X::A;
196 let y = match x {
197 X::A => { 1i32$0 }
198 X::B => { 1i32 }
199 X::C => { 2i32 }
200 }
201}
202"#,
203 r#"
204#[derive(Debug)]
205enum X { A, B, C }
206
207fn main() {
208 let x = X::A;
209 let y = match x {
210 X::A | X::B => { 1i32 },
211 X::C => { 2i32 }
212 }
213}
214"#,
215 );
216 }
217
218 #[test]
219 fn merge_match_arms_ambiguous_ident_patterns() {
220 check_assist(
221 merge_match_arms,
222 r#"
223#[derive(Debug)]
224enum X { A, B, C }
225use X::*;
226
227fn main() {
228 let x = A;
229 let y = match x {
230 A => { 1i32$0 }
231 B => { 1i32 }
232 C => { 2i32 }
233 }
234}
235"#,
236 r#"
237#[derive(Debug)]
238enum X { A, B, C }
239use X::*;
240
241fn main() {
242 let x = A;
243 let y = match x {
244 A | B => { 1i32 },
245 C => { 2i32 }
246 }
247}
248"#,
249 );
250 }
251
252 #[test]
253 fn merge_match_arms_multiple_patterns() {
254 check_assist(
255 merge_match_arms,
256 r#"
257#[derive(Debug)]
258enum X { A, B, C, D, E }
259
260fn main() {
261 let x = X::A;
262 let y = match x {
263 X::A | X::B => {$0 1i32 },
264 X::C | X::D => { 1i32 },
265 X::E => { 2i32 },
266 }
267}
268"#,
269 r#"
270#[derive(Debug)]
271enum X { A, B, C, D, E }
272
273fn main() {
274 let x = X::A;
275 let y = match x {
276 X::A | X::B | X::C | X::D => { 1i32 },
277 X::E => { 2i32 },
278 }
279}
280"#,
281 );
282 }
283
284 #[test]
285 fn merge_match_arms_placeholder_pattern() {
286 check_assist(
287 merge_match_arms,
288 r#"
289#[derive(Debug)]
290enum X { A, B, C, D, E }
291
292fn main() {
293 let x = X::A;
294 let y = match x {
295 X::A => { 1i32 },
296 X::B => { 2i$032 },
297 _ => { 2i32 }
298 }
299}
300"#,
301 r#"
302#[derive(Debug)]
303enum X { A, B, C, D, E }
304
305fn main() {
306 let x = X::A;
307 let y = match x {
308 X::A => { 1i32 },
309 _ => { 2i32 },
310 }
311}
312"#,
313 );
314 }
315
316 #[test]
317 fn merges_all_subsequent_arms() {
318 check_assist(
319 merge_match_arms,
320 r#"
321enum X { A, B, C, D, E }
322
323fn main() {
324 match X::A {
325 X::A$0 => 92,
326 X::B => 92,
327 X::C => 92,
328 X::D => 62,
329 _ => panic!(),
330 }
331}
332"#,
333 r#"
334enum X { A, B, C, D, E }
335
336fn main() {
337 match X::A {
338 X::A | X::B | X::C => 92,
339 X::D => 62,
340 _ => panic!(),
341 }
342}
343"#,
344 )
345 }
346
347 #[test]
348 fn merge_match_arms_selection_has_leading_whitespace() {
349 check_assist(
350 merge_match_arms,
351 r#"
352#[derive(Debug)]
353enum X { A, B, C }
354
355fn main() {
356 match X::A {
357 $0 X::A => 0,
358 X::B => 0,$0
359 X::C => 1,
360 }
361}
362"#,
363 r#"
364#[derive(Debug)]
365enum X { A, B, C }
366
367fn main() {
368 match X::A {
369 X::A | X::B => 0,
370 X::C => 1,
371 }
372}
373"#,
374 );
375 }
376
377 #[test]
378 fn merge_match_arms_stops_at_end_of_selection() {
379 check_assist(
380 merge_match_arms,
381 r#"
382#[derive(Debug)]
383enum X { A, B, C }
384
385fn main() {
386 match X::A {
387 $0 X::A => 0,
388 X::B => 0,
389 $0X::C => 0,
390 }
391}
392"#,
393 r#"
394#[derive(Debug)]
395enum X { A, B, C }
396
397fn main() {
398 match X::A {
399 X::A | X::B => 0,
400 X::C => 0,
401 }
402}
403"#,
404 );
405 }
406
407 #[test]
408 fn merge_match_arms_works_despite_accidental_selection() {
409 check_assist(
410 merge_match_arms,
411 r#"
412#[derive(Debug)]
413enum X { A, B, C }
414
415fn main() {
416 match X::A {
417 X::$0A$0 => 0,
418 X::B => 0,
419 X::C => 1,
420 }
421}
422"#,
423 r#"
424#[derive(Debug)]
425enum X { A, B, C }
426
427fn main() {
428 match X::A {
429 X::A | X::B => 0,
430 X::C => 1,
431 }
432}
433"#,
434 );
435 }
436
437 #[test]
438 fn merge_match_arms_rejects_guards() {
439 check_assist_not_applicable(
440 merge_match_arms,
441 r#"
442#[derive(Debug)]
443enum X {
444 A(i32),
445 B,
446 C
447}
448
449fn main() {
450 let x = X::A;
451 let y = match x {
452 X::A(a) if a > 5 => { $01i32 },
453 X::B => { 1i32 },
454 X::C => { 2i32 }
455 }
456}
457"#,
458 );
459 }
460
461 #[test]
462 fn merge_match_arms_different_type() {
463 check_assist_not_applicable(
464 merge_match_arms,
465 r#"
466//- minicore: result
467fn func() {
468 match Result::<f64, f32>::Ok(0f64) {
469 Ok(x) => $0x.classify(),
470 Err(x) => x.classify()
471 };
472}
473"#,
474 );
475 }
476
477 #[test]
478 fn merge_match_arms_different_type_multiple_fields() {
479 check_assist_not_applicable(
480 merge_match_arms,
481 r#"
482//- minicore: result
483fn func() {
484 match Result::<(f64, f64), (f32, f32)>::Ok((0f64, 0f64)) {
485 Ok(x) => $0x.1.classify(),
486 Err(x) => x.1.classify()
487 };
488}
489"#,
490 );
491 }
492
493 #[test]
494 fn merge_match_arms_same_type_multiple_fields() {
495 check_assist(
496 merge_match_arms,
497 r#"
498//- minicore: result
499fn func() {
500 match Result::<(f64, f64), (f64, f64)>::Ok((0f64, 0f64)) {
501 Ok(x) => $0x.1.classify(),
502 Err(x) => x.1.classify()
503 };
504}
505"#,
506 r#"
507fn func() {
508 match Result::<(f64, f64), (f64, f64)>::Ok((0f64, 0f64)) {
509 Ok(x) | Err(x) => x.1.classify(),
510 };
511}
512"#,
513 );
514 }
515
516 #[test]
517 fn merge_match_arms_same_type_subsequent_arm_with_different_type_in_other() {
518 check_assist(
519 merge_match_arms,
520 r#"
521enum MyEnum {
522 OptionA(f32),
523 OptionB(f32),
524 OptionC(f64)
525}
526
527fn func(e: MyEnum) {
528 match e {
529 MyEnum::OptionA(x) => $0x.classify(),
530 MyEnum::OptionB(x) => x.classify(),
531 MyEnum::OptionC(x) => x.classify(),
532 };
533}
534"#,
535 r#"
536enum MyEnum {
537 OptionA(f32),
538 OptionB(f32),
539 OptionC(f64)
540}
541
542fn func(e: MyEnum) {
543 match e {
544 MyEnum::OptionA(x) | MyEnum::OptionB(x) => x.classify(),
545 MyEnum::OptionC(x) => x.classify(),
546 };
547}
548"#,
549 );
550 }
551
552 #[test]
553 fn merge_match_arms_same_type_skip_arm_with_different_type_in_between() {
554 check_assist_not_applicable(
555 merge_match_arms,
556 r#"
557enum MyEnum {
558 OptionA(f32),
559 OptionB(f64),
560 OptionC(f32)
561}
562
563fn func(e: MyEnum) {
564 match e {
565 MyEnum::OptionA(x) => $0x.classify(),
566 MyEnum::OptionB(x) => x.classify(),
567 MyEnum::OptionC(x) => x.classify(),
568 };
569}
570"#,
571 );
572 }
573
574 #[test]
575 fn merge_match_arms_same_type_different_number_of_fields() {
576 check_assist_not_applicable(
577 merge_match_arms,
578 r#"
579//- minicore: result
580fn func() {
581 match Result::<(f64, f64, f64), (f64, f64)>::Ok((0f64, 0f64, 0f64)) {
582 Ok(x) => $0x.1.classify(),
583 Err(x) => x.1.classify()
584 };
585}
586"#,
587 );
588 }
589
590 #[test]
591 fn merge_match_same_destructuring_different_types() {
592 check_assist_not_applicable(
593 merge_match_arms,
594 r#"
595struct Point {
596 x: i32,
597 y: i32,
598}
599
600fn func() {
601 let p = Point { x: 0, y: 7 };
602
603 match p {
604 Point { x, y: 0 } => $0"",
605 Point { x: 0, y } => "",
606 Point { x, y } => "",
607 };
608}
609"#,
610 );
611 }
612
613 #[test]
614 fn merge_match_arms_range() {
615 check_assist(
616 merge_match_arms,
617 r#"
618fn func() {
619 let x = 'c';
620
621 match x {
622 'a'..='j' => $0"",
623 'c'..='z' => "",
624 _ => "other",
625 };
626}
627"#,
628 r#"
629fn func() {
630 let x = 'c';
631
632 match x {
633 'a'..='j' | 'c'..='z' => "",
634 _ => "other",
635 };
636}
637"#,
638 );
639 }
640
641 #[test]
642 fn merge_match_arms_enum_without_field() {
643 check_assist_not_applicable(
644 merge_match_arms,
645 r#"
646enum MyEnum {
647 NoField,
648 AField(u8)
649}
650
651fn func(x: MyEnum) {
652 match x {
653 MyEnum::NoField => $0"",
654 MyEnum::AField(x) => ""
655 };
656}
657 "#,
658 )
659 }
660
661 #[test]
662 fn merge_match_arms_enum_destructuring_different_types() {
663 check_assist_not_applicable(
664 merge_match_arms,
665 r#"
666enum MyEnum {
667 Move { x: i32, y: i32 },
668 Write(String),
669}
670
671fn func(x: MyEnum) {
672 match x {
673 MyEnum::Move { x, y } => $0"",
674 MyEnum::Write(text) => "",
675 };
676}
677 "#,
678 )
679 }
680
681 #[test]
682 fn merge_match_arms_enum_destructuring_same_types() {
683 check_assist(
684 merge_match_arms,
685 r#"
686enum MyEnum {
687 Move { x: i32, y: i32 },
688 Crawl { x: i32, y: i32 }
689}
690
691fn func(x: MyEnum) {
692 match x {
693 MyEnum::Move { x, y } => $0"",
694 MyEnum::Crawl { x, y } => "",
695 };
696}
697 "#,
698 r#"
699enum MyEnum {
700 Move { x: i32, y: i32 },
701 Crawl { x: i32, y: i32 }
702}
703
704fn func(x: MyEnum) {
705 match x {
706 MyEnum::Move { x, y } | MyEnum::Crawl { x, y } => "",
707 };
708}
709 "#,
710 )
711 }
712
713 #[test]
714 fn merge_match_arms_enum_destructuring_same_types_different_name() {
715 check_assist_not_applicable(
716 merge_match_arms,
717 r#"
718enum MyEnum {
719 Move { x: i32, y: i32 },
720 Crawl { a: i32, b: i32 }
721}
722
723fn func(x: MyEnum) {
724 match x {
725 MyEnum::Move { x, y } => $0"",
726 MyEnum::Crawl { a, b } => "",
727 };
728}
729 "#,
730 )
731 }
732
733 #[test]
734 fn merge_match_arms_enum_nested_pattern_different_names() {
735 check_assist_not_applicable(
736 merge_match_arms,
737 r#"
738enum Color {
739 Rgb(i32, i32, i32),
740 Hsv(i32, i32, i32),
741}
742
743enum Message {
744 Quit,
745 Move { x: i32, y: i32 },
746 Write(String),
747 ChangeColor(Color),
748}
749
750fn main(msg: Message) {
751 match msg {
752 Message::ChangeColor(Color::Rgb(r, g, b)) => $0"",
753 Message::ChangeColor(Color::Hsv(h, s, v)) => "",
754 _ => "other"
755 };
756}
757 "#,
758 )
759 }
760
761 #[test]
762 fn merge_match_arms_enum_nested_pattern_same_names() {
763 check_assist(
764 merge_match_arms,
765 r#"
766enum Color {
767 Rgb(i32, i32, i32),
768 Hsv(i32, i32, i32),
769}
770
771enum Message {
772 Quit,
773 Move { x: i32, y: i32 },
774 Write(String),
775 ChangeColor(Color),
776}
777
778fn main(msg: Message) {
779 match msg {
780 Message::ChangeColor(Color::Rgb(a, b, c)) => $0"",
781 Message::ChangeColor(Color::Hsv(a, b, c)) => "",
782 _ => "other"
783 };
784}
785 "#,
786 r#"
787enum Color {
788 Rgb(i32, i32, i32),
789 Hsv(i32, i32, i32),
790}
791
792enum Message {
793 Quit,
794 Move { x: i32, y: i32 },
795 Write(String),
796 ChangeColor(Color),
797}
798
799fn main(msg: Message) {
800 match msg {
801 Message::ChangeColor(Color::Rgb(a, b, c)) | Message::ChangeColor(Color::Hsv(a, b, c)) => "",
802 _ => "other"
803 };
804}
805 "#,
806 )
807 }
808
809 #[test]
810 fn merge_match_arms_enum_destructuring_with_ignore() {
811 check_assist(
812 merge_match_arms,
813 r#"
814enum MyEnum {
815 Move { x: i32, a: i32 },
816 Crawl { x: i32, b: i32 }
817}
818
819fn func(x: MyEnum) {
820 match x {
821 MyEnum::Move { x, .. } => $0"",
822 MyEnum::Crawl { x, .. } => "",
823 };
824}
825 "#,
826 r#"
827enum MyEnum {
828 Move { x: i32, a: i32 },
829 Crawl { x: i32, b: i32 }
830}
831
832fn func(x: MyEnum) {
833 match x {
834 MyEnum::Move { x, .. } | MyEnum::Crawl { x, .. } => "",
835 };
836}
837 "#,
838 )
839 }
840
841 #[test]
842 fn merge_match_arms_nested_with_conflicting_identifier() {
843 check_assist_not_applicable(
844 merge_match_arms,
845 r#"
846enum Color {
847 Rgb(i32, i32, i32),
848 Hsv(i32, i32, i32),
849}
850
851enum Message {
852 Move { x: i32, y: i32 },
853 ChangeColor(u8, Color),
854}
855
856fn main(msg: Message) {
857 match msg {
858 Message::ChangeColor(x, Color::Rgb(y, b, c)) => $0"",
859 Message::ChangeColor(y, Color::Hsv(x, b, c)) => "",
860 _ => "other"
861 };
862}
863 "#,
864 )
865 }
866
867 #[test]
868 fn merge_match_arms_tuple() {
869 check_assist_not_applicable(
870 merge_match_arms,
871 r#"
872fn func() {
873 match (0, "boo") {
874 (x, y) => $0"",
875 (y, x) => "",
876 };
877}
878 "#,
879 )
880 }
881
882 #[test]
883 fn merge_match_arms_parentheses() {
884 check_assist_not_applicable(
885 merge_match_arms,
886 r#"
887fn func(x: i32) {
888 let variable = 2;
889 match x {
890 1 => $0"",
891 ((((variable)))) => "",
892 _ => "other"
893 };
894}
895 "#,
896 )
897 }
898
899 #[test]
900 fn merge_match_arms_refpat() {
901 check_assist_not_applicable(
902 merge_match_arms,
903 r#"
904fn func() {
905 let name = Some(String::from(""));
906 let n = String::from("");
907 match name {
908 Some(ref n) => $0"",
909 Some(n) => "",
910 _ => "other",
911 };
912}
913 "#,
914 )
915 }
916
917 #[test]
918 fn merge_match_arms_slice() {
919 check_assist_not_applicable(
920 merge_match_arms,
921 r#"
922fn func(binary: &[u8]) {
923 let space = b' ';
924 match binary {
925 [0x7f, b'E', b'L', b'F', ..] => $0"",
926 [space] => "",
927 _ => "other",
928 };
929}
930 "#,
931 )
932 }
933
934 #[test]
935 fn merge_match_arms_slice_identical() {
936 check_assist(
937 merge_match_arms,
938 r#"
939fn func(binary: &[u8]) {
940 let space = b' ';
941 match binary {
942 [space, 5u8] => $0"",
943 [space] => "",
944 _ => "other",
945 };
946}
947 "#,
948 r#"
949fn func(binary: &[u8]) {
950 let space = b' ';
951 match binary {
952 [space, 5u8] | [space] => "",
953 _ => "other",
954 };
955}
956 "#,
957 )
958 }
959}