diff options
Diffstat (limited to 'src/parse_assoc_type.rs')
-rw-r--r-- | src/parse_assoc_type.rs | 119 |
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)) + )); + } +} |