diff options
author | Martin Fischer <martin@push-f.com> | 2021-11-25 10:30:54 +0100 |
---|---|---|
committer | Martin Fischer <martin@push-f.com> | 2021-11-26 11:45:53 +0100 |
commit | 7489a3c2246e7ea2483446dd2ed3fdbfaf462c1a (patch) | |
tree | fc2670f56f7cc16567eb10d88f9439abb9098298 /src | |
parent | 43950edc4f26a07055fb917fa8bbb262276e2a08 (diff) |
introduce #[convert] attribute
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 15 | ||||
-rw-r--r-- | src/parse_assoc_type.rs | 8 | ||||
-rw-r--r-- | src/parse_attrs.rs | 59 | ||||
-rw-r--r-- | src/trait_sig.rs | 9 | ||||
-rw-r--r-- | src/transform.rs | 16 |
5 files changed, 89 insertions, 18 deletions
@@ -93,9 +93,10 @@ pub fn dynamize(_attr: TokenStream, input: TokenStream) -> TokenStream { } let mut type_converter = TypeConverter { - collections: method_attrs.collections, - assoc_type_conversions: HashMap::new(), trait_ident: original_trait.ident.clone(), + assoc_type_conversions: HashMap::new(), + collections: method_attrs.collections, + type_conversions: method_attrs.type_conversions, }; for item in &original_trait.items { @@ -122,9 +123,10 @@ pub fn dynamize(_attr: TokenStream, input: TokenStream) -> TokenStream { let mut objectifiable_methods: Vec<(Signature, SignatureChanges)> = Vec::new(); - for item in &original_trait.items { + for item in &mut original_trait.items { if let TraitItem::Method(method) = item { let mut signature = method.sig.clone(); + match convert_trait_signature(&mut signature, &type_converter) { Ok(parsed_method) => objectifiable_methods.push((signature, parsed_method)), Err((span, err)) => match err { @@ -169,7 +171,7 @@ pub fn dynamize(_attr: TokenStream, input: TokenStream) -> TokenStream { return abort!(span, "dynamize does not support qualified associated types") } MethodError::Transform(TransformError::SelfQualifiedAsOtherTrait) => { - return abort!(span, "dynamize does not support Self qualified as another trait") + return abort!(span, "dynamize does not know how to convert this type (you can tell it with #[convert])") } MethodError::UnconvertedAssocType => { return abort!(span, "dynamize does not support associated types here") @@ -471,6 +473,11 @@ impl TypeTransform { quote! {#arg.into_iter().map(|(#(#idents),*)| (#(#transforms),*)).collect()} } TypeTransform::NoOp => arg, + TypeTransform::Verbatim(convert) => { + let ident = &convert.ident; + let block = &convert.block; + quote! { {let #ident = #arg; #block }} + } } } } diff --git a/src/parse_assoc_type.rs b/src/parse_assoc_type.rs index adde237..e52dd14 100644 --- a/src/parse_assoc_type.rs +++ b/src/parse_assoc_type.rs @@ -30,12 +30,12 @@ impl ToTokens for BoxType { } } -pub enum DestType<'a> { - Into(&'a Type), +pub enum DestType { + Into(Type), Box(BoxType), } -impl DestType<'_> { +impl DestType { pub fn get_dest(&self) -> Type { match self { DestType::Into(ty) => (*ty).clone(), @@ -78,7 +78,7 @@ pub fn parse_assoc_type( )); } - return Ok((&assoc_type.ident, DestType::Into(into_type))); + return Ok((&assoc_type.ident, DestType::Into(into_type.clone()))); } } } diff --git a/src/parse_attrs.rs b/src/parse_attrs.rs index d9725ff..06a5123 100644 --- a/src/parse_attrs.rs +++ b/src/parse_attrs.rs @@ -1,8 +1,14 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + rc::Rc, +}; use proc_macro2::{Group, TokenStream}; use quote::quote; -use syn::{parenthesized, parse::Parse, Attribute, Error, Ident, LitInt, Token}; +use syn::{ + braced, parenthesized, parse::Parse, spanned::Spanned, Attribute, Error, Ident, LitInt, Token, + Type, +}; struct Collection { pub id: Ident, @@ -32,8 +38,9 @@ impl Parse for Collection { pub struct TraitAttrs { pub blanket_impl_attrs: Vec<TokenStream>, pub dyn_trait_attrs: Vec<TokenStream>, - pub collections: HashMap<Ident, usize>, pub dynamized_supertraits: HashSet<Ident>, + pub collections: HashMap<Ident, usize>, + pub type_conversions: HashMap<Type, Rc<Convert>>, } struct Dynamized { @@ -82,6 +89,21 @@ impl TraitAttrs { }; let tokens = group.stream(); parsed.dyn_trait_attrs.push(quote! {#[#tokens]}); + } else if attrs[i].path.is_ident("convert") { + let attr = attrs.remove(i); + let convert: Convert = syn::parse2(attr.tokens)?; + let span = convert.original_type.span(); + + if parsed + .type_conversions + .insert(convert.original_type.clone(), Rc::new(convert)) + .is_some() + { + return Err(Error::new( + span, + format_args!("conversion is defined multiple times for this type"), + )); + } } else if attrs[i].path.is_ident("collection") { let attr = attrs.remove(i); let coll: Collection = syn::parse2(attr.tokens)?; @@ -118,3 +140,34 @@ impl TraitAttrs { Ok(parsed) } } + +#[derive(Debug, Clone)] +pub struct Convert { + pub ident: Ident, + pub original_type: Type, + pub dest_type: Type, + pub block: TokenStream, +} + +impl Parse for Convert { + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { + let _: Token![=] = input.parse()?; + let _: Token![|] = input.parse()?; + let param: Ident = input.parse()?; + let _: Token![:] = input.parse()?; + let original_type: Type = input.parse()?; + let _: Token![|] = input.parse()?; + let _: Token![->] = input.parse()?; + let dest_type: Type = input.parse()?; + + let inner; + braced!(inner in input); + + Ok(Self { + ident: param, + original_type, + dest_type, + block: inner.parse()?, + }) + } +} diff --git a/src/trait_sig.rs b/src/trait_sig.rs index 5d8eddf..a1be44f 100644 --- a/src/trait_sig.rs +++ b/src/trait_sig.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::rc::Rc; use proc_macro2::Span; use syn::{ @@ -8,6 +9,7 @@ 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}; @@ -21,6 +23,7 @@ pub enum TypeTransform { IntoIterMapCollect(Vec<TypeTransform>), Iterator(BoxType, Box<TypeTransform>), Result(Box<TypeTransform>, Box<TypeTransform>), + Verbatim(Rc<Convert>), } #[derive(Debug)] @@ -83,6 +86,7 @@ pub fn convert_trait_signature( }, ReturnType::Default => TypeTransform::NoOp, }; + Ok(SignatureChanges { return_type, type_param_transforms, @@ -151,11 +155,12 @@ mod tests { transform::{TransformError, TypeConverter}, }; - fn test_converter() -> TypeConverter<'static> { + fn test_converter() -> TypeConverter { TypeConverter { assoc_type_conversions: HashMap::new(), collections: HashMap::new(), trait_ident: format_ident!("test"), + type_conversions: HashMap::new(), } } @@ -185,7 +190,7 @@ mod tests { let mut type_converter = test_converter(); let ident = format_ident!("A"); let dest_inner = Type::Verbatim(quote! {Example}); - let dest = DestType::Into(&dest_inner); + let dest = DestType::Into(dest_inner); type_converter.assoc_type_conversions.insert(ident, dest); assert!(matches!( diff --git a/src/transform.rs b/src/transform.rs index 75138c9..ae345c8 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, rc::Rc}; use proc_macro2::Span; use quote::quote; @@ -10,14 +10,16 @@ use syn::{ use crate::{ filter_map_assoc_paths, match_assoc_type, parse_assoc_type::{BoxType, DestType}, + parse_attrs::Convert, syn_utils::{iter_path, iter_type, type_arguments_mut}, trait_sig::{MethodError, TypeTransform}, }; -pub struct TypeConverter<'a> { - pub assoc_type_conversions: HashMap<Ident, DestType<'a>>, +pub struct TypeConverter { + pub assoc_type_conversions: HashMap<Ident, DestType>, pub collections: HashMap<Ident, usize>, pub trait_ident: Ident, + pub type_conversions: HashMap<Type, Rc<Convert>>, } #[derive(Debug)] @@ -30,7 +32,7 @@ pub enum TransformError { SelfQualifiedAsOtherTrait, } -impl TypeConverter<'_> { +impl TypeConverter { /// A return type of Some(1) means that the type implements /// IntoIterator<Item=T1> and FromIterator<T1> /// with T1 being its first generic type parameter. @@ -59,6 +61,10 @@ impl TypeConverter<'_> { } pub fn convert_type(&self, type_: &mut Type) -> Result<TypeTransform, (Span, TransformError)> { + if let Some(conv) = self.type_conversions.get(type_) { + *type_ = conv.dest_type.clone(); + return Ok(TypeTransform::Verbatim(conv.clone())); + } if !iter_type(type_).any(match_assoc_type) { return Ok(TypeTransform::NoOp); } @@ -139,7 +145,7 @@ impl TypeConverter<'_> { return Ok(dest_type.type_transformation()); } - return Err((path.span(), TransformError::SelfQualifiedAsOtherTrait)); + return Err((type_.span(), TransformError::SelfQualifiedAsOtherTrait)); } } } else if let Type::Path(TypePath { path, qself: None }) = type_ { |