xref: /third_party/rust/crates/syn/tests/test_expr.rs (revision fad3a1d3)
1#![allow(clippy::single_element_loop, clippy::uninlined_format_args)]
2
3#[macro_use]
4mod macros;
5
6use proc_macro2::{Delimiter, Group};
7use quote::{quote, ToTokens as _};
8use syn::punctuated::Punctuated;
9use syn::{parse_quote, token, Expr, ExprRange, ExprTuple, Stmt, Token};
10
11#[test]
12fn 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]
35fn 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]
56fn 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]
102fn 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]
168fn 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]
191fn 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]
210fn 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]
235fn 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]
311fn 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]
328fn 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]
334fn 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]
352fn 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]
381fn 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]
489fn 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