| use hir::Semantics; |
| use ide_db::RootDatabase; |
| use stdx::format_to; |
| use syntax::ast::{self, AstNode}; |
| |
| use crate::{AssistContext, AssistId, AssistKind, Assists}; |
| |
| // Assist: convert_two_arm_bool_match_to_matches_macro |
| // |
| // Convert 2-arm match that evaluates to a boolean into the equivalent matches! invocation. |
| // |
| // ``` |
| // fn main() { |
| // match scrutinee$0 { |
| // Some(val) if val.cond() => true, |
| // _ => false, |
| // } |
| // } |
| // ``` |
| // -> |
| // ``` |
| // fn main() { |
| // matches!(scrutinee, Some(val) if val.cond()) |
| // } |
| // ``` |
| pub(crate) fn convert_two_arm_bool_match_to_matches_macro( |
| acc: &mut Assists, |
| ctx: &AssistContext<'_>, |
| ) -> Option<()> { |
| use ArmBodyExpression::*; |
| let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?; |
| let match_arm_list = match_expr.match_arm_list()?; |
| let mut arms = match_arm_list.arms(); |
| let first_arm = arms.next()?; |
| let second_arm = arms.next()?; |
| if arms.next().is_some() { |
| cov_mark::hit!(non_two_arm_match); |
| return None; |
| } |
| let first_arm_expr = first_arm.expr()?; |
| let second_arm_expr = second_arm.expr()?; |
| let first_arm_body = is_bool_literal_expr(&ctx.sema, &first_arm_expr)?; |
| let second_arm_body = is_bool_literal_expr(&ctx.sema, &second_arm_expr)?; |
| |
| if !matches!( |
| (&first_arm_body, &second_arm_body), |
| (Literal(true), Literal(false)) |
| | (Literal(false), Literal(true)) |
| | (Expression(_), Literal(false)) |
| ) { |
| cov_mark::hit!(non_invert_bool_literal_arms); |
| return None; |
| } |
| |
| let target_range = ctx.sema.original_range(match_expr.syntax()).range; |
| let expr = match_expr.expr()?; |
| |
| acc.add( |
| AssistId("convert_two_arm_bool_match_to_matches_macro", AssistKind::RefactorRewrite), |
| "Convert to matches!", |
| target_range, |
| |builder| { |
| let mut arm_str = String::new(); |
| if let Some(pat) = &first_arm.pat() { |
| format_to!(arm_str, "{pat}"); |
| } |
| if let Some(guard) = &first_arm.guard() { |
| arm_str += &format!(" {guard}"); |
| } |
| |
| let replace_with = match (first_arm_body, second_arm_body) { |
| (Literal(true), Literal(false)) => { |
| format!("matches!({expr}, {arm_str})") |
| } |
| (Literal(false), Literal(true)) => { |
| format!("!matches!({expr}, {arm_str})") |
| } |
| (Expression(body_expr), Literal(false)) => { |
| arm_str.push_str(match &first_arm.guard() { |
| Some(_) => " && ", |
| _ => " if ", |
| }); |
| format!("matches!({expr}, {arm_str}{body_expr})") |
| } |
| _ => { |
| unreachable!() |
| } |
| }; |
| builder.replace(target_range, replace_with); |
| }, |
| ) |
| } |
| |
| enum ArmBodyExpression { |
| Literal(bool), |
| Expression(ast::Expr), |
| } |
| |
| fn is_bool_literal_expr( |
| sema: &Semantics<'_, RootDatabase>, |
| expr: &ast::Expr, |
| ) -> Option<ArmBodyExpression> { |
| if let ast::Expr::Literal(lit) = expr { |
| if let ast::LiteralKind::Bool(b) = lit.kind() { |
| return Some(ArmBodyExpression::Literal(b)); |
| } |
| } |
| |
| if !sema.type_of_expr(expr)?.original.is_bool() { |
| return None; |
| } |
| |
| Some(ArmBodyExpression::Expression(expr.clone())) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
| |
| use super::convert_two_arm_bool_match_to_matches_macro; |
| |
| #[test] |
| fn not_applicable_outside_of_range_left() { |
| check_assist_not_applicable( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| $0 match a { |
| Some(_val) => true, |
| _ => false |
| } |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn not_applicable_non_two_arm_match() { |
| cov_mark::check!(non_two_arm_match); |
| check_assist_not_applicable( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| match a$0 { |
| Some(3) => true, |
| Some(4) => true, |
| _ => false |
| } |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn not_applicable_both_false_arms() { |
| cov_mark::check!(non_invert_bool_literal_arms); |
| check_assist_not_applicable( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| match a$0 { |
| Some(val) => false, |
| _ => false |
| } |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn not_applicable_both_true_arms() { |
| cov_mark::check!(non_invert_bool_literal_arms); |
| check_assist_not_applicable( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| match a$0 { |
| Some(val) => true, |
| _ => true |
| } |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn convert_simple_case() { |
| check_assist( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| match a$0 { |
| Some(_val) => true, |
| _ => false |
| } |
| } |
| "#, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| matches!(a, Some(_val)) |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn convert_simple_invert_case() { |
| check_assist( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| match a$0 { |
| Some(_val) => false, |
| _ => true |
| } |
| } |
| "#, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| !matches!(a, Some(_val)) |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn convert_with_guard_case() { |
| check_assist( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| match a$0 { |
| Some(val) if val > 3 => true, |
| _ => false |
| } |
| } |
| "#, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| matches!(a, Some(val) if val > 3) |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn convert_enum_match_cases() { |
| check_assist( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| enum X { A, B } |
| |
| fn foo(a: X) -> bool { |
| match a$0 { |
| X::A => true, |
| _ => false |
| } |
| } |
| "#, |
| r#" |
| enum X { A, B } |
| |
| fn foo(a: X) -> bool { |
| matches!(a, X::A) |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn convert_target_simple() { |
| check_assist_target( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn foo(a: Option<u32>) -> bool { |
| match a$0 { |
| Some(val) => true, |
| _ => false |
| } |
| } |
| "#, |
| r#"match a { |
| Some(val) => true, |
| _ => false |
| }"#, |
| ); |
| } |
| |
| #[test] |
| fn convert_target_complex() { |
| check_assist_target( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| enum E { X, Y } |
| |
| fn main() { |
| match E::X$0 { |
| E::X => true, |
| _ => false, |
| } |
| } |
| "#, |
| "match E::X { |
| E::X => true, |
| _ => false, |
| }", |
| ); |
| } |
| |
| #[test] |
| fn convert_non_literal_bool() { |
| check_assist( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn main() { |
| match 0$0 { |
| a @ 0..15 => a == 0, |
| _ => false, |
| } |
| } |
| "#, |
| r#" |
| fn main() { |
| matches!(0, a @ 0..15 if a == 0) |
| } |
| "#, |
| ); |
| check_assist( |
| convert_two_arm_bool_match_to_matches_macro, |
| r#" |
| fn main() { |
| match 0$0 { |
| a @ 0..15 if thing() => a == 0, |
| _ => false, |
| } |
| } |
| "#, |
| r#" |
| fn main() { |
| matches!(0, a @ 0..15 if thing() && a == 0) |
| } |
| "#, |
| ); |
| } |
| } |