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) {}
```
This commit is contained in:
A4-Tacks 2025-09-20 14:59:59 +08:00
parent b12a129347
commit d4731ad9e2
No known key found for this signature in database
GPG key ID: DBD861323040663B

View file

@ -7,7 +7,7 @@ use ide_db::{
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 @@ fn destructure_struct_binding_impl(
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 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
}
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 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
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 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
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 @@ fn destructure_pat(
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 @@ fn destructure_pat(
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 @@ fn destructure_pat(
// 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 @@ mod tests {
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;
}
@ -587,6 +594,86 @@ mod tests {
)
}
#[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(