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