1
- use hir:: { Function , ModuleDef } ;
2
- use ide_db:: { RootDatabase , assists:: AssistId , path_transform:: PathTransform } ;
1
+ use std:: ops:: Range ;
2
+
3
+ use hir:: { Function , HasCrate , Module , ModuleDef } ;
4
+ use ide_db:: {
5
+ FxHashSet , RootDatabase ,
6
+ assists:: AssistId ,
7
+ defs:: Definition ,
8
+ helpers:: mod_path_to_ast,
9
+ imports:: insert_use:: { ImportScope , InsertUseConfig , insert_use} ,
10
+ path_transform:: PathTransform ,
11
+ search:: FileReference ,
12
+ source_change:: SourceChangeBuilder ,
13
+ } ;
3
14
use itertools:: Itertools ;
4
15
use syntax:: {
5
- AstNode , SyntaxElement , SyntaxKind , SyntaxNode , T ,
16
+ AstNode , Edition , SyntaxElement , SyntaxKind , SyntaxNode , T ,
6
17
ast:: {
7
- self , HasAttrs , HasGenericParams , HasName , HasVisibility ,
18
+ self , CallExpr , HasArgList , HasAttrs , HasGenericParams , HasName , HasVisibility ,
19
+ RecordExprField ,
8
20
edit:: { AstNodeEdit , IndentLevel } ,
9
21
make,
10
22
} ,
@@ -24,7 +36,7 @@ use crate::{AssistContext, Assists};
24
36
// ```
25
37
// struct FooStruct{ bar: u32, baz: u32 }
26
38
//
27
- // fn foo(foo_struct : FooStruct) { ... }
39
+ // fn foo(FooStruct { bar, baz, .. } : FooStruct) { ... }
28
40
// ```
29
41
30
42
pub ( crate ) fn extract_struct_from_function_signature (
@@ -50,7 +62,7 @@ pub(crate) fn extract_struct_from_function_signature(
50
62
return None ;
51
63
}
52
64
53
- // TODO: special handiling for self?
65
+ // TODO: special handling for self?
54
66
// TODO: special handling for destrutered types (or maybe just don't support code action on
55
67
// destructed types yet
56
68
@@ -68,20 +80,68 @@ pub(crate) fn extract_struct_from_function_signature(
68
80
} )
69
81
. collect :: < Option < Vec < _ > > > ( ) ?,
70
82
) ;
83
+
84
+ let start_index = used_param_list. first ( ) ?. syntax ( ) . index ( ) ;
85
+ let end_index = used_param_list. last ( ) ?. syntax ( ) . index ( ) ;
86
+ let used_params_range = start_index..end_index + 1 ;
71
87
acc. add (
72
88
AssistId :: refactor_rewrite ( "extract_struct_from_function_signature" ) ,
73
89
"Extract struct from signature of a function" ,
74
90
target,
75
91
|builder| {
92
+ let edition = fn_hir. krate ( ctx. db ( ) ) . edition ( ctx. db ( ) ) ;
93
+ let enum_module_def = ModuleDef :: from ( fn_hir) ;
94
+
95
+ let usages = Definition :: Function ( fn_hir) . usages ( & ctx. sema ) . all ( ) ;
96
+ let mut visited_modules_set = FxHashSet :: default ( ) ;
97
+ let current_module = fn_hir. module ( ctx. db ( ) ) ;
98
+ visited_modules_set. insert ( current_module) ;
99
+ // record file references of the file the def resides in, we only want to swap to the edited file in the builder once
100
+
101
+ let mut def_file_references = None ;
102
+
103
+ for ( file_id, references) in usages {
104
+ if file_id == ctx. file_id ( ) {
105
+ def_file_references = Some ( references) ;
106
+ continue ;
107
+ }
108
+ builder. edit_file ( file_id. file_id ( ctx. db ( ) ) ) ;
109
+ let processed = process_references (
110
+ ctx,
111
+ builder,
112
+ & mut visited_modules_set,
113
+ & enum_module_def,
114
+ references,
115
+ name. clone ( )
116
+ ) ;
117
+ processed. into_iter ( ) . for_each ( |( path, node, import) | {
118
+ apply_references ( ctx. config . insert_use , path, node, import, edition, used_params_range. clone ( ) , & field_list) ;
119
+ } ) ;
120
+ }
121
+
76
122
// TODO: update calls to the function
77
123
tracing:: info!( "extract_struct_from_function_signature: starting edit" ) ;
78
124
builder. edit_file ( ctx. vfs_file_id ( ) ) ;
79
- // this has to be after the edit_file (order matters)
80
- // fn_ast and param_list must be "mut" for the effect to work on used_param_lsit
81
- let fn_ast = builder. make_mut ( fn_ast) ;
82
- let param_list = builder. make_mut ( param_list) ;
125
+ let fn_ast_mut = builder. make_mut ( fn_ast. clone ( ) ) ;
126
+ builder. make_mut ( param_list. clone ( ) ) ;
83
127
let used_param_list = used_param_list. into_iter ( ) . map ( |p| builder. make_mut ( p) ) . collect_vec ( ) ;
84
128
tracing:: info!( "extract_struct_from_function_signature: editing main file" ) ;
129
+ // this has to be after the edit_file (order matters)
130
+ // fn_ast and param_list must be "mut" for the effect to work on used_param_list
131
+ if let Some ( references) = def_file_references {
132
+ let processed = process_references (
133
+ ctx,
134
+ builder,
135
+ & mut visited_modules_set,
136
+ & enum_module_def,
137
+ references,
138
+ name. clone ( )
139
+ ) ;
140
+ processed. into_iter ( ) . for_each ( |( path, node, import) | {
141
+ apply_references ( ctx. config . insert_use , path, node, import, edition, used_params_range. clone ( ) , & field_list) ;
142
+ } ) ;
143
+ }
144
+
85
145
86
146
let generic_params = fn_ast
87
147
. generic_param_list ( )
@@ -112,15 +172,15 @@ pub(crate) fn extract_struct_from_function_signature(
112
172
field_list. clone_for_update ( )
113
173
} ;
114
174
tracing:: info!( "extract_struct_from_function_signature: collecting fields" ) ;
115
- let def = create_struct_def ( name. clone ( ) , & fn_ast , & used_param_list, & field_list, generics) ;
175
+ let def = create_struct_def ( name. clone ( ) , & fn_ast_mut , & used_param_list, & field_list, generics) ;
116
176
tracing:: info!( "extract_struct_from_function_signature: creating struct" ) ;
117
177
118
- let indent = fn_ast . indent_level ( ) ;
178
+ let indent = fn_ast_mut . indent_level ( ) ;
119
179
let def = def. indent ( indent) ;
120
180
121
181
122
182
ted:: insert_all (
123
- ted:: Position :: before ( fn_ast . syntax ( ) ) ,
183
+ ted:: Position :: before ( fn_ast_mut . syntax ( ) ) ,
124
184
vec ! [
125
185
def. syntax( ) . clone( ) . into( ) ,
126
186
make:: tokens:: whitespace( & format!( "\n \n {indent}" ) ) . into( ) ,
@@ -164,9 +224,11 @@ fn update_function(
164
224
. clone_for_update ( ) ;
165
225
// TODO: will eventually need to handle self too
166
226
167
- let range = used_param_list. first ( ) ?. syntax ( ) . syntax_element ( )
168
- ..=used_param_list. last ( ) ?. syntax ( ) . syntax_element ( ) ;
169
- ted:: replace_all ( range, vec ! [ param. syntax( ) . syntax_element( ) ] ) ;
227
+ let start_index = used_param_list. first ( ) . unwrap ( ) . syntax ( ) . index ( ) ;
228
+ let end_index = used_param_list. last ( ) . unwrap ( ) . syntax ( ) . index ( ) ;
229
+ let used_params_range = start_index..end_index + 1 ;
230
+ let new = vec ! [ param. syntax( ) . syntax_element( ) ] ;
231
+ used_param_list. first ( ) ?. syntax ( ) . parent ( ) ?. splice_children ( used_params_range, new) ;
170
232
// no need update uses of parameters in function, because we destructure the struct
171
233
Some ( ( ) )
172
234
}
@@ -334,6 +396,104 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Fu
334
396
. any ( |( name, _) | name. as_str ( ) == variant_name. text ( ) . trim_start_matches ( "r#" ) )
335
397
}
336
398
399
+ fn process_references (
400
+ ctx : & AssistContext < ' _ > ,
401
+ builder : & mut SourceChangeBuilder ,
402
+ visited_modules : & mut FxHashSet < Module > ,
403
+ function_module_def : & ModuleDef ,
404
+ refs : Vec < FileReference > ,
405
+ name : ast:: Name ,
406
+ ) -> Vec < ( ast:: PathSegment , SyntaxNode , Option < ( ImportScope , hir:: ModPath ) > ) > {
407
+ // we have to recollect here eagerly as we are about to edit the tree we need to calculate the changes
408
+ // and corresponding nodes up front
409
+ let name = make:: name_ref ( name. text_non_mutable ( ) ) ;
410
+ refs. into_iter ( )
411
+ . flat_map ( |reference| {
412
+ let ( segment, scope_node, module) =
413
+ reference_to_node ( & ctx. sema , reference, name. clone ( ) ) ?;
414
+ let scope_node = builder. make_syntax_mut ( scope_node) ;
415
+ if !visited_modules. contains ( & module) {
416
+ let mod_path = module. find_use_path (
417
+ ctx. sema . db ,
418
+ * function_module_def,
419
+ ctx. config . insert_use . prefix_kind ,
420
+ ctx. config . import_path_config ( ) ,
421
+ ) ;
422
+ if let Some ( mut mod_path) = mod_path {
423
+ mod_path. pop_segment ( ) ;
424
+ mod_path. push_segment ( hir:: Name :: new_root ( name. text_non_mutable ( ) ) . clone ( ) ) ;
425
+ let scope = ImportScope :: find_insert_use_container ( & scope_node, & ctx. sema ) ?;
426
+ visited_modules. insert ( module) ;
427
+ return Some ( ( segment, scope_node, Some ( ( scope, mod_path) ) ) ) ;
428
+ }
429
+ }
430
+ Some ( ( segment, scope_node, None ) )
431
+ } )
432
+ . collect ( )
433
+ }
434
+ fn reference_to_node (
435
+ sema : & hir:: Semantics < ' _ , RootDatabase > ,
436
+ reference : FileReference ,
437
+ name : ast:: NameRef ,
438
+ ) -> Option < ( ast:: PathSegment , SyntaxNode , hir:: Module ) > {
439
+ // filter out the reference in macro
440
+ let segment =
441
+ reference. name . as_name_ref ( ) ?. syntax ( ) . parent ( ) . and_then ( ast:: PathSegment :: cast) ?;
442
+
443
+ let segment_range = segment. syntax ( ) . text_range ( ) ;
444
+ if segment_range != reference. range {
445
+ return None ;
446
+ }
447
+
448
+ let parent = segment. parent_path ( ) . syntax ( ) . parent ( ) ?;
449
+ let expr_or_pat = match_ast ! {
450
+ match parent {
451
+ ast:: PathExpr ( _it) => parent. parent( ) ?,
452
+ ast:: RecordExpr ( _it) => parent,
453
+ ast:: TupleStructPat ( _it) => parent,
454
+ ast:: RecordPat ( _it) => parent,
455
+ _ => return None ,
456
+ }
457
+ } ;
458
+ let module = sema. scope ( & expr_or_pat) ?. module ( ) ;
459
+ let segment = make:: path_segment ( name) ;
460
+ Some ( ( segment, expr_or_pat, module) )
461
+ }
462
+
463
+ fn apply_references (
464
+ insert_use_cfg : InsertUseConfig ,
465
+ segment : ast:: PathSegment ,
466
+ node : SyntaxNode ,
467
+ import : Option < ( ImportScope , hir:: ModPath ) > ,
468
+ edition : Edition ,
469
+ used_params_range : Range < usize > ,
470
+ field_list : & ast:: RecordFieldList ,
471
+ ) -> Option < ( ) > {
472
+ if let Some ( ( scope, path) ) = import {
473
+ insert_use ( & scope, mod_path_to_ast ( & path, edition) , & insert_use_cfg) ;
474
+ }
475
+ // deep clone to prevent cycle
476
+ let path = make:: path_from_segments ( std:: iter:: once ( segment. clone_subtree ( ) ) , false ) ;
477
+ let call = CallExpr :: cast ( node) ?;
478
+ let fields = make:: record_expr_field_list (
479
+ call. arg_list ( ) ?
480
+ . args ( )
481
+ . skip ( used_params_range. start - 1 )
482
+ . take ( used_params_range. end - used_params_range. start )
483
+ . zip ( field_list. fields ( ) )
484
+ . map ( |e| {
485
+ e. 1 . name ( ) . map ( |name| -> RecordExprField {
486
+ make:: record_expr_field ( make:: name_ref ( name. text_non_mutable ( ) ) , Some ( e. 0 ) )
487
+ } )
488
+ } )
489
+ . collect :: < Option < Vec < _ > > > ( ) ?,
490
+ ) ;
491
+ let record_expr = make:: record_expr ( path, fields) . clone_for_update ( ) ;
492
+ call. arg_list ( ) ?
493
+ . syntax ( )
494
+ . splice_children ( used_params_range, vec ! [ record_expr. syntax( ) . syntax_element( ) ] ) ;
495
+ Some ( ( ) )
496
+ }
337
497
#[ cfg( test) ]
338
498
mod tests {
339
499
use super :: * ;
@@ -351,7 +511,7 @@ fn one($0x: u8, y: u32) {}
351
511
) ;
352
512
}
353
513
#[ test]
354
- fn test_extract_function_signature_single_parameters ( ) {
514
+ fn test_extract_function_signature_single_parameter ( ) {
355
515
check_assist (
356
516
extract_struct_from_function_signature,
357
517
r#"
@@ -378,4 +538,86 @@ fn foo(FooStruct { bar, baz, .. }: FooStruct) {}
378
538
"# ,
379
539
) ;
380
540
}
541
+ #[ test]
542
+ fn test_extract_function_signature_all_parameters_with_reference ( ) {
543
+ check_assist (
544
+ extract_struct_from_function_signature,
545
+ r#"
546
+ fn foo($0bar: i32, baz: i32$0) {}
547
+
548
+ fn main() {
549
+ foo(1, 2)
550
+ }
551
+ "# ,
552
+ r#"
553
+ struct FooStruct{ bar: i32, baz: i32 }
554
+
555
+ fn foo(FooStruct { bar, baz, .. }: FooStruct) {}
556
+
557
+ fn main() {
558
+ foo(FooStruct { bar: 1, baz: 2 })
559
+ }
560
+ "# ,
561
+ ) ;
562
+ }
563
+ #[ test]
564
+ fn test_extract_function_signature_single_parameter_with_reference_seperate_and_inself ( ) {
565
+ check_assist (
566
+ extract_struct_from_function_signature,
567
+ r#"
568
+ mod a {
569
+ pub fn foo($0bar: i32$0, baz: i32) {
570
+ foo(1, 2)
571
+ }
572
+ }
573
+
574
+ mod b {
575
+ use crate::a::foo;
576
+
577
+ fn main() {
578
+ foo(1, 2)
579
+ }
580
+ }
581
+ "# ,
582
+ r#"
583
+ mod a {
584
+ pub struct FooStruct{ pub bar: i32 }
585
+
586
+ pub fn foo(FooStruct { bar, .. }: FooStruct, baz: i32) {
587
+ foo(FooStruct { bar: 1 }, 2)
588
+ }
589
+ }
590
+
591
+ mod b {
592
+ use crate::a::{foo, FooStruct};
593
+
594
+ fn main() {
595
+ foo(FooStruct { bar: 1 }, 2)
596
+ }
597
+ }
598
+ "# ,
599
+ ) ;
600
+ }
601
+ #[ test]
602
+ fn test_extract_function_signature_single_parameter_with_reference ( ) {
603
+ check_assist (
604
+ extract_struct_from_function_signature,
605
+ r#"
606
+ fn foo($0bar: i32$0, baz: i32) {}
607
+
608
+ fn main() {
609
+ foo(1, 2)
610
+ }
611
+ "# ,
612
+ r#"
613
+ struct FooStruct{ bar: i32 }
614
+
615
+ fn foo(FooStruct { bar, .. }: FooStruct, baz: i32) {}
616
+
617
+ fn main() {
618
+ foo(FooStruct { bar: 1 }, 2)
619
+ }
620
+ "# ,
621
+ ) ;
622
+ }
381
623
}
0 commit comments