1#![allow(clippy::too_many_lines)]
2
3#[macro_use]
4mod macros;
5
6use quote::quote;
7use syn::{DeriveInput, ItemFn, TypeParamBound, WhereClause, WherePredicate};
8
9#[test]
10fn test_split_for_impl() {
11 let input = quote! {
12 struct S<'a, 'b: 'a, #[may_dangle] T: 'a = ()> where T: Debug;
13 };
14
15 snapshot!(input as DeriveInput, @r###"
16 DeriveInput {
17 vis: Inherited,
18 ident: "S",
19 generics: Generics {
20 lt_token: Some,
21 params: [
22 Lifetime(LifetimeDef {
23 lifetime: Lifetime {
24 ident: "a",
25 },
26 }),
27 Lifetime(LifetimeDef {
28 lifetime: Lifetime {
29 ident: "b",
30 },
31 colon_token: Some,
32 bounds: [
33 Lifetime {
34 ident: "a",
35 },
36 ],
37 }),
38 Type(TypeParam {
39 attrs: [
40 Attribute {
41 style: Outer,
42 path: Path {
43 segments: [
44 PathSegment {
45 ident: "may_dangle",
46 arguments: None,
47 },
48 ],
49 },
50 tokens: TokenStream(``),
51 },
52 ],
53 ident: "T",
54 colon_token: Some,
55 bounds: [
56 Lifetime(Lifetime {
57 ident: "a",
58 }),
59 ],
60 eq_token: Some,
61 default: Some(Type::Tuple),
62 }),
63 ],
64 gt_token: Some,
65 where_clause: Some(WhereClause {
66 predicates: [
67 Type(PredicateType {
68 bounded_ty: Type::Path {
69 path: Path {
70 segments: [
71 PathSegment {
72 ident: "T",
73 arguments: None,
74 },
75 ],
76 },
77 },
78 bounds: [
79 Trait(TraitBound {
80 modifier: None,
81 path: Path {
82 segments: [
83 PathSegment {
84 ident: "Debug",
85 arguments: None,
86 },
87 ],
88 },
89 }),
90 ],
91 }),
92 ],
93 }),
94 },
95 data: Data::Struct {
96 fields: Unit,
97 semi_token: Some,
98 },
99 }
100 "###);
101
102 let generics = input.generics;
103 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
104
105 let generated = quote! {
106 impl #impl_generics MyTrait for Test #ty_generics #where_clause {}
107 };
108 let expected = quote! {
109 impl<'a, 'b: 'a, #[may_dangle] T: 'a> MyTrait
110 for Test<'a, 'b, T>
111 where
112 T: Debug
113 {}
114 };
115 assert_eq!(generated.to_string(), expected.to_string());
116
117 let turbofish = ty_generics.as_turbofish();
118 let generated = quote! {
119 Test #turbofish
120 };
121 let expected = quote! {
122 Test::<'a, 'b, T>
123 };
124 assert_eq!(generated.to_string(), expected.to_string());
125}
126
127#[test]
128fn test_ty_param_bound() {
129 let tokens = quote!('a);
130 snapshot!(tokens as TypeParamBound, @r###"
131 Lifetime(Lifetime {
132 ident: "a",
133 })
134 "###);
135
136 let tokens = quote!('_);
137 snapshot!(tokens as TypeParamBound, @r###"
138 Lifetime(Lifetime {
139 ident: "_",
140 })
141 "###);
142
143 let tokens = quote!(Debug);
144 snapshot!(tokens as TypeParamBound, @r###"
145 Trait(TraitBound {
146 modifier: None,
147 path: Path {
148 segments: [
149 PathSegment {
150 ident: "Debug",
151 arguments: None,
152 },
153 ],
154 },
155 })
156 "###);
157
158 let tokens = quote!(?Sized);
159 snapshot!(tokens as TypeParamBound, @r###"
160 Trait(TraitBound {
161 modifier: Maybe,
162 path: Path {
163 segments: [
164 PathSegment {
165 ident: "Sized",
166 arguments: None,
167 },
168 ],
169 },
170 })
171 "###);
172}
173
174#[test]
175fn test_fn_precedence_in_where_clause() {
176 // This should parse as two separate bounds, `FnOnce() -> i32` and `Send` - not
177 // `FnOnce() -> (i32 + Send)`.
178 let input = quote! {
179 fn f<G>()
180 where
181 G: FnOnce() -> i32 + Send,
182 {
183 }
184 };
185
186 snapshot!(input as ItemFn, @r###"
187 ItemFn {
188 vis: Inherited,
189 sig: Signature {
190 ident: "f",
191 generics: Generics {
192 lt_token: Some,
193 params: [
194 Type(TypeParam {
195 ident: "G",
196 }),
197 ],
198 gt_token: Some,
199 where_clause: Some(WhereClause {
200 predicates: [
201 Type(PredicateType {
202 bounded_ty: Type::Path {
203 path: Path {
204 segments: [
205 PathSegment {
206 ident: "G",
207 arguments: None,
208 },
209 ],
210 },
211 },
212 bounds: [
213 Trait(TraitBound {
214 modifier: None,
215 path: Path {
216 segments: [
217 PathSegment {
218 ident: "FnOnce",
219 arguments: PathArguments::Parenthesized {
220 output: Type(
221 Type::Path {
222 path: Path {
223 segments: [
224 PathSegment {
225 ident: "i32",
226 arguments: None,
227 },
228 ],
229 },
230 },
231 ),
232 },
233 },
234 ],
235 },
236 }),
237 Trait(TraitBound {
238 modifier: None,
239 path: Path {
240 segments: [
241 PathSegment {
242 ident: "Send",
243 arguments: None,
244 },
245 ],
246 },
247 }),
248 ],
249 }),
250 ],
251 }),
252 },
253 output: Default,
254 },
255 block: Block,
256 }
257 "###);
258
259 let where_clause = input.sig.generics.where_clause.as_ref().unwrap();
260 assert_eq!(where_clause.predicates.len(), 1);
261
262 let predicate = match &where_clause.predicates[0] {
263 WherePredicate::Type(pred) => pred,
264 _ => panic!("wrong predicate kind"),
265 };
266
267 assert_eq!(predicate.bounds.len(), 2, "{:#?}", predicate.bounds);
268
269 let first_bound = &predicate.bounds[0];
270 assert_eq!(quote!(#first_bound).to_string(), "FnOnce () -> i32");
271
272 let second_bound = &predicate.bounds[1];
273 assert_eq!(quote!(#second_bound).to_string(), "Send");
274}
275
276#[test]
277fn test_where_clause_at_end_of_input() {
278 let input = quote! {
279 where
280 };
281
282 snapshot!(input as WhereClause, @"WhereClause");
283
284 assert_eq!(input.predicates.len(), 0);
285}
286