diff options
author | Martin Fischer <martin@push-f.com> | 2021-11-25 08:33:06 +0100 |
---|---|---|
committer | Martin Fischer <martin@push-f.com> | 2021-11-25 08:40:30 +0100 |
commit | 21583e526c8045c60026b1fe8ec9510734d1764d (patch) | |
tree | 53763ef8ff9bbc6f1288efb9c7a3ecb58afd15cc /src/trait_sig.rs | |
parent | 73b1ca899858b7079bec54a6e4f29941cbb8d0bd (diff) |
rename parse_trait_sig module to trait_sig
Diffstat (limited to 'src/trait_sig.rs')
-rw-r--r-- | src/trait_sig.rs | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/src/trait_sig.rs b/src/trait_sig.rs new file mode 100644 index 0000000..88aac6a --- /dev/null +++ b/src/trait_sig.rs @@ -0,0 +1,361 @@ +use std::collections::HashMap; + +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::syn_utils::{iter_type, trait_bounds, TypeOrPath}; +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>), +} + +#[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: TypeOrPath) -> Option<&TypeImplTrait> { + match item { + TypeOrPath::Type(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: &TypeConverter, +) -> Result<SignatureChanges, (Span, MethodError)> { + 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 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}, + }; + + #[test] + fn ok_void() { + let mut type1: TraitItemMethod = syn::parse2(quote! { + fn test(&self); + }) + .unwrap(); + + assert!(matches!( + convert_trait_signature(&mut type1.sig, &Default::default()), + 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 = TypeConverter::default(); + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + 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, &Default::default()), + Err((_, MethodError::Transform(TransformError::UnsupportedType))) + )); + } +} |