]> git.lizzy.rs Git - rust.git/commitdiff
Add replace_match_with_if_let assist
authorLukas Wirth <lukastw97@gmail.com>
Sat, 5 Dec 2020 14:41:36 +0000 (15:41 +0100)
committerLukas Wirth <lukastw97@gmail.com>
Sat, 5 Dec 2020 14:41:36 +0000 (15:41 +0100)
crates/assists/src/handlers/early_return.rs
crates/assists/src/handlers/move_guard.rs
crates/assists/src/handlers/replace_if_let_with_match.rs
crates/assists/src/handlers/replace_let_with_if_let.rs
crates/assists/src/lib.rs
crates/assists/src/tests/generated.rs
crates/syntax/src/ast/make.rs

index 7fd78e9d47052d500b1b09d90d11a38fda1a5bee..7bcc318a99d563c621720393791d6d5e33c10c08 100644 (file)
@@ -112,7 +112,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
                         let then_branch =
                             make::block_expr(once(make::expr_stmt(early_expression).into()), None);
                         let cond = invert_boolean_expression(cond_expr);
-                        make::expr_if(make::condition(cond, None), then_branch)
+                        make::expr_if(make::condition(cond, None), then_branch, None)
                             .indent(if_indent_level)
                     };
                     replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
index e1855b63d41a4bcccaba6a766fe59c2c10ddacba..eaffd80ce3b1b6af87e07d4ce01e0972ac986a82 100644 (file)
@@ -42,6 +42,7 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
     let if_expr = make::expr_if(
         make::condition(guard_condition, None),
         make::block_expr(None, Some(arm_expr.clone())),
+        None,
     )
     .indent(arm_expr.indent_level());
 
index 9a49c48c1fae967710c9100e38aa28ec281a471e..4a355c66f03de90384bc7cd06783381f21e90949 100644 (file)
@@ -1,3 +1,6 @@
+use std::iter;
+
+use ide_db::{ty_filter::TryEnum, RootDatabase};
 use syntax::{
     ast::{
         self,
@@ -8,7 +11,6 @@
 };
 
 use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
-use ide_db::ty_filter::TryEnum;
 
 // Assist: replace_if_let_with_match
 //
@@ -79,6 +81,91 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
     )
 }
 
+// Assist: replace_match_with_if_let
+//
+// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
+//
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+//     <|>match action {
+//         Action::Move { distance } => foo(distance),
+//         _ => bar(),
+//     }
+// }
+// ```
+// ->
+// ```
+// enum Action { Move { distance: u32 }, Stop }
+//
+// fn handle(action: Action) {
+//     if let Action::Move { distance } = action {
+//         foo(distance)
+//     } else {
+//         bar()
+//     }
+// }
+// ```
+pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
+    let mut arms = match_expr.match_arm_list()?.arms();
+    let first_arm = arms.next()?;
+    let second_arm = arms.next()?;
+    if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
+        return None;
+    }
+    let condition_expr = match_expr.expr()?;
+    let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
+    {
+        (second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
+    } else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
+        (first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
+    } else {
+        return None;
+    };
+
+    let target = match_expr.syntax().text_range();
+    acc.add(
+        AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
+        "Replace with if let",
+        target,
+        move |edit| {
+            let condition = make::condition(condition_expr, Some(if_let_pat));
+            let then_block = match then_expr.reset_indent() {
+                ast::Expr::BlockExpr(block) => block,
+                expr => make::block_expr(iter::empty(), Some(expr)),
+            };
+            let else_expr = match else_expr {
+                ast::Expr::BlockExpr(block)
+                    if block.statements().count() == 0 && block.expr().is_none() =>
+                {
+                    None
+                }
+                ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
+                expr => Some(expr),
+            };
+            let if_let_expr = make::expr_if(
+                condition,
+                then_block,
+                else_expr.map(|else_expr| {
+                    ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
+                }),
+            )
+            .indent(IndentLevel::from_node(match_expr.syntax()));
+
+            edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
+        },
+    )
+}
+
+fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
+    sema.type_of_pat(&pat)
+        .and_then(|ty| TryEnum::from_ty(sema, &ty))
+        .map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
+        .unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -249,6 +336,194 @@ fn main() {
         }
     }
 }
