use std::str::FromStr;
use {
proc_macro2::{Span, TokenStream},
syn::{
self, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, FieldsNamed, FieldsUnnamed,
Generics, Ident, Variant,
},
};
use crate::{
attr,
shared::{map_lifetimes, map_type_params, split_for_impl},
};
pub fn derive(input: TokenStream) -> TokenStream {
let derive_input = syn::parse2(input).expect("Input is checked by rustc");
let container = attr::Container::from_ast(&derive_input);
let DeriveInput {
ident,
data,
generics,
..
} = derive_input;
let tokens = match data {
Data::Struct(ast) => derive_struct(&container, ast, ident, generics),
Data::Enum(ast) => derive_enum(&container, ast, ident, generics),
Data::Union(_) => panic!("Unions are not supported"),
};
tokens.into()
}
fn derive_struct(
container: &attr::Container,
ast: DataStruct,
ident: Ident,
generics: Generics,
) -> TokenStream {
let cons = match ast.fields {
Fields::Named(FieldsNamed { named, .. }) => gen_struct_cons(&ident, named),
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => gen_tuple_struct_cons(&ident, unnamed),
Fields::Unit => quote! { #ident },
};
gen_impl(container, ident, generics, cons)
}
fn gen_struct_cons<I>(ident: &Ident, fields: I) -> TokenStream
where
I: IntoIterator<Item = Field>,
{
let field_initializers = fields.into_iter().map(|field| {
let field_ty = &field.ty;
let ident = field
.ident
.as_ref()
.expect("Struct fields always have names");
quote! {
#ident: <#field_ty as gluon_base::ast::AstClone<'ast, Id>>::ast_clone(&self.#ident, arena)
}
});
quote! {
#ident {
#(#field_initializers,)*
}
}
}
fn gen_tuple_struct_cons<I>(ident: &Ident, fields: I) -> TokenStream
where
I: IntoIterator<Item = Field>,
{
let mut fields = fields.into_iter().fuse();
let (first, second) = (fields.next(), fields.next());
match (&first, &second) {
(Some(field), None) => {
let field_ty = &field.ty;
return quote! {
#ident (
<#field_ty as gluon_base::ast::AstClone<'__vm, _>>::ast_clone(&self.0, arena)
)
};
}
_ => (),
}
let field_initializers = first
.into_iter()
.chain(second)
.chain(fields)
.enumerate()
.map(|(idx, field)| {
let field_ty = &field.ty;
let idx = syn::Index::from(idx);
quote! {
<#field_ty as gluon_base::ast::AstClone<'__vm, _>>::ast_clone(&self. #idx, arena)
}
});
quote! {
#ident (
#(#field_initializers,)*
)
}
}
fn derive_enum(
container: &attr::Container,
ast: DataEnum,
ident: Ident,
generics: Generics,
) -> TokenStream {
let cons = {
let variants = ast
.variants
.iter()
.enumerate()
.map(|(tag, variant)| gen_variant_match(&ident, tag, variant));
quote! {
match self {
#(#variants,)*
}
}
};
gen_impl(container, ident, generics, cons)
}
fn gen_impl(
container: &attr::Container,
ident: Ident,
generics: Generics,
clone_impl: TokenStream,
) -> TokenStream {
let lifetime_bounds = create_lifetime_bounds(&generics);
let ast_clone_bounds = create_ast_clone_bounds(&generics);
let (impl_generics, ty_generics, where_clause) = split_for_impl(&generics, &["Id"], &["'ast"]);
let dummy_const = Ident::new(&format!("_IMPL_AST_CLONE_FOR_{}", ident), Span::call_site());
let extra_bounds = container.ast_clone_bounds.as_ref().map(|b| {
let b = TokenStream::from_str(b).unwrap();
quote! { #b, }
});
quote! {
#[allow(non_upper_case_globals)]
const #dummy_const: () = {
use crate as gluon_base;
#[automatically_derived]
#[allow(unused_attributes, unused_variables)]
impl #impl_generics gluon_base::ast::AstClone<'ast, Id> for #ident #ty_generics
#where_clause #(#ast_clone_bounds,)* #(#lifetime_bounds),* #extra_bounds Id: Clone
{
fn ast_clone(&self, arena: gluon_base::ast::ArenaRef<'_, 'ast, Id>) -> Self {
#clone_impl
}
}
};
}
}
fn gen_variant_match(ident: &Ident, _tag: usize, variant: &Variant) -> TokenStream {
let variant_ident = &variant.ident;
match &variant.fields {
Fields::Unit => quote! {
#ident::#variant_ident => #ident::#variant_ident
},
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
let fields: Vec<_> = unnamed
.iter()
.enumerate()
.map(|(idx, _field)| syn::Ident::new(&format!("_{}", idx), Span::call_site()))
.collect();
let cons = gen_tuple_variant_cons(unnamed.iter().zip(fields.iter()));
quote! {
#ident::#variant_ident ( #(#fields),* ) =>
#ident::#variant_ident#cons
}
}
Fields::Named(FieldsNamed { named, .. }) => {
let cons = gen_struct_variant_cons(ident, variant_ident, named);
let named = named.iter().map(|field| field.ident.as_ref().unwrap());
quote! {
#ident::#variant_ident { #(#named),* } => #cons
}
}
}
}
fn gen_tuple_variant_cons<'a, I>(fields: I) -> TokenStream
where
I: IntoIterator<Item = (&'a syn::Field, &'a syn::Ident)>,
{
let fields = fields.into_iter().map(|(field, ident)| {
let field_ty = &field.ty;
quote! {
<#field_ty as gluon_base::ast::AstClone<_>>::ast_clone(#ident, arena)
}
});
quote! {
(#(#fields),*)
}
}
fn gen_struct_variant_cons<'a, I>(ident: &Ident, variant_ident: &Ident, fields: I) -> TokenStream
where
I: IntoIterator<Item = &'a Field>,
{
let fields = fields.into_iter().map(|field| {
let field_ty = &field.ty;
let field_ident = field
.ident
.as_ref()
.expect("Struct fields always have names");
quote! {
#field_ident: <#field_ty as gluon_base::ast::AstClone<_>>::ast_clone(#field_ident, arena)
}
});
quote! {
#ident::#variant_ident { #(#fields),* }
}
}
fn create_ast_clone_bounds(generics: &Generics) -> Vec<TokenStream> {
map_type_params(generics, |ty| {
quote! {
#ty: gluon_base::ast::AstClone<'ast, Id>
}
})
}
fn create_lifetime_bounds(generics: &Generics) -> Vec<TokenStream> {
map_lifetimes(generics, |_lifetime| {
quote! {}
})
}