use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::spanned::Spanned;
use syn::{
parse_quote, Data, GenericArgument, GenericParam, Generics, Ident, Lifetime, Path,
PathArguments, Token, TraitBound, Type, TypeParam, TypePath, WhereClause,
};
use crate::transparent::{TransparentStruct, TransparentStructKind};
use crate::versionize_attribute::{
ClassicVersionizeAttribute, ConversionType, ConvertVersionizeAttribute, VersionizeAttribute,
};
use crate::{
add_lifetime_where_clause, add_trait_where_clause, add_where_lifetime_bound_to_generics,
parse_const_str, DISPATCH_TRAIT_NAME, ERROR_TRAIT_NAME, FROM_TRAIT_NAME, INTO_TRAIT_NAME,
SEND_TRAIT_NAME, STATIC_LIFETIME_NAME, SYNC_TRAIT_NAME, TRY_INTO_TRAIT_NAME,
UNVERSIONIZE_ERROR_NAME, UNVERSIONIZE_TRAIT_NAME, VERSIONIZE_OWNED_TRAIT_NAME,
VERSIONIZE_TRAIT_NAME,
};
pub(crate) enum VersionizeImplementor {
Classic(ClassicVersionizeAttribute),
Convert(ConvertVersionizeAttribute),
Transparent(Box<TransparentStruct>),
}
impl VersionizeImplementor {
pub(crate) fn new(
attributes: VersionizeAttribute,
decla: &Data,
base_span: Span,
) -> syn::Result<Self> {
match attributes {
VersionizeAttribute::Classic(classic) => Ok(Self::Classic(classic)),
VersionizeAttribute::Convert(convert) => Ok(Self::Convert(convert)),
VersionizeAttribute::Transparent => Ok(Self::Transparent(Box::new(
TransparentStruct::new(decla, base_span)?,
))),
}
}
pub(crate) fn is_directly_versioned(&self) -> bool {
match self {
Self::Classic(_) => true,
Self::Convert(_) => false,
Self::Transparent(_) => false,
}
}
pub(crate) fn versioned_type(
&self,
lifetime: &Lifetime,
input_generics: &Generics,
) -> proc_macro2::TokenStream {
match self {
Self::Classic(attr) => {
let (_, ty_generics, _) = input_generics.split_for_impl();
let dispatch_trait: Path = parse_const_str(DISPATCH_TRAIT_NAME);
let dispatch_enum_path = &attr.dispatch_enum;
quote! {
<#dispatch_enum_path #ty_generics as
#dispatch_trait<Self>>::Ref<#lifetime>
}
}
Self::Convert(_) => {
self.versioned_owned_type(input_generics)
}
Self::Transparent(transparent) => {
let versionize_trait: Path = parse_const_str(VERSIONIZE_TRAIT_NAME);
let inner_type = &transparent.inner_type;
quote! { <#inner_type as #versionize_trait>::Versioned<#lifetime>}
}
}
}
pub(crate) fn versioned_type_where_clause(
&self,
lifetime: &Lifetime,
input_generics: &Generics,
) -> Option<WhereClause> {
let mut generics = input_generics.clone();
add_where_lifetime_bound_to_generics(&mut generics, lifetime);
let (_, _, where_clause) = generics.split_for_impl();
where_clause.cloned()
}
pub(crate) fn versioned_owned_type(
&self,
input_generics: &Generics,
) -> proc_macro2::TokenStream {
let (_, ty_generics, _) = input_generics.split_for_impl();
match self {
Self::Classic(attr) => {
let dispatch_trait: Path = parse_const_str(DISPATCH_TRAIT_NAME);
let dispatch_enum_path = &attr.dispatch_enum;
quote! {
<#dispatch_enum_path #ty_generics as
#dispatch_trait<Self>>::Owned
}
}
Self::Convert(convert_attr) => {
let convert_type_path = &convert_attr.conversion_target;
let versionize_owned_trait: Path = parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
quote! {
<#convert_type_path as #versionize_owned_trait>::VersionedOwned
}
}
Self::Transparent(transparent) => {
let versionize_owned_trait: Path = parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
let inner_type = &transparent.inner_type;
quote! { <#inner_type as #versionize_owned_trait>::VersionedOwned }
}
}
}
pub(crate) fn versioned_owned_type_where_clause(
&self,
input_generics: &Generics,
) -> Option<WhereClause> {
match self {
Self::Classic(_) => input_generics.split_for_impl().2.cloned(),
Self::Convert(convert_attr) => extract_generics(&convert_attr.conversion_target)
.split_for_impl()
.2
.cloned(),
Self::Transparent(_) => input_generics.split_for_impl().2.cloned(),
}
}
pub(crate) fn versionize_trait_where_clause(
&self,
input_generics: &Generics,
) -> syn::Result<Option<WhereClause>> {
let mut generics = input_generics.clone();
match self {
VersionizeImplementor::Classic(_) => {
self.versionize_owned_trait_where_clause(&generics)
}
VersionizeImplementor::Convert(_) => {
add_trait_where_clause(&mut generics, [&parse_quote! { Self }], &["Clone"])?;
self.versionize_owned_trait_where_clause(&generics)
}
VersionizeImplementor::Transparent(transparent) => {
add_trait_where_clause(
&mut generics,
[&transparent.inner_type],
&[VERSIONIZE_TRAIT_NAME],
)?;
Ok(generics.split_for_impl().2.cloned())
}
}
}
pub(crate) fn versionize_owned_trait_where_clause(
&self,
input_generics: &Generics,
) -> syn::Result<Option<WhereClause>> {
let mut generics = input_generics.clone();
match self {
Self::Classic(attr) => {
let dispatch_generics = generics.clone();
let dispatch_ty_generics = dispatch_generics.split_for_impl().1;
let dispatch_enum_path = &attr.dispatch_enum;
add_trait_where_clause(
&mut generics,
[&parse_quote!(#dispatch_enum_path #dispatch_ty_generics)],
&[format!("{DISPATCH_TRAIT_NAME}<Self>")],
)?;
}
Self::Convert(convert_attr) => {
let convert_type_path = &convert_attr.conversion_target;
add_trait_where_clause(
&mut generics,
[&parse_quote!(#convert_type_path)],
&[
VERSIONIZE_OWNED_TRAIT_NAME,
&format!("{FROM_TRAIT_NAME}<Self>"),
],
)?;
}
Self::Transparent(transparent) => {
add_trait_where_clause(
&mut generics,
[&transparent.inner_type],
&[VERSIONIZE_OWNED_TRAIT_NAME],
)?;
}
}
Ok(generics.split_for_impl().2.cloned())
}
pub(crate) fn unversionize_trait_where_clause(
&self,
input_generics: &Generics,
) -> syn::Result<Option<WhereClause>> {
match self {
Self::Classic(_) => self.versionize_owned_trait_where_clause(input_generics),
Self::Convert(convert_attr) => {
let mut generics = input_generics.clone();
let convert_type_path = &convert_attr.conversion_target;
let into_trait = match convert_attr.conversion_type {
ConversionType::Direct => format!("{INTO_TRAIT_NAME}<Self>"),
ConversionType::Try => {
let try_into_trait: Path = parse_const_str(TRY_INTO_TRAIT_NAME);
add_trait_where_clause(
&mut generics,
[&parse_quote!(<#convert_type_path as #try_into_trait<Self>>::Error)],
&[ERROR_TRAIT_NAME, SYNC_TRAIT_NAME, SEND_TRAIT_NAME],
)?;
add_lifetime_where_clause(
&mut generics,
[&parse_quote!(<#convert_type_path as #try_into_trait<Self>>::Error)],
&[STATIC_LIFETIME_NAME],
)?;
format!("{TRY_INTO_TRAIT_NAME}<Self>")
}
};
add_trait_where_clause(
&mut generics,
[&parse_quote!(#convert_type_path)],
&[
UNVERSIONIZE_TRAIT_NAME,
&format!("{FROM_TRAIT_NAME}<Self>"),
&into_trait,
],
)?;
Ok(generics.split_for_impl().2.cloned())
}
Self::Transparent(transparent) => {
let mut generics = input_generics.clone();
add_trait_where_clause(
&mut generics,
[&transparent.inner_type],
&[UNVERSIONIZE_TRAIT_NAME],
)?;
Ok(generics.split_for_impl().2.cloned())
}
}
}
pub(crate) fn versionize_method_body(&self) -> proc_macro2::TokenStream {
match self {
Self::Classic(_) => {
quote! {
self.into()
}
}
Self::Convert(convert_attr) => {
let versionize_owned_trait: TraitBound =
parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
let convert_type_path = with_turbofish(&convert_attr.conversion_target);
quote! {
#versionize_owned_trait::versionize_owned(#convert_type_path::from(self.to_owned()))
}
}
Self::Transparent(transparent) => match &transparent.kind {
TransparentStructKind::NewType => {
quote! {
self.0.versionize()
}
}
TransparentStructKind::SingleField(field_name) => {
quote! {
self.#field_name.versionize()
}
}
},
}
}
pub(crate) fn versionize_owned_method_body(&self) -> proc_macro2::TokenStream {
let versionize_owned_trait: TraitBound = parse_const_str(VERSIONIZE_OWNED_TRAIT_NAME);
match self {
Self::Classic(_) => {
quote! {
self.into()
}
}
Self::Convert(convert_attr) => {
let convert_type_path = with_turbofish(&convert_attr.conversion_target);
quote! {
#versionize_owned_trait::versionize_owned(#convert_type_path::from(self))
}
}
Self::Transparent(transparent) => match &transparent.kind {
TransparentStructKind::NewType => {
quote! {
self.0.versionize_owned()
}
}
TransparentStructKind::SingleField(field_name) => {
quote! {
self.#field_name.versionize_owned()
}
}
},
}
}
pub(crate) fn unversionize_method_body(&self, arg_name: &Ident) -> proc_macro2::TokenStream {
let error: Type = parse_const_str(UNVERSIONIZE_ERROR_NAME);
match self {
Self::Classic(_) => {
quote! { #arg_name.try_into() }
}
Self::Convert(convert_attr) => {
let target = with_turbofish(&convert_attr.conversion_target);
match convert_attr.conversion_type {
ConversionType::Direct => {
quote! { #target::unversionize(#arg_name).map(|value| value.into()) }
}
ConversionType::Try => {
let target_name = format!("{}", target.to_token_stream());
quote! { #target::unversionize(#arg_name).and_then(|value| TryInto::<Self>::try_into(value)
.map_err(|e| #error::conversion(#target_name, e)))
}
}
}
}
Self::Transparent(transparent) => {
let inner = match &transparent.inner_type {
Type::Path(path) => Type::Path(TypePath {
qself: path.qself.clone(),
path: with_turbofish(&path.path),
}),
inner => inner.clone(),
};
match &transparent.kind {
TransparentStructKind::NewType => {
quote! {
#inner::unversionize(#arg_name).map(Self)
}
}
TransparentStructKind::SingleField(field_name) => {
quote! {
Ok(Self { #field_name: #inner::unversionize(#arg_name)? })
}
}
}
}
}
}
}
fn with_turbofish(path: &Path) -> Path {
let mut with_turbo = path.clone();
for segment in with_turbo.segments.iter_mut() {
if let PathArguments::AngleBracketed(generics) = &mut segment.arguments {
generics.colon2_token = Some(Token));
}
}
with_turbo
}
fn extract_generics(path: &Path) -> Generics {
let mut generics = Generics::default();
if let Some(last_segment) = path.segments.last() {
if let PathArguments::AngleBracketed(args) = &last_segment.arguments {
for arg in &args.args {
if let GenericArgument::Type(Type::Path(type_path)) = arg {
if let Some(ident) = type_path.path.get_ident() {
let param = TypeParam::from(ident.clone());
generics.params.push(GenericParam::Type(param));
}
}
}
}
}
generics
}