1 | #![allow (clippy::single_element_loop, clippy::uninlined_format_args)] |
2 | |
3 | #[macro_use ] |
4 | mod macros; |
5 | |
6 | use proc_macro2::{Delimiter, Group}; |
7 | use quote::quote; |
8 | use syn::{Expr, ExprRange, Stmt}; |
9 | |
10 | #[test] |
11 | fn test_expr_parse() { |
12 | let tokens = quote!(..100u32); |
13 | snapshot!(tokens as Expr, @r###" |
14 | Expr::Range { |
15 | limits: RangeLimits::HalfOpen, |
16 | end: Some(Expr::Lit { |
17 | lit: 100u32, |
18 | }), |
19 | } |
20 | "### ); |
21 | |
22 | let tokens = quote!(..100u32); |
23 | snapshot!(tokens as ExprRange, @r###" |
24 | ExprRange { |
25 | limits: RangeLimits::HalfOpen, |
26 | end: Some(Expr::Lit { |
27 | lit: 100u32, |
28 | }), |
29 | } |
30 | "### ); |
31 | } |
32 | |
33 | #[test] |
34 | fn test_await() { |
35 | // Must not parse as Expr::Field. |
36 | let tokens = quote!(fut.await); |
37 | |
38 | snapshot!(tokens as Expr, @r###" |
39 | Expr::Await { |
40 | base: Expr::Path { |
41 | path: Path { |
42 | segments: [ |
43 | PathSegment { |
44 | ident: "fut", |
45 | }, |
46 | ], |
47 | }, |
48 | }, |
49 | } |
50 | "### ); |
51 | } |
52 | |
53 | #[rustfmt::skip] |
54 | #[test] |
55 | fn test_tuple_multi_index() { |
56 | let expected = snapshot!("tuple.0.0" as Expr, @r###" |
57 | Expr::Field { |
58 | base: Expr::Field { |
59 | base: Expr::Path { |
60 | path: Path { |
61 | segments: [ |
62 | PathSegment { |
63 | ident: "tuple", |
64 | }, |
65 | ], |
66 | }, |
67 | }, |
68 | member: Member::Unnamed(Index { |
69 | index: 0, |
70 | }), |
71 | }, |
72 | member: Member::Unnamed(Index { |
73 | index: 0, |
74 | }), |
75 | } |
76 | "### ); |
77 | |
78 | for &input in &[ |
79 | "tuple .0.0" , |
80 | "tuple. 0.0" , |
81 | "tuple.0 .0" , |
82 | "tuple.0. 0" , |
83 | "tuple . 0 . 0" , |
84 | ] { |
85 | assert_eq!(expected, syn::parse_str(input).unwrap()); |
86 | } |
87 | |
88 | for tokens in [ |
89 | quote!(tuple.0.0), |
90 | quote!(tuple .0.0), |
91 | quote!(tuple. 0.0), |
92 | quote!(tuple.0 .0), |
93 | quote!(tuple.0. 0), |
94 | quote!(tuple . 0 . 0), |
95 | ] { |
96 | assert_eq!(expected, syn::parse2(tokens).unwrap()); |
97 | } |
98 | } |
99 | |
100 | #[test] |
101 | fn test_macro_variable_func() { |
102 | // mimics the token stream corresponding to `$fn()` |
103 | let path = Group::new(Delimiter::None, quote!(f)); |
104 | let tokens = quote!(#path()); |
105 | |
106 | snapshot!(tokens as Expr, @r###" |
107 | Expr::Call { |
108 | func: Expr::Group { |
109 | expr: Expr::Path { |
110 | path: Path { |
111 | segments: [ |
112 | PathSegment { |
113 | ident: "f", |
114 | }, |
115 | ], |
116 | }, |
117 | }, |
118 | }, |
119 | } |
120 | "### ); |
121 | |
122 | let path = Group::new(Delimiter::None, quote! { #[inside] f }); |
123 | let tokens = quote!(#[outside] #path()); |
124 | |
125 | snapshot!(tokens as Expr, @r###" |
126 | Expr::Call { |
127 | attrs: [ |
128 | Attribute { |
129 | style: AttrStyle::Outer, |
130 | meta: Meta::Path { |
131 | segments: [ |
132 | PathSegment { |
133 | ident: "outside", |
134 | }, |
135 | ], |
136 | }, |
137 | }, |
138 | ], |
139 | func: Expr::Group { |
140 | expr: Expr::Path { |
141 | attrs: [ |
142 | Attribute { |
143 | style: AttrStyle::Outer, |
144 | meta: Meta::Path { |
145 | segments: [ |
146 | PathSegment { |
147 | ident: "inside", |
148 | }, |
149 | ], |
150 | }, |
151 | }, |
152 | ], |
153 | path: Path { |
154 | segments: [ |
155 | PathSegment { |
156 | ident: "f", |
157 | }, |
158 | ], |
159 | }, |
160 | }, |
161 | }, |
162 | } |
163 | "### ); |
164 | } |
165 | |
166 | #[test] |
167 | fn test_macro_variable_macro() { |
168 | // mimics the token stream corresponding to `$macro!()` |
169 | let mac = Group::new(Delimiter::None, quote!(m)); |
170 | let tokens = quote!(#mac!()); |
171 | |
172 | snapshot!(tokens as Expr, @r###" |
173 | Expr::Macro { |
174 | mac: Macro { |
175 | path: Path { |
176 | segments: [ |
177 | PathSegment { |
178 | ident: "m", |
179 | }, |
180 | ], |
181 | }, |
182 | delimiter: MacroDelimiter::Paren, |
183 | tokens: TokenStream(``), |
184 | }, |
185 | } |
186 | "### ); |
187 | } |
188 | |
189 | #[test] |
190 | fn test_macro_variable_struct() { |
191 | // mimics the token stream corresponding to `$struct {}` |
192 | let s = Group::new(Delimiter::None, quote! { S }); |
193 | let tokens = quote!(#s {}); |
194 | |
195 | snapshot!(tokens as Expr, @r###" |
196 | Expr::Struct { |
197 | path: Path { |
198 | segments: [ |
199 | PathSegment { |
200 | ident: "S", |
201 | }, |
202 | ], |
203 | }, |
204 | } |
205 | "### ); |
206 | } |
207 | |
208 | #[test] |
209 | fn test_macro_variable_unary() { |
210 | // mimics the token stream corresponding to `$expr.method()` where expr is `&self` |
211 | let inner = Group::new(Delimiter::None, quote!(&self)); |
212 | let tokens = quote!(#inner.method()); |
213 | snapshot!(tokens as Expr, @r###" |
214 | Expr::MethodCall { |
215 | receiver: Expr::Group { |
216 | expr: Expr::Reference { |
217 | expr: Expr::Path { |
218 | path: Path { |
219 | segments: [ |
220 | PathSegment { |
221 | ident: "self", |
222 | }, |
223 | ], |
224 | }, |
225 | }, |
226 | }, |
227 | }, |
228 | method: "method", |
229 | } |
230 | "### ); |
231 | } |
232 | |
233 | #[test] |
234 | fn test_macro_variable_match_arm() { |
235 | // mimics the token stream corresponding to `match v { _ => $expr }` |
236 | let expr = Group::new(Delimiter::None, quote! { #[a] () }); |
237 | let tokens = quote!(match v { _ => #expr }); |
238 | snapshot!(tokens as Expr, @r###" |
239 | Expr::Match { |
240 | expr: Expr::Path { |
241 | path: Path { |
242 | segments: [ |
243 | PathSegment { |
244 | ident: "v", |
245 | }, |
246 | ], |
247 | }, |
248 | }, |
249 | arms: [ |
250 | Arm { |
251 | pat: Pat::Wild, |
252 | body: Expr::Group { |
253 | expr: Expr::Tuple { |
254 | attrs: [ |
255 | Attribute { |
256 | style: AttrStyle::Outer, |
257 | meta: Meta::Path { |
258 | segments: [ |
259 | PathSegment { |
260 | ident: "a", |
261 | }, |
262 | ], |
263 | }, |
264 | }, |
265 | ], |
266 | }, |
267 | }, |
268 | }, |
269 | ], |
270 | } |
271 | "### ); |
272 | |
273 | let expr = Group::new(Delimiter::None, quote!(loop {} + 1)); |
274 | let tokens = quote!(match v { _ => #expr }); |
275 | snapshot!(tokens as Expr, @r###" |
276 | Expr::Match { |
277 | expr: Expr::Path { |
278 | path: Path { |
279 | segments: [ |
280 | PathSegment { |
281 | ident: "v", |
282 | }, |
283 | ], |
284 | }, |
285 | }, |
286 | arms: [ |
287 | Arm { |
288 | pat: Pat::Wild, |
289 | body: Expr::Group { |
290 | expr: Expr::Binary { |
291 | left: Expr::Loop { |
292 | body: Block { |
293 | stmts: [], |
294 | }, |
295 | }, |
296 | op: BinOp::Add, |
297 | right: Expr::Lit { |
298 | lit: 1, |
299 | }, |
300 | }, |
301 | }, |
302 | }, |
303 | ], |
304 | } |
305 | "### ); |
306 | } |
307 | |
308 | // https://github.com/dtolnay/syn/issues/1019 |
309 | #[test] |
310 | fn test_closure_vs_rangefull() { |
311 | #[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/4808 |
312 | let tokens = quote!(|| .. .method()); |
313 | snapshot!(tokens as Expr, @r###" |
314 | Expr::MethodCall { |
315 | receiver: Expr::Closure { |
316 | output: ReturnType::Default, |
317 | body: Expr::Range { |
318 | limits: RangeLimits::HalfOpen, |
319 | }, |
320 | }, |
321 | method: "method", |
322 | } |
323 | "### ); |
324 | } |
325 | |
326 | #[test] |
327 | fn test_postfix_operator_after_cast() { |
328 | syn::parse_str::<Expr>("|| &x as T[0]" ).unwrap_err(); |
329 | syn::parse_str::<Expr>("|| () as ()()" ).unwrap_err(); |
330 | } |
331 | |
332 | #[test] |
333 | fn test_ranges() { |
334 | syn::parse_str::<Expr>(".." ).unwrap(); |
335 | syn::parse_str::<Expr>("..hi" ).unwrap(); |
336 | syn::parse_str::<Expr>("lo.." ).unwrap(); |
337 | syn::parse_str::<Expr>("lo..hi" ).unwrap(); |
338 | |
339 | syn::parse_str::<Expr>("..=" ).unwrap_err(); |
340 | syn::parse_str::<Expr>("..=hi" ).unwrap(); |
341 | syn::parse_str::<Expr>("lo..=" ).unwrap_err(); |
342 | syn::parse_str::<Expr>("lo..=hi" ).unwrap(); |
343 | |
344 | syn::parse_str::<Expr>("..." ).unwrap_err(); |
345 | syn::parse_str::<Expr>("...hi" ).unwrap_err(); |
346 | syn::parse_str::<Expr>("lo..." ).unwrap_err(); |
347 | syn::parse_str::<Expr>("lo...hi" ).unwrap_err(); |
348 | } |
349 | |
350 | #[test] |
351 | fn test_ambiguous_label() { |
352 | for stmt in [ |
353 | quote! { |
354 | return 'label: loop { break 'label 42; }; |
355 | }, |
356 | quote! { |
357 | break ('label: loop { break 'label 42; }); |
358 | }, |
359 | quote! { |
360 | break 1 + 'label: loop { break 'label 42; }; |
361 | }, |
362 | quote! { |
363 | break 'outer 'inner: loop { break 'inner 42; }; |
364 | }, |
365 | ] { |
366 | syn::parse2::<Stmt>(stmt).unwrap(); |
367 | } |
368 | |
369 | for stmt in [ |
370 | // Parentheses required. See https://github.com/rust-lang/rust/pull/87026. |
371 | quote! { |
372 | break 'label: loop { break 'label 42; }; |
373 | }, |
374 | ] { |
375 | syn::parse2::<Stmt>(stmt).unwrap_err(); |
376 | } |
377 | } |
378 | |
379 | #[test] |
380 | fn test_extended_interpolated_path() { |
381 | let path = Group::new(Delimiter::None, quote!(a::b)); |
382 | |
383 | let tokens = quote!(if #path {}); |
384 | snapshot!(tokens as Expr, @r###" |
385 | Expr::If { |
386 | cond: Expr::Group { |
387 | expr: Expr::Path { |
388 | path: Path { |
389 | segments: [ |
390 | PathSegment { |
391 | ident: "a", |
392 | }, |
393 | PathSegment { |
394 | ident: "b", |
395 | }, |
396 | ], |
397 | }, |
398 | }, |
399 | }, |
400 | then_branch: Block { |
401 | stmts: [], |
402 | }, |
403 | } |
404 | "### ); |
405 | |
406 | let tokens = quote!(#path {}); |
407 | snapshot!(tokens as Expr, @r###" |
408 | Expr::Struct { |
409 | path: Path { |
410 | segments: [ |
411 | PathSegment { |
412 | ident: "a", |
413 | }, |
414 | PathSegment { |
415 | ident: "b", |
416 | }, |
417 | ], |
418 | }, |
419 | } |
420 | "### ); |
421 | |
422 | let tokens = quote!(#path :: c); |
423 | snapshot!(tokens as Expr, @r###" |
424 | Expr::Path { |
425 | path: Path { |
426 | segments: [ |
427 | PathSegment { |
428 | ident: "a", |
429 | }, |
430 | PathSegment { |
431 | ident: "b", |
432 | }, |
433 | PathSegment { |
434 | ident: "c", |
435 | }, |
436 | ], |
437 | }, |
438 | } |
439 | "### ); |
440 | |
441 | let nested = Group::new(Delimiter::None, quote!(a::b || true)); |
442 | let tokens = quote!(if #nested && false {}); |
443 | snapshot!(tokens as Expr, @r###" |
444 | Expr::If { |
445 | cond: Expr::Binary { |
446 | left: Expr::Group { |
447 | expr: Expr::Binary { |
448 | left: Expr::Path { |
449 | path: Path { |
450 | segments: [ |
451 | PathSegment { |
452 | ident: "a", |
453 | }, |
454 | PathSegment { |
455 | ident: "b", |
456 | }, |
457 | ], |
458 | }, |
459 | }, |
460 | op: BinOp::Or, |
461 | right: Expr::Lit { |
462 | lit: Lit::Bool { |
463 | value: true, |
464 | }, |
465 | }, |
466 | }, |
467 | }, |
468 | op: BinOp::And, |
469 | right: Expr::Lit { |
470 | lit: Lit::Bool { |
471 | value: false, |
472 | }, |
473 | }, |
474 | }, |
475 | then_branch: Block { |
476 | stmts: [], |
477 | }, |
478 | } |
479 | "### ); |
480 | } |
481 | |