1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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))
));
}
}
|