use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
Data, DeriveInput, Fields, GenericArgument, GenericParam, PathArguments, Type, ext::IdentExt,
};
pub fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
let ident = &input.ident;
let name_str = ident.to_string();
let type_params: Vec<&syn::Ident> = input
.generics
.params
.iter()
.filter_map(|p| match p {
GenericParam::Type(t) => Some(&t.ident),
_ => None,
})
.collect();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let body = match &input.data {
Data::Struct(data) => {
let fields_expr = struct_fields_expr(&data.fields, &type_params)?;
quote! {
crate::move_shape::Shape::Struct { fields: #fields_expr }
}
}
Data::Enum(data) => {
let variants_expr = enum_variants_expr(&data.variants, &type_params)?;
quote! {
crate::move_shape::Shape::Enum { variants: #variants_expr }
}
}
Data::Union(_) => {
return Err(syn::Error::new_spanned(
ident,
"MoveShape cannot be derived for unions",
));
}
};
Ok(quote! {
#[automatically_derived]
impl #impl_generics crate::move_shape::MoveShape for #ident #ty_generics #where_clause {
const NAME: &'static str = #name_str;
fn move_shape() -> crate::move_shape::Shape {
#body
}
}
})
}
fn enum_variants_expr(
variants: &syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>,
type_params: &[&syn::Ident],
) -> syn::Result<TokenStream2> {
let mut entries = Vec::new();
for variant in variants {
let name = variant.ident.to_string();
let fields_expr = match &variant.fields {
Fields::Unit => quote! { ::std::vec![] },
other => struct_fields_expr(other, type_params)?,
};
entries.push(quote! {
crate::move_shape::Variant { name: #name, fields: #fields_expr }
});
}
Ok(quote! { ::std::vec![ #( #entries ),* ] })
}
fn struct_fields_expr(fields: &Fields, type_params: &[&syn::Ident]) -> syn::Result<TokenStream2> {
let mut entries = Vec::new();
match fields {
Fields::Named(named) => {
for field in &named.named {
let name = field.ident.as_ref().unwrap().unraw().to_string();
let shape = type_to_shape_expr(&field.ty, type_params);
entries.push(quote! {
crate::move_shape::Field { name: #name, shape: #shape }
});
}
}
Fields::Unnamed(unnamed) => {
for (idx, field) in unnamed.unnamed.iter().enumerate() {
let name = format!("pos{idx}");
let shape = type_to_shape_expr(&field.ty, type_params);
entries.push(quote! {
crate::move_shape::Field { name: #name, shape: #shape }
});
}
}
Fields::Unit => {
return Err(syn::Error::new_spanned(
fields,
"MoveShape: unit structs are not supported",
));
}
}
Ok(quote! { ::std::vec![ #( #entries ),* ] })
}
fn type_to_shape_expr(ty: &Type, type_params: &[&syn::Ident]) -> TokenStream2 {
if let Type::Path(tp) = ty
&& let Some(seg) = tp.path.segments.last()
{
let name_ident = &seg.ident;
let name_str = name_ident.to_string();
match name_str.as_str() {
"bool" => return quote! { crate::move_shape::Shape::Bool },
"u8" => return quote! { crate::move_shape::Shape::U8 },
"u16" => return quote! { crate::move_shape::Shape::U16 },
"u32" => return quote! { crate::move_shape::Shape::U32 },
"u64" => return quote! { crate::move_shape::Shape::U64 },
"u128" => return quote! { crate::move_shape::Shape::U128 },
"Vec" => {
if let Some(inner) = extract_single_generic(seg) {
let inner_expr = type_to_shape_expr(&inner, type_params);
return quote! {
crate::move_shape::Shape::Vector(::std::boxed::Box::new(#inner_expr))
};
}
}
"Option" => {
if let Some(inner) = extract_single_generic(seg) {
let inner_expr = type_to_shape_expr(&inner, type_params);
return quote! {
crate::move_shape::Shape::Option(::std::boxed::Box::new(#inner_expr))
};
}
}
"PhantomData" => return quote! { crate::move_shape::Shape::Phantom },
_ => {}
}
if tp.qself.is_none()
&& tp.path.segments.len() == 1
&& matches!(seg.arguments, PathArguments::None)
&& let Some(idx) = type_params.iter().position(|p| *p == name_ident)
{
let idx = idx as u16;
return quote! { crate::move_shape::Shape::TypeParameter(#idx) };
}
let args_expr = match &seg.arguments {
PathArguments::AngleBracketed(args) => {
let arg_exprs: Vec<TokenStream2> = args
.args
.iter()
.filter_map(|a| match a {
GenericArgument::Type(t) => Some(type_to_shape_expr(t, type_params)),
_ => None,
})
.collect();
quote! { ::std::vec![ #( #arg_exprs ),* ] }
}
_ => quote! { ::std::vec![] },
};
return quote! {
crate::move_shape::Shape::Datatype {
name: <#ty as crate::move_shape::MoveShape>::NAME,
args: #args_expr,
}
};
}
quote! { crate::move_shape::Shape::Datatype { name: "<unsupported>", args: ::std::vec![] } }
}
fn extract_single_generic(seg: &syn::PathSegment) -> Option<Type> {
if let PathArguments::AngleBracketed(args) = &seg.arguments
&& let Some(GenericArgument::Type(ty)) = args.args.first()
{
return Some(ty.clone());
}
None
}