aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2021-11-22 12:57:53 +0100
committerMartin Fischer <martin@push-f.com>2021-11-22 13:03:47 +0100
commit684d7e5873aa0b6423e167124fd8c72e02173ba2 (patch)
treee53af9b8b4abcd815cfdac5871d145ef73ac4d39
parent890247746e74e015acbb290c925284c5047a580c (diff)
better errors for type arguments of collections
-rw-r--r--src/lib.rs23
-rw-r--r--src/transform.rs98
-rw-r--r--ui-tests/src/bin/unconvertible_type_map.rs8
-rw-r--r--ui-tests/src/bin/unconvertible_type_map.stderr5
-rw-r--r--ui-tests/src/bin/unconvertible_type_vec.stderr6
5 files changed, 95 insertions, 45 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 67c1223..1a84a09 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -53,9 +53,11 @@ mod syn_utils;
mod transform;
macro_rules! abort {
- ($span:expr, $message:literal) => {
- quote_spanned! {$span => compile_error!($message);}.into()
- };
+ ($span:expr, $message:literal $(,$args:tt)*) => {{
+ let msg = format!($message $(,$args)*);
+ let tokens = quote_spanned! {$span => compile_error!(#msg);}.into();
+ tokens
+ }};
}
#[proc_macro_attribute]
@@ -119,6 +121,21 @@ pub fn dynamize(_attr: TokenStream, input: TokenStream) -> TokenStream {
MethodError::Transform(TransformError::UnsupportedType) => {
return abort!(span, "dynamize does not know how to convert this type")
}
+ MethodError::Transform(TransformError::ExpectedAtLeastNTypes(n)) => {
+ return abort!(
+ span,
+ "dynamize expects at least {} generic type arguments for this type",
+ n
+ )
+ }
+ MethodError::Transform(TransformError::AssocTypeAfterFirstNTypes(n, ident)) => {
+ return abort!(
+ span,
+ "for {} dynamize supports associated types only within the first {} generic type parameters",
+ ident,
+ n
+ )
+ }
MethodError::UnconvertedAssocType => {
return abort!(span, "dynamize does not support associated types here")
}
diff --git a/src/transform.rs b/src/transform.rs
index 60ac6e8..05866b6 100644
--- a/src/transform.rs
+++ b/src/transform.rs
@@ -23,26 +23,32 @@ pub struct TypeConverter<'a> {
pub enum TransformError {
AssocTypeWithoutDestType,
UnsupportedType,
+ ExpectedAtLeastNTypes(usize),
+ AssocTypeAfterFirstNTypes(usize, Ident),
}
impl TypeConverter<'_> {
- /// Returns true for types that take one generic type parameter T
- /// and implement IntoIterator<Item=T> and FromIterator<T>
- fn known_as_collection_with_one_type(&self, ident: &Ident) -> bool {
+ /// 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.
+ ///
+ /// A return type of Some(2) means that the type implements
+ /// IntoIterator<Item=(T1, T2)> and FromIterator<(T1, T2)>
+ /// with T1 and T2 being its first two generic type parameters.
+ ///
+ /// ... etc. A return type of None means the type isn't recognized.
+ #[rustfmt::skip]
+ fn get_collection_type_count(&self, ident: &Ident) -> Option<usize> {
// when adding a type here don't forget to document it in the README
- ident == "Vec"
- || ident == "VecDeque"
- || ident == "LinkedList"
- || ident == "HashSet"
- || ident == "BinaryHeap"
- || ident == "BTreeSet"
- }
-
- /// Returns true for types that take two generic type parameter K and V
- /// and implement IntoIterator<Item=(K, V)> and FromIterator<(K, V)>
- fn known_as_collection_with_two_types(&self, ident: &Ident) -> bool {
- // when adding a type here don't forget to document it in the README
- ident == "HashMap" || ident == "BTreeMap"
+ if ident == "Vec" { return Some(1); }
+ if ident == "VecDeque" { return Some(1); }
+ if ident == "LinkedList" { return Some(1); }
+ if ident == "HashSet" { return Some(1); }
+ if ident == "BinaryHeap" { return Some(1); }
+ if ident == "BTreeSet" { return Some(1); }
+ if ident == "HashMap" { return Some(2); }
+ if ident == "BTreeMap" { return Some(2); }
+ None
}
pub fn convert_type(&self, type_: &mut Type) -> Result<TypeTransform, (Span, TransformError)> {
@@ -113,36 +119,50 @@ impl TypeConverter<'_> {
if let PathArguments::AngleBracketed(args) = &mut last_seg.arguments {
let mut args: Vec<_> = type_arguments_mut(&mut args.args).collect();
- if args.len() == 1 {
- if iter_type(args[0]).any(match_assoc_type) {
- if (last_seg.ident == "Option" && path_len == 1)
- || last_seg.ident == "Result"
- {
- return Ok(TypeTransform::Map(self.convert_type(args[0])?.into()));
- } else if self.known_as_collection_with_one_type(&last_seg.ident)
- && path_len == 1
- {
- return Ok(TypeTransform::IntoIterMapCollect(vec![
- self.convert_type(args[0])?
- ]));
+ if path_len == 1 {
+ if let Some(type_count) = self.get_collection_type_count(&last_seg.ident) {
+ if args.len() < type_count {
+ return Err((
+ last_seg.span(),
+ TransformError::ExpectedAtLeastNTypes(type_count),
+ ));
+ }
+ for i in type_count..args.len() {
+ if iter_type(args[i]).any(match_assoc_type) {
+ return Err((
+ args[i].span(),
+ TransformError::AssocTypeAfterFirstNTypes(
+ type_count,
+ last_seg.ident.clone(),
+ ),
+ ));
+ }
+ }
+ let mut transforms = Vec::new();
+ for arg in args {
+ transforms.push(self.convert_type(arg)?);
}
+ return Ok(TypeTransform::IntoIterMapCollect(transforms));
+ }
+ }
+
+ if args.len() == 1 {
+ if iter_type(args[0]).any(match_assoc_type)
+ && ((last_seg.ident == "Option" && path_len == 1)
+ || last_seg.ident == "Result")
+ {
+ return Ok(TypeTransform::Map(self.convert_type(args[0])?.into()));
}
} else if args.len() == 2
&& path_len == 1
&& (iter_type(args[0]).any(match_assoc_type)
|| iter_type(args[1]).any(match_assoc_type))
+ && last_seg.ident == "Result"
{
- if last_seg.ident == "Result" {
- return Ok(TypeTransform::Result(
- self.convert_type(args[0])?.into(),
- self.convert_type(args[1])?.into(),
- ));
- } else if self.known_as_collection_with_two_types(&last_seg.ident) {
- return Ok(TypeTransform::IntoIterMapCollect(vec![
- self.convert_type(args[0])?,
- self.convert_type(args[1])?,
- ]));
- }
+ return Ok(TypeTransform::Result(
+ self.convert_type(args[0])?.into(),
+ self.convert_type(args[1])?.into(),
+ ));
}
}
}
diff --git a/ui-tests/src/bin/unconvertible_type_map.rs b/ui-tests/src/bin/unconvertible_type_map.rs
new file mode 100644
index 0000000..40f883b
--- /dev/null
+++ b/ui-tests/src/bin/unconvertible_type_map.rs
@@ -0,0 +1,8 @@
+#[dynamize::dynamize]
+trait Trait {
+ type A: Into<String>;
+
+ fn a(&self) -> HashMap<Self::A>;
+}
+
+fn main() {}
diff --git a/ui-tests/src/bin/unconvertible_type_map.stderr b/ui-tests/src/bin/unconvertible_type_map.stderr
new file mode 100644
index 0000000..a6833d2
--- /dev/null
+++ b/ui-tests/src/bin/unconvertible_type_map.stderr
@@ -0,0 +1,5 @@
+error: dynamize expects at least 2 generic type arguments for this type
+ --> src/bin/unconvertible_type_map.rs:5:20
+ |
+5 | fn a(&self) -> HashMap<Self::A>;
+ | ^^^^^^^
diff --git a/ui-tests/src/bin/unconvertible_type_vec.stderr b/ui-tests/src/bin/unconvertible_type_vec.stderr
index 65306f0..f966ede 100644
--- a/ui-tests/src/bin/unconvertible_type_vec.stderr
+++ b/ui-tests/src/bin/unconvertible_type_vec.stderr
@@ -1,5 +1,5 @@
-error: dynamize does not know how to convert this type
- --> src/bin/unconvertible_type_vec.rs:5:20
+error: for Vec dynamize supports associated types only within the first 1 generic type parameters
+ --> src/bin/unconvertible_type_vec.rs:5:32
|
5 | fn a(&self) -> Vec<String, Self::A>;
- | ^^^
+ | ^^^^