Fix expand rest pattern in tuple and slice pattern

Assist: expand_tuple_rest_pattern

Fills fields by replacing rest pattern in tuple patterns.

Example
---
```
fn foo(bar: (char, i32, i32)) {
    let (ch, ..$0) = bar;
}
```
->
```
fn foo(bar: (char, i32, i32)) {
    let (ch, _1, _2) = bar;
}
```

---

Assist: expand_slice_rest_pattern

Fills fields by replacing rest pattern in slice patterns.

Example
---
```
fn foo(bar: [i32; 3]) {
    let [first, ..$0] = bar;
}
```
->
```
fn foo(bar: [i32; 3]) {
    let [first, _1, _2] = bar;
}
```
This commit is contained in:
A4-Tacks 2025-09-23 13:18:33 +08:00
parent a96d92e9e9
commit 6d85fd739f
No known key found for this signature in database
GPG key ID: DBD861323040663B
2 changed files with 289 additions and 19 deletions

View file

@ -113,9 +113,7 @@ fn expand_tuple_struct_rest_pattern(
};
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();
let (prefix_count, suffix_count) = calculate_counts(&rest_pat, pat.fields())?;
if fields.len().saturating_sub(prefix_count).saturating_sub(suffix_count) == 0 {
cov_mark::hit!(no_missing_fields_tuple_struct);
@ -141,19 +139,13 @@ fn expand_tuple_struct_rest_pattern(
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).to_type(ctx.sema.db),
ctx.sema.db,
ctx.edition(),
) {
Some(name) => make.name(&name),
None => make.name(&format!("_{}", f.index())),
},
gen_unnamed_pat(
ctx,
&make,
&mut name_gen,
&f.ty(ctx.db()).to_type(ctx.sema.db),
f.index(),
)
.into()
}))
.chain(pat.fields().skip(prefix_count + 1)),
);
@ -166,6 +158,134 @@ fn expand_tuple_struct_rest_pattern(
)
}
// Assist: expand_tuple_rest_pattern
//
// Fills fields by replacing rest pattern in tuple patterns.
//
// ```
// fn foo(bar: (char, i32, i32)) {
// let (ch, ..$0) = bar;
// }
// ```
// ->
// ```
// fn foo(bar: (char, i32, i32)) {
// let (ch, _1, _2) = bar;
// }
// ```
fn expand_tuple_rest_pattern(
acc: &mut Assists,
ctx: &AssistContext<'_>,
pat: ast::TuplePat,
rest_pat: ast::RestPat,
) -> Option<()> {
let fields = ctx.sema.type_of_pat(&pat.clone().into())?.original.tuple_fields(ctx.db());
let len = fields.len();
let rest_pat = rest_pat.into();
let (prefix_count, suffix_count) = calculate_counts(&rest_pat, pat.fields())?;
if len.saturating_sub(prefix_count).saturating_sub(suffix_count) == 0 {
cov_mark::hit!(no_missing_fields_tuple);
return None;
}
let old_range = ctx.sema.original_range_opt(pat.syntax())?;
if old_range.file_id != ctx.file_id() {
return None;
}
acc.add(
AssistId::refactor_rewrite("expand_tuple_rest_pattern"),
"Fill tuple fields",
rest_pat.syntax().text_range(),
|builder| {
let make = SyntaxFactory::with_mappings();
let mut editor = builder.make_editor(rest_pat.syntax());
let mut name_gen = NameGenerator::new_from_scope_locals(ctx.sema.scope(pat.syntax()));
let new_pat = make.tuple_pat(
pat.fields()
.take(prefix_count)
.chain(fields[prefix_count..len - suffix_count].iter().enumerate().map(
|(index, ty)| {
gen_unnamed_pat(ctx, &make, &mut name_gen, ty, prefix_count + index)
},
))
.chain(pat.fields().skip(prefix_count + 1)),
);
editor.replace(pat.syntax(), new_pat.syntax());
editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.vfs_file_id(), editor);
},
)
}
// Assist: expand_slice_rest_pattern
//
// Fills fields by replacing rest pattern in slice patterns.
//
// ```
// fn foo(bar: [i32; 3]) {
// let [first, ..$0] = bar;
// }
// ```
// ->
// ```
// fn foo(bar: [i32; 3]) {
// let [first, _1, _2] = bar;
// }
// ```
fn expand_slice_rest_pattern(
acc: &mut Assists,
ctx: &AssistContext<'_>,
pat: ast::SlicePat,
rest_pat: ast::RestPat,
) -> Option<()> {
let (ty, len) = ctx.sema.type_of_pat(&pat.clone().into())?.original.as_array(ctx.db())?;
let rest_pat = rest_pat.into();
let (prefix_count, suffix_count) = calculate_counts(&rest_pat, pat.pats())?;
if len.saturating_sub(prefix_count).saturating_sub(suffix_count) == 0 {
cov_mark::hit!(no_missing_fields_slice);
return None;
}
let old_range = ctx.sema.original_range_opt(pat.syntax())?;
if old_range.file_id != ctx.file_id() {
return None;
}
acc.add(
AssistId::refactor_rewrite("expand_slice_rest_pattern"),
"Fill slice fields",
rest_pat.syntax().text_range(),
|builder| {
let make = SyntaxFactory::with_mappings();
let mut editor = builder.make_editor(rest_pat.syntax());
let mut name_gen = NameGenerator::new_from_scope_locals(ctx.sema.scope(pat.syntax()));
let new_pat = make.slice_pat(
pat.pats()
.take(prefix_count)
.chain(
(prefix_count..len - suffix_count)
.map(|index| gen_unnamed_pat(ctx, &make, &mut name_gen, &ty, index)),
)
.chain(pat.pats().skip(prefix_count + 1)),
);
editor.replace(pat.syntax(), new_pat.syntax());
editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.vfs_file_id(), editor);
},
)
}
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()?;
@ -173,15 +293,40 @@ pub(crate) fn expand_rest_pattern(acc: &mut Assists, ctx: &AssistContext<'_>) ->
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) => (),
ast::TuplePat(it) => expand_tuple_rest_pattern(acc, ctx, it, rest_pat),
ast::SlicePat(it) => expand_slice_rest_pattern(acc, ctx, it, rest_pat),
_ => None,
}
}
}
fn gen_unnamed_pat(
ctx: &AssistContext<'_>,
make: &SyntaxFactory,
name_gen: &mut NameGenerator,
ty: &hir::Type<'_>,
index: usize,
) -> ast::Pat {
make.ident_pat(
false,
false,
match name_gen.for_type(ty, ctx.sema.db, ctx.edition()) {
Some(name) => make.name(&name),
None => make.name(&format!("_{index}")),
},
)
.into()
}
fn calculate_counts(
rest_pat: &ast::Pat,
mut pats: ast::AstChildren<ast::Pat>,
) -> Option<(usize, usize)> {
let prefix_count = pats.by_ref().position(|p| p == *rest_pat)?;
let suffix_count = pats.count();
Some((prefix_count, suffix_count))
}
#[cfg(test)]
mod tests {
use super::*;
@ -351,6 +496,79 @@ fn foo(bar: Bar) {
)
}
#[test]
fn fill_tuple_with_fields() {
check_assist(
expand_rest_pattern,
r#"
fn foo(bar: (char, i32, i32)) {
let (ch, ..$0) = bar;
}
"#,
r#"
fn foo(bar: (char, i32, i32)) {
let (ch, _1, _2) = bar;
}
"#,
);
check_assist(
expand_rest_pattern,
r#"
fn foo(bar: (char, i32, i32)) {
let (ch, ..$0, end) = bar;
}
"#,
r#"
fn foo(bar: (char, i32, i32)) {
let (ch, _1, end) = bar;
}
"#,
);
}
#[test]
fn fill_array_with_fields() {
check_assist(
expand_rest_pattern,
r#"
fn foo(bar: [i32; 4]) {
let [first, ..$0] = bar;
}
"#,
r#"
fn foo(bar: [i32; 4]) {
let [first, _1, _2, _3] = bar;
}
"#,
);
check_assist(
expand_rest_pattern,
r#"
fn foo(bar: [i32; 4]) {
let [first, second, ..$0] = bar;
}
"#,
r#"
fn foo(bar: [i32; 4]) {
let [first, second, _2, _3] = bar;
}
"#,
);
check_assist(
expand_rest_pattern,
r#"
fn foo(bar: [i32; 4]) {
let [first, second, ..$0, end] = bar;
}
"#,
r#"
fn foo(bar: [i32; 4]) {
let [first, second, _2, end] = bar;
}
"#,
);
}
#[test]
fn fill_fields_struct_generated_by_macro() {
check_assist(
@ -486,6 +704,8 @@ fn bar(foo: Foo) {
// This is still possible even though it's meaningless
cov_mark::check!(no_missing_fields);
cov_mark::check!(no_missing_fields_tuple_struct);
cov_mark::check!(no_missing_fields_tuple);
cov_mark::check!(no_missing_fields_slice);
check_assist_not_applicable(
expand_rest_pattern,
r#"
@ -523,6 +743,22 @@ struct Bar(Y, Z)
fn foo(bar: Bar) {
let Bar(y, ..$0, z) = bar;
}
"#,
);
check_assist_not_applicable(
expand_rest_pattern,
r#"
fn foo(bar: (i32, i32)) {
let (y, ..$0, z) = bar;
}
"#,
);
check_assist_not_applicable(
expand_rest_pattern,
r#"
fn foo(bar: [i32; 2]) {
let [y, ..$0, z] = bar;
}
"#,
);
}

View file

@ -1041,6 +1041,40 @@ fn foo(bar: Bar) {
)
}
#[test]
fn doctest_expand_slice_rest_pattern() {
check_doc_test(
"expand_slice_rest_pattern",
r#####"
fn foo(bar: [i32; 3]) {
let [first, ..$0] = bar;
}
"#####,
r#####"
fn foo(bar: [i32; 3]) {
let [first, _1, _2] = bar;
}
"#####,
)
}
#[test]
fn doctest_expand_tuple_rest_pattern() {
check_doc_test(
"expand_tuple_rest_pattern",
r#####"
fn foo(bar: (char, i32, i32)) {
let (ch, ..$0) = bar;
}
"#####,
r#####"
fn foo(bar: (char, i32, i32)) {
let (ch, _1, _2) = bar;
}
"#####,
)
}
#[test]
fn doctest_expand_tuple_struct_rest_pattern() {
check_doc_test(