Fix replace-if-let-with-match generates non-exhausive match

This commit is contained in:
yanglsh 2024-12-30 12:07:41 -07:00
parent fc847323f9
commit 60c3fb12d3
2 changed files with 430 additions and 63 deletions

View file

@ -17,7 +17,7 @@ use syntax::{
}; };
use crate::{ use crate::{
utils::{does_nested_pattern, does_pat_match_variant, unwrap_trivial_block}, utils::{does_pat_match_variant, does_pat_variant_nested_or_literal, unwrap_trivial_block},
AssistContext, AssistId, AssistKind, Assists, AssistContext, AssistId, AssistKind, Assists,
}; };
@ -163,7 +163,7 @@ fn make_else_arm(
Some(it) => { Some(it) => {
if does_pat_match_variant(pat, &it.sad_pattern()) { if does_pat_match_variant(pat, &it.sad_pattern()) {
it.happy_pattern_wildcard() it.happy_pattern_wildcard()
} else if does_nested_pattern(pat) { } else if does_pat_variant_nested_or_literal(ctx, pat) {
make::wildcard_pat().into() make::wildcard_pat().into()
} else { } else {
it.sad_pattern() it.sad_pattern()
@ -702,11 +702,11 @@ fn main() {
} }
#[test] #[test]
fn nested_type() { fn test_if_let_with_match_nested_tuple_struct() {
check_assist( check_assist(
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
//- minicore: result //- minicore: result, option
fn foo(x: Result<i32, ()>) { fn foo(x: Result<i32, ()>) {
let bar: Result<_, ()> = Ok(Some(1)); let bar: Result<_, ()> = Ok(Some(1));
$0if let Ok(Some(_)) = bar { $0if let Ok(Some(_)) = bar {
@ -724,12 +724,38 @@ fn foo(x: Result<i32, ()>) {
_ => (), _ => (),
} }
} }
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
struct MyStruct(i32, i32);
fn foo(x: Result<MyStruct, ()>) {
let bar: Result<MyStruct, ()> = Ok(MyStruct(1, 2));
$0if let Ok(MyStruct(a, b)) = bar {
()
} else {
()
}
}
"#,
r#"
struct MyStruct(i32, i32);
fn foo(x: Result<MyStruct, ()>) {
let bar: Result<MyStruct, ()> = Ok(MyStruct(1, 2));
match bar {
Ok(MyStruct(a, b)) => (),
Err(_) => (),
}
}
"#, "#,
); );
} }
#[test] #[test]
fn test_if_let_with_match_variant_nested_or_literal() { fn test_if_let_with_match_nested_slice() {
check_assist( check_assist(
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
@ -758,30 +784,6 @@ fn foo(x: Result<&[i32], ()>) {
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
//- minicore: result //- minicore: result
fn foo(x: Result<&'static str, ()>) {
let bar: Result<&_, ()> = Ok("bar");
$0if let Ok("foo") = bar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<&'static str, ()>) {
let bar: Result<&_, ()> = Ok("bar");
match bar {
Ok("foo") => (),
_ => (),
}
}
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<[&'static str; 2], ()>) { fn foo(x: Result<[&'static str; 2], ()>) {
let foobar: Result<_, ()> = Ok(["foo", "bar"]); let foobar: Result<_, ()> = Ok(["foo", "bar"]);
$0if let Ok([_, "bar"]) = foobar { $0if let Ok([_, "bar"]) = foobar {
@ -806,6 +808,78 @@ fn foo(x: Result<[&'static str; 2], ()>) {
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
//- minicore: result //- minicore: result
fn foo(x: Result<[&'static str; 2], ()>) {
let foobar: Result<_, ()> = Ok(["foo", "bar"]);
$0if let Ok([..]) = foobar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<[&'static str; 2], ()>) {
let foobar: Result<_, ()> = Ok(["foo", "bar"]);
match foobar {
Ok([..]) => (),
Err(_) => (),
}
}
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<&[&'static str], ()>) {
let foobar: Result<&[&'static str], ()> = Ok(&["foo", "bar"]);
$0if let Ok([a, ..]) = foobar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<&[&'static str], ()>) {
let foobar: Result<&[&'static str], ()> = Ok(&["foo", "bar"]);
match foobar {
Ok([a, ..]) => (),
_ => (),
}
}
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<&[&'static str], ()>) {
let foobar: Result<&[&'static str], ()> = Ok(&["foo", "bar"]);
$0if let Ok([a, .., b, c]) = foobar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<&[&'static str], ()>) {
let foobar: Result<&[&'static str], ()> = Ok(&["foo", "bar"]);
match foobar {
Ok([a, .., b, c]) => (),
_ => (),
}
}
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<Option<[&'static str; 2]>, ()>) { fn foo(x: Result<Option<[&'static str; 2]>, ()>) {
let foobar: Result<_, ()> = Ok(Some(["foo", "bar"])); let foobar: Result<_, ()> = Ok(Some(["foo", "bar"]));
$0if let Ok(Some([_, "bar"])) = foobar { $0if let Ok(Some([_, "bar"])) = foobar {
@ -825,7 +899,37 @@ fn foo(x: Result<Option<[&'static str; 2]>, ()>) {
} }
"#, "#,
); );
}
#[test]
fn test_if_let_with_match_nested_literal() {
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<&'static str, ()>) {
let bar: Result<&_, ()> = Ok("bar");
$0if let Ok("foo") = bar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<&'static str, ()>) {
let bar: Result<&_, ()> = Ok("bar");
match bar {
Ok("foo") => (),
_ => (),
}
}
"#,
);
}
#[test]
fn test_if_let_with_match_nested_tuple() {
check_assist( check_assist(
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
@ -854,6 +958,33 @@ fn foo(x: Result<(i32, i32, i32), ()>) {
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
//- minicore: result //- minicore: result
fn foo(x: Result<(i32, i32, i32), ()>) {
let bar: Result<(i32, i32, i32), ()> = Ok((1, 2, 3));
$0if let Ok((first, second, third)) = bar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<(i32, i32, i32), ()>) {
let bar: Result<(i32, i32, i32), ()> = Ok((1, 2, 3));
match bar {
Ok((first, second, third)) => (),
Err(_) => (),
}
}
"#,
);
}
#[test]
fn test_if_let_with_match_nested_or() {
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<i32, ()>) { fn foo(x: Result<i32, ()>) {
let bar: Result<i32, ()> = Ok(1); let bar: Result<i32, ()> = Ok(1);
$0if let Ok(1 | 2) = bar { $0if let Ok(1 | 2) = bar {
@ -878,6 +1009,57 @@ fn foo(x: Result<i32, ()>) {
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
//- minicore: result //- minicore: result
fn foo(x: Result<(i32, i32), ()>) {
let bar: Result<(i32, i32), ()> = Ok((1, 2));
$0if let Ok((b, a) | (a, b)) = bar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<(i32, i32), ()>) {
let bar: Result<(i32, i32), ()> = Ok((1, 2));
match bar {
Ok((b, a) | (a, b)) => (),
Err(_) => (),
}
}
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<(i32, i32), ()>) {
let bar: Result<(i32, i32), ()> = Ok((1, 2));
$0if let Ok((1, a) | (a, 2)) = bar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<(i32, i32), ()>) {
let bar: Result<(i32, i32), ()> = Ok((1, 2));
match bar {
Ok((1, a) | (a, 2)) => (),
_ => (),
}
}
"#,
);
}
#[test]
fn test_if_let_with_match_nested_range() {
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<i32, ()>) { fn foo(x: Result<i32, ()>) {
let bar: Result<i32, ()> = Ok(1); let bar: Result<i32, ()> = Ok(1);
$0if let Ok(1..2) = bar { $0if let Ok(1..2) = bar {
@ -897,7 +1079,10 @@ fn foo(x: Result<i32, ()>) {
} }
"#, "#,
); );
}
#[test]
fn test_if_let_with_match_nested_paren() {
check_assist( check_assist(
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
@ -945,7 +1130,10 @@ fn foo(x: Result<(i32, i32), ()>) {
} }
"#, "#,
); );
}
#[test]
fn test_if_let_with_match_nested_macro() {
check_assist( check_assist(
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
@ -981,7 +1169,10 @@ fn foo(x: Result<i32, ()>) {
} }
"#, "#,
); );
}
#[test]
fn test_if_let_with_match_nested_path() {
check_assist( check_assist(
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
@ -1015,7 +1206,10 @@ fn foo(x: Result<MyEnum, ()>) {
} }
"#, "#,
); );
}
#[test]
fn test_if_let_with_match_nested_record() {
check_assist( check_assist(
replace_if_let_with_match, replace_if_let_with_match,
r#" r#"
@ -1081,6 +1275,149 @@ fn foo(x: Result<MyStruct, ()>) {
_ => (), _ => (),
} }
} }
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
struct MyStruct {
foo: i32,
bar: i32,
}
fn foo(x: Result<MyStruct, ()>) {
let bar: Result<MyStruct, ()> = Ok(MyStruct { foo: 1, bar: 2 });
$0if let Ok(MyStruct { foo, .. }) = bar {
()
} else {
()
}
}
"#,
r#"
struct MyStruct {
foo: i32,
bar: i32,
}
fn foo(x: Result<MyStruct, ()>) {
let bar: Result<MyStruct, ()> = Ok(MyStruct { foo: 1, bar: 2 });
match bar {
Ok(MyStruct { foo, .. }) => (),
Err(_) => (),
}
}
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
enum MyEnum {
Foo(i32, i32),
Bar { a: i32, b: i32 },
}
fn foo(x: Result<MyEnum, ()>) {
let bar: Result<MyEnum, ()> = Ok(MyEnum::Foo(1, 2));
$0if let Ok(MyEnum::Bar { a, b }) = bar {
()
} else {
()
}
}
"#,
r#"
enum MyEnum {
Foo(i32, i32),
Bar { a: i32, b: i32 },
}
fn foo(x: Result<MyEnum, ()>) {
let bar: Result<MyEnum, ()> = Ok(MyEnum::Foo(1, 2));
match bar {
Ok(MyEnum::Bar { a, b }) => (),
_ => (),
}
}
"#,
);
}
#[test]
fn test_if_let_with_match_nested_ident() {
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
let bar: Result<i32, ()> = Ok(1);
$0if let Ok(a @ 1..2) = bar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<i32, ()>) {
let bar: Result<i32, ()> = Ok(1);
match bar {
Ok(a @ 1..2) => (),
_ => (),
}
}
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
let bar: Result<i32, ()> = Ok(1);
$0if let Ok(a) = bar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<i32, ()>) {
let bar: Result<i32, ()> = Ok(1);
match bar {
Ok(a) => (),
Err(_) => (),
}
}
"#,
);
check_assist(
replace_if_let_with_match,
r#"
//- minicore: result
fn foo(x: Result<i32, ()>) {
let bar: Result<i32, ()> = Ok(1);
$0if let Ok(a @ b @ c @ d) = bar {
()
} else {
()
}
}
"#,
r#"
fn foo(x: Result<i32, ()>) {
let bar: Result<i32, ()> = Ok(1);
match bar {
Ok(a @ b @ c @ d) => (),
Err(_) => (),
}
}
"#, "#,
); );
} }

View file

@ -316,43 +316,73 @@ pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool {
pat_head == var_head pat_head == var_head
} }
pub(crate) fn does_nested_pattern(pat: &ast::Pat) -> bool { pub(crate) fn does_pat_variant_nested_or_literal(ctx: &AssistContext<'_>, pat: &ast::Pat) -> bool {
let depth = calc_depth(pat, 0); check_pat_variant_nested_or_literal_with_depth(ctx, pat, 0)
if 1 < depth {
return true;
}
false
} }
fn calc_depth(pat: &ast::Pat, depth: usize) -> usize { fn check_pat_variant_from_enum(ctx: &AssistContext<'_>, pat: &ast::Pat) -> bool {
match pat { ctx.sema.type_of_pat(pat).map_or(true, |ty: hir::TypeInfo| {
ast::Pat::IdentPat(_) ty.adjusted().as_adt().map_or(false, |adt| matches!(adt, hir::Adt::Enum(_)))
| ast::Pat::BoxPat(_) })
| ast::Pat::RestPat(_) }
| ast::Pat::LiteralPat(_)
| ast::Pat::MacroPat(_)
| ast::Pat::OrPat(_)
| ast::Pat::ParenPat(_)
| ast::Pat::PathPat(_)
| ast::Pat::WildcardPat(_)
| ast::Pat::RangePat(_)
| ast::Pat::RecordPat(_)
| ast::Pat::RefPat(_)
| ast::Pat::SlicePat(_)
| ast::Pat::TuplePat(_)
| ast::Pat::ConstBlockPat(_) => depth,
// FIXME: Other patterns may also be nested. Currently it simply supports only `TupleStructPat` fn check_pat_variant_nested_or_literal_with_depth(
ast::Pat::TupleStructPat(pat) => { ctx: &AssistContext<'_>,
let mut max_depth = depth; pat: &ast::Pat,
for p in pat.fields() { depth_after_refutable: usize,
let d = calc_depth(&p, depth + 1); ) -> bool {
if d > max_depth { if depth_after_refutable > 1 {
max_depth = d return true;
} }
}
max_depth match pat {
ast::Pat::RestPat(_) | ast::Pat::WildcardPat(_) | ast::Pat::RefPat(_) => false,
ast::Pat::LiteralPat(_)
| ast::Pat::RangePat(_)
| ast::Pat::MacroPat(_)
| ast::Pat::PathPat(_)
| ast::Pat::BoxPat(_)
| ast::Pat::ConstBlockPat(_) => true,
ast::Pat::IdentPat(ident_pat) => ident_pat.pat().map_or(false, |pat| {
check_pat_variant_nested_or_literal_with_depth(ctx, &pat, depth_after_refutable)
}),
ast::Pat::ParenPat(paren_pat) => paren_pat.pat().map_or(true, |pat| {
check_pat_variant_nested_or_literal_with_depth(ctx, &pat, depth_after_refutable)
}),
ast::Pat::TuplePat(tuple_pat) => tuple_pat.fields().any(|pat| {
check_pat_variant_nested_or_literal_with_depth(ctx, &pat, depth_after_refutable)
}),
ast::Pat::RecordPat(record_pat) => {
let adjusted_next_depth =
depth_after_refutable + if check_pat_variant_from_enum(ctx, pat) { 1 } else { 0 };
record_pat.record_pat_field_list().map_or(true, |pat| {
pat.fields().any(|pat| {
pat.pat().map_or(true, |pat| {
check_pat_variant_nested_or_literal_with_depth(
ctx,
&pat,
adjusted_next_depth,
)
})
})
})
}
ast::Pat::OrPat(or_pat) => or_pat.pats().any(|pat| {
check_pat_variant_nested_or_literal_with_depth(ctx, &pat, depth_after_refutable)
}),
ast::Pat::TupleStructPat(tuple_struct_pat) => {
let adjusted_next_depth =
depth_after_refutable + if check_pat_variant_from_enum(ctx, pat) { 1 } else { 0 };
tuple_struct_pat.fields().any(|pat| {
check_pat_variant_nested_or_literal_with_depth(ctx, &pat, adjusted_next_depth)
})
}
ast::Pat::SlicePat(slice_pat) => {
let mut pats = slice_pat.pats();
pats.next() // Edge case for `[..]`
.map_or(true, |pat| !matches!(pat, ast::Pat::RestPat(_)) || pats.next().is_some())
} }
} }
} }