11use either:: Either ;
2- use ide_db:: defs:: Definition ;
2+ use ide_db:: { defs:: Definition , search :: FileReference } ;
33use itertools:: Itertools ;
44use syntax:: {
55 ast:: { self , AstNode , HasGenericParams , HasVisibility } ,
6- match_ast, SyntaxKind , SyntaxNode ,
6+ match_ast, SyntaxKind ,
77} ;
88
99use crate :: { assist_context:: SourceChangeBuilder , AssistContext , AssistId , AssistKind , Assists } ;
@@ -52,6 +52,9 @@ pub(crate) fn convert_named_struct_to_tuple_struct(
5252 acc : & mut Assists ,
5353 ctx : & AssistContext < ' _ > ,
5454) -> 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.
5558 let strukt = ctx. find_node_at_offset :: < Either < ast:: Struct , ast:: Variant > > ( ) ?;
5659 let field_list = strukt. as_ref ( ) . either ( |s| s. field_list ( ) , |v| v. field_list ( ) ) ?;
5760 let record_fields = match field_list {
@@ -62,12 +65,11 @@ pub(crate) fn convert_named_struct_to_tuple_struct(
6265 Either :: Left ( s) => Either :: Left ( ctx. sema . to_def ( s) ?) ,
6366 Either :: Right ( v) => Either :: Right ( ctx. sema . to_def ( v) ?) ,
6467 } ;
65- let target = strukt. as_ref ( ) . either ( |s| s. syntax ( ) , |v| v. syntax ( ) ) . text_range ( ) ;
6668
6769 acc. add (
6870 AssistId ( "convert_named_struct_to_tuple_struct" , AssistKind :: RefactorRewrite ) ,
6971 "Convert to tuple struct" ,
70- target ,
72+ strukt . syntax ( ) . text_range ( ) ,
7173 |edit| {
7274 edit_field_references ( ctx, edit, record_fields. fields ( ) ) ;
7375 edit_struct_references ( ctx, edit, strukt_def) ;
@@ -82,6 +84,8 @@ fn edit_struct_def(
8284 strukt : & Either < ast:: Struct , ast:: Variant > ,
8385 record_fields : ast:: RecordFieldList ,
8486) {
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.
8589 let tuple_fields = record_fields
8690 . fields ( )
8791 . filter_map ( |f| Some ( ast:: make:: tuple_field ( f. visibility ( ) , f. ty ( ) ?) ) ) ;
@@ -137,50 +141,72 @@ fn edit_struct_references(
137141 } ;
138142 let usages = strukt_def. usages ( & ctx. sema ) . include_self_refs ( ) . all ( ) ;
139143
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-
172144 for ( file_id, refs) in usages {
173145 edit. edit_file ( file_id) ;
174146 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) ;
180148 }
181149 }
182150}
183151
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+
184210fn edit_field_references (
185211 ctx : & AssistContext < ' _ > ,
186212 edit : & mut SourceChangeBuilder ,
@@ -199,7 +225,7 @@ fn edit_field_references(
199225 if let Some ( name_ref) = r. name . as_name_ref ( ) {
200226 // Only edit the field reference if it's part of a `.field` access
201227 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 ( ) ) ;
203229 }
204230 }
205231 }
@@ -813,6 +839,141 @@ use crate::{A::Variant, Inner};
813839fn f() {
814840 let a = Variant(Inner);
815841}
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+ }
816977"# ,
817978 ) ;
818979 }
0 commit comments