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;
8use syn::{Expr, ExprRange, Stmt};
9
10#[test]
11fn 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]
34fn 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]
55fn 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]
101fn 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]
167fn 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]
190fn 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]
209fn 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]
234fn 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]
310fn 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]
327fn 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]
333fn 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]
351fn 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]
380fn 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