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