@@ -3,10 +3,12 @@ use ide_db::{
33 defs:: Definition ,
44 search:: { FileReference , SearchScope , UsageSearchResult } ,
55} ;
6+ use itertools:: Itertools ;
67use syntax:: {
7- ast:: { self , AstNode , FieldExpr , HasName , IdentPat , MethodCallExpr } ,
8- TextRange ,
8+ ast:: { self , make , AstNode , FieldExpr , HasName , IdentPat , MethodCallExpr } ,
9+ ted , T ,
910} ;
11+ use text_edit:: TextRange ;
1012
1113use crate :: assist_context:: { AssistContext , Assists , SourceChangeBuilder } ;
1214
@@ -61,27 +63,36 @@ pub(crate) fn destructure_tuple_binding_impl(
6163 acc. add (
6264 AssistId ( "destructure_tuple_binding_in_sub_pattern" , AssistKind :: RefactorRewrite ) ,
6365 "Destructure tuple in sub-pattern" ,
64- data. range ,
65- |builder| {
66- edit_tuple_assignment ( ctx, builder, & data, true ) ;
67- edit_tuple_usages ( & data, builder, ctx, true ) ;
68- } ,
66+ data. ident_pat . syntax ( ) . text_range ( ) ,
67+ |edit| destructure_tuple_edit_impl ( ctx, edit, & data, true ) ,
6968 ) ;
7069 }
7170
7271 acc. add (
7372 AssistId ( "destructure_tuple_binding" , AssistKind :: RefactorRewrite ) ,
7473 if with_sub_pattern { "Destructure tuple in place" } else { "Destructure tuple" } ,
75- data. range ,
76- |builder| {
77- edit_tuple_assignment ( ctx, builder, & data, false ) ;
78- edit_tuple_usages ( & data, builder, ctx, false ) ;
79- } ,
74+ data. ident_pat . syntax ( ) . text_range ( ) ,
75+ |edit| destructure_tuple_edit_impl ( ctx, edit, & data, false ) ,
8076 ) ;
8177
8278 Some ( ( ) )
8379}
8480
81+ fn destructure_tuple_edit_impl (
82+ ctx : & AssistContext < ' _ > ,
83+ edit : & mut SourceChangeBuilder ,
84+ data : & TupleData ,
85+ in_sub_pattern : bool ,
86+ ) {
87+ let assignment_edit = edit_tuple_assignment ( ctx, edit, & data, in_sub_pattern) ;
88+ let current_file_usages_edit = edit_tuple_usages ( & data, edit, ctx, in_sub_pattern) ;
89+
90+ assignment_edit. apply ( ) ;
91+ if let Some ( usages_edit) = current_file_usages_edit {
92+ usages_edit. into_iter ( ) . for_each ( |usage_edit| usage_edit. apply ( edit) )
93+ }
94+ }
95+
8596fn collect_data ( ident_pat : IdentPat , ctx : & AssistContext < ' _ > ) -> Option < TupleData > {
8697 if ident_pat. at_token ( ) . is_some ( ) {
8798 // Cannot destructure pattern with sub-pattern:
@@ -109,7 +120,6 @@ fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleDat
109120 }
110121
111122 let name = ident_pat. name ( ) ?. to_string ( ) ;
112- let range = ident_pat. syntax ( ) . text_range ( ) ;
113123
114124 let usages = ctx. sema . to_def ( & ident_pat) . map ( |def| {
115125 Definition :: Local ( def)
@@ -122,7 +132,7 @@ fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleDat
122132 . map ( |i| generate_name ( ctx, i, & name, & ident_pat, & usages) )
123133 . collect :: < Vec < _ > > ( ) ;
124134
125- Some ( TupleData { ident_pat, range , ref_type, field_names, usages } )
135+ Some ( TupleData { ident_pat, ref_type, field_names, usages } )
126136}
127137
128138fn generate_name (
@@ -142,98 +152,121 @@ enum RefType {
142152}
143153struct TupleData {
144154 ident_pat : IdentPat ,
145- // name: String,
146- range : TextRange ,
147155 ref_type : Option < RefType > ,
148156 field_names : Vec < String > ,
149- // field_types: Vec<Type>,
150157 usages : Option < UsageSearchResult > ,
151158}
152159fn edit_tuple_assignment (
153160 ctx : & AssistContext < ' _ > ,
154- builder : & mut SourceChangeBuilder ,
161+ edit : & mut SourceChangeBuilder ,
155162 data : & TupleData ,
156163 in_sub_pattern : bool ,
157- ) {
164+ ) -> AssignmentEdit {
165+ let ident_pat = edit. make_mut ( data. ident_pat . clone ( ) ) ;
166+
158167 let tuple_pat = {
159168 let original = & data. ident_pat ;
160169 let is_ref = original. ref_token ( ) . is_some ( ) ;
161170 let is_mut = original. mut_token ( ) . is_some ( ) ;
162- let fields = data. field_names . iter ( ) . map ( |name| {
163- ast:: Pat :: from ( ast:: make:: ident_pat ( is_ref, is_mut, ast:: make:: name ( name) ) )
164- } ) ;
165- ast:: make:: tuple_pat ( fields)
166- } ;
167-
168- let add_cursor = |text : & str | {
169- // place cursor on first tuple item
170- let first_tuple = & data. field_names [ 0 ] ;
171- text. replacen ( first_tuple, & format ! ( "$0{first_tuple}" ) , 1 )
171+ let fields = data
172+ . field_names
173+ . iter ( )
174+ . map ( |name| ast:: Pat :: from ( make:: ident_pat ( is_ref, is_mut, make:: name ( name) ) ) ) ;
175+ make:: tuple_pat ( fields) . clone_for_update ( )
172176 } ;
173177
174- // with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
175- if in_sub_pattern {
176- let text = format ! ( " @ {tuple_pat}" ) ;
177- match ctx. config . snippet_cap {
178- Some ( cap) => {
179- let snip = add_cursor ( & text) ;
180- builder. insert_snippet ( cap, data. range . end ( ) , snip) ;
181- }
182- None => builder. insert ( data. range . end ( ) , text) ,
183- } ;
184- } else {
185- let text = tuple_pat. to_string ( ) ;
186- match ctx. config . snippet_cap {
187- Some ( cap) => {
188- let snip = add_cursor ( & text) ;
189- builder. replace_snippet ( cap, data. range , snip) ;
190- }
191- None => builder. replace ( data. range , text) ,
178+ if let Some ( cap) = ctx. config . snippet_cap {
179+ // place cursor on first tuple name
180+ let ast:: Pat :: IdentPat ( first_pat) = tuple_pat. fields ( ) . next ( ) . unwrap ( ) else {
181+ unreachable ! ( )
192182 } ;
183+ edit. add_tabstop_before ( cap, first_pat. name ( ) . unwrap ( ) )
184+ }
185+
186+ AssignmentEdit { ident_pat, tuple_pat, in_sub_pattern }
187+ }
188+ struct AssignmentEdit {
189+ ident_pat : ast:: IdentPat ,
190+ tuple_pat : ast:: TuplePat ,
191+ in_sub_pattern : bool ,
192+ }
193+
194+ impl AssignmentEdit {
195+ fn apply ( self ) {
196+ // with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
197+ if self . in_sub_pattern {
198+ ted:: insert_all_raw (
199+ ted:: Position :: after ( self . ident_pat . syntax ( ) ) ,
200+ vec ! [
201+ make:: tokens:: single_space( ) . into( ) ,
202+ make:: token( T ![ @] ) . into( ) ,
203+ make:: tokens:: single_space( ) . into( ) ,
204+ self . tuple_pat. syntax( ) . clone( ) . into( ) ,
205+ ] ,
206+ )
207+ } else {
208+ ted:: replace ( self . ident_pat . syntax ( ) , self . tuple_pat . syntax ( ) )
209+ }
193210 }
194211}
195212
196213fn edit_tuple_usages (
197214 data : & TupleData ,
198- builder : & mut SourceChangeBuilder ,
215+ edit : & mut SourceChangeBuilder ,
199216 ctx : & AssistContext < ' _ > ,
200217 in_sub_pattern : bool ,
201- ) {
218+ ) -> Option < Vec < EditTupleUsage > > {
219+ let mut current_file_usages = None ;
220+
202221 if let Some ( usages) = data. usages . as_ref ( ) {
203- for ( file_id, refs) in usages. iter ( ) {
204- builder. edit_file ( * file_id) ;
222+ // We need to collect edits first before actually applying them
223+ // as mapping nodes to their mutable node versions requires an
224+ // unmodified syntax tree.
225+ //
226+ // We also defer editing usages in the current file first since
227+ // tree mutation in the same file breaks when `builder.edit_file`
228+ // is called
229+
230+ if let Some ( ( _, refs) ) = usages. iter ( ) . find ( |( file_id, _) | * * file_id == ctx. file_id ( ) ) {
231+ current_file_usages = Some (
232+ refs. iter ( )
233+ . filter_map ( |r| edit_tuple_usage ( ctx, edit, r, data, in_sub_pattern) )
234+ . collect_vec ( ) ,
235+ ) ;
236+ }
205237
206- for r in refs {
207- edit_tuple_usage ( ctx, builder, r, data, in_sub_pattern) ;
238+ for ( file_id, refs) in usages. iter ( ) {
239+ if * file_id == ctx. file_id ( ) {
240+ continue ;
208241 }
242+
243+ edit. edit_file ( * file_id) ;
244+
245+ let tuple_edits = refs
246+ . iter ( )
247+ . filter_map ( |r| edit_tuple_usage ( ctx, edit, r, data, in_sub_pattern) )
248+ . collect_vec ( ) ;
249+
250+ tuple_edits. into_iter ( ) . for_each ( |tuple_edit| tuple_edit. apply ( edit) )
209251 }
210252 }
253+
254+ current_file_usages
211255}
212256fn edit_tuple_usage (
213257 ctx : & AssistContext < ' _ > ,
214258 builder : & mut SourceChangeBuilder ,
215259 usage : & FileReference ,
216260 data : & TupleData ,
217261 in_sub_pattern : bool ,
218- ) {
262+ ) -> Option < EditTupleUsage > {
219263 match detect_tuple_index ( usage, data) {
220- Some ( index) => edit_tuple_field_usage ( ctx, builder, data, index) ,
221- None => {
222- if in_sub_pattern {
223- cov_mark:: hit!( destructure_tuple_call_with_subpattern) ;
224- return ;
225- }
226-
227- // no index access -> make invalid -> requires handling by user
228- // -> put usage in block comment
229- //
230- // Note: For macro invocations this might result in still valid code:
231- // When a macro accepts the tuple as argument, as well as no arguments at all,
232- // uncommenting the tuple still leaves the macro call working (see `tests::in_macro_call::empty_macro`).
233- // But this is an unlikely case. Usually the resulting macro call will become erroneous.
234- builder. insert ( usage. range . start ( ) , "/*" ) ;
235- builder. insert ( usage. range . end ( ) , "*/" ) ;
264+ Some ( index) => Some ( edit_tuple_field_usage ( ctx, builder, data, index) ) ,
265+ None if in_sub_pattern => {
266+ cov_mark:: hit!( destructure_tuple_call_with_subpattern) ;
267+ return None ;
236268 }
269+ None => Some ( EditTupleUsage :: NoIndex ( usage. range ) ) ,
237270 }
238271}
239272
@@ -242,19 +275,47 @@ fn edit_tuple_field_usage(
242275 builder : & mut SourceChangeBuilder ,
243276 data : & TupleData ,
244277 index : TupleIndex ,
245- ) {
278+ ) -> EditTupleUsage {
246279 let field_name = & data. field_names [ index. index ] ;
280+ let field_name = make:: expr_path ( make:: ext:: ident_path ( field_name) ) ;
247281
248282 if data. ref_type . is_some ( ) {
249- let ref_data = handle_ref_field_usage ( ctx, & index. field_expr ) ;
250- builder. replace ( ref_data. range , ref_data. format ( field_name) ) ;
283+ let ( replace_expr, ref_data) = handle_ref_field_usage ( ctx, & index. field_expr ) ;
284+ let replace_expr = builder. make_mut ( replace_expr) ;
285+ EditTupleUsage :: ReplaceExpr ( replace_expr, ref_data. wrap_expr ( field_name) )
251286 } else {
252- builder. replace ( index. range , field_name) ;
287+ let field_expr = builder. make_mut ( index. field_expr ) ;
288+ EditTupleUsage :: ReplaceExpr ( field_expr. into ( ) , field_name)
289+ }
290+ }
291+ enum EditTupleUsage {
292+ /// no index access -> make invalid -> requires handling by user
293+ /// -> put usage in block comment
294+ ///
295+ /// Note: For macro invocations this might result in still valid code:
296+ /// When a macro accepts the tuple as argument, as well as no arguments at all,
297+ /// uncommenting the tuple still leaves the macro call working (see `tests::in_macro_call::empty_macro`).
298+ /// But this is an unlikely case. Usually the resulting macro call will become erroneous.
299+ NoIndex ( TextRange ) ,
300+ ReplaceExpr ( ast:: Expr , ast:: Expr ) ,
301+ }
302+
303+ impl EditTupleUsage {
304+ fn apply ( self , edit : & mut SourceChangeBuilder ) {
305+ match self {
306+ EditTupleUsage :: NoIndex ( range) => {
307+ edit. insert ( range. start ( ) , "/*" ) ;
308+ edit. insert ( range. end ( ) , "*/" ) ;
309+ }
310+ EditTupleUsage :: ReplaceExpr ( target_expr, replace_with) => {
311+ ted:: replace ( target_expr. syntax ( ) , replace_with. clone_for_update ( ) . syntax ( ) )
312+ }
313+ }
253314 }
254315}
316+
255317struct TupleIndex {
256318 index : usize ,
257- range : TextRange ,
258319 field_expr : FieldExpr ,
259320}
260321fn detect_tuple_index ( usage : & FileReference , data : & TupleData ) -> Option < TupleIndex > {
@@ -296,7 +357,7 @@ fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIn
296357 return None ;
297358 }
298359
299- Some ( TupleIndex { index : idx, range : field_expr . syntax ( ) . text_range ( ) , field_expr } )
360+ Some ( TupleIndex { index : idx, field_expr } )
300361 } else {
301362 // tuple index out of range
302363 None
@@ -307,32 +368,34 @@ fn detect_tuple_index(usage: &FileReference, data: &TupleData) -> Option<TupleIn
307368}
308369
309370struct RefData {
310- range : TextRange ,
311371 needs_deref : bool ,
312372 needs_parentheses : bool ,
313373}
314374impl RefData {
315- fn format ( & self , field_name : & str ) -> String {
316- match ( self . needs_deref , self . needs_parentheses ) {
317- ( true , true ) => format ! ( "(*{field_name})" ) ,
318- ( true , false ) => format ! ( "*{field_name}" ) ,
319- ( false , true ) => format ! ( "({field_name})" ) ,
320- ( false , false ) => field_name. to_string ( ) ,
375+ fn wrap_expr ( & self , mut expr : ast:: Expr ) -> ast:: Expr {
376+ if self . needs_deref {
377+ expr = make:: expr_prefix ( T ! [ * ] , expr) ;
321378 }
379+
380+ if self . needs_parentheses {
381+ expr = make:: expr_paren ( expr) ;
382+ }
383+
384+ return expr;
322385 }
323386}
324- fn handle_ref_field_usage ( ctx : & AssistContext < ' _ > , field_expr : & FieldExpr ) -> RefData {
387+ fn handle_ref_field_usage ( ctx : & AssistContext < ' _ > , field_expr : & FieldExpr ) -> ( ast :: Expr , RefData ) {
325388 let s = field_expr. syntax ( ) ;
326- let mut ref_data =
327- RefData { range : s . text_range ( ) , needs_deref : true , needs_parentheses : true } ;
389+ let mut ref_data = RefData { needs_deref : true , needs_parentheses : true } ;
390+ let mut target_node = field_expr . clone ( ) . into ( ) ;
328391
329392 let parent = match s. parent ( ) . map ( ast:: Expr :: cast) {
330393 Some ( Some ( parent) ) => parent,
331394 Some ( None ) => {
332395 ref_data. needs_parentheses = false ;
333- return ref_data;
396+ return ( target_node , ref_data) ;
334397 }
335- None => return ref_data,
398+ None => return ( target_node , ref_data) ,
336399 } ;
337400
338401 match parent {
@@ -342,7 +405,7 @@ fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> Re
342405 // there might be a ref outside: `&(t.0)` -> can be removed
343406 if let Some ( it) = it. syntax ( ) . parent ( ) . and_then ( ast:: RefExpr :: cast) {
344407 ref_data. needs_deref = false ;
345- ref_data . range = it. syntax ( ) . text_range ( ) ;
408+ target_node = it. into ( ) ;
346409 }
347410 }
348411 ast:: Expr :: RefExpr ( it) => {
@@ -351,8 +414,8 @@ fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> Re
351414 ref_data. needs_parentheses = false ;
352415 // might be surrounded by parens -> can be removed too
353416 match it. syntax ( ) . parent ( ) . and_then ( ast:: ParenExpr :: cast) {
354- Some ( parent) => ref_data . range = parent. syntax ( ) . text_range ( ) ,
355- None => ref_data . range = it. syntax ( ) . text_range ( ) ,
417+ Some ( parent) => target_node = parent. into ( ) ,
418+ None => target_node = it. into ( ) ,
356419 } ;
357420 }
358421 // higher precedence than deref `*`
@@ -414,7 +477,7 @@ fn handle_ref_field_usage(ctx: &AssistContext<'_>, field_expr: &FieldExpr) -> Re
414477 }
415478 } ;
416479
417- ref_data
480+ ( target_node , ref_data)
418481}
419482
420483#[ cfg( test) ]
0 commit comments