1 use std::iter::successors;
3 use hir::{TypeInfo, HirDisplay};
4 use itertools::Itertools;
11 use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
13 // Assist: merge_match_arms
15 // Merges the current match arm with the following if their bodies are identical.
18 // enum Action { Move { distance: u32 }, Stop }
20 // fn handle(action: Action) {
22 // $0Action::Move(..) => foo(),
23 // Action::Stop => foo(),
29 // enum Action { Move { distance: u32 }, Stop }
31 // fn handle(action: Action) {
33 // Action::Move(..) | Action::Stop => foo(),
37 pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
39 // Don't try to handle arms with guards for now - can add support for this later
40 if current_arm.guard().is_some() {
43 let current_expr = current_arm.expr()?;
44 let current_text_range = current_arm.syntax().text_range();
45 let current_arm_types = get_arm_types(&ctx, ¤t_arm);
47 // We check if the following match arms match this one. We could, but don't,
48 // compare to the previous match arm as well.
49 let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
50 .take_while(|arm| match arm.expr() {
51 Some(expr) if arm.guard().is_none() => {
52 let same_text = expr.syntax().text() == current_expr.syntax().text();
57 let arm_types = get_arm_types(&ctx, &arm);
58 for i in 0..arm_types.len() {
59 let other_arm_type = &arm_types[i].as_ref();
60 let current_arm_type = current_arm_types[i].as_ref();
61 if let (Some(other_arm_type), Some(current_arm_type)) = (other_arm_type, current_arm_type) {
62 return &other_arm_type.original == ¤t_arm_type.original;
72 if arms_to_merge.len() <= 1 {
77 AssistId("merge_match_arms", AssistKind::RefactorRewrite),
81 let pats = if arms_to_merge.iter().any(contains_placeholder) {
86 .filter_map(ast::MatchArm::pat)
87 .map(|x| x.syntax().to_string())
88 .collect::<Vec<String>>()
92 let arm = format!("{} => {},", pats, current_expr.syntax().text());
94 if let [first, .., last] = &*arms_to_merge {
95 let start = first.syntax().text_range().start();
96 let end = last.syntax().text_range().end();
98 edit.replace(TextRange::new(start, end), arm);
104 fn contains_placeholder(a: &ast::MatchArm) -> bool {
105 matches!(a.pat(), Some(ast::Pat::WildcardPat(..)))
108 fn get_arm_types(ctx: &AssistContext, arm: &ast::MatchArm) -> Vec<Option<TypeInfo>> {
110 Some(ast::Pat::TupleStructPat(tp)) => tp
114 let pat_type = ctx.sema.type_of_pat(&field);
124 use crate::tests::{check_assist, check_assist_not_applicable};
129 fn merge_match_arms_single_patterns() {
152 X::A | X::B => { 1i32 },
161 fn merge_match_arms_multiple_patterns() {
166 enum X { A, B, C, D, E }
171 X::A | X::B => {$0 1i32 },
172 X::C | X::D => { 1i32 },
179 enum X { A, B, C, D, E }
184 X::A | X::B | X::C | X::D => { 1i32 },
193 fn merge_match_arms_placeholder_pattern() {
198 enum X { A, B, C, D, E }
211 enum X { A, B, C, D, E }
225 fn merges_all_subsequent_arms() {
229 enum X { A, B, C, D, E }
242 enum X { A, B, C, D, E }
246 X::A | X::B | X::C => 92,
256 fn merge_match_arms_rejects_guards() {
257 check_assist_not_applicable(
270 X::A(a) if a > 5 => { $01i32 },
280 fn merge_match_arms_different_type() {
281 check_assist_not_applicable(
283 r#"//- minicore: result
285 match Result::<i32, f32>::Ok(0) {
286 Ok(x) => $0x.to_string(),
287 Err(x) => x.to_string()
296 // match Result::<i32, f32>::Ok(0) {
297 // Ok(x) => x.to_string(),
298 // Err(x) => x.to_string()