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 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; }) .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; }) .unwrap(); assert!(matches!( parse_assoc_type(&type1), Err((_, AssocTypeParseError::AssocTypeInBound)) )); } #[test] fn err_gat_type() { let type1: TraitItemType = syn::parse2(quote! { type A: Into>; }) .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>; }) .unwrap(); assert!(matches!( parse_assoc_type(&type1), Err((_, AssocTypeParseError::GenericAssociatedType)) )); } }