]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_assists/src/handlers/move_guard.rs
Merge #11461
[rust.git] / crates / ide_assists / src / handlers / move_guard.rs
index d7e7c363af622513592bd6a7043acc902524eb0e..5c05cb921d9dcd66eae026c061f2e18ad686b89b 100644 (file)
@@ -1,5 +1,5 @@
 use syntax::{
-    ast::{edit::AstNodeEdit, make, AstNode, BlockExpr, Expr, IfExpr, MatchArm},
+    ast::{edit::AstNodeEdit, make, AstNode, BlockExpr, ElseBranch, Expr, IfExpr, MatchArm, Pat},
     SyntaxKind::WHITESPACE,
 };
 
@@ -41,18 +41,11 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
     }
     let space_before_guard = guard.syntax().prev_sibling_or_token();
 
-    // FIXME: support `if let` guards too
-    if guard.let_token().is_some() {
-        return None;
-    }
-    let guard_condition = guard.expr()?;
+    let guard_condition = guard.condition()?;
     let arm_expr = match_arm.expr()?;
-    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());
+    let if_expr =
+        make::expr_if(guard_condition, make::block_expr(None, Some(arm_expr.clone())), None)
+            .indent(arm_expr.indent_level());
 
     let target = guard.syntax().text_range();
     acc.add(
@@ -114,45 +107,103 @@ pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContex
         }
     })?;
     let replace_node = replace_node.unwrap_or_else(|| if_expr.syntax().clone());
