From 4ad546f6a662684522159dfcaf24bf43d20b352b Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 2 Mar 2025 09:49:52 +0100 Subject: [PATCH] Support tuple struct patterns for expand_rest_pattern assist --- crates/hir/src/lib.rs | 2 + .../src/handlers/expand_rest_pattern.rs | 187 ++++++++++++++++-- crates/ide-assists/src/tests/generated.rs | 25 ++- docs/book/src/assists_generated.md | 28 ++- 4 files changed, 217 insertions(+), 25 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 55448d4ae8..7549d54a54 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -296,6 +296,7 @@ pub enum ModuleDef { Function(Function), Adt(Adt), // Can't be directly declared, but can be imported. + // FIXME: Rename to `EnumVariant` Variant(Variant), Const(Const), Static(Static), @@ -1564,6 +1565,7 @@ impl From<&Variant> for DefWithBodyId { } } +// FIXME: Rename to `EnumVariant` #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Variant { pub(crate) id: EnumVariantId, diff --git a/crates/ide-assists/src/handlers/expand_rest_pattern.rs b/crates/ide-assists/src/handlers/expand_rest_pattern.rs index 315d7e727b..c79a982c38 100644 --- a/crates/ide-assists/src/handlers/expand_rest_pattern.rs +++ b/crates/ide-assists/src/handlers/expand_rest_pattern.rs @@ -1,3 +1,5 @@ +use hir::{PathResolution, StructKind}; +use ide_db::syntax_helpers::suggest_name::NameGenerator; use syntax::{ ast::{self, make}, match_ast, AstNode, ToSmolStr, @@ -5,7 +7,23 @@ use syntax::{ use crate::{AssistContext, AssistId, Assists}; -// Assist: expand_rest_pattern +pub(crate) fn expand_rest_pattern(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let rest_pat = ctx.find_node_at_offset::()?; + let parent = rest_pat.syntax().parent()?; + match_ast! { + match parent { + ast::RecordPatFieldList(it) => expand_record_rest_pattern(acc, ctx, it.syntax().parent().and_then(ast::RecordPat::cast)?, rest_pat), + ast::TupleStructPat(it) => expand_tuple_struct_rest_pattern(acc, ctx, it, rest_pat), + // FIXME + // ast::TuplePat(it) => (), + // FIXME + // ast::SlicePat(it) => (), + _ => return None, + } + } +} + +// Assist: expand_record_rest_pattern // // Fills fields by replacing rest pattern in record patterns. // @@ -24,22 +42,12 @@ use crate::{AssistContext, AssistId, Assists}; // let Bar { y, z } = bar; // } // ``` -pub(crate) fn expand_rest_pattern(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let rest_pat = ctx.find_node_at_offset::()?; - let parent = rest_pat.syntax().parent()?; - let record_pat = match_ast! { - match parent { - ast::RecordPatFieldList(it) => ast::RecordPat::cast(it.syntax().parent()?)?, - // ast::TupleStructPat(it) => (), - // ast::TuplePat(it) => (), - // ast::SlicePat(it) => (), - _ => return None, - } - }; - - let ellipsis = record_pat.record_pat_field_list().and_then(|r| r.rest_pat())?; - let target_range = ellipsis.syntax().text_range(); - +fn expand_record_rest_pattern( + acc: &mut Assists, + ctx: &AssistContext<'_>, + record_pat: ast::RecordPat, + rest_pat: ast::RestPat, +) -> Option<()> { let missing_fields = ctx.sema.record_pattern_missing_fields(&record_pat); if missing_fields.is_empty() { @@ -48,6 +56,11 @@ pub(crate) fn expand_rest_pattern(acc: &mut Assists, ctx: &AssistContext<'_>) -> } let old_field_list = record_pat.record_pat_field_list()?; + let old_range = ctx.sema.original_range_opt(old_field_list.syntax())?; + if old_range.file_id != ctx.file_id() { + return None; + } + let new_field_list = make::record_pat_field_list(old_field_list.fields(), None).clone_for_update(); for (f, _) in missing_fields.iter() { @@ -58,16 +71,93 @@ pub(crate) fn expand_rest_pattern(acc: &mut Assists, ctx: &AssistContext<'_>) -> new_field_list.add_field(field.clone_for_update()); } - let old_range = ctx.sema.original_range_opt(old_field_list.syntax())?; + let target_range = rest_pat.syntax().text_range(); + acc.add( + AssistId("expand_record_rest_pattern", crate::AssistKind::RefactorRewrite), + "Fill struct fields", + target_range, + move |builder| builder.replace_ast(old_field_list, new_field_list), + ) +} +// Assist: expand_tuple_struct_rest_pattern +// +// Fills fields by replacing rest pattern in tuple struct patterns. +// +// ``` +// struct Bar(Y, Z); +// +// fn foo(bar: Bar) { +// let Bar(..$0) = bar; +// } +// ``` +// -> +// ``` +// struct Bar(Y, Z); +// +// fn foo(bar: Bar) { +// let Bar(_0, _1) = bar; +// } +// ``` +fn expand_tuple_struct_rest_pattern( + acc: &mut Assists, + ctx: &AssistContext<'_>, + pat: ast::TupleStructPat, + rest_pat: ast::RestPat, +) -> Option<()> { + let path = pat.path()?; + let fields = match ctx.sema.type_of_pat(&pat.clone().into())?.original.as_adt()? { + hir::Adt::Struct(s) if s.kind(ctx.sema.db) == StructKind::Tuple => s.fields(ctx.sema.db), + hir::Adt::Enum(_) => match ctx.sema.resolve_path(&path)? { + PathResolution::Def(hir::ModuleDef::Variant(v)) + if v.kind(ctx.sema.db) == StructKind::Tuple => + { + v.fields(ctx.sema.db) + } + _ => return None, + }, + _ => return None, + }; + + let rest_pat = rest_pat.into(); + let mut pats = pat.fields(); + let prefix_count = pats.by_ref().position(|p| p == rest_pat)?; + let suffix_count = pats.count(); + + if fields.len().saturating_sub(prefix_count).saturating_sub(suffix_count) == 0 { + cov_mark::hit!(no_missing_fields_tuple_struct); + return None; + } + + let old_range = ctx.sema.original_range_opt(pat.syntax())?; if old_range.file_id != ctx.file_id() { return None; } + let mut name_gen = NameGenerator::new_from_scope_locals(ctx.sema.scope(pat.syntax())); + let new_pat = make::tuple_struct_pat( + path, + pat.fields() + .take(prefix_count) + .chain(fields[prefix_count..fields.len() - suffix_count].iter().map(|f| { + make::ident_pat( + false, + false, + match name_gen.for_type(&f.ty(ctx.sema.db), ctx.sema.db, ctx.edition()) { + Some(name) => make::name(&name), + None => make::name(&format!("_{}", f.index())), + }, + ) + .into() + })) + .chain(pat.fields().skip(prefix_count + 1)), + ); + + let target_range = rest_pat.syntax().text_range(); acc.add( - AssistId("expand_rest_pattern", crate::AssistKind::RefactorRewrite), - "Fill structure fields", + AssistId("expand_tuple_struct_rest_pattern", crate::AssistKind::RefactorRewrite), + "Fill tuple struct fields", target_range, - move |builder| builder.replace_ast(old_field_list, new_field_list), + move |builder| builder.replace_ast(pat, new_pat), ) } @@ -165,6 +255,27 @@ struct Bar { fn foo(bar: Bar) { let Bar { y, z } = bar; } +"#, + ); + check_assist( + expand_rest_pattern, + r#" +struct Y; +struct Z; +struct Bar(Y, Z) + +fn foo(bar: Bar) { + let Bar(..$0) = bar; +} +"#, + r#" +struct Y; +struct Z; +struct Bar(Y, Z) + +fn foo(bar: Bar) { + let Bar(y, z) = bar; +} "#, ) } @@ -192,6 +303,29 @@ struct Bar { fn foo(bar: Bar) { let Bar { y, z } = bar; } +"#, + ); + check_assist( + expand_rest_pattern, + r#" +struct X; +struct Y; +struct Z; +struct Bar(X, Y, Z) + +fn foo(bar: Bar) { + let Bar(x, ..$0, z) = bar; +} +"#, + r#" +struct X; +struct Y; +struct Z; +struct Bar(X, Y, Z) + +fn foo(bar: Bar) { + let Bar(x, y, z) = bar; +} "#, ) } @@ -330,6 +464,7 @@ fn bar(foo: Foo) { fn not_applicable_when_no_missing_fields() { // This is still possible even though it's meaningless cov_mark::check!(no_missing_fields); + cov_mark::check!(no_missing_fields_tuple_struct); check_assist_not_applicable( expand_rest_pattern, r#" @@ -357,6 +492,16 @@ struct Bar { fn foo(bar: Bar) { let Bar { y, z, ..$0 } = bar; } +"#, + ); + check_assist_not_applicable( + expand_rest_pattern, + r#" +struct Bar(Y, Z) + +fn foo(bar: Bar) { + let Bar(y, ..$0, z) = bar; +} "#, ); } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index dd994ef4a6..46688a22f3 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -956,9 +956,9 @@ pub use foo::{Bar, Baz}; } #[test] -fn doctest_expand_rest_pattern() { +fn doctest_expand_record_rest_pattern() { check_doc_test( - "expand_rest_pattern", + "expand_record_rest_pattern", r#####" struct Bar { y: Y, z: Z } @@ -976,6 +976,27 @@ fn foo(bar: Bar) { ) } +#[test] +fn doctest_expand_tuple_struct_rest_pattern() { + check_doc_test( + "expand_tuple_struct_rest_pattern", + r#####" +struct Bar(Y, Z); + +fn foo(bar: Bar) { + let Bar(..$0) = bar; +} +"#####, + r#####" +struct Bar(Y, Z); + +fn foo(bar: Bar) { + let Bar(_0, _1) = bar; +} +"#####, + ) +} + #[test] fn doctest_extract_constant() { check_doc_test( diff --git a/docs/book/src/assists_generated.md b/docs/book/src/assists_generated.md index 743e5c5e10..b815cad0a2 100644 --- a/docs/book/src/assists_generated.md +++ b/docs/book/src/assists_generated.md @@ -1069,8 +1069,8 @@ pub use foo::{Bar, Baz}; ``` -### `expand_rest_pattern` -**Source:** [expand_rest_pattern.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/expand_rest_pattern.rs#L8) +### `expand_record_rest_pattern` +**Source:** [expand_rest_pattern.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/expand_rest_pattern.rs#L24) Fills fields by replacing rest pattern in record patterns. @@ -1093,6 +1093,30 @@ fn foo(bar: Bar) { ``` +### `expand_tuple_struct_rest_pattern` +**Source:** [expand_rest_pattern.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/expand_rest_pattern.rs#L80) + +Fills fields by replacing rest pattern in tuple struct patterns. + +#### Before +```rust +struct Bar(Y, Z); + +fn foo(bar: Bar) { + let Bar(..┃) = bar; +} +``` + +#### After +```rust +struct Bar(Y, Z); + +fn foo(bar: Bar) { + let Bar(_0, _1) = bar; +} +``` + + ### `extract_constant` **Source:** [extract_variable.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/extract_variable.rs#L35)