use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Field, parse_macro_input};
mod attr;
pub(crate) mod case;
mod logic;
use attr::{has_tyzen_optional, option_inner_type};
use logic::structure_definition;
pub fn derive_type(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
if let Some(error) = validate(&input) {
return error.into_compile_error().into();
}
let name = &input.ident;
let name_str = name.to_string();
let generic_params: Vec<String> = input
.generics
.params
.iter()
.filter_map(|p| {
if let syn::GenericParam::Type(t) = p {
Some(t.ident.to_string())
} else {
None
}
})
.collect();
let structure = structure_definition(&input, &generic_params);
let generics = &input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut where_clause = where_clause
.cloned()
.unwrap_or_else(|| syn::parse_quote!(where));
for param in &generics.params {
if let syn::GenericParam::Type(type_param) = param {
let ident = &type_param.ident;
where_clause
.predicates
.push(syn::parse_quote!(#ident: ::tyzen::TsType));
}
}
let ts_name_impl = if generics.params.is_empty() {
quote! { #name_str.to_string() }
} else {
let param_names = generics.params.iter().filter_map(|p| {
if let syn::GenericParam::Type(t) = p {
let ident = &t.ident;
Some(quote! { <#ident as ::tyzen::TsType>::ts_name() })
} else {
None
}
});
quote! {
format!("{}<{}>", #name_str, vec![#(#param_names),*].join(", "))
}
};
let generic_params_str = if generic_params.is_empty() {
"".to_string()
} else {
format!("<{}>", generic_params.join(", "))
};
quote! {
impl #impl_generics ::tyzen::TsType for #name #ty_generics #where_clause {
fn ts_name() -> String {
#ts_name_impl
}
}
::tyzen::__private::inventory::submit! {
::tyzen::TypeMeta {
name: #name_str,
generic_params: #generic_params_str,
structure: #structure,
}
}
}
.into()
}
fn validate(input: &DeriveInput) -> Option<syn::Error> {
let mut error = None;
for field in all_fields(input) {
if has_tyzen_optional(&field.attrs) && option_inner_type(&field.ty).is_none() {
push_error(
&mut error,
syn::Error::new_spanned(
field,
"#[tyzen(optional)] can only be used on Option<T> fields",
),
);
}
}
error
}
fn all_fields(input: &DeriveInput) -> Vec<&Field> {
match &input.data {
Data::Struct(data) => data.fields.iter().collect(),
Data::Enum(data) => data
.variants
.iter()
.flat_map(|variant| variant.fields.iter())
.collect(),
_ => Vec::new(),
}
}
fn push_error(target: &mut Option<syn::Error>, error: syn::Error) {
if let Some(existing) = target {
existing.combine(error);
} else {
*target = Some(error);
}
}