aboutsummaryrefslogtreecommitdiff
path: root/src/parse_assoc_type.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse_assoc_type.rs')
-rw-r--r--src/parse_assoc_type.rs119
1 files changed, 119 insertions, 0 deletions
diff --git a/src/parse_assoc_type.rs b/src/parse_assoc_type.rs
new file mode 100644
index 0000000..37fd78c
--- /dev/null
+++ b/src/parse_assoc_type.rs
@@ -0,0 +1,119 @@
+use proc_macro2::Span;
+use syn::spanned::Spanned;
+use syn::{GenericArgument, Ident, PathArguments, PathSegment, TraitItemType, Type};
+
+use crate::syn_utils::{find_in_type, trait_bounds};
+use crate::AssocTypeMatcher;
+
+#[derive(Debug)]
+pub enum AssocTypeParseError {
+ AssocTypeInBound,
+ GenericAssociatedType,
+ NoIntoBound,
+}
+
+pub fn parse_assoc_type(
+ assoc_type: &TraitItemType,
+) -> Result<(&Ident, &Type), (Span, AssocTypeParseError)> {
+ for bound in trait_bounds(&assoc_type.bounds) {
+ if let PathSegment {
+ ident,
+ arguments: PathArguments::AngleBracketed(args),
+ } = bound.path.segments.first().unwrap()
+ {
+ if ident == "Into" && args.args.len() == 1 {
+ if let GenericArgument::Type(into_type) = args.args.first().unwrap() {
+ // provide a better error message for type A: Into<Self::B>
+ if find_in_type(into_type, &AssocTypeMatcher).is_some() {
+ return Err((into_type.span(), AssocTypeParseError::AssocTypeInBound));
+ }
+
+ // TODO: support lifetime GATs (see the currently failing tests/gats.rs)
+ if !assoc_type.generics.params.is_empty() {
+ return Err((
+ assoc_type.generics.params.span(),
+ AssocTypeParseError::GenericAssociatedType,
+ ));
+ }
+
+ return Ok((&assoc_type.ident, into_type));
+ }
+ }
+ }
+ }
+ Err((assoc_type.span(), AssocTypeParseError::NoIntoBound))
+}
+
+#[cfg(test)]
+mod tests {
+ use quote::quote;
+ use syn::{TraitItemType, Type};
+
+ use crate::parse_assoc_type::{parse_assoc_type, AssocTypeParseError};
+
+ #[test]
+ fn ok() {
+ let type1: TraitItemType = syn::parse2(quote! {
+ type A: Into<String>;
+ })
+ .unwrap();
+
+ assert!(matches!(
+ parse_assoc_type(&type1),
+ Ok((id, Type::Path(path)))
+ if id == "A" && path.path.is_ident("String")
+ ));
+ }
+
+ #[test]
+ fn err_no_bound() {
+ let type1: TraitItemType = syn::parse2(quote! {
+ type A;
+ })
+ .unwrap();
+
+ assert!(matches!(
+ parse_assoc_type(&type1),
+ Err((_, AssocTypeParseError::NoIntoBound))
+ ));
+ }
+
+ #[test]
+ fn err_assoc_type_in_bound() {
+ let type1: TraitItemType = syn::parse2(quote! {
+ type A: Into<Self::B>;
+ })
+ .unwrap();
+
+ assert!(matches!(
+ parse_assoc_type(&type1),
+ Err((_, AssocTypeParseError::AssocTypeInBound))
+ ));
+ }
+
+ #[test]
+ fn err_gat_type() {
+ let type1: TraitItemType = syn::parse2(quote! {
+ type A<X>: Into<Foobar<X>>;
+ })
+ .unwrap();
+
+ assert!(matches!(
+ parse_assoc_type(&type1),
+ Err((_, AssocTypeParseError::GenericAssociatedType))
+ ));
+ }
+
+ #[test]
+ fn err_gat_lifetime() {
+ let type1: TraitItemType = syn::parse2(quote! {
+ type A<'a>: Into<Foobar<'a>>;
+ })
+ .unwrap();
+
+ assert!(matches!(
+ parse_assoc_type(&type1),
+ Err((_, AssocTypeParseError::GenericAssociatedType))
+ ));
+ }
+}