diff --git a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index 56dd6cf29aef..4313cc1ac9dc 100644 --- a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -17,7 +17,7 @@ use syntax::{ }; 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, }; @@ -163,7 +163,7 @@ fn make_else_arm( Some(it) => { if does_pat_match_variant(pat, &it.sad_pattern()) { it.happy_pattern_wildcard() - } else if does_nested_pattern(pat) { + } else if does_pat_variant_nested_or_literal(ctx, pat) { make::wildcard_pat().into() } else { it.sad_pattern() @@ -702,11 +702,11 @@ fn main() { } #[test] - fn nested_type() { + fn test_if_let_with_match_nested_tuple_struct() { check_assist( replace_if_let_with_match, r#" -//- minicore: result +//- minicore: result, option fn foo(x: Result) { let bar: Result<_, ()> = Ok(Some(1)); $0if let Ok(Some(_)) = bar { @@ -724,6 +724,700 @@ fn foo(x: Result) { _ => (), } } +"#, + ); + + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +struct MyStruct(i32, i32); +fn foo(x: Result) { + let bar: Result = Ok(MyStruct(1, 2)); + $0if let Ok(MyStruct(a, b)) = bar { + () + } else { + () + } +} +"#, + r#" +struct MyStruct(i32, i32); +fn foo(x: Result) { + let bar: Result = Ok(MyStruct(1, 2)); + match bar { + Ok(MyStruct(a, b)) => (), + Err(_) => (), + } +} +"#, + ); + } + + #[test] + fn test_if_let_with_match_nested_slice() { + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +fn foo(x: Result<&[i32], ()>) { + let foo: Result<&[_], ()> = Ok(&[0, 1, 2]); + $0if let Ok([]) = foo { + () + } else { + () + } +} + "#, + r#" +fn foo(x: Result<&[i32], ()>) { + let foo: Result<&[_], ()> = Ok(&[0, 1, 2]); + match foo { + Ok([]) => (), + _ => (), + } +} + "#, + ); + + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +fn foo(x: Result<[&'static str; 2], ()>) { + let foobar: Result<_, ()> = Ok(["foo", "bar"]); + $0if let Ok([_, "bar"]) = foobar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result<[&'static str; 2], ()>) { + let foobar: Result<_, ()> = Ok(["foo", "bar"]); + match foobar { + Ok([_, "bar"]) => (), + _ => (), + } +} +"#, + ); + + check_assist( + replace_if_let_with_match, + r#" +//- 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, ()>) { + let foobar: Result<_, ()> = Ok(Some(["foo", "bar"])); + $0if let Ok(Some([_, "bar"])) = foobar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result, ()>) { + let foobar: Result<_, ()> = Ok(Some(["foo", "bar"])); + match foobar { + Ok(Some([_, "bar"])) => (), + _ => (), + } +} +"#, + ); + } + + #[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( + replace_if_let_with_match, + r#" +//- minicore: result +fn foo(x: Result<(i32, i32, i32), ()>) { + let bar: Result<(i32, i32, i32), ()> = Ok((1, 2, 3)); + $0if let Ok((1, 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((1, second, third)) => (), + _ => (), + } +} +"#, + ); + + check_assist( + replace_if_let_with_match, + r#" +//- 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) { + let bar: Result = Ok(1); + $0if let Ok(1 | 2) = bar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result) { + let bar: Result = Ok(1); + match bar { + Ok(1 | 2) => (), + _ => (), + } +} +"#, + ); + + 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((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) { + let bar: Result = Ok(1); + $0if let Ok(1..2) = bar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result) { + let bar: Result = Ok(1); + match bar { + Ok(1..2) => (), + _ => (), + } +} +"#, + ); + } + + #[test] + fn test_if_let_with_match_nested_paren() { + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +fn foo(x: Result<(i32, i32), ()>) { + let bar: Result<(i32, i32), ()> = Ok((1, 1)); + $0if let Ok(((1, 2))) = bar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result<(i32, i32), ()>) { + let bar: Result<(i32, i32), ()> = Ok((1, 1)); + match bar { + Ok(((1, 2))) => (), + _ => (), + } +} +"#, + ); + + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +fn foo(x: Result<(i32, i32), ()>) { + let bar: Result<(i32, i32), ()> = Ok((1, 1)); + $0if let Ok(((a, b))) = bar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result<(i32, i32), ()>) { + let bar: Result<(i32, i32), ()> = Ok((1, 1)); + match bar { + Ok(((a, b))) => (), + Err(_) => (), + } +} +"#, + ); + } + + #[test] + fn test_if_let_with_match_nested_macro() { + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +fn foo(x: Result) { + macro_rules! is_42 { + () => { + 42 + }; + } + + let bar: Result = Ok(1); + $0if let Ok(is_42!()) = bar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result) { + macro_rules! is_42 { + () => { + 42 + }; + } + + let bar: Result = Ok(1); + match bar { + Ok(is_42!()) => (), + _ => (), + } +} +"#, + ); + } + + #[test] + fn test_if_let_with_match_nested_path() { + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +enum MyEnum { + Foo, + Bar, +} + +fn foo(x: Result) { + let bar: Result = Ok(MyEnum::Foo); + $0if let Ok(MyEnum::Foo) = bar { + () + } else { + () + } +} +"#, + r#" +enum MyEnum { + Foo, + Bar, +} + +fn foo(x: Result) { + let bar: Result = Ok(MyEnum::Foo); + match bar { + Ok(MyEnum::Foo) => (), + _ => (), + } +} +"#, + ); + } + + #[test] + fn test_if_let_with_match_nested_record() { + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +struct MyStruct { + foo: i32, + bar: i32, +} + +fn foo(x: Result) { + let bar: Result = Ok(MyStruct { foo: 1, bar: 2 }); + $0if let Ok(MyStruct { foo, bar }) = bar { + () + } else { + () + } +} +"#, + r#" +struct MyStruct { + foo: i32, + bar: i32, +} + +fn foo(x: Result) { + let bar: Result = Ok(MyStruct { foo: 1, bar: 2 }); + match bar { + Ok(MyStruct { foo, bar }) => (), + Err(_) => (), + } +} +"#, + ); + + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +struct MyStruct { + foo: i32, + bar: i32, +} + +fn foo(x: Result) { + let bar: Result = Ok(MyStruct { foo: 1, bar: 2 }); + $0if let Ok(MyStruct { foo, bar: 12 }) = bar { + () + } else { + () + } +} +"#, + r#" +struct MyStruct { + foo: i32, + bar: i32, +} + +fn foo(x: Result) { + let bar: Result = Ok(MyStruct { foo: 1, bar: 2 }); + match bar { + Ok(MyStruct { foo, bar: 12 }) => (), + _ => (), + } +} +"#, + ); + + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +struct MyStruct { + foo: i32, + bar: i32, +} + +fn foo(x: Result) { + let bar: Result = Ok(MyStruct { foo: 1, bar: 2 }); + $0if let Ok(MyStruct { foo, .. }) = bar { + () + } else { + () + } +} +"#, + r#" +struct MyStruct { + foo: i32, + bar: i32, +} + +fn foo(x: Result) { + let bar: Result = 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) { + let bar: Result = 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) { + let bar: Result = 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) { + let bar: Result = Ok(1); + $0if let Ok(a @ 1..2) = bar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result) { + let bar: Result = Ok(1); + match bar { + Ok(a @ 1..2) => (), + _ => (), + } +} +"#, + ); + + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +fn foo(x: Result) { + let bar: Result = Ok(1); + $0if let Ok(a) = bar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result) { + let bar: Result = Ok(1); + match bar { + Ok(a) => (), + Err(_) => (), + } +} +"#, + ); + + check_assist( + replace_if_let_with_match, + r#" +//- minicore: result +fn foo(x: Result) { + let bar: Result = Ok(1); + $0if let Ok(a @ b @ c @ d) = bar { + () + } else { + () + } +} +"#, + r#" +fn foo(x: Result) { + let bar: Result = Ok(1); + match bar { + Ok(a @ b @ c @ d) => (), + Err(_) => (), + } +} "#, ); } diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index 3c26b0435977..ce0bd46e6f3f 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -316,43 +316,73 @@ pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool { pat_head == var_head } -pub(crate) fn does_nested_pattern(pat: &ast::Pat) -> bool { - let depth = calc_depth(pat, 0); +pub(crate) fn does_pat_variant_nested_or_literal(ctx: &AssistContext<'_>, pat: &ast::Pat) -> bool { + check_pat_variant_nested_or_literal_with_depth(ctx, pat, 0) +} + +fn check_pat_variant_from_enum(ctx: &AssistContext<'_>, pat: &ast::Pat) -> bool { + ctx.sema.type_of_pat(pat).map_or(true, |ty: hir::TypeInfo| { + ty.adjusted().as_adt().map_or(false, |adt| matches!(adt, hir::Adt::Enum(_))) + }) +} - if 1 < depth { +fn check_pat_variant_nested_or_literal_with_depth( + ctx: &AssistContext<'_>, + pat: &ast::Pat, + depth_after_refutable: usize, +) -> bool { + if depth_after_refutable > 1 { return true; } - false -} -fn calc_depth(pat: &ast::Pat, depth: usize) -> usize { match pat { - ast::Pat::IdentPat(_) - | ast::Pat::BoxPat(_) - | ast::Pat::RestPat(_) - | ast::Pat::LiteralPat(_) + ast::Pat::RestPat(_) | ast::Pat::WildcardPat(_) | ast::Pat::RefPat(_) => false, + + ast::Pat::LiteralPat(_) + | ast::Pat::RangePat(_) | 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` - ast::Pat::TupleStructPat(pat) => { - let mut max_depth = depth; - for p in pat.fields() { - let d = calc_depth(&p, depth + 1); - if d > max_depth { - max_depth = d - } - } - max_depth + | 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()) } } }