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