about summary refs log tree commit diff
path: root/src/tools
diff options
context:
space:
mode:
authorA4-Tacks <wdsjxhno1001@163.com>2025-09-23 13:18:33 +0800
committerA4-Tacks <wdsjxhno1001@163.com>2025-09-26 14:44:05 +0800
commit38284d10dc45b3bb2c9aa412dce1e5b1e16c639a (patch)
tree978ebaeede7059e1a40f1d3ae72cca0d802e2c03 /src/tools
parent35f76dfcd99f66684544ebee4c9c25bdba01dd82 (diff)
downloadrust-38284d10dc45b3bb2c9aa412dce1e5b1e16c639a.tar.gz
rust-38284d10dc45b3bb2c9aa412dce1e5b1e16c639a.zip
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;
}
```
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs274
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs34
2 files changed, 289 insertions, 19 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs
index 932166adb0a..b746099e727 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/expand_rest_pattern.rs
@@ -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::*;
@@ -352,6 +497,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(
             expand_rest_pattern,
@@ -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#"
@@ -525,5 +745,21 @@ fn foo(bar: 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;
+}
+"#,
+        );
     }
 }
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
index 71ffaa23fc6..e7f91ff3fbc 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
@@ -1042,6 +1042,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(
         "expand_tuple_struct_rest_pattern",