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] |
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 | 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] |
127 | fn 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] |
171 | fn 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] |
270 | fn 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 | |