+"#,
+        )
+    }
+
+    #[test]
+    fn test_replace_match_with_if_let_unwraps_simple_expressions() {
+        check_assist(
+            replace_match_with_if_let,
+            r#"
+impl VariantData {
+    pub fn is_struct(&self) -> bool {
+        <|>match *self {
+            VariantData::Struct(..) => true,
+            _ => false,
+        }
+    }
+}           "#,
+            r#"
+impl VariantData {
+    pub fn is_struct(&self) -> bool {
+        if let VariantData::Struct(..) = *self {
+            true
+        } else {
+            false
+        }
+    }
+}           "#,
+        )
+    }
+
+    #[test]
+    fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
+        check_assist(
+            replace_match_with_if_let,
+            r#"
+fn foo() {
+    <|>match a {
+        VariantData::Struct(..) => {
+            bar(
+                123
+            )
+        }
+        _ => false,
+    }
+}           "#,
+            r#"
+fn foo() {
+    if let VariantData::Struct(..) = a {
+        bar(
+            123
+        )
+    } else {
+        false
+    }
+}           "#,
+        )
+    }
+
+    #[test]
+    fn replace_match_with_if_let_target() {
+        check_assist_target(
+            replace_match_with_if_let,
+            r#"
+impl VariantData {
+    pub fn is_struct(&self) -> bool {
+        <|>match *self {
+            VariantData::Struct(..) => true,
+            _ => false,
+        }
+    }
+}           "#,
+            r#"match *self {
+            VariantData::Struct(..) => true,
+            _ => false,
+        }"#,
+        );
+    }
+
+    #[test]
+    fn special_case_option_match_to_if_let() {
+        check_assist(
+            replace_match_with_if_let,
+            r#"
+enum Option<T> { Some(T), None }
+use Option::*;
+
+fn foo(x: Option<i32>) {
+    <|>match x {
+        Some(x) => println!("{}", x),
+        None => println!("none"),
+    }
+}
+           "#,
+            r#"
+enum Option<T> { Some(T), None }
+use Option::*;
+
+fn foo(x: Option<i32>) {
+    if let Some(x) = x {
+        println!("{}", x)
+    } else {
+        println!("none")
+    }
+}
+           "#,
+        );
+    }
+
+    #[test]
+    fn special_case_result_match_to_if_let() {
+        check_assist(
+            replace_match_with_if_let,
+            r#"
+enum Result<T, E> { Ok(T), Err(E) }
+use Result::*;
+
+fn foo(x: Result<i32, ()>) {
+    <|>match x {
+        Ok(x) => println!("{}", x),
+        Err(_) => println!("none"),
+    }
+}
+           "#,
+            r#"
+enum Result<T, E> { Ok(T), Err(E) }
+use Result::*;
+
+fn foo(x: Result<i32, ()>) {
+    if let Ok(x) = x {
+        println!("{}", x)
+    } else {
+        println!("none")
+    }
+}
+           "#,
+        );
+    }
+
+    #[test]
+    fn nested_indent_match_to_if_let() {
+        check_assist(
+            replace_match_with_if_let,
+            r#"
+fn main() {
+    if true {
+        <|>match path.strip_prefix(root_path) {
+            Ok(rel_path) => {
+                let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
+                Some((*id, rel_path))
+            }
+            _ => None,
+        }
+    }
+}
+"#,
+            r#"
+fn main() {
+    if true {
+        if let Ok(rel_path) = path.strip_prefix(root_path) {
+            let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
+            Some((*id, rel_path))
+        } else {
+            None
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn replace_match_with_if_let_empty_wildcard_expr() {
+        check_assist(
+            replace_match_with_if_let,
+            r#"
+fn main() {
+    <|>match path.strip_prefix(root_path) {
+        Ok(rel_path) => println!("{}", rel_path),
+        _ => (),
+    }
+}
+"#,
+            r#"
+fn main() {
+    if let Ok(rel_path) = path.strip_prefix(root_path) {
+        println!("{}", rel_path)
+    }
+}
 "#,
         )
     }
index 69d3b08d36cb7065fb225cc410b636554703e0e1..5970e283cfb300d30c6331d64906b74ef35b17da 100644 (file)
@@ -60,7 +60,7 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
             };
             let block =
                 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
-            let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block);
+            let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block, None);
             let stmt = make::expr_stmt(if_);
 
             let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap();
index dfe6c27295be181b1876661a91d6b5e02b09a5c5..b8ce7418d6f7e686310ed0c5efc6ca3329fa2313 100644 (file)
@@ -209,6 +209,7 @@ pub(crate) fn all() -> &'static [Handler] {
             reorder_fields::reorder_fields,
             replace_derive_with_manual_impl::replace_derive_with_manual_impl,
             replace_if_let_with_match::replace_if_let_with_match,
+            replace_if_let_with_match::replace_match_with_if_let,
             replace_impl_trait_with_generic::replace_impl_trait_with_generic,
             replace_let_with_if_let::replace_let_with_if_let,
             replace_qualified_name_with_use::replace_qualified_name_with_use,
index 8d50c8791a8d860ebb31a4c53731164a1d95330d..853bde09ca25299939cb8de1ce0374092c09e0fb 100644 (file)
@@ -889,6 +889,34 @@ fn compute() -> Option<i32> { None }
     )
 }
 
+#[test]
+fn doctest_replace_match_with_if_let() {
+    check_doc_test(
+        "replace_match_with_if_let",
+        r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+    <|>match action {
+        Action::Move { distance } => foo(distance),
+        _ => bar(),
+    }
+}
+"#####,
+        r#####"
+enum Action { Move { distance: u32 }, Stop }
+
+fn handle(action: Action) {
+    if let Action::Move { distance } = action {
+        foo(distance)
+    } else {
+        bar()
+    }
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_replace_qualified_name_with_use() {
     check_doc_test(
index 876659a2b76a7796fc14bae095dc947f48e1a498..cc09b77a590348f1add8d7bab956ed813ba20a0a 100644 (file)
@@ -171,8 +171,17 @@ pub fn expr_return() -> ast::Expr {
 pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
     expr_from_text(&format!("match {} {}", expr, match_arm_list))
 }
-pub fn expr_if(condition: ast::Condition, then_branch: ast::BlockExpr) -> ast::Expr {
-    expr_from_text(&format!("if {} {}", condition, then_branch))
+pub fn expr_if(
+    condition: ast::Condition,
+    then_branch: ast::BlockExpr,
+    else_branch: Option<ast::ElseBranch>,
+) -> ast::Expr {
+    let else_branch = match else_branch {
+        Some(ast::ElseBranch::Block(block)) => format!("else {}", block),
+        Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {}", if_expr),
+        None => String::new(),
+    };
+    expr_from_text(&format!("if {} {} {}", condition, then_branch, else_branch))
 }
 pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
     let token = token(op);