4204: Use specific pattern when translating if-let-else to match r=matklad a=matklad

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.



bors r+
🤖

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2020-04-29 10:01:26 +00:00 committed by GitHub
commit c3dfeba165
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 144 additions and 54 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

@ -1,6 +1,5 @@
use std::iter::once;
use hir::Adt;
use ra_syntax::{
ast::{
self,
@ -12,6 +11,7 @@ use ra_syntax::{
use crate::{
assist_ctx::{Assist, AssistCtx},
utils::TryEnum,
AssistId,
};
@ -45,20 +45,10 @@ 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 enum_ = match ty.as_adt() {
Some(Adt::Enum(it)) => it,
_ => return None,
};
let happy_case =
[("Result", "Ok"), ("Option", "Some")].iter().find_map(|(known_type, happy_case)| {
if &enum_.name(ctx.db).to_string() == known_type {
return Some(happy_case);
}
None
});
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_case {
let with_placeholder: ast::Pat = match happy_variant {
None => make::placeholder_pat().into(),
Some(var_name) => make::tuple_struct_pat(
make::path_unqualified(make::path_segment(make::name_ref(var_name))),

View file

@ -1,12 +1,11 @@
use std::iter;
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_unwrap_with_match
//
@ -38,42 +37,27 @@ 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 = TryEnum::from_ty(ctx.sema, &ty)?.happy_case();
let type_name = ty.as_adt()?.name(ctx.sema.db).to_string();
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)));
let it = make::bind_pat(make::name("a")).into();
let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
for (unwrap_type, variant_name) in [("Result", "Ok"), ("Option", "Some")].iter() {
if &type_name == unwrap_type {
return 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(variant_name)));
let it = make::bind_pat(make::name("a")).into();
let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
let unreachable_call = make::unreachable_macro_call().into();
let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
let unreachable_call = make::unreachable_macro_call().into();
let err_arm = make::match_arm(
iter::once(make::placeholder_pat().into()),
unreachable_call,
);
let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
let match_expr = make::expr_match(caller.clone(), match_arm_list);
let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
let match_expr = make::expr_match(caller.clone(), match_arm_list);
let match_expr =
IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
edit.target(method_call.syntax().text_range());
edit.set_cursor(caller.syntax().text_range().start());
edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
},
);
}
}
None
edit.target(method_call.syntax().text_range());
edit.set_cursor(caller.syntax().text_range().start());
edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
})
}
#[cfg(test)]

View file

@ -1,7 +1,9 @@
//! Assorted functions shared by several assists.
pub(crate) mod insert_use;
use hir::Semantics;
use std::iter;
use hir::{Adt, Semantics, Type};
use ra_ide_db::RootDatabase;
use ra_syntax::{
ast::{self, make, NameOwner},
@ -99,3 +101,51 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
_ => 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",
}
}
}