Fix panics on `Foo{mut x}` for destructure_struct_binding
Example
---
```rust
struct Foo { x: () }
struct Bar { foo: Foo }
fn f(Bar { mut $0foo }: Bar) {}
```
**Before this PR**:
Panic `Option::unwrap`
**After this PR**:
```rust
struct Foo { x: () }
struct Bar { foo: Foo }
fn f(Bar { foo: Foo { mut x } }: Bar) {}
```
diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
index 397327c..63a41ae 100644
--- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
@@ -7,7 +7,7 @@
search::{FileReference, SearchScope},
};
use itertools::Itertools;
-use syntax::ast::syntax_factory::SyntaxFactory;
+use syntax::ast::{HasName, syntax_factory::SyntaxFactory};
use syntax::syntax_editor::SyntaxEditor;
use syntax::{AstNode, Edition, SmolStr, SyntaxNode, ToSmolStr, ast};
@@ -71,13 +71,14 @@
struct StructEditData {
ident_pat: ast::IdentPat,
+ name: ast::Name,
kind: hir::StructKind,
struct_def_path: hir::ModPath,
visible_fields: Vec<hir::Field>,
usages: Vec<FileReference>,
names_in_scope: FxHashSet<SmolStr>,
has_private_members: bool,
- is_nested: bool,
+ need_record_field_name: bool,
is_ref: bool,
edition: Edition,
}
@@ -114,7 +115,11 @@
}
let is_ref = ty.is_reference();
- let is_nested = ident_pat.syntax().parent().and_then(ast::RecordPatField::cast).is_some();
+ let need_record_field_name = ident_pat
+ .syntax()
+ .parent()
+ .and_then(ast::RecordPatField::cast)
+ .is_some_and(|field| field.colon_token().is_none());
let usages = ctx
.sema
@@ -133,6 +138,7 @@
let names_in_scope = get_names_in_scope(ctx, &ident_pat, &usages).unwrap_or_default();
Some(StructEditData {
+ name: ident_pat.name()?,
ident_pat,
kind,
struct_def_path,
@@ -140,7 +146,7 @@
has_private_members,
visible_fields,
names_in_scope,
- is_nested,
+ need_record_field_name,
is_ref,
edition: module.krate().edition(ctx.db()),
})
@@ -177,6 +183,7 @@
field_names: &[(SmolStr, SmolStr)],
) {
let ident_pat = &data.ident_pat;
+ let name = &data.name;
let struct_path = mod_path_to_ast(&data.struct_def_path, data.edition);
let is_ref = ident_pat.ref_token().is_some();
@@ -194,9 +201,9 @@
hir::StructKind::Record => {
let fields = field_names.iter().map(|(old_name, new_name)| {
// Use shorthand syntax if possible
- if old_name == new_name && !is_mut {
+ if old_name == new_name {
make.record_pat_field_shorthand(
- make.ident_pat(false, false, make.name(old_name)).into(),
+ make.ident_pat(is_ref, is_mut, make.name(old_name)).into(),
)
} else {
make.record_pat_field(
@@ -215,8 +222,8 @@
// If the binding is nested inside a record, we need to wrap the new
// destructured pattern in a non-shorthand record field
- let destructured_pat = if data.is_nested {
- make.record_pat_field(make.name_ref(&ident_pat.to_string()), new_pat).syntax().clone()
+ let destructured_pat = if data.need_record_field_name {
+ make.record_pat_field(make.name_ref(&name.to_string()), new_pat).syntax().clone()
} else {
new_pat.syntax().clone()
};
@@ -579,7 +586,7 @@
struct Foo { bar: i32, baz: i32 }
fn main() {
- let Foo { bar: mut bar, baz: mut baz } = Foo { bar: 1, baz: 2 };
+ let Foo { mut bar, mut baz } = Foo { bar: 1, baz: 2 };
let bar2 = bar;
let baz2 = &baz;
}
@@ -588,6 +595,86 @@
}
#[test]
+ fn mut_record_field() {
+ check_assist(
+ destructure_struct_binding,
+ r#"
+ struct Foo { x: () }
+ struct Bar { foo: Foo }
+ fn f(Bar { mut $0foo }: Bar) {}
+ "#,
+ r#"
+ struct Foo { x: () }
+ struct Bar { foo: Foo }
+ fn f(Bar { foo: Foo { mut x } }: Bar) {}
+ "#,
+ )
+ }
+
+ #[test]
+ fn ref_record_field() {
+ check_assist(
+ destructure_struct_binding,
+ r#"
+ struct Foo { x: () }
+ struct Bar { foo: Foo }
+ fn f(Bar { ref $0foo }: Bar) {
+ let _ = foo.x;
+ }
+ "#,
+ r#"
+ struct Foo { x: () }
+ struct Bar { foo: Foo }
+ fn f(Bar { foo: Foo { ref x } }: Bar) {
+ let _ = *x;
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn ref_mut_record_field() {
+ check_assist(
+ destructure_struct_binding,
+ r#"
+ struct Foo { x: () }
+ struct Bar { foo: Foo }
+ fn f(Bar { ref mut $0foo }: Bar) {
+ let _ = foo.x;
+ }
+ "#,
+ r#"
+ struct Foo { x: () }
+ struct Bar { foo: Foo }
+ fn f(Bar { foo: Foo { ref mut x } }: Bar) {
+ let _ = *x;
+ }
+ "#,
+ )
+ }
+
+ #[test]
+ fn ref_mut_record_renamed_field() {
+ check_assist(
+ destructure_struct_binding,
+ r#"
+ struct Foo { x: () }
+ struct Bar { foo: Foo }
+ fn f(Bar { foo: ref mut $0foo1 }: Bar) {
+ let _ = foo1.x;
+ }
+ "#,
+ r#"
+ struct Foo { x: () }
+ struct Bar { foo: Foo }
+ fn f(Bar { foo: Foo { ref mut x } }: Bar) {
+ let _ = *x;
+ }
+ "#,
+ )
+ }
+
+ #[test]
fn mut_ref() {
check_assist(
destructure_struct_binding,