2020-12-05 08:41:36 -06:00
|
|
|
use std::iter;
|
|
|
|
|
|
|
|
use ide_db::{ty_filter::TryEnum, RootDatabase};
|
2020-08-12 11:26:51 -05:00
|
|
|
use syntax::{
|
2020-05-09 07:40:11 -05:00
|
|
|
ast::{
|
|
|
|
self,
|
|
|
|
edit::{AstNodeEdit, IndentLevel},
|
|
|
|
make,
|
|
|
|
},
|
2020-02-05 03:50:07 -06:00
|
|
|
AstNode,
|
|
|
|
};
|
2019-01-08 05:21:29 -06:00
|
|
|
|
2020-10-24 02:47:23 -05:00
|
|
|
use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
|
2019-01-08 05:21:29 -06:00
|
|
|
|
2019-10-27 04:22:53 -05:00
|
|
|
// Assist: replace_if_let_with_match
|
|
|
|
//
|
|
|
|
// Replaces `if let` with an else branch with a `match` expression.
|
|
|
|
//
|
|
|
|
// ```
|
|
|
|
// enum Action { Move { distance: u32 }, Stop }
|
|
|
|
//
|
|
|
|
// fn handle(action: Action) {
|
|
|
|
// <|>if let Action::Move { distance } = action {
|
|
|
|
// foo(distance)
|
|
|
|
// } else {
|
|
|
|
// bar()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// ```
|
|
|
|
// ->
|
|
|
|
// ```
|
|
|
|
// enum Action { Move { distance: u32 }, Stop }
|
|
|
|
//
|
|
|
|
// fn handle(action: Action) {
|
|
|
|
// match action {
|
|
|
|
// Action::Move { distance } => foo(distance),
|
|
|
|
// _ => bar(),
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// ```
|
2020-05-06 11:45:35 -05:00
|
|
|
pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
2019-10-27 03:48:40 -05:00
|
|
|
let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
|
2019-01-08 05:21:29 -06:00
|
|
|
let cond = if_expr.condition()?;
|
|
|
|
let pat = cond.pat()?;
|
|
|
|
let expr = cond.expr()?;
|
|
|
|
let then_block = if_expr.then_branch()?;
|
2019-01-26 15:23:07 -06:00
|
|
|
let else_block = match if_expr.else_branch()? {
|
2019-04-02 04:25:24 -05:00
|
|
|
ast::ElseBranch::Block(it) => it,
|
|
|
|
ast::ElseBranch::IfExpr(_) => return None,
|
2019-01-26 15:23:07 -06:00
|
|
|
};
|
2019-01-08 05:21:29 -06:00
|
|
|
|
2020-05-06 05:51:28 -05:00
|
|
|
let target = if_expr.syntax().text_range();
|
2020-06-28 17:36:05 -05:00
|
|
|
acc.add(
|
2020-07-02 16:48:35 -05:00
|
|
|
AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
|
2020-06-28 17:36:05 -05:00
|
|
|
"Replace with match",
|
|
|
|
target,
|
|
|
|
move |edit| {
|
|
|
|
let match_expr = {
|
|
|
|
let then_arm = {
|
|
|
|
let then_block = then_block.reset_indent().indent(IndentLevel(1));
|
|
|
|
let then_expr = unwrap_trivial_block(then_block);
|
|
|
|
make::match_arm(vec![pat.clone()], then_expr)
|
|
|
|
};
|
|
|
|
let else_arm = {
|
|
|
|
let pattern = ctx
|
|
|
|
.sema
|
|
|
|
.type_of_pat(&pat)
|
|
|
|
.and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
|
|
|
|
.map(|it| it.sad_pattern())
|
2020-08-05 12:29:24 -05:00
|
|
|
.unwrap_or_else(|| make::wildcard_pat().into());
|
2020-06-28 17:36:05 -05:00
|
|
|
let else_expr = unwrap_trivial_block(else_block);
|
|
|
|
make::match_arm(vec![pattern], else_expr)
|
|
|
|
};
|
|
|
|
let match_expr =
|
|
|
|
make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
|
|
|
|
match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
|
2020-02-05 03:50:07 -06:00
|
|
|
};
|
2019-01-08 05:21:29 -06:00
|
|
|
|
2020-06-28 17:36:05 -05:00
|
|
|
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
|
|
|
|
},
|
|
|
|
)
|
2019-01-08 05:21:29 -06:00
|
|
|
}
|
|
|
|
|
2020-12-05 08:41:36 -06:00
|
|
|
// 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)
|
2021-01-05 06:45:46 -06:00
|
|
|
if block.statements().count() == 0 && block.tail_expr().is_none() =>
|
2020-12-05 08:41:36 -06:00
|
|
|
{
|
|
|
|
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(_)))
|
|
|
|
}
|
|
|
|
|
2019-01-08 05:21:29 -06:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2020-04-29 04:57:06 -05:00
|
|
|
|
2020-05-06 03:16:55 -05:00
|
|
|
use crate::tests::{check_assist, check_assist_target};
|
2019-01-08 05:21:29 -06:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_replace_if_let_with_match_unwraps_simple_expressions() {
|
|
|
|
check_assist(
|
|
|
|
replace_if_let_with_match,
|
2020-05-20 16:50:29 -05:00
|
|
|
r#"
|
2019-01-08 05:21:29 -06:00
|
|
|
impl VariantData {
|
|
|
|
pub fn is_struct(&self) -> bool {
|
|
|
|
if <|>let VariantData::Struct(..) = *self {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2020-05-20 16:50:29 -05:00
|
|
|
} "#,
|
|
|
|
r#"
|
2019-01-08 05:21:29 -06:00
|
|
|
impl VariantData {
|
|
|
|
pub fn is_struct(&self) -> bool {
|
2020-05-20 16:50:29 -05:00
|
|
|
match *self {
|
2019-01-08 05:21:29 -06:00
|
|
|
VariantData::Struct(..) => true,
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
2020-05-20 16:50:29 -05:00
|
|
|
} "#,
|
2019-01-08 05:21:29 -06:00
|
|
|
)
|
|
|
|
}
|
2019-02-08 17:34:05 -06:00
|
|
|
|
2019-11-20 12:01:06 -06:00
|
|
|
#[test]
|
|
|
|
fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
|
|
|
|
check_assist(
|
|
|
|
replace_if_let_with_match,
|
2020-05-20 16:50:29 -05:00
|
|
|
r#"
|
2019-11-20 12:01:06 -06:00
|
|
|
fn foo() {
|
|
|
|
if <|>let VariantData::Struct(..) = a {
|
|
|
|
bar(
|
|
|
|
123
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
2020-05-20 16:50:29 -05:00
|
|
|
} "#,
|
|
|
|
r#"
|
2019-11-20 12:01:06 -06:00
|
|
|
fn foo() {
|
2020-05-20 16:50:29 -05:00
|
|
|
match a {
|
2019-11-20 12:01:06 -06:00
|
|
|
VariantData::Struct(..) => {
|
|
|
|
bar(
|
|
|
|
123
|
|
|
|
)
|
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
}
|
2020-05-20 16:50:29 -05:00
|
|
|
} "#,
|
2019-11-20 12:01:06 -06:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-02-08 17:34:05 -06:00
|
|
|
#[test]
|
|
|
|
fn replace_if_let_with_match_target() {
|
|
|
|
check_assist_target(
|
|
|
|
replace_if_let_with_match,
|
2020-05-20 16:50:29 -05:00
|
|
|
r#"
|
2019-02-08 17:34:05 -06:00
|
|
|
impl VariantData {
|
|
|
|
pub fn is_struct(&self) -> bool {
|
|
|
|
if <|>let VariantData::Struct(..) = *self {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2020-05-20 16:50:29 -05:00
|
|
|
} "#,
|
2019-02-08 17:34:05 -06:00
|
|
|
"if let VariantData::Struct(..) = *self {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}",
|
|
|
|
);
|
|
|
|
}
|
2020-04-29 04:57:06 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn special_case_option() {
|
|
|
|
check_assist(
|
|
|
|
replace_if_let_with_match,
|
|
|
|
r#"
|
|
|
|
enum Option<T> { Some(T), None }
|
|
|
|
use Option::*;
|
|
|
|
|
|
|
|
fn foo(x: Option<i32>) {
|
|
|
|
<|>if let Some(x) = x {
|
|
|
|
println!("{}", x)
|
|
|
|
} else {
|
|
|
|
println!("none")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
enum Option<T> { Some(T), None }
|
|
|
|
use Option::*;
|
|
|
|
|
|
|
|
fn foo(x: Option<i32>) {
|
2020-05-20 16:50:29 -05:00
|
|
|
match x {
|
2020-04-29 04:57:06 -05:00
|
|
|
Some(x) => println!("{}", x),
|
|
|
|
None => println!("none"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn special_case_result() {
|
|
|
|
check_assist(
|
|
|
|
replace_if_let_with_match,
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
enum Result<T, E> { Ok(T), Err(E) }
|
|
|
|
use Result::*;
|
|
|
|
|
|
|
|
fn foo(x: Result<i32, ()>) {
|
2020-05-20 16:50:29 -05:00
|
|
|
match x {
|
2020-04-29 04:57:06 -05:00
|
|
|
Ok(x) => println!("{}", x),
|
|
|
|
Err(_) => println!("none"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
2020-06-09 05:38:47 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn nested_indent() {
|
|
|
|
check_assist(
|
|
|
|
replace_if_let_with_match,
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-05 08:41:36 -06:00
|
|
|
"#,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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)
|
|
|
|
}
|
|
|
|
}
|
2020-06-09 05:38:47 -05:00
|
|
|
"#,
|
|
|
|
)
|
|
|
|
}
|
2019-01-08 05:21:29 -06:00
|
|
|
}
|