use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{
parse_quote, DeriveInput, Generics, ImplGenerics, Item, ItemImpl, Lifetime, Path, Type,
WhereClause,
};
use crate::{
add_lifetime_param, add_trait_where_clause, add_where_lifetime_bound_to_generics,
extend_where_clause, filter_unsized_bounds, parse_const_str, DESERIALIZE_TRAIT_NAME,
FROM_TRAIT_NAME, LIFETIME_NAME, RESULT_TYPE_NAME, SERIALIZE_TRAIT_NAME, TRY_FROM_TRAIT_NAME,
};
pub(crate) fn generate_from_trait_impl(
src: &Type,
dest: &Type,
impl_generics: &ImplGenerics,
where_clause: Option<&WhereClause>,
constructor: &TokenStream,
from_variable_name: &str,
) -> syn::Result<ItemImpl> {
let from_variable = Ident::new(from_variable_name, Span::call_site());
let from_trait: Path = parse_const_str(FROM_TRAIT_NAME);
Ok(parse_quote! {
#[automatically_derived]
impl #impl_generics #from_trait<#src> for #dest #where_clause {
fn from(#from_variable: #src) -> Self {
#constructor
}
}
})
}
pub(crate) fn generate_try_from_trait_impl(
src: &Type,
dest: &Type,
error: &Type,
impl_generics: &ImplGenerics,
where_clause: Option<&WhereClause>,
constructor: &TokenStream,
from_variable_name: &str,
) -> syn::Result<ItemImpl> {
let from_variable = Ident::new(from_variable_name, Span::call_site());
let result_type: Path = parse_const_str(RESULT_TYPE_NAME);
let try_from_trait: Path = parse_const_str(TRY_FROM_TRAIT_NAME);
Ok(parse_quote! {
#[automatically_derived]
impl #impl_generics #try_from_trait<#src> for #dest #where_clause {
type Error = #error;
fn try_from(#from_variable: #src) -> #result_type<Self, Self::Error> {
#constructor
}
}
})
}
#[derive(Clone)]
pub(crate) enum AssociatedTypeKind {
Ref(Option<Lifetime>),
Owned,
}
pub(crate) trait AssociatedType: Sized {
fn ref_bounds(&self) -> &'static [&'static str];
fn owned_bounds(&self) -> &'static [&'static str];
fn new_ref(orig_type: &DeriveInput) -> syn::Result<Self>;
fn new_owned(orig_type: &DeriveInput) -> syn::Result<Self>;
fn generate_type_declaration(&self) -> syn::Result<Item>;
fn kind(&self) -> &AssociatedTypeKind;
fn is_transparent(&self) -> bool;
fn orig_type_generics(&self) -> &Generics;
fn type_generics(&self) -> syn::Result<Generics> {
let mut generics = filter_unsized_bounds(self.orig_type_generics());
if let AssociatedTypeKind::Ref(opt_lifetime) = &self.kind() {
if let Some(lifetime) = opt_lifetime {
add_lifetime_param(&mut generics, lifetime);
}
add_trait_where_clause(&mut generics, self.inner_types()?, self.ref_bounds())?;
} else {
add_trait_where_clause(&mut generics, self.inner_types()?, self.owned_bounds())?;
}
Ok(generics)
}
fn conversion_generics(&self, direction: ConversionDirection) -> syn::Result<Generics>;
fn generate_conversion(&self) -> syn::Result<Vec<ItemImpl>>;
fn ident(&self) -> Ident;
fn lifetime(&self) -> Option<&Lifetime>;
fn inner_types(&self) -> syn::Result<Vec<&Type>>;
fn as_trait_param(&self) -> Option<syn::Result<&Type>>;
}
#[derive(Clone, Copy)]
pub(crate) enum ConversionDirection {
OrigToAssociated,
AssociatedToOrig,
}
pub(crate) struct AssociatingTrait<T> {
ref_type: T,
owned_type: T,
orig_type: DeriveInput,
trait_path: Path,
}
impl<T: AssociatedType> AssociatingTrait<T> {
pub(crate) fn new(orig_type: &DeriveInput, name: &str) -> syn::Result<Self> {
let ref_type = T::new_ref(orig_type)?;
let owned_type = T::new_owned(orig_type)?;
let trait_path = syn::parse_str(name)?;
Ok(Self {
ref_type,
owned_type,
orig_type: orig_type.clone(),
trait_path,
})
}
pub(crate) fn generate_impl(&self) -> syn::Result<TokenStream> {
let orig_ident = &self.orig_type.ident;
let lifetime = Lifetime::new(LIFETIME_NAME, Span::call_site());
let ref_ident = self.ref_type.ident();
let owned_ident = self.owned_type.ident();
let trait_param = self.ref_type.as_trait_param().transpose()?;
let mut generics = self
.owned_type
.conversion_generics(ConversionDirection::AssociatedToOrig)?;
let owned_where_clause = generics.make_where_clause();
if let Some(ref_where_clause) = self
.ref_type
.conversion_generics(ConversionDirection::AssociatedToOrig)?
.where_clause
{
extend_where_clause(owned_where_clause, &ref_where_clause);
}
let mut ref_type_generics = self.ref_type.orig_type_generics().clone();
if let Some(lifetime) = self.ref_type.lifetime() {
add_lifetime_param(&mut ref_type_generics, lifetime);
add_where_lifetime_bound_to_generics(&mut ref_type_generics, lifetime);
}
let (impl_generics, orig_generics, where_clause) = generics.split_for_impl();
let (_, ref_generics, ref_where_clause) = ref_type_generics.split_for_impl();
let trait_ident = &self.trait_path;
Ok(quote! {
impl #impl_generics #trait_ident<#trait_param> for #orig_ident #orig_generics #where_clause {
type Ref<#lifetime> = #ref_ident #ref_generics #ref_where_clause;
type Owned = #owned_ident #orig_generics;
}
})
}
pub(crate) fn generate_types_declarations(&self) -> syn::Result<TokenStream> {
let owned_decla = self.owned_type.generate_type_declaration()?;
let owned_conversion = self.owned_type.generate_conversion()?;
let serialize_trait: Path = parse_const_str(SERIALIZE_TRAIT_NAME);
let deserialize_trait: Path = parse_const_str(DESERIALIZE_TRAIT_NAME);
let ignored_lints = quote! {
#[allow(
// We add bounds on the generated code because it will make the compiler
// generate better errors in case of misuse of the macros. However in some cases
// this may generate a warning, so we silence it.
private_bounds,
// If these lints doesn't trigger on the original type, we don't want them to trigger
// on the generated one
clippy::upper_case_acronyms,
clippy::large_enum_variant,
clippy::struct_field_names
)
]};
let owned_attributes = if self.owned_type.is_transparent() {
quote! {
#[derive(#serialize_trait, #deserialize_trait)]
#[repr(transparent)]
#[serde(bound = "")]
#ignored_lints
}
} else {
quote! {
#[derive(#serialize_trait, #deserialize_trait)]
#[serde(bound = "")]
#ignored_lints
}
};
let owned_tokens = quote! {
#owned_attributes
#owned_decla
#(#owned_conversion)*
};
let ref_decla = self.ref_type.generate_type_declaration()?;
let ref_conversion = self.ref_type.generate_conversion()?;
let ref_attributes = if self.ref_type.is_transparent() {
quote! {
#[derive(#serialize_trait)]
#[repr(transparent)]
#[serde(bound = "")]
#ignored_lints
}
} else {
quote! {
#[derive(#serialize_trait)]
#[serde(bound = "")]
#ignored_lints
}
};
let ref_tokens = quote! {
#ref_attributes
#ref_decla
#(#ref_conversion)*
};
Ok(quote! {
#owned_tokens
#ref_tokens
})
}
}