use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
fn enum_repr(enum_: &syn::ItemEnum) -> TokenStream2 {
if enum_.variants.iter().any(|var| !matches!(var.fields, syn::Fields::Unit)) {
quote!(#[repr(C, u8)])
} else {
quote!(#[repr(u8)])
}
}
fn assert_generated_derives(attrs: &[syn::Attribute]) -> TokenStream2 {
#[inline]
fn parse(attr: &syn::Attribute) -> impl Iterator<Item = syn::Ident> {
attr.parse_args_with(
syn::punctuated::Punctuated::<syn::Ident, syn::token::Comma>::parse_terminated,
)
.expect("`generate_derive` only accepts traits as single segment paths, Found an invalid argument")
.into_iter()
}
#[inline]
fn abs_trait(
ident: &syn::Ident,
) -> (/* absolute type path */ TokenStream2, /* possible generics */ TokenStream2) {
if ident == "CloneIn" {
(quote!(::oxc_allocator::CloneIn), quote!(<'static>))
} else if ident == "GetSpan" {
(quote!(::oxc_span::GetSpan), TokenStream2::default())
} else if ident == "GetSpanMut" {
(quote!(::oxc_span::GetSpanMut), TokenStream2::default())
} else {
panic!("Invalid derive trait(generate_derive): {ident}");
}
}
let assertion =
attrs.iter().filter(|attr| attr.path().is_ident("generate_derive")).flat_map(parse).map(
|derive| {
let (abs_derive, generics) = abs_trait(&derive);
quote! {{
trait AssertionTrait: #abs_derive #generics {}
impl<T: #derive #generics> AssertionTrait for T {}
}}
},
);
quote!(const _: () = { #(#assertion)* };)
}
#[proc_macro_attribute]
#[allow(clippy::missing_panics_doc)]
pub fn ast(_args: TokenStream, input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::Item);
let (head, tail) = match &input {
syn::Item::Enum(enum_) => (enum_repr(enum_), assert_generated_derives(&enum_.attrs)),
syn::Item::Struct(struct_) => {
(quote!(#[repr(C)]), assert_generated_derives(&struct_.attrs))
}
_ => unreachable!(),
};
let expanded = quote! {
#[derive(::oxc_ast_macros::Ast)]
#head
#input
#tail
};
TokenStream::from(expanded)
}
#[proc_macro_derive(Ast, attributes(scope, visit, span, serde, tsify, generate_derive))]
pub fn ast_derive(_item: TokenStream) -> TokenStream {
TokenStream::new()
}
#[proc_macro_derive(CloneIn)]
pub fn derive_clone_in(item: TokenStream) -> TokenStream {
let item = syn::parse_macro_input!(item as syn::Item);
match &item {
syn::Item::Struct(syn::ItemStruct { ident, generics, .. })
| syn::Item::Enum(syn::ItemEnum { ident, generics, .. })
if generics.params.is_empty() =>
{
quote! {
#[automatically_derived]
impl<'alloc> ::oxc_allocator::CloneIn<'alloc> for #ident {
type Cloned = #ident;
fn clone_in(&self, _: &'alloc ::oxc_allocator::Allocator) -> Self::Cloned {
std::clone::Clone::clone(self)
}
}
}
.into()
}
_ => panic!("At the moment `CloneIn` derive macro only works for types without lifetimes and/or generic params"),
}
}