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), Tuple(Vec), IntoIterMapCollect(Vec), Iterator(BoxType, Box), Result(Box, Box), Verbatim(Rc), } #[derive(Debug)] pub enum MethodError { NonDispatchableMethod, AssocTypeInInputs, ImplTraitInInputs, Transform(TransformError), UnconvertedAssocType, } impl From 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>, } pub fn convert_trait_signature( signature: &mut Signature, type_converter: &TypeConverter, ) -> Result { if is_non_dispatchable(signature) { return Err((signature.span(), MethodError::NonDispatchableMethod)); } // provide better error messages for associated types in params 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 { // non-dispatchable: fn example(&self) where Self: Sized; if let Some(where_clause) = &signature.generics.where_clause { if where_clause .predicates .iter() .any(bounds_self_and_has_bound_sized) { return true; } } // non-dispatchable: fn example(); if signature.inputs.is_empty() { return true; } // non-dispatchable: fn example(arg: Type); if matches!(signature.inputs.first(), Some(FnArg::Typed(_))) { return true; } // non-dispatchable: fn example(self); if matches!( signature.inputs.first(), Some(FnArg::Receiver(Receiver { reference: None, .. })) ) { return true; } false } /// Returns true if the bounded type is `Self` and the bounds contain `Sized`. 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; 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(), } } #[test] fn ok_void() { let mut type1: TraitItemMethod = syn::parse2(quote! { fn test(&self); }) .unwrap(); assert!(matches!( convert_trait_signature(&mut type1.sig, &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, &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, &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, &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, &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, &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, &test_converter()), Err((_, MethodError::NonDispatchableMethod)) )); } #[test] fn err_assoc_type_in_unsupported_return() { let mut type1: TraitItemMethod = syn::parse2(quote! { fn test(&self) -> Foo; }) .unwrap(); assert!(matches!( convert_trait_signature(&mut type1.sig, &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>; }) .unwrap(); assert!(matches!( convert_trait_signature(&mut type1.sig, &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, Error>; }) .unwrap(); assert!(matches!( convert_trait_signature(&mut type1.sig, &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>; }) .unwrap(); assert!(matches!( convert_trait_signature(&mut type1.sig, &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, &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); }) .unwrap(); assert!(matches!( convert_trait_signature(&mut type1.sig, &test_converter()), Err((_, MethodError::AssocTypeInInputs)) )); } #[test] fn err_impl_in_input() { let mut type1: TraitItemMethod = syn::parse2(quote! { fn test(&self, arg: Option); }) .unwrap(); assert!(matches!( convert_trait_signature(&mut type1.sig, &test_converter()), Err((_, MethodError::ImplTraitInInputs)) )); } #[test] fn err_assoc_type_in_generic() { let mut type1: TraitItemMethod = syn::parse2(quote! { fn test)>(&self, fun: F); }) .unwrap(); assert!(matches!( convert_trait_signature(&mut type1.sig, &test_converter()), Err((_, MethodError::Transform(TransformError::UnsupportedType))) )); } }