aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md55
-rw-r--r--src/lib.rs15
-rw-r--r--src/parse_assoc_type.rs8
-rw-r--r--src/parse_attrs.rs59
-rw-r--r--src/trait_sig.rs9
-rw-r--r--src/transform.rs16
-rw-r--r--tests/tests.rs19
-rw-r--r--ui-tests/src/bin/attr_convert_duplicate.rs8
-rw-r--r--ui-tests/src/bin/attr_convert_duplicate.stderr5
-rw-r--r--ui-tests/src/bin/qualified_self.stderr6
-rw-r--r--ui-tests/src/bin/qualified_self_opt.stderr6
11 files changed, 182 insertions, 24 deletions
diff --git a/README.md b/README.md
index 3fb8d39..6067520 100644
--- a/README.md
+++ b/README.md
@@ -169,6 +169,61 @@ trait Client: Sync {
Note that it is important that the `#[dynamize]` attribute comes before the
`#[async_trait]` attribute, since dynamize must run before async_trait.
+## Dynamized supertraits
+
+In Rust a macro only operates on the passed input; it does not have access to
+the surrounding source code. This also means that a `#[dynamize]` macro cannot
+know which other traits have been dynamized. When you want to dynamize a trait
+with a dynamized supertrait, you have to tell dynamize about it with the
+`#[dynamized(...)]` attribute:
+
+```rust ignore
+#[dynamize::dynamize]
+trait Client {
+ type Error: Into<SuperError>;
+
+ fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
+}
+
+#[dynamize::dynamize]
+#[dynamized(Client)]
+trait ClientWithCache: Client {
+ type Error: Into<SuperError>;
+
+ fn get_with_cache<C: Cache>(
+ &self,
+ url: String,
+ cache: C,
+ ) -> Result<Vec<u8>, <Self as ClientWithCache>::Error>;
+}
+```
+
+This results in `DynClientWithCache` having the dynamized `DynClient` supertrait.
+
+With the above code both traits have independent associated types. So a trait
+could implement one trait with one `Error` type and and the other trait with
+another `Error` type. If you don't want that to be possible you can change the
+second trait to:
+
+```rust ignore
+#[dynamize::dynamize]
+#[dynamized(Client)]
+#[convert = |x: <Self as Client>::Error| -> SuperError {x.into()}]
+trait ClientWithCache: Client {
+ fn get_with_cache<C: Cache>(
+ &self,
+ url: String,
+ cache: C,
+ ) -> Result<Vec<u8>, <Self as Client>::Error>;
+}
+```
+
+Note that we removed the associated type and are now using the associated type
+from the supertrait by qualifying `Self as Client`. Since the `#[dynamize]`
+attribute on the `ClientWithCache` trait however cannot know the associated
+type from another trait, we also need to add a `#[convert = ...]` attribute to
+tell dynamize how to convert `<Self as Client>::Error>`.
+
## Using dynamize with other collections
Dynamize automatically recognizes collections from the standard library like
diff --git a/src/lib.rs b/src/lib.rs
index 017a58b..785827a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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_ {
diff --git a/tests/tests.rs b/tests/tests.rs
index b75c605..11bf10d 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -297,3 +297,22 @@ trait OneTrait: SomeTrait {
fn test(&self) -> Self::X;
}
+
+struct SuperError;
+trait Cache {}
+#[dynamize::dynamize]
+trait Client {
+ type Error: Into<SuperError>;
+
+ fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
+}
+#[dynamize::dynamize]
+#[dynamized(Client)]
+#[convert = |x: <Self as Client>::Error| -> SuperError {x.into()}]
+trait ClientWithCache: Client {
+ fn get_with_cache<C: Cache>(
+ &self,
+ url: String,
+ cache: C,
+ ) -> Result<Vec<u8>, <Self as Client>::Error>;
+}
diff --git a/ui-tests/src/bin/attr_convert_duplicate.rs b/ui-tests/src/bin/attr_convert_duplicate.rs
new file mode 100644
index 0000000..b074d81
--- /dev/null
+++ b/ui-tests/src/bin/attr_convert_duplicate.rs
@@ -0,0 +1,8 @@
+#[dynamize::dynamize]
+#[convert = |x: Foo| -> Bar {x.baz()}]
+#[convert = |x: Foo| -> Bar {x.baz()}]
+trait Trait {
+ fn test(&self) -> Foo;
+}
+
+fn main() {}
diff --git a/ui-tests/src/bin/attr_convert_duplicate.stderr b/ui-tests/src/bin/attr_convert_duplicate.stderr
new file mode 100644
index 0000000..e4f3622
--- /dev/null
+++ b/ui-tests/src/bin/attr_convert_duplicate.stderr
@@ -0,0 +1,5 @@
+error: conversion is defined multiple times for this type
+ --> src/bin/attr_convert_duplicate.rs:3:17
+ |
+3 | #[convert = |x: Foo| -> Bar {x.baz()}]
+ | ^^^
diff --git a/ui-tests/src/bin/qualified_self.stderr b/ui-tests/src/bin/qualified_self.stderr
index d8c3a37..87dc0c2 100644
--- a/ui-tests/src/bin/qualified_self.stderr
+++ b/ui-tests/src/bin/qualified_self.stderr
@@ -1,5 +1,5 @@
-error: dynamize does not support Self qualified as another trait
- --> src/bin/qualified_self.rs:7:32
+error: dynamize does not know how to convert this type (you can tell it with #[convert])
+ --> src/bin/qualified_self.rs:7:23
|
7 | fn test(&self) -> <Self as Foo>::X;
- | ^^^
+ | ^
diff --git a/ui-tests/src/bin/qualified_self_opt.stderr b/ui-tests/src/bin/qualified_self_opt.stderr
index dd99406..e20e9c3 100644
--- a/ui-tests/src/bin/qualified_self_opt.stderr
+++ b/ui-tests/src/bin/qualified_self_opt.stderr
@@ -1,5 +1,5 @@
-error: dynamize does not support Self qualified as another trait
- --> src/bin/qualified_self_opt.rs:7:39
+error: dynamize does not know how to convert this type (you can tell it with #[convert])
+ --> src/bin/qualified_self_opt.rs:7:29
|
7 | fn test(&self) -> Option<<Self as Foo>::X>;
- | ^^^
+ | ^^