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