-
-    let cond = if_expr.condition()?;
-    let then_block = if_expr.then_branch()?;
-
-    // Not support if with else branch
-    if if_expr.else_branch().is_some() {
-        return None;
-    }
-    // Not support moving if let to arm guard
-    if cond.is_pattern_cond() {
-        return None;
-    }
-
-    let buf = format!(" if {}", cond.syntax().text());
+    let needs_dedent = replace_node != *if_expr.syntax();
+    let (conds_blocks, tail) = parse_if_chain(if_expr)?;
 
     acc.add(
         AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
         "Move condition to match guard",
         replace_node.text_range(),
         |edit| {
-            let then_only_expr = then_block.statements().next().is_none();
+            edit.delete(match_arm.syntax().text_range());
+            // Dedent if if_expr is in a BlockExpr
+            let dedent = if needs_dedent {
+                cov_mark::hit!(move_guard_ifelse_in_block);
+                1
+            } else {
+                cov_mark::hit!(move_guard_ifelse_else_block);
+                0
+            };
+            let then_arm_end = match_arm.syntax().text_range().end();
+            let indent_level = match_arm.indent_level();
+            let spaces = "    ".repeat(indent_level.0 as _);
 
-            match &then_block.tail_expr() {
-                Some(then_expr) if then_only_expr => {
-                    edit.replace(replace_node.text_range(), then_expr.syntax().text())
+            let mut first = true;
+            for (cond, block) in conds_blocks {
+                if !first {
+                    edit.insert(then_arm_end, format!("\n{}", spaces));
+                } else {
+                    first = false;
                 }
-                _ if replace_node != *if_expr.syntax() => {
-                    // Dedent because if_expr is in a BlockExpr
-                    let replace_with = then_block.dedent(1.into()).syntax().text();
-                    edit.replace(replace_node.text_range(), replace_with)
+                let guard = format!("{} if {} => ", match_pat, cond.syntax().text());
+                edit.insert(then_arm_end, guard);
+                let only_expr = block.statements().next().is_none();
+                match &block.tail_expr() {
+                    Some(then_expr) if only_expr => {
+                        edit.insert(then_arm_end, then_expr.syntax().text());
+                        edit.insert(then_arm_end, ",");
+                    }
+                    _ => {
+                        let to_insert = block.dedent(dedent.into()).syntax().text();
+                        edit.insert(then_arm_end, to_insert)
+                    }
+                }
+            }
+            if let Some(e) = tail {
+                cov_mark::hit!(move_guard_ifelse_else_tail);
+                let guard = format!("\n{}{} => ", spaces, match_pat);
+                edit.insert(then_arm_end, guard);
+                let only_expr = e.statements().next().is_none();
+                match &e.tail_expr() {
+                    Some(expr) if only_expr => {
+                        cov_mark::hit!(move_guard_ifelse_expr_only);
+                        edit.insert(then_arm_end, expr.syntax().text());
+                        edit.insert(then_arm_end, ",");
+                    }
+                    _ => {
+                        let to_insert = e.dedent(dedent.into()).syntax().text();
+                        edit.insert(then_arm_end, to_insert)
+                    }
+                }
+            } else {
+                // There's no else branch. Add a pattern without guard, unless the following match
+                // arm is `_ => ...`
+                cov_mark::hit!(move_guard_ifelse_notail);
+                match match_arm.syntax().next_sibling().and_then(MatchArm::cast) {
+                    Some(next_arm)
+                        if matches!(next_arm.pat(), Some(Pat::WildcardPat(_)))
+                            && next_arm.guard().is_none() =>
+                    {
+                        cov_mark::hit!(move_guard_ifelse_has_wildcard);
+                    }
+                    _ => edit.insert(then_arm_end, format!("\n{}{} => {{}}", spaces, match_pat)),
                 }
-                _ => edit.replace(replace_node.text_range(), then_block.syntax().text()),
             }
-
-            edit.insert(match_pat.syntax().text_range().end(), buf);
         },
     )
 }
 
+// Parses an if-else-if chain to get the conditions and the then branches until we encounter an else
+// branch or the end.
+fn parse_if_chain(if_expr: IfExpr) -> Option<(Vec<(Expr, BlockExpr)>, Option<BlockExpr>)> {
+    let mut conds_blocks = Vec::new();
+    let mut curr_if = if_expr;
+    let tail = loop {
+        let cond = curr_if.condition()?;
+        conds_blocks.push((cond, curr_if.then_branch()?));
+        match curr_if.else_branch() {
+            Some(ElseBranch::IfExpr(e)) => {
+                curr_if = e;
+            }
+            Some(ElseBranch::Block(b)) => {
+                break Some(b);
+            }
+            None => break None,
+        }
+    };
+    Some((conds_blocks, tail))
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -215,6 +266,31 @@ fn main() {
         );
     }
 
+    #[test]
+    fn move_let_guard_to_arm_body_works() {
+        check_assist(
+            move_guard_to_arm_body,
+            r#"
+fn main() {
+    match 92 {
+        x $0if (let 1 = x) => false,
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x => if (let 1 = x) {
+            false
+        },
+        _ => true
+    }
+}
+"#,
+        );
+    }
+
     #[test]
     fn move_guard_to_arm_body_works_complex_match() {
         check_assist(
@@ -265,6 +341,7 @@ fn main() {
 
     #[test]
     fn move_arm_cond_in_block_to_match_guard_works() {
+        cov_mark::check!(move_guard_ifelse_has_wildcard);
         check_assist(
             move_arm_cond_to_match_guard,
             r#"
@@ -291,13 +368,104 @@ fn main() {
     }
 
     #[test]
-    fn move_arm_cond_to_match_guard_if_let_not_works() {
-        check_assist_not_applicable(
+    fn move_arm_cond_in_block_to_match_guard_no_wildcard_works() {
+        cov_mark::check_count!(move_guard_ifelse_has_wildcard, 0);
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        x => {
+            $0if x > 10 {
+                false
+            }
+        }
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if x > 10 => false,
+        x => {}
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn move_arm_cond_in_block_to_match_guard_wildcard_guard_works() {
+        cov_mark::check_count!(move_guard_ifelse_has_wildcard, 0);
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        x => {
+            $0if x > 10 {
+                false
+            }
+        }
+        _ if x > 10 => true,
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if x > 10 => false,
+        x => {}
+        _ if x > 10 => true,
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn move_arm_cond_in_block_to_match_guard_add_comma_works() {
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        x => {
+            $0if x > 10 {
+                false
+            }
+        }
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if x > 10 => false,
+        _ => true
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_if_let_works() {
+        check_assist(
             move_arm_cond_to_match_guard,
             r#"
 fn main() {
     match 92 {
-        x => if let 62 = x { $0false },
+        x => if let 62 = x && true { $0false },
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if let 62 = x && true => false,
         _ => true
     }
 }
@@ -320,7 +488,7 @@ fn main() {
             r#"
 fn main() {
     match 92 {
-        x if x > 10 => {  },
+        x if x > 10 => {  }
         _ => true
     }
 }
@@ -349,7 +517,7 @@ fn main() {
         x if x > 10 => {
             92;
             false
-        },
+        }
         _ => true
     }
 }
@@ -384,6 +552,438 @@ fn main() {
         _ => true
     }
 }
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_with_else_works() {
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        x => if x > 10 {$0
+            false
+        } else {
+            true
+        }
+        _ => true,
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if x > 10 => false,
+        x => true,
+        _ => true,
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_with_else_block_works() {
+        cov_mark::check!(move_guard_ifelse_expr_only);
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        x => {
+            if x > 10 {$0
+                false
+            } else {
+                true
+            }
+        }
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if x > 10 => false,
+        x => true,
+        _ => true
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_else_if_empty_body_works() {
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        x => if x > 10 { $0 } else { },
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if x > 10 => {  }
+        x => { }
+        _ => true
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_with_else_multiline_works() {
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        x => if x > 10 {
+            92;$0
+            false
+        } else {
+            true
+        }
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if x > 10 => {
+            92;
+            false
+        }
+        x => true,
+        _ => true
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_with_else_multiline_else_works() {
+        cov_mark::check!(move_guard_ifelse_else_block);
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        x => if x > 10 {$0
+            false
+        } else {
+            42;
+            true
+        }
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if x > 10 => false,
+        x => {
+            42;
+            true
+        }
+        _ => true
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_with_else_multiline_else_block_works() {
+        cov_mark::check!(move_guard_ifelse_in_block);
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        x => {
+            if x > 10 {$0
+                false
+            } else {
+                42;
+                true
+            }
+        }
+        _ => true
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        x if x > 10 => false,
+        x => {
+            42;
+            true
+        }
+        _ => true
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_with_else_last_arm_works() {
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        3 => true,
+        x => {
+            if x > 10 {$0
+                false
+            } else {
+                92;
+                true
+            }
+        }
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        3 => true,
+        x if x > 10 => false,
+        x => {
+            92;
+            true
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_with_else_comma_works() {
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        3 => true,
+        x => if x > 10 {$0
+            false
+        } else {
+            92;
+            true
+        },
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        3 => true,
+        x if x > 10 => false,
+        x => {
+            92;
+            true
+        }
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_elseif() {
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        3 => true,
+        x => if x > 10 {$0
+            false
+        } else if x > 5 {
+            true
+        } else if x > 4 {
+            false
+        } else {
+            true
+        },
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        3 => true,
+        x if x > 10 => false,
+        x if x > 5 => true,
+        x if x > 4 => false,
+        x => true,
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_elseif_in_block() {
+        cov_mark::check!(move_guard_ifelse_in_block);
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        3 => true,
+        x => {
+            if x > 10 {$0
+                false
+            } else if x > 5 {
+                true
+            } else if x > 4 {
+                false
+            } else {
+                true
+            }
+        }
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        3 => true,
+        x if x > 10 => false,
+        x if x > 5 => true,
+        x if x > 4 => false,
+        x => true,
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_elseif_chain() {
+        cov_mark::check!(move_guard_ifelse_else_tail);
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        3 => 0,
+        x => if x > 10 {$0
+            1
+        } else if x > 5 {
+            2
+        } else if x > 3 {
+            42;
+            3
+        } else {
+            4
+        },
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        3 => 0,
+        x if x > 10 => 1,
+        x if x > 5 => 2,
+        x if x > 3 => {
+            42;
+            3
+        }
+        x => 4,
+    }
+}
+"#,
+        )
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_elseif_iflet() {
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        3 => 0,
+        x => if x > 10 {$0
+            1
+        } else if x > 5 {
+            2
+        } else if let 4 = 4 {
+            42;
+            3
+        } else {
+            4
+        },
+    }
+}"#,
+            r#"
+fn main() {
+    match 92 {
+        3 => 0,
+        x if x > 10 => 1,
+        x if x > 5 => 2,
+        x if let 4 = 4 => {
+            42;
+            3
+        }
+        x => 4,
+    }
+}"#,
+        );
+    }
+
+    #[test]
+    fn move_arm_cond_to_match_guard_elseif_notail() {
+        cov_mark::check!(move_guard_ifelse_notail);
+        check_assist(
+            move_arm_cond_to_match_guard,
+            r#"
+fn main() {
+    match 92 {
+        3 => 0,
+        x => if x > 10 {$0
+            1
+        } else if x > 5 {
+            2
+        } else if x > 4 {
+            42;
+            3
+        },
+    }
+}
+"#,
+            r#"
+fn main() {
+    match 92 {
+        3 => 0,
+        x if x > 10 => 1,
+        x if x > 5 => 2,
+        x if x > 4 => {
+            42;
+            3
+        }
+        x => {}
+    }
+}
 "#,
         )
     }