aboutsummaryrefslogtreecommitdiff
path: root/src/trait_sig.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/trait_sig.rs')
-rw-r--r--src/trait_sig.rs361
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)))
+ ));
+ }
+}