2 use std::{collections::HashMap, iter::successors};
5 ast::{self, AstNode, HasName, MatchArm, Pat},
9 use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
11 // Assist: merge_match_arms
13 // Merges the current match arm with the following if their bodies are identical.
16 // enum Action { Move { distance: u32 }, Stop }
18 // fn handle(action: Action) {
20 // $0Action::Move(..) => foo(),
21 // Action::Stop => foo(),
27 // enum Action { Move { distance: u32 }, Stop }
29 // fn handle(action: Action) {
31 // Action::Move(..) | Action::Stop => foo(),
35 pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
37 // Don't try to handle arms with guards for now - can add support for this later
38 if current_arm.guard().is_some() {
41 let current_expr = current_arm.expr()?;
42 let current_text_range = current_arm.syntax().text_range();
43 let current_arm_types = get_arm_types(&ctx, ¤t_arm);
45 // We check if the following match arms match this one. We could, but don't,
46 // compare to the previous match arm as well.
47 let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
48 .take_while(|arm| match arm.expr() {
49 Some(expr) if arm.guard().is_none() => {
50 let same_text = expr.syntax().text() == current_expr.syntax().text();
55 are_same_types(¤t_arm_types, arm, ctx)
61 if arms_to_merge.len() <= 1 {
66 AssistId("merge_match_arms", AssistKind::RefactorRewrite),
70 let pats = if arms_to_merge.iter().any(contains_placeholder) {
75 .filter_map(ast::MatchArm::pat)
76 .map(|x| x.syntax().to_string())
77 .collect::<Vec<String>>()
81 let arm = format!("{} => {},", pats, current_expr.syntax().text());
83 if let [first, .., last] = &*arms_to_merge {
84 let start = first.syntax().text_range().start();
85 let end = last.syntax().text_range().end();
87 edit.replace(TextRange::new(start, end), arm);
93 fn contains_placeholder(a: &MatchArm) -> bool {
94 matches!(a.pat(), Some(ast::Pat::WildcardPat(..)))
98 current_arm_types: &HashMap<String, Option<TypeInfo>>,
102 let arm_types = get_arm_types(&ctx, &arm);
103 for (other_arm_type_name, other_arm_type) in arm_types {
104 if let (Some(Some(current_arm_type)), Some(other_arm_type)) =
105 (current_arm_types.get(&other_arm_type_name), other_arm_type)
107 if other_arm_type.original != current_arm_type.original {
111 // No corresponding field found
119 fn get_arm_types(context: &AssistContext, arm: &MatchArm) -> HashMap<String, Option<TypeInfo>> {
120 let mut mapping: HashMap<String, Option<TypeInfo>> = HashMap::new();
124 map: &mut HashMap<String, Option<TypeInfo>>,
127 if let Some(local_pat) = pat {
129 Some(ast::Pat::TupleStructPat(tuple)) => {
130 for field in tuple.fields() {
131 recurse(&Some(field), map, ctx);
134 Some(ast::Pat::TuplePat(tuple)) => {
135 for field in tuple.fields() {
136 recurse(&Some(field), map, ctx);
139 Some(ast::Pat::RecordPat(record)) => {
140 if let Some(field_list) = record.record_pat_field_list() {
141 for field in field_list.fields() {
142 recurse(&field.pat(), map, ctx);
146 Some(ast::Pat::ParenPat(parentheses)) => {
147 recurse(&parentheses.pat(), map, ctx);
149 Some(ast::Pat::SlicePat(slice)) => {
150 for slice_pat in slice.pats() {
151 recurse(&Some(slice_pat), map, ctx);
154 Some(ast::Pat::IdentPat(ident_pat)) => {
155 if let Some(name) = ident_pat.name() {
156 let pat_type = ctx.sema.type_of_pat(local_pat);
157 map.insert(name.text().to_string(), pat_type);
165 recurse(&arm.pat(), &mut mapping, &context);
171 use crate::tests::{check_assist, check_assist_not_applicable};
176 fn merge_match_arms_single_patterns() {
199 X::A | X::B => { 1i32 },
208 fn merge_match_arms_multiple_patterns() {
213 enum X { A, B, C, D, E }
218 X::A | X::B => {$0 1i32 },
219 X::C | X::D => { 1i32 },
226 enum X { A, B, C, D, E }
231 X::A | X::B | X::C | X::D => { 1i32 },
240 fn merge_match_arms_placeholder_pattern() {
245 enum X { A, B, C, D, E }
258 enum X { A, B, C, D, E }
272 fn merges_all_subsequent_arms() {
276 enum X { A, B, C, D, E }
289 enum X { A, B, C, D, E }
293 X::A | X::B | X::C => 92,
303 fn merge_match_arms_rejects_guards() {
304 check_assist_not_applicable(
317 X::A(a) if a > 5 => { $01i32 },
327 fn merge_match_arms_different_type() {
328 check_assist_not_applicable(
330 r#"//- minicore: result
332 match Result::<f64, f32>::Ok(0f64) {
333 Ok(x) => $0x.classify(),
334 Err(x) => x.classify()
342 fn merge_match_arms_different_type_multiple_fields() {
343 check_assist_not_applicable(
345 r#"//- minicore: result
347 match Result::<(f64, f64), (f32, f32)>::Ok((0f64, 0f64)) {
348 Ok(x) => $0x.1.classify(),
349 Err(x) => x.1.classify()
357 fn merge_match_arms_same_type_multiple_fields() {
360 r#"//- minicore: result
362 match Result::<(f64, f64), (f64, f64)>::Ok((0f64, 0f64)) {
363 Ok(x) => $0x.1.classify(),
364 Err(x) => x.1.classify()
370 match Result::<(f64, f64), (f64, f64)>::Ok((0f64, 0f64)) {
371 Ok(x) | Err(x) => x.1.classify(),
379 fn merge_match_arms_same_type_subsequent_arm_with_different_type_in_other() {
391 MyEnum::OptionA(x) => $0x.classify(),
392 MyEnum::OptionB(x) => x.classify(),
393 MyEnum::OptionC(x) => x.classify(),
406 MyEnum::OptionA(x) | MyEnum::OptionB(x) => x.classify(),
407 MyEnum::OptionC(x) => x.classify(),
415 fn merge_match_arms_same_type_skip_arm_with_different_type_in_between() {
416 check_assist_not_applicable(
427 MyEnum::OptionA(x) => $0x.classify(),
428 MyEnum::OptionB(x) => x.classify(),
429 MyEnum::OptionC(x) => x.classify(),
437 fn merge_match_arms_same_type_different_number_of_fields() {
438 check_assist_not_applicable(
440 r#"//- minicore: result
442 match Result::<(f64, f64, f64), (f64, f64)>::Ok((0f64, 0f64, 0f64)) {
443 Ok(x) => $0x.1.classify(),
444 Err(x) => x.1.classify()
452 fn merge_match_same_destructuring_different_types() {
453 check_assist_not_applicable(
462 let p = Point { x: 0, y: 7 };
465 Point { x, y: 0 } => $0"",
466 Point { x: 0, y } => "",
467 Point { x, y } => "",
475 fn merge_match_arms_range() {
494 'a'..='j' | 'c'..='z' => "",
503 fn merge_match_arms_enum_without_field() {
504 check_assist_not_applicable(
514 MyEnum::NoField => $0"",
515 MyEnum::AField(x) => ""
523 fn merge_match_arms_enum_destructuring_different_types() {
524 check_assist_not_applicable(
528 Move { x: i32, y: i32 },
534 MyEnum::Move { x, y } => $0"",
535 MyEnum::Write(text) => "",
543 fn merge_match_arms_enum_destructuring_same_types() {
548 Move { x: i32, y: i32 },
549 Crawl { x: i32, y: i32 }
554 MyEnum::Move { x, y } => $0"",
555 MyEnum::Crawl { x, y } => "",
561 Move { x: i32, y: i32 },
562 Crawl { x: i32, y: i32 }
567 MyEnum::Move { x, y } | MyEnum::Crawl { x, y } => "",
575 fn merge_match_arms_enum_destructuring_same_types_different_name() {
576 check_assist_not_applicable(
580 Move { x: i32, y: i32 },
581 Crawl { a: i32, b: i32 }
586 MyEnum::Move { x, y } => $0"",
587 MyEnum::Crawl { a, b } => "",
595 fn merge_match_arms_enum_nested_pattern_different_names() {
596 check_assist_not_applicable(
606 Move { x: i32, y: i32 },
611 fn main(msg: Message) {
613 Message::ChangeColor(Color::Rgb(r, g, b)) => $0"",
614 Message::ChangeColor(Color::Hsv(h, s, v)) => "",
623 fn merge_match_arms_enum_nested_pattern_same_names() {
634 Move { x: i32, y: i32 },
639 fn main(msg: Message) {
641 Message::ChangeColor(Color::Rgb(a, b, c)) => $0"",
642 Message::ChangeColor(Color::Hsv(a, b, c)) => "",
655 Move { x: i32, y: i32 },
660 fn main(msg: Message) {
662 Message::ChangeColor(Color::Rgb(a, b, c)) | Message::ChangeColor(Color::Hsv(a, b, c)) => "",
671 fn merge_match_arms_enum_destructuring_with_ignore() {
676 Move { x: i32, a: i32 },
677 Crawl { x: i32, b: i32 }
682 MyEnum::Move { x, .. } => $0"",
683 MyEnum::Crawl { x, .. } => "",
689 Move { x: i32, a: i32 },
690 Crawl { x: i32, b: i32 }
695 MyEnum::Move { x, .. } | MyEnum::Crawl { x, .. } => "",
703 fn merge_match_arms_nested_with_conflicting_identifier() {
704 check_assist_not_applicable(
713 Move { x: i32, y: i32 },
714 ChangeColor(u8, Color),
717 fn main(msg: Message) {
719 Message::ChangeColor(x, Color::Rgb(y, b, c)) => $0"",
720 Message::ChangeColor(y, Color::Hsv(x, b, c)) => "",
729 fn merge_match_arms_tuple() {
730 check_assist_not_applicable(
744 fn merge_match_arms_parentheses() {
745 check_assist_not_applicable(
752 ((((variable)))) => "",
761 fn merge_match_arms_refpat() {
762 check_assist_not_applicable(
766 let name = Some(String::from(""));
767 let n = String::from("");
779 fn merge_match_arms_slice() {
780 check_assist_not_applicable(
783 fn func(binary: &[u8]) {
786 [0x7f, b'E', b'L', b'F', ..] => $0"",
796 fn merge_match_arms_slice_identical() {
800 fn func(binary: &[u8]) {
803 [space, 5u8] => $0"",
810 fn func(binary: &[u8]) {
813 [space, 5u8] | [space] => "",