From d4731ad9e29a67bed02d5667d5bb49d7edaa779c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 20 Sep 2025 14:59:59 +0800 Subject: [PATCH] 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) {} ``` --- .../handlers/destructure_struct_binding.rs | 105 ++++++++++++++++-- 1 file changed, 96 insertions(+), 9 deletions(-) diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs index 397327cb4f..63a41ae5dc 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 @@ 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, usages: Vec, names_in_scope: FxHashSet, 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) -> Option) -> Option { 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(