1
1
use either:: Either ;
2
- use ide_db:: defs:: Definition ;
2
+ use ide_db:: { defs:: Definition , search :: FileReference } ;
3
3
use itertools:: Itertools ;
4
4
use syntax:: {
5
5
ast:: { self , AstNode , HasGenericParams , HasVisibility } ,
6
- match_ast, SyntaxKind , SyntaxNode ,
6
+ match_ast, SyntaxKind ,
7
7
} ;
8
8
9
9
use crate :: { assist_context:: SourceChangeBuilder , AssistContext , AssistId , AssistKind , Assists } ;
@@ -52,6 +52,9 @@ pub(crate) fn convert_named_struct_to_tuple_struct(
52
52
acc : & mut Assists ,
53
53
ctx : & AssistContext < ' _ > ,
54
54
) -> Option < ( ) > {
55
+ // XXX: We don't currently provide this assist for struct definitions inside macros, but if we
56
+ // are to lift this limitation, don't forget to make `edit_struct_def()` consider macro files
57
+ // too.
55
58
let strukt = ctx. find_node_at_offset :: < Either < ast:: Struct , ast:: Variant > > ( ) ?;
56
59
let field_list = strukt. as_ref ( ) . either ( |s| s. field_list ( ) , |v| v. field_list ( ) ) ?;
57
60
let record_fields = match field_list {
@@ -62,12 +65,11 @@ pub(crate) fn convert_named_struct_to_tuple_struct(
62
65
Either :: Left ( s) => Either :: Left ( ctx. sema . to_def ( s) ?) ,
63
66
Either :: Right ( v) => Either :: Right ( ctx. sema . to_def ( v) ?) ,
64
67
} ;
65
- let target = strukt. as_ref ( ) . either ( |s| s. syntax ( ) , |v| v. syntax ( ) ) . text_range ( ) ;
66
68
67
69
acc. add (
68
70
AssistId ( "convert_named_struct_to_tuple_struct" , AssistKind :: RefactorRewrite ) ,
69
71
"Convert to tuple struct" ,
70
- target ,
72
+ strukt . syntax ( ) . text_range ( ) ,
71
73
|edit| {
72
74
edit_field_references ( ctx, edit, record_fields. fields ( ) ) ;
73
75
edit_struct_references ( ctx, edit, strukt_def) ;
@@ -82,6 +84,8 @@ fn edit_struct_def(
82
84
strukt : & Either < ast:: Struct , ast:: Variant > ,
83
85
record_fields : ast:: RecordFieldList ,
84
86
) {
87
+ // Note that we don't need to consider macro files in this function because this this is
88
+ // currently not triggered for struct definitions inside macro calls.
85
89
let tuple_fields = record_fields
86
90
. fields ( )
87
91
. filter_map ( |f| Some ( ast:: make:: tuple_field ( f. visibility ( ) , f. ty ( ) ?) ) ) ;
@@ -137,50 +141,72 @@ fn edit_struct_references(
137
141
} ;
138
142
let usages = strukt_def. usages ( & ctx. sema ) . include_self_refs ( ) . all ( ) ;
139
143
140
- let edit_node = |edit : & mut SourceChangeBuilder , node : SyntaxNode | -> Option < ( ) > {
141
- match_ast ! {
142
- match node {
143
- ast:: RecordPat ( record_struct_pat) => {
144
- edit. replace(
145
- record_struct_pat. syntax( ) . text_range( ) ,
146
- ast:: make:: tuple_struct_pat(
147
- record_struct_pat. path( ) ?,
148
- record_struct_pat
149
- . record_pat_field_list( ) ?
150
- . fields( )
151
- . filter_map( |pat| pat. pat( ) )
152
- )
153
- . to_string( )
154
- ) ;
155
- } ,
156
- ast:: RecordExpr ( record_expr) => {
157
- let path = record_expr. path( ) ?;
158
- let args = record_expr
159
- . record_expr_field_list( ) ?
160
- . fields( )
161
- . filter_map( |f| f. expr( ) )
162
- . join( ", " ) ;
163
-
164
- edit. replace( record_expr. syntax( ) . text_range( ) , format!( "{path}({args})" ) ) ;
165
- } ,
166
- _ => return None ,
167
- }
168
- }
169
- Some ( ( ) )
170
- } ;
171
-
172
144
for ( file_id, refs) in usages {
173
145
edit. edit_file ( file_id) ;
174
146
for r in refs {
175
- for node in r. name . syntax ( ) . ancestors ( ) {
176
- if edit_node ( edit, node) . is_some ( ) {
177
- break ;
178
- }
179
- }
147
+ process_struct_name_reference ( ctx, r, edit) ;
180
148
}
181
149
}
182
150
}
183
151
152
+ fn process_struct_name_reference (
153
+ ctx : & AssistContext < ' _ > ,
154
+ r : FileReference ,
155
+ edit : & mut SourceChangeBuilder ,
156
+ ) -> Option < ( ) > {
157
+ // First check if it's the last semgnet of a path that directly belongs to a record
158
+ // expression/pattern.
159
+ let name_ref = r. name . as_name_ref ( ) ?;
160
+ let path_segment = name_ref. syntax ( ) . parent ( ) . and_then ( ast:: PathSegment :: cast) ?;
161
+ // A `PathSegment` always belongs to a `Path`, so there's at least one `Path` at this point.
162
+ let full_path =
163
+ path_segment. syntax ( ) . parent ( ) ?. ancestors ( ) . map_while ( ast:: Path :: cast) . last ( ) . unwrap ( ) ;
164
+
165
+ if full_path. segment ( ) . unwrap ( ) . name_ref ( ) ? != * name_ref {
166
+ // `name_ref` isn't the last segment of the path, so `full_path` doesn't point to the
167
+ // struct we want to edit.
168
+ return None ;
169
+ }
170
+
171
+ let parent = full_path. syntax ( ) . parent ( ) ?;
172
+ match_ast ! {
173
+ match parent {
174
+ ast:: RecordPat ( record_struct_pat) => {
175
+ // When we failed to get the original range for the whole struct expression node,
176
+ // we can't provide any reasonable edit. Leave it untouched.
177
+ let file_range = ctx. sema. original_range_opt( record_struct_pat. syntax( ) ) ?;
178
+ edit. replace(
179
+ file_range. range,
180
+ ast:: make:: tuple_struct_pat(
181
+ record_struct_pat. path( ) ?,
182
+ record_struct_pat
183
+ . record_pat_field_list( ) ?
184
+ . fields( )
185
+ . filter_map( |pat| pat. pat( ) )
186
+ )
187
+ . to_string( )
188
+ ) ;
189
+ } ,
190
+ ast:: RecordExpr ( record_expr) => {
191
+ // When we failed to get the original range for the whole struct pattern node,
192
+ // we can't provide any reasonable edit. Leave it untouched.
193
+ let file_range = ctx. sema. original_range_opt( record_expr. syntax( ) ) ?;
194
+ let path = record_expr. path( ) ?;
195
+ let args = record_expr
196
+ . record_expr_field_list( ) ?
197
+ . fields( )
198
+ . filter_map( |f| f. expr( ) )
199
+ . join( ", " ) ;
200
+
201
+ edit. replace( file_range. range, format!( "{path}({args})" ) ) ;
202
+ } ,
203
+ _ => { }
204
+ }
205
+ }
206
+
207
+ Some ( ( ) )
208
+ }
209
+
184
210
fn edit_field_references (
185
211
ctx : & AssistContext < ' _ > ,
186
212
edit : & mut SourceChangeBuilder ,
@@ -199,7 +225,7 @@ fn edit_field_references(
199
225
if let Some ( name_ref) = r. name . as_name_ref ( ) {
200
226
// Only edit the field reference if it's part of a `.field` access
201
227
if name_ref. syntax ( ) . parent ( ) . and_then ( ast:: FieldExpr :: cast) . is_some ( ) {
202
- edit. replace ( name_ref . syntax ( ) . text_range ( ) , index. to_string ( ) ) ;
228
+ edit. replace ( r . range , index. to_string ( ) ) ;
203
229
}
204
230
}
205
231
}
@@ -813,6 +839,141 @@ use crate::{A::Variant, Inner};
813
839
fn f() {
814
840
let a = Variant(Inner);
815
841
}
842
+ "# ,
843
+ ) ;
844
+ }
845
+
846
+ #[ test]
847
+ fn field_access_inside_macro_call ( ) {
848
+ check_assist (
849
+ convert_named_struct_to_tuple_struct,
850
+ r#"
851
+ struct $0Struct {
852
+ inner: i32,
853
+ }
854
+
855
+ macro_rules! id {
856
+ ($e:expr) => { $e }
857
+ }
858
+
859
+ fn test(c: Struct) {
860
+ id!(c.inner);
861
+ }
862
+ "# ,
863
+ r#"
864
+ struct Struct(i32);
865
+
866
+ macro_rules! id {
867
+ ($e:expr) => { $e }
868
+ }
869
+
870
+ fn test(c: Struct) {
871
+ id!(c.0);
872
+ }
873
+ "# ,
874
+ )
875
+ }
876
+
877
+ #[ test]
878
+ fn struct_usage_inside_macro_call ( ) {
879
+ check_assist (
880
+ convert_named_struct_to_tuple_struct,
881
+ r#"
882
+ macro_rules! id {
883
+ ($($t:tt)*) => { $($t)* }
884
+ }
885
+
886
+ struct $0Struct {
887
+ inner: i32,
888
+ }
889
+
890
+ fn test() {
891
+ id! {
892
+ let s = Struct {
893
+ inner: 42,
894
+ };
895
+ let Struct { inner: value } = s;
896
+ let Struct { inner } = s;
897
+ }
898
+ }
899
+ "# ,
900
+ r#"
901
+ macro_rules! id {
902
+ ($($t:tt)*) => { $($t)* }
903
+ }
904
+
905
+ struct Struct(i32);
906
+
907
+ fn test() {
908
+ id! {
909
+ let s = Struct(42);
910
+ let Struct(value) = s;
911
+ let Struct(inner) = s;
912
+ }
913
+ }
914
+ "# ,
915
+ ) ;
916
+ }
917
+
918
+ #[ test]
919
+ fn struct_name_ref_may_not_be_part_of_struct_expr_or_struct_pat ( ) {
920
+ check_assist (
921
+ convert_named_struct_to_tuple_struct,
922
+ r#"
923
+ struct $0Struct {
924
+ inner: i32,
925
+ }
926
+ struct Outer<T> {
927
+ value: T,
928
+ }
929
+ fn foo<T>() -> T { loop {} }
930
+
931
+ fn test() {
932
+ Outer {
933
+ value: foo::<Struct>();
934
+ }
935
+ }
936
+
937
+ trait HasAssoc {
938
+ type Assoc;
939
+ fn test();
940
+ }
941
+ impl HasAssoc for Struct {
942
+ type Assoc = Outer<i32>;
943
+ fn test() {
944
+ let a = Self::Assoc {
945
+ value: 42,
946
+ };
947
+ let Self::Assoc { value } = a;
948
+ }
949
+ }
950
+ "# ,
951
+ r#"
952
+ struct Struct(i32);
953
+ struct Outer<T> {
954
+ value: T,
955
+ }
956
+ fn foo<T>() -> T { loop {} }
957
+
958
+ fn test() {
959
+ Outer {
960
+ value: foo::<Struct>();
961
+ }
962
+ }
963
+
964
+ trait HasAssoc {
965
+ type Assoc;
966
+ fn test();
967
+ }
968
+ impl HasAssoc for Struct {
969
+ type Assoc = Outer<i32>;
970
+ fn test() {
971
+ let a = Self::Assoc {
972
+ value: 42,
973
+ };
974
+ let Self::Assoc { value } = a;
975
+ }
976
+ }
816
977
"# ,
817
978
) ;
818
979
}
0 commit comments