2020-02-05 04:53:33 -06:00
|
|
|
use std::iter::successors;
|
|
|
|
|
2022-01-07 17:44:14 -06:00
|
|
|
use hir::{TypeInfo, HirDisplay};
|
|
|
|
use itertools::Itertools;
|
2020-08-12 11:26:51 -05:00
|
|
|
use syntax::{
|
2020-03-19 05:38:26 -05:00
|
|
|
algo::neighbor,
|
2020-02-05 04:53:33 -06:00
|
|
|
ast::{self, AstNode},
|
2020-05-20 16:14:31 -05:00
|
|
|
Direction,
|
2020-02-05 04:53:33 -06:00
|
|
|
};
|
2020-02-05 04:46:05 -06:00
|
|
|
|
2020-06-28 17:36:05 -05:00
|
|
|
use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
|
2019-07-29 15:59:52 -05:00
|
|
|
|
2019-10-27 03:26:46 -05:00
|
|
|
// Assist: merge_match_arms
|
|
|
|
//
|
2021-07-01 14:10:45 -05:00
|
|
|
// Merges the current match arm with the following if their bodies are identical.
|
2019-10-27 03:26:46 -05:00
|
|
|
//
|
|
|
|
// ```
|
|
|
|
// enum Action { Move { distance: u32 }, Stop }
|
|
|
|
//
|
|
|
|
// fn handle(action: Action) {
|
|
|
|
// match action {
|
2021-01-06 14:15:48 -06:00
|
|
|
// $0Action::Move(..) => foo(),
|
2019-10-27 03:26:46 -05:00
|
|
|
// Action::Stop => foo(),
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// ```
|
|
|
|
// ->
|
|
|
|
// ```
|
|
|
|
// enum Action { Move { distance: u32 }, Stop }
|
|
|
|
//
|
|
|
|
// fn handle(action: Action) {
|
|
|
|
// match action {
|
|
|
|
// Action::Move(..) | Action::Stop => foo(),
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// ```
|
2020-05-06 11:45:35 -05:00
|
|
|
pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
2020-02-05 04:46:05 -06:00
|
|
|
let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
|
2019-07-29 15:59:52 -05:00
|
|
|
// Don't try to handle arms with guards for now - can add support for this later
|
2020-02-05 04:53:33 -06:00
|
|
|
if current_arm.guard().is_some() {
|
2019-07-29 15:59:52 -05:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let current_expr = current_arm.expr()?;
|
2020-02-05 04:53:33 -06:00
|
|
|
let current_text_range = current_arm.syntax().text_range();
|
2022-01-07 17:44:14 -06:00
|
|
|
let current_arm_types = get_arm_types(&ctx, ¤t_arm);
|
2020-02-05 06:41:43 -06:00
|
|
|
|
2020-02-05 04:53:33 -06:00
|
|
|
// We check if the following match arms match this one. We could, but don't,
|
|
|
|
// compare to the previous match arm as well.
|
2020-03-19 05:38:26 -05:00
|
|
|
let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
|
2021-07-01 14:10:45 -05:00
|
|
|
.take_while(|arm| match arm.expr() {
|
2022-01-07 17:44:14 -06:00
|
|
|
Some(expr) if arm.guard().is_none() && arm.pat().is_some() => {
|
|
|
|
let same_text = expr.syntax().text() == current_expr.syntax().text();
|
|
|
|
if !same_text {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
let arm_types = get_arm_types(&ctx, &arm);
|
|
|
|
for i in 0..arm_types.len() {
|
|
|
|
let other_arm_type = &arm_types[i].as_ref();
|
|
|
|
let current_arm_type = current_arm_types[i].as_ref();
|
|
|
|
if other_arm_type.is_some() && current_arm_type.is_some() {
|
|
|
|
let other_arm_type = other_arm_type.unwrap().original.clone().as_adt();
|
|
|
|
let current_arm_type = current_arm_type.unwrap().original.clone().as_adt();
|
|
|
|
println!("Same types!");
|
|
|
|
println!("{:?}", other_arm_type);
|
|
|
|
println!("{:?}", current_arm_type);
|
|
|
|
return other_arm_type == current_arm_type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
true
|
2020-02-05 04:53:33 -06:00
|
|
|
}
|
2021-07-01 14:10:45 -05:00
|
|
|
_ => false,
|
2020-02-05 04:53:33 -06:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
if arms_to_merge.len() <= 1 {
|
2019-07-29 15:59:52 -05:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2020-06-28 17:36:05 -05:00
|
|
|
acc.add(
|
2020-07-02 16:48:35 -05:00
|
|
|
AssistId("merge_match_arms", AssistKind::RefactorRewrite),
|
2020-06-28 17:36:05 -05:00
|
|
|
"Merge match arms",
|
|
|
|
current_text_range,
|
|
|
|
|edit| {
|
|
|
|
let pats = if arms_to_merge.iter().any(contains_placeholder) {
|
|
|
|
"_".into()
|
|
|
|
} else {
|
|
|
|
arms_to_merge
|
|
|
|
.iter()
|
|
|
|
.filter_map(ast::MatchArm::pat)
|
|
|
|
.map(|x| x.syntax().to_string())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(" | ")
|
|
|
|
};
|
2019-07-29 15:59:52 -05:00
|
|
|
|
2021-07-30 08:51:07 -05:00
|
|
|
let arm = format!("{} => {},", pats, current_expr.syntax().text());
|
2019-07-29 15:59:52 -05:00
|
|
|
|
2021-07-01 14:10:45 -05:00
|
|
|
if let [first, .., last] = &*arms_to_merge {
|
|
|
|
let start = first.syntax().text_range().start();
|
|
|
|
let end = last.syntax().text_range().end();
|
2019-07-29 15:59:52 -05:00
|
|
|
|
2021-07-01 14:10:45 -05:00
|
|
|
edit.replace(TextRange::new(start, end), arm);
|
|
|
|
}
|
2020-06-28 17:36:05 -05:00
|
|
|
},
|
|
|
|
)
|
2019-07-29 15:59:52 -05:00
|
|
|
}
|
|
|
|
|
2020-02-05 04:46:05 -06:00
|
|
|
fn contains_placeholder(a: &ast::MatchArm) -> bool {
|
2020-07-31 13:07:21 -05:00
|
|
|
matches!(a.pat(), Some(ast::Pat::WildcardPat(..)))
|
2020-02-05 04:46:05 -06:00
|
|
|
}
|
|
|
|
|
2022-01-07 17:44:14 -06:00
|
|
|
fn get_arm_types(ctx: &AssistContext, arm: &ast::MatchArm) -> Vec<Option<TypeInfo>> {
|
|
|
|
match arm.pat() {
|
|
|
|
Some(ast::Pat::TupleStructPat(tp)) => tp
|
|
|
|
.fields()
|
|
|
|
.into_iter()
|
|
|
|
.map(|field| {
|
|
|
|
let pat_type = ctx.sema.type_of_pat(&field);
|
|
|
|
pat_type
|
|
|
|
})
|
|
|
|
.collect_vec(),
|
|
|
|
_ => Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-29 15:59:52 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-05-06 03:16:55 -05:00
|
|
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
2019-07-29 15:59:52 -05:00
|
|
|
|
2020-03-19 05:38:26 -05:00
|
|
|
use super::*;
|
|
|
|
|
2019-07-29 15:59:52 -05:00
|
|
|
#[test]
|
|
|
|
fn merge_match_arms_single_patterns() {
|
|
|
|
check_assist(
|
|
|
|
merge_match_arms,
|
|
|
|
r#"
|
2021-07-01 14:10:45 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum X { A, B, C }
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let x = X::A;
|
|
|
|
let y = match x {
|
|
|
|
X::A => { 1i32$0 }
|
|
|
|
X::B => { 1i32 }
|
|
|
|
X::C => { 2i32 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
2019-07-29 15:59:52 -05:00
|
|
|
r#"
|
2021-07-01 14:10:45 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum X { A, B, C }
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let x = X::A;
|
|
|
|
let y = match x {
|
2021-07-30 08:51:07 -05:00
|
|
|
X::A | X::B => { 1i32 },
|
2021-07-01 14:10:45 -05:00
|
|
|
X::C => { 2i32 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
2019-07-29 15:59:52 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn merge_match_arms_multiple_patterns() {
|
|
|
|
check_assist(
|
|
|
|
merge_match_arms,
|
|
|
|
r#"
|
2021-07-01 14:10:45 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum X { A, B, C, D, E }
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let x = X::A;
|
|
|
|
let y = match x {
|
|
|
|
X::A | X::B => {$0 1i32 },
|
|
|
|
X::C | X::D => { 1i32 },
|
|
|
|
X::E => { 2i32 },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
2019-07-29 15:59:52 -05:00
|
|
|
r#"
|
2021-07-01 14:10:45 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum X { A, B, C, D, E }
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let x = X::A;
|
|
|
|
let y = match x {
|
|
|
|
X::A | X::B | X::C | X::D => { 1i32 },
|
|
|
|
X::E => { 2i32 },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
2019-07-29 15:59:52 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn merge_match_arms_placeholder_pattern() {
|
|
|
|
check_assist(
|
|
|
|
merge_match_arms,
|
|
|
|
r#"
|
2021-07-01 14:10:45 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum X { A, B, C, D, E }
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let x = X::A;
|
|
|
|
let y = match x {
|
|
|
|
X::A => { 1i32 },
|
|
|
|
X::B => { 2i$032 },
|
|
|
|
_ => { 2i32 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
2019-07-29 15:59:52 -05:00
|
|
|
r#"
|
2021-07-01 14:10:45 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum X { A, B, C, D, E }
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let x = X::A;
|
|
|
|
let y = match x {
|
|
|
|
X::A => { 1i32 },
|
2021-07-30 08:51:07 -05:00
|
|
|
_ => { 2i32 },
|
2021-07-01 14:10:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
2019-07-29 15:59:52 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-05 04:53:33 -06:00
|
|
|
#[test]
|
|
|
|
fn merges_all_subsequent_arms() {
|
|
|
|
check_assist(
|
|
|
|
merge_match_arms,
|
|
|
|
r#"
|
2021-07-01 14:10:45 -05:00
|
|
|
enum X { A, B, C, D, E }
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
match X::A {
|
|
|
|
X::A$0 => 92,
|
|
|
|
X::B => 92,
|
|
|
|
X::C => 92,
|
|
|
|
X::D => 62,
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
2020-02-05 04:53:33 -06:00
|
|
|
r#"
|
2021-07-01 14:10:45 -05:00
|
|
|
enum X { A, B, C, D, E }
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
match X::A {
|
|
|
|
X::A | X::B | X::C => 92,
|
|
|
|
X::D => 62,
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
2020-02-05 04:53:33 -06:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-07-29 15:59:52 -05:00
|
|
|
#[test]
|
|
|
|
fn merge_match_arms_rejects_guards() {
|
|
|
|
check_assist_not_applicable(
|
|
|
|
merge_match_arms,
|
|
|
|
r#"
|
2021-07-01 14:10:45 -05:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum X {
|
|
|
|
A(i32),
|
|
|
|
B,
|
|
|
|
C
|
|
|
|
}
|
2019-07-29 15:59:52 -05:00
|
|
|
|
2021-07-01 14:10:45 -05:00
|
|
|
fn main() {
|
|
|
|
let x = X::A;
|
|
|
|
let y = match x {
|
|
|
|
X::A(a) if a > 5 => { $01i32 },
|
|
|
|
X::B => { 1i32 },
|
|
|
|
X::C => { 2i32 }
|
|
|
|
}
|
|
|
|
}
|
2022-01-07 17:44:14 -06:00
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn merge_match_arms_different_type() {
|
|
|
|
check_assist_not_applicable(
|
|
|
|
merge_match_arms,
|
|
|
|
r#"
|
|
|
|
fn func() {
|
|
|
|
match Result::<i32, f32>::Ok(0) {
|
|
|
|
Ok(x) => $0x.to_string(),
|
|
|
|
Err(x) => x.to_string()
|
|
|
|
};
|
|
|
|
}
|
2021-07-01 14:10:45 -05:00
|
|
|
"#,
|
2019-07-29 15:59:52 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-01-07 17:44:14 -06:00
|
|
|
|
|
|
|
// fn func() {
|
|
|
|
// match Result::<i32, f32>::Ok(0) {
|
|
|
|
// Ok(x) => x.to_string(),
|
|
|
|
// Err(x) => x.to_string()
|
|
|
|
// };
|
|
|
|
// }
|