11use proc_macro2:: { TokenStream , TokenTree } ;
2- use quote:: quote;
2+ use quote:: { quote, quote_spanned } ;
33use syn:: {
44 Attribute , Fields , FieldsNamed , Ident , Item , ItemEnum , ItemStruct , parse_quote,
5- punctuated:: Punctuated , token:: Comma ,
5+ punctuated:: Punctuated , spanned :: Spanned , token:: Comma ,
66} ;
77
88use crate :: generated:: { derived_traits:: get_trait_crate_and_generics, structs:: STRUCTS } ;
@@ -47,41 +47,48 @@ pub struct StructDetails {
4747fn modify_struct ( item : & mut ItemStruct , args : TokenStream ) -> TokenStream {
4848 let assertions = assert_generated_derives ( & item. attrs ) ;
4949
50- reorder_struct_fields ( item, args) ;
50+ let reorder_result = reorder_struct_fields ( item, args) ;
51+ let error = reorder_result. err ( ) . map ( |message| compile_error ( & item. ident , message) ) ;
5152
5253 quote ! {
5354 #[ repr( C ) ]
5455 #[ derive( :: oxc_ast_macros:: Ast ) ]
5556 #item
57+ #error
5658 #assertions
5759 }
5860}
5961
6062/// Re-order struct fields, depending on instructions in `STRUCTS` (which is codegen-ed).
6163///
6264/// Mutates `item` in place, re-ordering its fields.
63- fn reorder_struct_fields ( item : & mut ItemStruct , args : TokenStream ) {
65+ fn reorder_struct_fields ( item : & mut ItemStruct , args : TokenStream ) -> Result < ( ) , & ' static str > {
6466 // Skip foreign types
6567 if let Some ( TokenTree :: Ident ( ident) ) = args. into_iter ( ) . next ( ) {
6668 if ident == "foreign" {
67- return ;
69+ return Ok ( ( ) ) ;
6870 }
6971 }
7072
71- // Get struct data. Exit if no fields need re-ordering.
73+ // Get struct data
7274 let struct_name = item. ident . to_string ( ) ;
73- let Some ( field_order) = STRUCTS [ & struct_name] . field_order else {
74- return ;
75+ let Some ( struct_details) = STRUCTS . get ( & struct_name) else {
76+ return Err ( "Struct is unknown. Run `just ast` to re-run the codegen." ) ;
77+ } ;
78+
79+ // Exit if fields don't need re-ordering
80+ let Some ( field_order) = struct_details. field_order else {
81+ return Ok ( ( ) ) ;
7582 } ;
7683
7784 // Re-order fields.
7885 // `field_order` contains indexes of fields in the order they should be.
79- let Fields :: Named ( FieldsNamed { named, .. } ) = & mut item. fields else { unreachable ! ( ) } ;
80-
81- assert ! (
82- named . len ( ) == field_order . len ( ) ,
83- "Wrong number of fields for `{struct_name}` in `STRUCTS`"
84- ) ;
86+ let named = match & mut item. fields {
87+ Fields :: Named ( FieldsNamed { named , .. } ) if named . len ( ) == field_order . len ( ) => named ,
88+ _ => {
89+ return Err ( "Struct has been altered. Run `just ast` to re-run the codegen." ) ;
90+ }
91+ } ;
8592
8693 // Create 2 sets of fields.
8794 // 1st set are the fields in original order, each prefixed with `#[cfg(doc)]`.
@@ -98,6 +105,8 @@ fn reorder_struct_fields(item: &mut ItemStruct, args: TokenStream) {
98105 pair. value_mut ( ) . attrs . insert ( 0 , parse_quote ! ( #[ cfg( not( doc) ) ] ) ) ;
99106 pair
100107 } ) ) ;
108+
109+ Ok ( ( ) )
101110}
102111
103112/// Generate assertions that traits used in `#[generate_derive]` are in scope.
@@ -115,31 +124,40 @@ fn reorder_struct_fields(item: &mut ItemStruct, args: TokenStream) {
115124///
116125/// If `GetSpan` is not in scope, or it is not the correct `oxc_span::GetSpan`,
117126/// this will raise a compilation error.
127+ ///
128+ /// If any errors e.g. cannot parse `#[generate_derive]`, or unknown traits, just skip them.
129+ /// It is the responsibility of `oxc_ast_tools` to raise errors for those.
118130fn assert_generated_derives ( attrs : & [ Attribute ] ) -> TokenStream {
119- // We don't care here if a trait is derived multiple times.
120- // It is the responsibility of `oxc_ast_tools` to raise errors for those.
121- let assertions = attrs
122- . iter ( )
123- . filter ( |attr| attr. path ( ) . is_ident ( "generate_derive" ) )
124- . flat_map ( parse_attr)
125- . map ( |trait_ident| {
131+ let mut assertions = quote ! ( ) ;
132+ for attr in attrs {
133+ if !attr. path ( ) . is_ident ( "generate_derive" ) {
134+ continue ;
135+ }
136+
137+ let Ok ( parsed) = attr. parse_args_with ( Punctuated :: < Ident , Comma > :: parse_terminated) else {
138+ continue ;
139+ } ;
140+
141+ for trait_ident in parsed {
126142 let trait_name = trait_ident. to_string ( ) ;
127143 let Some ( ( trait_path, generics) ) = get_trait_crate_and_generics ( & trait_name) else {
128- panic ! ( "Invalid derive trait(generate_derive): {trait_name}" ) ;
144+ continue ;
129145 } ;
130146
131147 // These are wrapped in a scope to avoid the need for unique identifiers
132- quote ! { {
148+ assertions . extend ( quote ! { {
133149 trait AssertionTrait : #trait_path #generics { }
134150 impl <T : #trait_ident #generics> AssertionTrait for T { }
135- } }
136- } ) ;
137- quote ! ( const _: ( ) = { #( #assertions) * } ; )
151+ } } ) ;
152+ }
153+ }
154+
155+ quote ! {
156+ const _: ( ) = { #assertions } ;
157+ }
138158}
139159
140- #[ inline]
141- fn parse_attr ( attr : & Attribute ) -> impl Iterator < Item = Ident > + use < > {
142- attr. parse_args_with ( Punctuated :: < Ident , Comma > :: parse_terminated)
143- . expect ( "`#[generate_derive]` only accepts traits as single segment paths. Found an invalid argument." )
144- . into_iter ( )
160+ /// Generate a `compile_error!` macro invocation with the given message, and the span of `spanned`.
161+ fn compile_error < S : Spanned > ( spanned : & S , message : & str ) -> TokenStream {
162+ quote_spanned ! { spanned. span( ) => compile_error!( #message) ; }
145163}
0 commit comments