diff options
author | Martin Fischer <martin@push-f.com> | 2021-11-22 12:57:53 +0100 |
---|---|---|
committer | Martin Fischer <martin@push-f.com> | 2021-11-22 13:03:47 +0100 |
commit | 684d7e5873aa0b6423e167124fd8c72e02173ba2 (patch) | |
tree | e53af9b8b4abcd815cfdac5871d145ef73ac4d39 /src | |
parent | 890247746e74e015acbb290c925284c5047a580c (diff) |
better errors for type arguments of collections
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 23 | ||||
-rw-r--r-- | src/transform.rs | 98 |
2 files changed, 79 insertions, 42 deletions
@@ -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(), + )); } } } |