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