1
Fork 0

Support tuple struct patterns for expand_rest_pattern assist

This commit is contained in:
Lukas Wirth 2025-03-02 09:49:52 +01:00
parent 43c5e95af9
commit c315ad914f
4 changed files with 217 additions and 25 deletions

View file

@ -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,

View file

@ -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::<ast::RestPat>()?;
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::<ast::RestPat>()?;
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;
}
"#,
);
}

View file

@ -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(

View file

@ -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)