1 use itertools::Itertools;
2 use std::iter::successors;
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 return 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: &ast::MatchArm) -> bool {
94 matches!(a.pat(), Some(ast::Pat::WildcardPat(..)))
98 current_arm_types: &Vec<Option<hir::TypeInfo>>,
102 let arm_types = get_arm_types(&ctx, &arm);
103 for i in 0..arm_types.len() {
104 let other_arm_type = &arm_types[i];
105 let current_arm_type = ¤t_arm_types[i];
106 if let (Some(other_arm_type), Some(current_arm_type)) = (other_arm_type, current_arm_type) {
107 return &other_arm_type.original == ¤t_arm_type.original;
114 fn get_arm_types(ctx: &AssistContext, arm: &ast::MatchArm) -> Vec<Option<hir::TypeInfo>> {
116 Some(ast::Pat::TupleStructPat(tp)) => {
117 tp.fields().into_iter().map(|field| ctx.sema.type_of_pat(&field)).collect_vec()
125 use crate::tests::{check_assist, check_assist_not_applicable};
130 fn merge_match_arms_single_patterns() {
153 X::A | X::B => { 1i32 },
162 fn merge_match_arms_multiple_patterns() {
167 enum X { A, B, C, D, E }
172 X::A | X::B => {$0 1i32 },
173 X::C | X::D => { 1i32 },
180 enum X { A, B, C, D, E }
185 X::A | X::B | X::C | X::D => { 1i32 },
194 fn merge_match_arms_placeholder_pattern() {
199 enum X { A, B, C, D, E }
212 enum X { A, B, C, D, E }
226 fn merges_all_subsequent_arms() {
230 enum X { A, B, C, D, E }
243 enum X { A, B, C, D, E }
247 X::A | X::B | X::C => 92,
257 fn merge_match_arms_rejects_guards() {
258 check_assist_not_applicable(
271 X::A(a) if a > 5 => { $01i32 },
281 fn merge_match_arms_different_type() {
282 check_assist_not_applicable(
284 r#"//- minicore: result
286 match Result::<f64, f32>::Ok(0f64) {
287 Ok(x) => $0x.classify(),
288 Err(x) => x.classify()
296 fn merge_match_arms_different_type_multiple_fields() {
297 check_assist_not_applicable(
299 r#"//- minicore: result
301 match Result::<(f64, f64), (f32, f32)>::Ok((0f64, 0f64)) {
302 Ok(x) => $0x.1.classify(),
303 Err(x) => x.1.classify()
311 fn merge_match_arms_same_type_multiple_fields() {
314 r#"//- minicore: result
316 match Result::<(f64, f64), (f64, f64)>::Ok((0f64, 0f64)) {
317 Ok(x) => $0x.1.classify(),
318 Err(x) => x.1.classify()
324 match Result::<(f64, f64), (f64, f64)>::Ok((0f64, 0f64)) {
325 Ok(x) | Err(x) => x.1.classify(),
333 fn merge_match_arms_same_type_subsequent_arm_with_different_type_in_other() {
345 MyEnum::OptionA(x) => $0x.classify(),
346 MyEnum::OptionB(x) => x.classify(),
347 MyEnum::OptionC(x) => x.classify(),
360 MyEnum::OptionA(x) | MyEnum::OptionB(x) => x.classify(),
361 MyEnum::OptionC(x) => x.classify(),
369 fn merge_match_arms_same_type_skip_arm_with_different_type_in_between() {
370 check_assist_not_applicable(
381 MyEnum::OptionA(x) => $0x.classify(),
382 MyEnum::OptionB(x) => x.classify(),
383 MyEnum::OptionC(x) => x.classify(),