11061: Support "move if to guard" for if else chains r=weirane a=weirane

The idea is to first parse the if else chain into a vector of `(Condition, BlockExpr)`s until we reach an iflet branch, an else branch, or the end (the tail). Then add the match arms with guard for the vector, and add the tail with no if guard.

Because the whole original match arm is replaced and the generated code doesn't have redundent commas, I removed redundent commas in some test cases.

Closes #11033.

Co-authored-by: Wang Ruochen <wrc@ruo-chen.wang>
This commit is contained in:
bors[bot] 2022-01-03 17:59:00 +00:00 committed by GitHub
commit 7409880a07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,7 +1,9 @@
use syntax::{ use syntax::{
ast::{edit::AstNodeEdit, make, AstNode, BlockExpr, ElseBranch, Expr, IfExpr, MatchArm}, ast::{
NodeOrToken, edit::AstNodeEdit, make, AstNode, BlockExpr, Condition, ElseBranch, Expr, IfExpr, MatchArm,
SyntaxKind::{COMMA, WHITESPACE}, Pat,
},
SyntaxKind::WHITESPACE,
}; };
use crate::{AssistContext, AssistId, AssistKind, Assists}; use crate::{AssistContext, AssistId, AssistKind, Assists};
@ -115,73 +117,107 @@ 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 replace_node = replace_node.unwrap_or_else(|| if_expr.syntax().clone());
let needs_dedent = replace_node != *if_expr.syntax();
let cond = if_expr.condition()?; let (conds_blocks, tail) = parse_if_chain(if_expr)?;
let then_block = if_expr.then_branch()?;
// Not support moving if let to arm guard
if cond.is_pattern_cond() {
return None;
}
let buf = format!(" if {}", cond.syntax().text());
acc.add( acc.add(
AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite), AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
"Move condition to match guard", "Move condition to match guard",
replace_node.text_range(), replace_node.text_range(),
|edit| { |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
match &then_block.tail_expr() { let dedent = if needs_dedent {
Some(then_expr) if then_only_expr => { cov_mark::hit!(move_guard_ifelse_in_block);
edit.replace(replace_node.text_range(), then_expr.syntax().text()); 1
// Insert comma for expression if there isn't one } else {
match match_arm.syntax().last_child_or_token() { cov_mark::hit!(move_guard_ifelse_else_block);
Some(NodeOrToken::Token(t)) if t.kind() == COMMA => {} 0
_ => { };
cov_mark::hit!(move_guard_if_add_comma);
edit.insert(match_arm.syntax().text_range().end(), ",");
}
}
}
_ 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)
}
_ => edit.replace(replace_node.text_range(), then_block.syntax().text()),
}
edit.insert(match_pat.syntax().text_range().end(), buf);
// If with only an else branch
if let Some(ElseBranch::Block(else_block)) = if_expr.else_branch() {
let then_arm_end = match_arm.syntax().text_range().end(); let then_arm_end = match_arm.syntax().text_range().end();
let else_only_expr = else_block.statements().next().is_none();
let indent_level = match_arm.indent_level(); let indent_level = match_arm.indent_level();
let spaces = " ".repeat(indent_level.0 as _); let spaces = " ".repeat(indent_level.0 as _);
edit.insert(then_arm_end, format!("\n{}{} => ", spaces, match_pat));
match &else_block.tail_expr() { let mut first = true;
Some(else_expr) if else_only_expr => { for (cond, block) in conds_blocks {
cov_mark::hit!(move_guard_ifelse_expr_only); if !first {
edit.insert(then_arm_end, else_expr.syntax().text()); edit.insert(then_arm_end, format!("\n{}", spaces));
} else {
first = false;
}
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, ","); edit.insert(then_arm_end, ",");
} }
_ if replace_node != *if_expr.syntax() => { _ => {
cov_mark::hit!(move_guard_ifelse_in_block); let to_insert = block.dedent(dedent.into()).syntax().text();
edit.insert(then_arm_end, else_block.dedent(1.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, ",");
} }
_ => { _ => {
cov_mark::hit!(move_guard_ifelse_else_block); let to_insert = e.dedent(dedent.into()).syntax().text();
edit.insert(then_arm_end, else_block.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)),
}
} }
}, },
) )
} }
// Parses an if-else-if chain to get the conditons and the then branches until we encounter an else
// branch or the end.
fn parse_if_chain(if_expr: IfExpr) -> Option<(Vec<(Condition, BlockExpr)>, Option<BlockExpr>)> {
let mut conds_blocks = Vec::new();
let mut curr_if = if_expr;
let tail = loop {
let cond = curr_if.condition()?;
// Not support moving if let to arm guard
if cond.is_pattern_cond() {
return None;
}
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -294,6 +330,7 @@ fn main() {
#[test] #[test]
fn move_arm_cond_in_block_to_match_guard_works() { fn move_arm_cond_in_block_to_match_guard_works() {
cov_mark::check!(move_guard_ifelse_has_wildcard);
check_assist( check_assist(
move_arm_cond_to_match_guard, move_arm_cond_to_match_guard,
r#" r#"
@ -319,9 +356,64 @@ fn main() {
); );
} }
#[test]
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] #[test]
fn move_arm_cond_in_block_to_match_guard_add_comma_works() { fn move_arm_cond_in_block_to_match_guard_add_comma_works() {
cov_mark::check!(move_guard_if_add_comma);
check_assist( check_assist(
move_arm_cond_to_match_guard, move_arm_cond_to_match_guard,
r#" r#"
@ -377,7 +469,7 @@ fn main() {
r#" r#"
fn main() { fn main() {
match 92 { match 92 {
x if x > 10 => { }, x if x > 10 => { }
_ => true _ => true
} }
} }
@ -406,7 +498,7 @@ fn main() {
x if x > 10 => { x if x > 10 => {
92; 92;
false false
}, }
_ => true _ => true
} }
} }
@ -519,7 +611,7 @@ fn main() {
r#" r#"
fn main() { fn main() {
match 92 { match 92 {
x if x > 10 => { }, x if x > 10 => { }
x => { } x => { }
_ => true _ => true
} }
@ -690,6 +782,177 @@ fn main() {
} }
} }
} }
"#,
)
}
#[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_not_applicable(
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
},
}
}
"#,
)
}
#[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 => {}
}
}
"#, "#,
) )
} }