use std::collections::HashMap;
use std::rc::Rc;
use proc_macro2::Span;
use syn::{
spanned::Spanned, FnArg, PredicateType, Receiver, ReturnType, Type, TypePath, WherePredicate,
};
use syn::{Ident, Signature, TypeImplTrait};
use crate::match_assoc_type;
use crate::parse_assoc_type::BoxType;
use crate::parse_attrs::Convert;
use crate::syn_utils::{iter_type, trait_bounds};
use crate::transform::{dynamize_function_bounds, TransformError, TypeConverter};
#[derive(Debug, Clone)]
pub enum TypeTransform {
NoOp,
Into,
Box(BoxType),
Map(Box<TypeTransform>),
Tuple(Vec<TypeTransform>),
IntoIterMapCollect(Vec<TypeTransform>),
Iterator(BoxType, Box<TypeTransform>),
Result(Box<TypeTransform>, Box<TypeTransform>),
Verbatim(Rc<Convert>),
}
#[derive(Debug)]
pub enum MethodError {
NonDispatchableMethod,
AssocTypeInInputs,
ImplTraitInInputs,
Transform(TransformError),
UnconvertedAssocType,
}
impl From<TransformError> for MethodError {
fn from(err: TransformError) -> Self {
Self::Transform(err)
}
}
fn filter_map_impl_trait(item: &Type) -> Option<&TypeImplTrait> {
match item {
Type::ImplTrait(impltrait) => Some(impltrait),
_other => None,
}
}
pub struct SignatureChanges {
pub return_type: TypeTransform,
pub type_param_transforms: HashMap<Ident, Vec<TypeTransform>>,
}
pub fn convert_trait_signature(
signature: &mut Signature,
type_converter: &mut TypeConverter,
) -> Result<SignatureChanges, (Span, MethodError)> {
if is_non_dispatchable(signature) {
return Err((signature.span(), MethodError::NonDispatchableMethod));
}
for input in &signature.inputs {
if let FnArg::Typed(pattype) = input {
if iter_type(&pattype.ty).any(match_assoc_type) {
return Err((pattype.ty.span(), MethodError::AssocTypeInInputs));
}
if let Some(impl_trait) = iter_type(&pattype.ty).find_map(filter_map_impl_trait) {
return Err((impl_trait.span(), MethodError::ImplTraitInInputs));
}
}
}
let type_param_transforms = dynamize_function_bounds(&mut signature.generics, type_converter)?;
let return_type = match &mut signature.output {
ReturnType::Type(_, og_type) => match type_converter.convert_type(og_type) {
Ok(ret_type) => ret_type,
Err((span, err)) => {
return Err((span, err.into()));
}
},
ReturnType::Default => TypeTransform::NoOp,
};
Ok(SignatureChanges {
return_type,
type_param_transforms,
})
}
fn is_non_dispatchable(signature: &Signature) -> bool {
if let Some(where_clause) = &signature.generics.where_clause {
if where_clause
.predicates
.iter()
.any(bounds_self_and_has_bound_sized)
{
return true;
}
}
if signature.inputs.is_empty() {
return true;
}
if matches!(signature.inputs.first(), Some(FnArg::Typed(_))) {
return true;
}
if matches!(
signature.inputs.first(),
Some(FnArg::Receiver(Receiver {
reference: None,
..
}))
) {
return true;
}
false
}
fn bounds_self_and_has_bound_sized(predicate: &WherePredicate) -> bool {
matches!(
predicate,
WherePredicate::Type(PredicateType {
bounded_ty: Type::Path(TypePath { path, .. }),
bounds,
..
})
if path.is_ident("Self")
&& trait_bounds(bounds).any(|b| b.path.is_ident("Sized"))
)
}
#[cfg(test)]
mod tests {
use std::collections::{HashMap, HashSet};
use quote::{format_ident, quote};
use syn::{TraitItemMethod, Type};
use crate::{
parse_assoc_type::DestType,
trait_sig::{convert_trait_signature, MethodError, SignatureChanges, TypeTransform},
transform::{TransformError, TypeConverter},
};
fn test_converter() -> TypeConverter {
TypeConverter {
assoc_type_conversions: HashMap::new(),
collections: HashMap::new(),
trait_ident: format_ident!("test"),
type_conversions: HashMap::new(),
used_conversions: HashSet::new(),
}
}
#[test]
fn ok_void() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self);
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Ok(SignatureChanges {
return_type: TypeTransform::NoOp,
..
})
));
}
#[test]
fn ok_assoc_type() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self) -> Self::A;
})
.unwrap();
let mut type_converter = test_converter();
let ident = format_ident!("A");
let dest_inner = Type::Verbatim(quote! {Example});
let dest = DestType::Into(dest_inner);
type_converter.assoc_type_conversions.insert(ident, dest);
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut type_converter),
Ok(SignatureChanges {
return_type: TypeTransform::Into,
..
})
));
}
#[test]
fn err_unconvertible_assoc_type() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self) -> Self::A;
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((
_,
MethodError::Transform(TransformError::AssocTypeWithoutDestType)
))
));
}
#[test]
fn err_non_dispatchable_assoc_function_no_args() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test();
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::NonDispatchableMethod))
));
}
#[test]
fn err_non_dispatchable_assoc_function_with_args() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(arg: Type);
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::NonDispatchableMethod))
));
}
#[test]
fn err_non_dispatchable_consume_self() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(self);
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::NonDispatchableMethod))
));
}
#[test]
fn err_non_dispatchable_where_self_sized() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self) where Self: Sized;
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::NonDispatchableMethod))
));
}
#[test]
fn err_assoc_type_in_unsupported_return() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self) -> Foo<Self::A>;
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::Transform(TransformError::UnsupportedType)))
));
}
#[test]
fn err_assoc_type_in_unsupported_return_in_opt() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self) -> Option<Foo<Self::A>>;
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::Transform(TransformError::UnsupportedType)))
));
}
#[test]
fn err_assoc_type_in_unsupported_return_in_ok() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self) -> Result<Foo<Self::A>, Error>;
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::Transform(TransformError::UnsupportedType)))
));
}
#[test]
fn err_assoc_type_in_unsupported_return_in_err() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self) -> Result<Ok, Foo<Self::A>>;
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::Transform(TransformError::UnsupportedType)))
));
}
#[test]
fn err_assoc_type_in_input() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self, x: Self::A);
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::AssocTypeInInputs))
));
}
#[test]
fn err_assoc_type_in_input_opt() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self, x: Option<Self::A>);
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::AssocTypeInInputs))
));
}
#[test]
fn err_impl_in_input() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test(&self, arg: Option<impl SomeTrait>);
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::ImplTraitInInputs))
));
}
#[test]
fn err_assoc_type_in_generic() {
let mut type1: TraitItemMethod = syn::parse2(quote! {
fn test<F: Fn(Foo<Self::A>)>(&self, fun: F);
})
.unwrap();
assert!(matches!(
convert_trait_signature(&mut type1.sig, &mut test_converter()),
Err((_, MethodError::Transform(TransformError::UnsupportedType)))
));
}
}