Use specific pattern when translating if-let-else to match

We *probably* should actually use the same machinery here, as we do
for fill match arms, but just special-casing options and results seems
to be a good first step.
This commit is contained in:
Aleksey Kladov 2020-04-29 11:57:06 +02:00
parent 73bef854ab
commit 7c3c289dab
4 changed files with 124 additions and 21 deletions

View file

@ -1,11 +1,10 @@
use ra_fmt::unwrap_trivial_block;
use ra_syntax::{
ast::{self, make},
ast::{self, edit::IndentLevel, make},
AstNode,
};
use crate::{Assist, AssistCtx, AssistId};
use ast::edit::IndentLevel;
use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
// Assist: replace_if_let_with_match
//
@ -44,15 +43,21 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
ast::ElseBranch::IfExpr(_) => return None,
};
ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| {
let sema = ctx.sema;
ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| {
let match_expr = {
let then_arm = {
let then_expr = unwrap_trivial_block(then_block);
make::match_arm(vec![pat], then_expr)
make::match_arm(vec![pat.clone()], then_expr)
};
let else_arm = {
let pattern = sema
.type_of_pat(&pat)
.and_then(|ty| TryEnum::from_ty(sema, &ty))
.map(|it| it.sad_pattern())
.unwrap_or_else(|| make::placeholder_pat().into());
let else_expr = unwrap_trivial_block(else_block);
make::match_arm(vec![make::placeholder_pat().into()], else_expr)
make::match_arm(vec![pattern], else_expr)
};
make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
};
@ -68,6 +73,7 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::{check_assist, check_assist_target};
#[test]
@ -145,4 +151,64 @@ impl VariantData {
}",
);
}
#[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>) {
<|>match x {
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, ()>) {
<|>match x {
Ok(x) => println!("{}", x),
Err(_) => println!("none"),
}
}
"#,
);
}
}

View file

@ -11,7 +11,7 @@ use ra_syntax::{
use crate::{
assist_ctx::{Assist, AssistCtx},
utils::happy_try_variant,
utils::TryEnum,
AssistId,
};
@ -45,7 +45,7 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
let init = let_stmt.initializer()?;
let original_pat = let_stmt.pat()?;
let ty = ctx.sema.type_of_expr(&init)?;
let happy_variant = happy_try_variant(ctx.sema, &ty);
let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case());
ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| {
let with_placeholder: ast::Pat = match happy_variant {

View file

@ -5,7 +5,7 @@ use ra_syntax::{
AstNode,
};
use crate::{utils::happy_try_variant, Assist, AssistCtx, AssistId};
use crate::{utils::TryEnum, Assist, AssistCtx, AssistId};
// Assist: replace_unwrap_with_match
//
@ -37,7 +37,7 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
}
let caller = method_call.expr()?;
let ty = ctx.sema.type_of_expr(&caller)?;
let happy_variant = happy_try_variant(ctx.sema, &ty)?;
let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case();
ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| {
let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));

View file

@ -1,6 +1,8 @@
//! Assorted functions shared by several assists.
pub(crate) mod insert_use;
use std::iter;
use hir::{Adt, Semantics, Type};
use ra_ide_db::RootDatabase;
use ra_syntax::{
@ -100,15 +102,50 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
}
}
pub(crate) fn happy_try_variant(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<&'static str> {
let enum_ = match ty.as_adt() {
Some(Adt::Enum(it)) => it,
_ => return None,
};
[("Result", "Ok"), ("Option", "Some")].iter().find_map(|(known_type, happy_case)| {
if &enum_.name(sema.db).to_string() == known_type {
return Some(*happy_case);
}
None
})
#[derive(Clone, Copy)]
pub(crate) enum TryEnum {
Result,
Option,
}
impl TryEnum {
const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result];
pub(crate) fn from_ty(sema: &Semantics<RootDatabase>, ty: &Type) -> Option<TryEnum> {
let enum_ = match ty.as_adt() {
Some(Adt::Enum(it)) => it,
_ => return None,
};
TryEnum::ALL.iter().find_map(|&var| {
if &enum_.name(sema.db).to_string() == var.type_name() {
return Some(var);
}
None
})
}
pub(crate) fn happy_case(self) -> &'static str {
match self {
TryEnum::Result => "Ok",
TryEnum::Option => "Some",
}
}
pub(crate) fn sad_pattern(self) -> ast::Pat {
match self {
TryEnum::Result => make::tuple_struct_pat(
make::path_unqualified(make::path_segment(make::name_ref("Err"))),
iter::once(make::placeholder_pat().into()),
)
.into(),
TryEnum::Option => make::bind_pat(make::name("None")).into(),
}
}
fn type_name(self) -> &'static str {
match self {
TryEnum::Result => "Result",
TryEnum::Option => "Option",
}
}
}