@@ -10,7 +10,10 @@ use itertools::Itertools;
1010use syntax:: { ast, ted, AstNode , SmolStr } ;
1111use text_edit:: TextRange ;
1212
13- use crate :: assist_context:: { AssistContext , Assists , SourceChangeBuilder } ;
13+ use crate :: {
14+ assist_context:: { AssistContext , Assists , SourceChangeBuilder } ,
15+ utils:: ref_field_expr:: determine_ref_and_parens,
16+ } ;
1417
1518// Assist: destructure_struct_binding
1619//
@@ -58,11 +61,12 @@ fn destructure_struct_binding_impl(
5861 builder : & mut SourceChangeBuilder ,
5962 data : & StructEditData ,
6063) {
61- let assignment_edit = build_assignment_edit ( ctx, builder, data) ;
62- let usage_edits = build_usage_edits ( ctx, builder, data, & assignment_edit. field_name_map ) ;
64+ let field_names = generate_field_names ( ctx, data) ;
65+ let assignment_edit = build_assignment_edit ( ctx, builder, data, & field_names) ;
66+ let usage_edits = build_usage_edits ( ctx, builder, data, & field_names. into_iter ( ) . collect ( ) ) ;
6367
6468 assignment_edit. apply ( ) ;
65- for edit in usage_edits. unwrap_or_default ( ) {
69+ for edit in usage_edits. into_iter ( ) . flatten ( ) {
6670 edit. apply ( builder) ;
6771 }
6872}
@@ -74,14 +78,16 @@ struct StructEditData {
7478 visible_fields : Vec < hir:: Field > ,
7579 usages : Option < UsageSearchResult > ,
7680 names_in_scope : FxHashSet < SmolStr > , // TODO currently always empty
77- add_rest : bool ,
81+ has_private_members : bool ,
7882 is_nested : bool ,
83+ is_ref : bool ,
7984}
8085
8186fn collect_data ( ident_pat : ast:: IdentPat , ctx : & AssistContext < ' _ > ) -> Option < StructEditData > {
82- let ty = ctx. sema . type_of_binding_in_pat ( & ident_pat) ?. strip_references ( ) . as_adt ( ) ?;
87+ let ty = ctx. sema . type_of_binding_in_pat ( & ident_pat) ?;
88+ let is_ref = ty. is_reference ( ) ;
8389
84- let hir:: Adt :: Struct ( struct_type) = ty else { return None } ;
90+ let hir:: Adt :: Struct ( struct_type) = ty. strip_references ( ) . as_adt ( ) ? else { return None } ;
8591
8692 let module = ctx. sema . scope ( ident_pat. syntax ( ) ) ?. module ( ) ;
8793 let struct_def = hir:: ModuleDef :: from ( struct_type) ;
@@ -97,8 +103,9 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
97103 let visible_fields =
98104 fields. into_iter ( ) . filter ( |field| field. is_visible_from ( ctx. db ( ) , module) ) . collect_vec ( ) ;
99105
100- let add_rest = ( is_non_exhaustive && is_foreign_crate) || visible_fields. len ( ) < n_fields;
101- if !matches ! ( kind, hir:: StructKind :: Record ) && add_rest {
106+ let has_private_members =
107+ ( is_non_exhaustive && is_foreign_crate) || visible_fields. len ( ) < n_fields;
108+ if !matches ! ( kind, hir:: StructKind :: Record ) && has_private_members {
102109 return None ;
103110 }
104111
@@ -123,26 +130,26 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
123130 kind,
124131 struct_def_path,
125132 usages,
126- add_rest ,
133+ has_private_members ,
127134 visible_fields,
128135 names_in_scope : FxHashSet :: default ( ) , // TODO
129136 is_nested,
137+ is_ref,
130138 } )
131139}
132140
133141fn build_assignment_edit (
134- ctx : & AssistContext < ' _ > ,
142+ _ctx : & AssistContext < ' _ > ,
135143 builder : & mut SourceChangeBuilder ,
136144 data : & StructEditData ,
145+ field_names : & [ ( SmolStr , SmolStr ) ] ,
137146) -> AssignmentEdit {
138147 let ident_pat = builder. make_mut ( data. ident_pat . clone ( ) ) ;
139148
140149 let struct_path = mod_path_to_ast ( & data. struct_def_path ) ;
141150 let is_ref = ident_pat. ref_token ( ) . is_some ( ) ;
142151 let is_mut = ident_pat. mut_token ( ) . is_some ( ) ;
143152
144- let field_names = generate_field_names ( ctx, data) ;
145-
146153 let new_pat = match data. kind {
147154 hir:: StructKind :: Tuple => {
148155 let ident_pats = field_names. iter ( ) . map ( |( _, new_name) | {
@@ -169,7 +176,7 @@ fn build_assignment_edit(
169176
170177 let field_list = ast:: make:: record_pat_field_list (
171178 fields,
172- data. add_rest . then_some ( ast:: make:: rest_pat ( ) ) ,
179+ data. has_private_members . then_some ( ast:: make:: rest_pat ( ) ) ,
173180 ) ;
174181 ast:: Pat :: RecordPat ( ast:: make:: record_pat_with_fields ( struct_path, field_list) )
175182 }
@@ -185,7 +192,7 @@ fn build_assignment_edit(
185192 NewPat :: Pat ( new_pat. clone_for_update ( ) )
186193 } ;
187194
188- AssignmentEdit { ident_pat, new_pat, field_name_map : field_names . into_iter ( ) . collect ( ) }
195+ AssignmentEdit { ident_pat, new_pat }
189196}
190197
191198fn generate_field_names ( ctx : & AssistContext < ' _ > , data : & StructEditData ) -> Vec < ( SmolStr , SmolStr ) > {
@@ -195,17 +202,17 @@ fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(
195202 . iter ( )
196203 . enumerate ( )
197204 . map ( |( index, _) | {
198- let new_name = format ! ( "_{}" , index) ;
199- ( index. to_string ( ) . into ( ) , new_name. into ( ) )
205+ let new_name = new_field_name ( ( format ! ( "_{}" , index) ) . into ( ) , & data . names_in_scope ) ;
206+ ( index. to_string ( ) . into ( ) , new_name)
200207 } )
201208 . collect ( ) ,
202209 hir:: StructKind :: Record => data
203210 . visible_fields
204211 . iter ( )
205212 . map ( |field| {
206213 let field_name = field. name ( ctx. db ( ) ) . to_smol_str ( ) ;
207- let new_field_name = new_field_name ( field_name. clone ( ) , & data. names_in_scope ) ;
208- ( field_name, new_field_name )
214+ let new_name = new_field_name ( field_name. clone ( ) , & data. names_in_scope ) ;
215+ ( field_name, new_name )
209216 } )
210217 . collect ( ) ,
211218 hir:: StructKind :: Unit => Vec :: new ( ) ,
@@ -225,7 +232,6 @@ fn new_field_name(base_name: SmolStr, names_in_scope: &FxHashSet<SmolStr>) -> Sm
225232struct AssignmentEdit {
226233 ident_pat : ast:: IdentPat ,
227234 new_pat : NewPat ,
228- field_name_map : FxHashMap < SmolStr , SmolStr > ,
229235}
230236
231237enum NewPat {
@@ -260,26 +266,37 @@ fn build_usage_edits(
260266 . iter ( )
261267 . find_map ( |( file_id, refs) | ( * file_id == ctx. file_id ( ) ) . then_some ( refs) ) ?
262268 . iter ( )
263- . filter_map ( |r| build_usage_edit ( builder, r, field_names) )
269+ . filter_map ( |r| build_usage_edit ( ctx , builder, data , r, field_names) )
264270 . collect_vec ( ) ;
265271
266272 Some ( edits)
267273}
268274
269275fn build_usage_edit (
276+ ctx : & AssistContext < ' _ > ,
270277 builder : & mut SourceChangeBuilder ,
278+ data : & StructEditData ,
271279 usage : & FileReference ,
272280 field_names : & FxHashMap < SmolStr , SmolStr > ,
273281) -> Option < StructUsageEdit > {
274282 match usage. name . syntax ( ) . ancestors ( ) . find_map ( ast:: FieldExpr :: cast) {
275283 Some ( field_expr) => Some ( {
276284 let field_name: SmolStr = field_expr. name_ref ( ) ?. to_string ( ) . into ( ) ;
277285 let new_field_name = field_names. get ( & field_name) ?;
278-
279- let expr = builder. make_mut ( field_expr) . into ( ) ;
280- let new_expr =
281- ast:: make:: expr_path ( ast:: make:: ext:: ident_path ( new_field_name) ) . clone_for_update ( ) ;
282- StructUsageEdit :: IndexField ( expr, new_expr)
286+ let new_expr = ast:: make:: expr_path ( ast:: make:: ext:: ident_path ( new_field_name) ) ;
287+
288+ if data. is_ref {
289+ let ( replace_expr, ref_data) = determine_ref_and_parens ( ctx, & field_expr) ;
290+ StructUsageEdit :: IndexField (
291+ builder. make_mut ( replace_expr) ,
292+ ref_data. wrap_expr ( new_expr) . clone_for_update ( ) ,
293+ )
294+ } else {
295+ StructUsageEdit :: IndexField (
296+ builder. make_mut ( field_expr) . into ( ) ,
297+ new_expr. clone_for_update ( ) ,
298+ )
299+ }
283300 } ) ,
284301 None => Some ( StructUsageEdit :: Path ( usage. range ) ) ,
285302 }
@@ -602,4 +619,33 @@ mod tests {
602619 "# ,
603620 )
604621 }
622+
623+ #[ test]
624+ fn mut_ref ( ) {
625+ check_assist (
626+ destructure_struct_binding,
627+ r#"
628+ struct Foo {
629+ bar: i32,
630+ baz: i32
631+ }
632+
633+ fn main() {
634+ let $0foo = &mut Foo { bar: 1, baz: 2 };
635+ foo.bar = 5;
636+ }
637+ "# ,
638+ r#"
639+ struct Foo {
640+ bar: i32,
641+ baz: i32
642+ }
643+
644+ fn main() {
645+ let Foo { bar, baz } = &mut Foo { bar: 1, baz: 2 };
646+ *bar = 5;
647+ }
648+ "# ,
649+ )
650+ }
605651}
0 commit comments