Skip to content

Commit 304011c

Browse files
committed
fix(ast_macros): do not panic in macro
1 parent c63c944 commit 304011c

File tree

1 file changed

+49
-31
lines changed
  • crates/oxc_ast_macros/src

1 file changed

+49
-31
lines changed

crates/oxc_ast_macros/src/ast.rs

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use proc_macro2::{TokenStream, TokenTree};
2-
use quote::quote;
2+
use quote::{ToTokens, quote, quote_spanned};
33
use 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

88
use crate::generated::{derived_traits::get_trait_crate_and_generics, structs::STRUCTS};
@@ -47,41 +47,48 @@ pub struct StructDetails {
4747
fn 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, 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.\nRun `just ast` to update 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.\nRun `just ast` to update 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.
118130
fn 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 `item`.
161+
fn compile_error<S: Spanned + ToTokens>(spanned: &S, message: &str) -> TokenStream {
162+
quote_spanned! { spanned.span() => compile_error!(#message); }
145163
}

0 commit comments

Comments
 (0)