| #![cfg(not(miri))] |
| #![recursion_limit = "1024"] |
| #![feature(rustc_private)] |
| #![allow( |
| clippy::match_like_matches_macro, |
| clippy::needless_lifetimes, |
| clippy::single_element_loop, |
| clippy::too_many_lines, |
| clippy::uninlined_format_args, |
| clippy::unreadable_literal |
| )] |
| |
| #[macro_use] |
| mod macros; |
| |
| mod common; |
| |
| use crate::common::visit::{AsIfPrinted, FlattenParens}; |
| use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream}; |
| use quote::{quote, ToTokens as _}; |
| use std::process::ExitCode; |
| use syn::punctuated::Punctuated; |
| use syn::visit_mut::VisitMut as _; |
| use syn::{ |
| parse_quote, token, AngleBracketedGenericArguments, Arm, BinOp, Block, Expr, ExprArray, |
| ExprAssign, ExprAsync, ExprAwait, ExprBinary, ExprBlock, ExprBreak, ExprCall, ExprCast, |
| ExprClosure, ExprConst, ExprContinue, ExprField, ExprForLoop, ExprIf, ExprIndex, ExprLet, |
| ExprLit, ExprLoop, ExprMacro, ExprMatch, ExprMethodCall, ExprPath, ExprRange, ExprRawAddr, |
| ExprReference, ExprReturn, ExprStruct, ExprTry, ExprTryBlock, ExprTuple, ExprUnary, ExprUnsafe, |
| ExprWhile, ExprYield, GenericArgument, Label, Lifetime, Lit, LitInt, Macro, MacroDelimiter, |
| Member, Pat, PatWild, Path, PathArguments, PathSegment, PointerMutability, QSelf, RangeLimits, |
| ReturnType, Stmt, Token, Type, TypePath, UnOp, |
| }; |
| |
| #[test] |
| fn test_expr_parse() { |
| let tokens = quote!(..100u32); |
| snapshot!(tokens as Expr, @r#" |
| Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| end: Some(Expr::Lit { |
| lit: 100u32, |
| }), |
| } |
| "#); |
| |
| let tokens = quote!(..100u32); |
| snapshot!(tokens as ExprRange, @r#" |
| ExprRange { |
| limits: RangeLimits::HalfOpen, |
| end: Some(Expr::Lit { |
| lit: 100u32, |
| }), |
| } |
| "#); |
| } |
| |
| #[test] |
| fn test_await() { |
| // Must not parse as Expr::Field. |
| let tokens = quote!(fut.await); |
| |
| snapshot!(tokens as Expr, @r#" |
| Expr::Await { |
| base: Expr::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "fut", |
| }, |
| ], |
| }, |
| }, |
| } |
| "#); |
| } |
| |
| #[rustfmt::skip] |
| #[test] |
| fn test_tuple_multi_index() { |
| let expected = snapshot!("tuple.0.0" as Expr, @r#" |
| Expr::Field { |
| base: Expr::Field { |
| base: Expr::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "tuple", |
| }, |
| ], |
| }, |
| }, |
| member: Member::Unnamed(Index { |
| index: 0, |
| }), |
| }, |
| member: Member::Unnamed(Index { |
| index: 0, |
| }), |
| } |
| "#); |
| |
| for &input in &[ |
| "tuple .0.0", |
| "tuple. 0.0", |
| "tuple.0 .0", |
| "tuple.0. 0", |
| "tuple . 0 . 0", |
| ] { |
| assert_eq!(expected, syn::parse_str(input).unwrap()); |
| } |
| |
| for tokens in [ |
| quote!(tuple.0.0), |
| quote!(tuple .0.0), |
| quote!(tuple. 0.0), |
| quote!(tuple.0 .0), |
| quote!(tuple.0. 0), |
| quote!(tuple . 0 . 0), |
| ] { |
| assert_eq!(expected, syn::parse2(tokens).unwrap()); |
| } |
| } |
| |
| #[test] |
| fn test_macro_variable_func() { |
| // mimics the token stream corresponding to `$fn()` |
| let path = Group::new(Delimiter::None, quote!(f)); |
| let tokens = quote!(#path()); |
| |
| snapshot!(tokens as Expr, @r#" |
| Expr::Call { |
| func: Expr::Group { |
| expr: Expr::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "f", |
| }, |
| ], |
| }, |
| }, |
| }, |
| } |
| "#); |
| |
| let path = Group::new(Delimiter::None, quote! { #[inside] f }); |
| let tokens = quote!(#[outside] #path()); |
| |
| snapshot!(tokens as Expr, @r#" |
| Expr::Call { |
| attrs: [ |
| Attribute { |
| style: AttrStyle::Outer, |
| meta: Meta::Path { |
| segments: [ |
| PathSegment { |
| ident: "outside", |
| }, |
| ], |
| }, |
| }, |
| ], |
| func: Expr::Group { |
| expr: Expr::Path { |
| attrs: [ |
| Attribute { |
| style: AttrStyle::Outer, |
| meta: Meta::Path { |
| segments: [ |
| PathSegment { |
| ident: "inside", |
| }, |
| ], |
| }, |
| }, |
| ], |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "f", |
| }, |
| ], |
| }, |
| }, |
| }, |
| } |
| "#); |
| } |
| |
| #[test] |
| fn test_macro_variable_macro() { |
| // mimics the token stream corresponding to `$macro!()` |
| let mac = Group::new(Delimiter::None, quote!(m)); |
| let tokens = quote!(#mac!()); |
| |
| snapshot!(tokens as Expr, @r#" |
| Expr::Macro { |
| mac: Macro { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "m", |
| }, |
| ], |
| }, |
| delimiter: MacroDelimiter::Paren, |
| tokens: TokenStream(``), |
| }, |
| } |
| "#); |
| } |
| |
| #[test] |
| fn test_macro_variable_struct() { |
| // mimics the token stream corresponding to `$struct {}` |
| let s = Group::new(Delimiter::None, quote! { S }); |
| let tokens = quote!(#s {}); |
| |
| snapshot!(tokens as Expr, @r#" |
| Expr::Struct { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "S", |
| }, |
| ], |
| }, |
| } |
| "#); |
| } |
| |
| #[test] |
| fn test_macro_variable_unary() { |
| // mimics the token stream corresponding to `$expr.method()` where expr is `&self` |
| let inner = Group::new(Delimiter::None, quote!(&self)); |
| let tokens = quote!(#inner.method()); |
| snapshot!(tokens as Expr, @r#" |
| Expr::MethodCall { |
| receiver: Expr::Group { |
| expr: Expr::Reference { |
| expr: Expr::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "self", |
| }, |
| ], |
| }, |
| }, |
| }, |
| }, |
| method: "method", |
| } |
| "#); |
| } |
| |
| #[test] |
| fn test_macro_variable_match_arm() { |
| // mimics the token stream corresponding to `match v { _ => $expr }` |
| let expr = Group::new(Delimiter::None, quote! { #[a] () }); |
| let tokens = quote!(match v { _ => #expr }); |
| snapshot!(tokens as Expr, @r#" |
| Expr::Match { |
| expr: Expr::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "v", |
| }, |
| ], |
| }, |
| }, |
| arms: [ |
| Arm { |
| pat: Pat::Wild, |
| body: Expr::Group { |
| expr: Expr::Tuple { |
| attrs: [ |
| Attribute { |
| style: AttrStyle::Outer, |
| meta: Meta::Path { |
| segments: [ |
| PathSegment { |
| ident: "a", |
| }, |
| ], |
| }, |
| }, |
| ], |
| }, |
| }, |
| }, |
| ], |
| } |
| "#); |
| |
| let expr = Group::new(Delimiter::None, quote!(loop {} + 1)); |
| let tokens = quote!(match v { _ => #expr }); |
| snapshot!(tokens as Expr, @r#" |
| Expr::Match { |
| expr: Expr::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "v", |
| }, |
| ], |
| }, |
| }, |
| arms: [ |
| Arm { |
| pat: Pat::Wild, |
| body: Expr::Group { |
| expr: Expr::Binary { |
| left: Expr::Loop { |
| body: Block { |
| stmts: [], |
| }, |
| }, |
| op: BinOp::Add, |
| right: Expr::Lit { |
| lit: 1, |
| }, |
| }, |
| }, |
| }, |
| ], |
| } |
| "#); |
| } |
| |
| // https://github.com/dtolnay/syn/issues/1019 |
| #[test] |
| fn test_closure_vs_rangefull() { |
| #[rustfmt::skip] // rustfmt bug: https://github.com/rust-lang/rustfmt/issues/4808 |
| let tokens = quote!(|| .. .method()); |
| snapshot!(tokens as Expr, @r#" |
| Expr::MethodCall { |
| receiver: Expr::Closure { |
| output: ReturnType::Default, |
| body: Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }, |
| }, |
| method: "method", |
| } |
| "#); |
| } |
| |
| #[test] |
| fn test_postfix_operator_after_cast() { |
| syn::parse_str::<Expr>("|| &x as T[0]").unwrap_err(); |
| syn::parse_str::<Expr>("|| () as ()()").unwrap_err(); |
| } |
| |
| #[test] |
| fn test_range_kinds() { |
| syn::parse_str::<Expr>("..").unwrap(); |
| syn::parse_str::<Expr>("..hi").unwrap(); |
| syn::parse_str::<Expr>("lo..").unwrap(); |
| syn::parse_str::<Expr>("lo..hi").unwrap(); |
| |
| syn::parse_str::<Expr>("..=").unwrap_err(); |
| syn::parse_str::<Expr>("..=hi").unwrap(); |
| syn::parse_str::<Expr>("lo..=").unwrap_err(); |
| syn::parse_str::<Expr>("lo..=hi").unwrap(); |
| |
| syn::parse_str::<Expr>("...").unwrap_err(); |
| syn::parse_str::<Expr>("...hi").unwrap_err(); |
| syn::parse_str::<Expr>("lo...").unwrap_err(); |
| syn::parse_str::<Expr>("lo...hi").unwrap_err(); |
| } |
| |
| #[test] |
| fn test_range_precedence() { |
| snapshot!(".. .." as Expr, @r#" |
| Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| end: Some(Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }), |
| } |
| "#); |
| |
| snapshot!(".. .. ()" as Expr, @r#" |
| Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| end: Some(Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| end: Some(Expr::Tuple), |
| }), |
| } |
| "#); |
| |
| snapshot!("() .. .." as Expr, @r#" |
| Expr::Range { |
| start: Some(Expr::Tuple), |
| limits: RangeLimits::HalfOpen, |
| end: Some(Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }), |
| } |
| "#); |
| |
| snapshot!("() = .. + ()" as Expr, @r" |
| Expr::Binary { |
| left: Expr::Assign { |
| left: Expr::Tuple, |
| right: Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }, |
| }, |
| op: BinOp::Add, |
| right: Expr::Tuple, |
| } |
| "); |
| |
| // A range with a lower bound cannot be the upper bound of another range, |
| // and a range with an upper bound cannot be the lower bound of another |
| // range. |
| syn::parse_str::<Expr>(".. x ..").unwrap_err(); |
| syn::parse_str::<Expr>("x .. x ..").unwrap_err(); |
| } |
| |
| #[test] |
| fn test_ranges_bailout() { |
| syn::parse_str::<Expr>(".. ?").unwrap_err(); |
| syn::parse_str::<Expr>(".. .field").unwrap_err(); |
| |
| snapshot!("return .. ?" as Expr, @r" |
| Expr::Try { |
| expr: Expr::Return { |
| expr: Some(Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }), |
| }, |
| } |
| "); |
| |
| snapshot!("break .. ?" as Expr, @r" |
| Expr::Try { |
| expr: Expr::Break { |
| expr: Some(Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }), |
| }, |
| } |
| "); |
| |
| snapshot!("|| .. ?" as Expr, @r" |
| Expr::Try { |
| expr: Expr::Closure { |
| output: ReturnType::Default, |
| body: Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }, |
| }, |
| } |
| "); |
| |
| snapshot!("return .. .field" as Expr, @r#" |
| Expr::Field { |
| base: Expr::Return { |
| expr: Some(Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }), |
| }, |
| member: Member::Named("field"), |
| } |
| "#); |
| |
| snapshot!("break .. .field" as Expr, @r#" |
| Expr::Field { |
| base: Expr::Break { |
| expr: Some(Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }), |
| }, |
| member: Member::Named("field"), |
| } |
| "#); |
| |
| snapshot!("|| .. .field" as Expr, @r#" |
| Expr::Field { |
| base: Expr::Closure { |
| output: ReturnType::Default, |
| body: Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }, |
| }, |
| member: Member::Named("field"), |
| } |
| "#); |
| |
| snapshot!("return .. = ()" as Expr, @r" |
| Expr::Assign { |
| left: Expr::Return { |
| expr: Some(Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }), |
| }, |
| right: Expr::Tuple, |
| } |
| "); |
| |
| snapshot!("return .. += ()" as Expr, @r" |
| Expr::Binary { |
| left: Expr::Return { |
| expr: Some(Expr::Range { |
| limits: RangeLimits::HalfOpen, |
| }), |
| }, |
| op: BinOp::AddAssign, |
| right: Expr::Tuple, |
| } |
| "); |
| } |
| |
| #[test] |
| fn test_ambiguous_label() { |
| for stmt in [ |
| quote! { |
| return 'label: loop { break 'label 42; }; |
| }, |
| quote! { |
| break ('label: loop { break 'label 42; }); |
| }, |
| quote! { |
| break 1 + 'label: loop { break 'label 42; }; |
| }, |
| quote! { |
| break 'outer 'inner: loop { break 'inner 42; }; |
| }, |
| ] { |
| syn::parse2::<Stmt>(stmt).unwrap(); |
| } |
| |
| for stmt in [ |
| // Parentheses required. See https://github.com/rust-lang/rust/pull/87026. |
| quote! { |
| break 'label: loop { break 'label 42; }; |
| }, |
| ] { |
| syn::parse2::<Stmt>(stmt).unwrap_err(); |
| } |
| } |
| |
| #[test] |
| fn test_extended_interpolated_path() { |
| let path = Group::new(Delimiter::None, quote!(a::b)); |
| |
| let tokens = quote!(if #path {}); |
| snapshot!(tokens as Expr, @r#" |
| Expr::If { |
| cond: Expr::Group { |
| expr: Expr::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "a", |
| }, |
| Token![::], |
| PathSegment { |
| ident: "b", |
| }, |
| ], |
| }, |
| }, |
| }, |
| then_branch: Block { |
| stmts: [], |
| }, |
| } |
| "#); |
| |
| let tokens = quote!(#path {}); |
| snapshot!(tokens as Expr, @r#" |
| Expr::Struct { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "a", |
| }, |
| Token![::], |
| PathSegment { |
| ident: "b", |
| }, |
| ], |
| }, |
| } |
| "#); |
| |
| let tokens = quote!(#path :: c); |
| snapshot!(tokens as Expr, @r#" |
| Expr::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "a", |
| }, |
| Token![::], |
| PathSegment { |
| ident: "b", |
| }, |
| Token![::], |
| PathSegment { |
| ident: "c", |
| }, |
| ], |
| }, |
| } |
| "#); |
| |
| let nested = Group::new(Delimiter::None, quote!(a::b || true)); |
| let tokens = quote!(if #nested && false {}); |
| snapshot!(tokens as Expr, @r#" |
| Expr::If { |
| cond: Expr::Binary { |
| left: Expr::Group { |
| expr: Expr::Binary { |
| left: Expr::Path { |
| path: Path { |
| segments: [ |
| PathSegment { |
| ident: "a", |
| }, |
| Token![::], |
| PathSegment { |
| ident: "b", |
| }, |
| ], |
| }, |
| }, |
| op: BinOp::Or, |
| right: Expr::Lit { |
| lit: Lit::Bool { |
| value: true, |
| }, |
| }, |
| }, |
| }, |
| op: BinOp::And, |
| right: Expr::Lit { |
| lit: Lit::Bool { |
| value: false, |
| }, |
| }, |
| }, |
| then_branch: Block { |
| stmts: [], |
| }, |
| } |
| "#); |
| } |
| |
| #[test] |
| fn test_tuple_comma() { |
| let mut expr = ExprTuple { |
| attrs: Vec::new(), |
| paren_token: token::Paren::default(), |
| elems: Punctuated::new(), |
| }; |
| snapshot!(expr.to_token_stream() as Expr, @"Expr::Tuple"); |
| |
| expr.elems.push_value(parse_quote!(continue)); |
| // Must not parse to Expr::Paren |
| snapshot!(expr.to_token_stream() as Expr, @r#" |
| Expr::Tuple { |
| elems: [ |
| Expr::Continue, |
| Token![,], |
| ], |
| } |
| "#); |
| |
| expr.elems.push_punct(<Token![,]>::default()); |
| snapshot!(expr.to_token_stream() as Expr, @r#" |
| Expr::Tuple { |
| elems: [ |
| Expr::Continue, |
| Token![,], |
| ], |
| } |
| "#); |
| |
| expr.elems.push_value(parse_quote!(continue)); |
| snapshot!(expr.to_token_stream() as Expr, @r#" |
| Expr::Tuple { |
| elems: [ |
| Expr::Continue, |
| Token![,], |
| Expr::Continue, |
| ], |
| } |
| "#); |
| |
| expr.elems.push_punct(<Token![,]>::default()); |
| snapshot!(expr.to_token_stream() as Expr, @r#" |
| Expr::Tuple { |
| elems: [ |
| Expr::Continue, |
| Token![,], |
| Expr::Continue, |
| Token![,], |
| ], |
| } |
| "#); |
| } |
| |
| #[test] |
| fn test_binop_associativity() { |
| // Left to right. |
| snapshot!("() + () + ()" as Expr, @r#" |
| Expr::Binary { |
| left: Expr::Binary { |
| left: Expr::Tuple, |
| op: BinOp::Add, |
| right: Expr::Tuple, |
| }, |
| op: BinOp::Add, |
| right: Expr::Tuple, |
| } |
| "#); |
| |
| // Right to left. |
| snapshot!("() += () += ()" as Expr, @r#" |
| Expr::Binary { |
| left: Expr::Tuple, |
| op: BinOp::AddAssign, |
| right: Expr::Binary { |
| left: Expr::Tuple, |
| op: BinOp::AddAssign, |
| right: Expr::Tuple, |
| }, |
| } |
| "#); |
| |
| // Parenthesization is required. |
| syn::parse_str::<Expr>("() == () == ()").unwrap_err(); |
| } |
| |
| #[test] |
| fn test_assign_range_precedence() { |
| // Range has higher precedence as the right-hand of an assignment, but |
| // ambiguous precedence as the left-hand of an assignment. |
| snapshot!("() = () .. ()" as Expr, @r#" |
| Expr::Assign { |
| left: Expr::Tuple, |
| right: Expr::Range { |
| start: Some(Expr::Tuple), |
| limits: RangeLimits::HalfOpen, |
| end: Some(Expr::Tuple), |
| }, |
| } |
| "#); |
| |
| snapshot!("() += () .. ()" as Expr, @r#" |
| Expr::Binary { |
| left: Expr::Tuple, |
| op: BinOp::AddAssign, |
| right: Expr::Range { |
| start: Some(Expr::Tuple), |
| limits: RangeLimits::HalfOpen, |
| end: Some(Expr::Tuple), |
| }, |
| } |
| "#); |
| |
| syn::parse_str::<Expr>("() .. () = ()").unwrap_err(); |
| syn::parse_str::<Expr>("() .. () += ()").unwrap_err(); |
| } |
| |
| #[test] |
| fn test_chained_comparison() { |
| // https://github.com/dtolnay/syn/issues/1738 |
| let _ = syn::parse_str::<Expr>("a = a < a <"); |
| let _ = syn::parse_str::<Expr>("a = a .. a .."); |
| let _ = syn::parse_str::<Expr>("a = a .. a +="); |
| |
| let err = syn::parse_str::<Expr>("a < a < a").unwrap_err(); |
| assert_eq!("comparison operators cannot be chained", err.to_string()); |
| |
| let err = syn::parse_str::<Expr>("a .. a .. a").unwrap_err(); |
| assert_eq!("unexpected token", err.to_string()); |
| |
| let err = syn::parse_str::<Expr>("a .. a += a").unwrap_err(); |
| assert_eq!("unexpected token", err.to_string()); |
| } |
| |
| #[test] |
| fn test_fixup() { |
| for tokens in [ |
| quote! { 2 * (1 + 1) }, |
| quote! { 0 + (0 + 0) }, |
| quote! { (a = b) = c }, |
| quote! { (x as i32) < 0 }, |
| quote! { 1 + (x as i32) < 0 }, |
| quote! { (1 + 1).abs() }, |
| quote! { (lo..hi)[..] }, |
| quote! { (a..b)..(c..d) }, |
| quote! { (x > ..) > x }, |
| quote! { (&mut fut).await }, |
| quote! { &mut (x as i32) }, |
| quote! { -(x as i32) }, |
| quote! { if (S {}) == 1 {} }, |
| quote! { { (m! {}) - 1 } }, |
| quote! { match m { _ => ({}) - 1 } }, |
| quote! { if let _ = (a && b) && c {} }, |
| quote! { if let _ = (S {}) {} }, |
| quote! { if (S {}) == 0 && let Some(_) = x {} }, |
| quote! { break ('a: loop { break 'a 1 } + 1) }, |
| quote! { a + (|| b) + c }, |
| quote! { if let _ = ((break) - 1 || true) {} }, |
| quote! { if let _ = (break + 1 || true) {} }, |
| quote! { if break (break) {} }, |
| quote! { if break break {} {} }, |
| quote! { if return (..) {} }, |
| quote! { if return .. {} {} }, |
| quote! { if || (Struct {}) {} }, |
| quote! { if || (Struct {}).await {} }, |
| quote! { if break || Struct {}.await {} }, |
| quote! { if break 'outer 'block: {} {} }, |
| quote! { if ..'block: {} {} }, |
| quote! { if break ({}).await {} }, |
| quote! { (break)() }, |
| quote! { (..) = () }, |
| quote! { (..) += () }, |
| quote! { (1 < 2) == (3 < 4) }, |
| quote! { { (let _ = ()) } }, |
| quote! { (#[attr] thing).field }, |
| quote! { (self.f)() }, |
| quote! { (return)..=return }, |
| quote! { 1 + (return)..=1 + return }, |
| quote! { .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. }, |
| ] { |
| let original: Expr = syn::parse2(tokens).unwrap(); |
| |
| let mut flat = original.clone(); |
| FlattenParens.visit_expr_mut(&mut flat); |
| let reconstructed: Expr = match syn::parse2(flat.to_token_stream()) { |
| Ok(reconstructed) => reconstructed, |
| Err(err) => panic!("failed to parse `{}`: {}", flat.to_token_stream(), err), |
| }; |
| |
| assert!( |
| original == reconstructed, |
| "original: {}\n{:#?}\nreconstructed: {}\n{:#?}", |
| original.to_token_stream(), |
| crate::macros::debug::Lite(&original), |
| reconstructed.to_token_stream(), |
| crate::macros::debug::Lite(&reconstructed), |
| ); |
| } |
| } |
| |
| #[test] |
| fn test_permutations() -> ExitCode { |
| fn iter(depth: usize, f: &mut dyn FnMut(Expr)) { |
| let span = Span::call_site(); |
| |
| // Expr::Path |
| f(Expr::Path(ExprPath { |
| // `x` |
| attrs: Vec::new(), |
| qself: None, |
| path: Path::from(Ident::new("x", span)), |
| })); |
| if false { |
| f(Expr::Path(ExprPath { |
| // `x::<T>` |
| attrs: Vec::new(), |
| qself: None, |
| path: Path { |
| leading_colon: None, |
| segments: Punctuated::from_iter([PathSegment { |
| ident: Ident::new("x", span), |
| arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { |
| colon2_token: Some(Token), |
| lt_token: Token, |
| args: Punctuated::from_iter([GenericArgument::Type(Type::Path( |
| TypePath { |
| qself: None, |
| path: Path::from(Ident::new("T", span)), |
| }, |
| ))]), |
| gt_token: Token, |
| }), |
| }]), |
| }, |
| })); |
| f(Expr::Path(ExprPath { |
| // `<T as Trait>::CONST` |
| attrs: Vec::new(), |
| qself: Some(QSelf { |
| lt_token: Token, |
| ty: Box::new(Type::Path(TypePath { |
| qself: None, |
| path: Path::from(Ident::new("T", span)), |
| })), |
| position: 1, |
| as_token: Some(Token), |
| gt_token: Token, |
| }), |
| path: Path { |
| leading_colon: None, |
| segments: Punctuated::from_iter([ |
| PathSegment::from(Ident::new("Trait", span)), |
| PathSegment::from(Ident::new("CONST", span)), |
| ]), |
| }, |
| })); |
| } |
| |
| let Some(depth) = depth.checked_sub(1) else { |
| return; |
| }; |
| |
| // Expr::Assign |
| iter(depth, &mut |expr| { |
| iter(0, &mut |simple| { |
| f(Expr::Assign(ExprAssign { |
| // `x = $expr` |
| attrs: Vec::new(), |
| left: Box::new(simple.clone()), |
| eq_token: Token, |
| right: Box::new(expr.clone()), |
| })); |
| f(Expr::Assign(ExprAssign { |
| // `$expr = x` |
| attrs: Vec::new(), |
| left: Box::new(expr.clone()), |
| eq_token: Token, |
| right: Box::new(simple), |
| })); |
| }); |
| }); |
| |
| // Expr::Binary |
| iter(depth, &mut |expr| { |
| iter(0, &mut |simple| { |
| for op in [ |
| BinOp::Add(Token), |
| //BinOp::Sub(Token), |
| //BinOp::Mul(Token), |
| //BinOp::Div(Token), |
| //BinOp::Rem(Token), |
| //BinOp::And(Token), |
| //BinOp::Or(Token), |
| //BinOp::BitXor(Token), |
| //BinOp::BitAnd(Token), |
| //BinOp::BitOr(Token), |
| //BinOp::Shl(Token), |
| //BinOp::Shr(Token), |
| //BinOp::Eq(Token), |
| BinOp::Lt(Token), |
| //BinOp::Le(Token), |
| //BinOp::Ne(Token), |
| //BinOp::Ge(Token), |
| //BinOp::Gt(Token), |
| BinOp::ShlAssign(Token), |
| ] { |
| f(Expr::Binary(ExprBinary { |
| // `x + $expr` |
| attrs: Vec::new(), |
| left: Box::new(simple.clone()), |
| op, |
| right: Box::new(expr.clone()), |
| })); |
| f(Expr::Binary(ExprBinary { |
| // `$expr + x` |
| attrs: Vec::new(), |
| left: Box::new(expr.clone()), |
| op, |
| right: Box::new(simple.clone()), |
| })); |
| } |
| }); |
| }); |
| |
| // Expr::Block |
| f(Expr::Block(ExprBlock { |
| // `{}` |
| attrs: Vec::new(), |
| label: None, |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| |
| // Expr::Break |
| f(Expr::Break(ExprBreak { |
| // `break` |
| attrs: Vec::new(), |
| break_token: Token, |
| label: None, |
| expr: None, |
| })); |
| iter(depth, &mut |expr| { |
| f(Expr::Break(ExprBreak { |
| // `break $expr` |
| attrs: Vec::new(), |
| break_token: Token, |
| label: None, |
| expr: Some(Box::new(expr)), |
| })); |
| }); |
| |
| // Expr::Call |
| iter(depth, &mut |expr| { |
| f(Expr::Call(ExprCall { |
| // `$expr()` |
| attrs: Vec::new(), |
| func: Box::new(expr), |
| paren_token: token::Paren(span), |
| args: Punctuated::new(), |
| })); |
| }); |
| |
| // Expr::Cast |
| iter(depth, &mut |expr| { |
| f(Expr::Cast(ExprCast { |
| // `$expr as T` |
| attrs: Vec::new(), |
| expr: Box::new(expr), |
| as_token: Token, |
| ty: Box::new(Type::Path(TypePath { |
| qself: None, |
| path: Path::from(Ident::new("T", span)), |
| })), |
| })); |
| }); |
| |
| // Expr::Closure |
| iter(depth, &mut |expr| { |
| f(Expr::Closure(ExprClosure { |
| // `|| $expr` |
| attrs: Vec::new(), |
| lifetimes: None, |
| constness: None, |
| movability: None, |
| asyncness: None, |
| capture: None, |
| or1_token: Token, |
| inputs: Punctuated::new(), |
| or2_token: Token, |
| output: ReturnType::Default, |
| body: Box::new(expr), |
| })); |
| }); |
| |
| // Expr::Field |
| iter(depth, &mut |expr| { |
| f(Expr::Field(ExprField { |
| // `$expr.field` |
| attrs: Vec::new(), |
| base: Box::new(expr), |
| dot_token: Token, |
| member: Member::Named(Ident::new("field", span)), |
| })); |
| }); |
| |
| // Expr::If |
| iter(depth, &mut |expr| { |
| f(Expr::If(ExprIf { |
| // `if $expr {}` |
| attrs: Vec::new(), |
| if_token: Token, |
| cond: Box::new(expr), |
| then_branch: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| else_branch: None, |
| })); |
| }); |
| |
| // Expr::Let |
| iter(depth, &mut |expr| { |
| f(Expr::Let(ExprLet { |
| attrs: Vec::new(), |
| let_token: Token, |
| pat: Box::new(Pat::Wild(PatWild { |
| attrs: Vec::new(), |
| underscore_token: Token, |
| })), |
| eq_token: Token, |
| expr: Box::new(expr), |
| })); |
| }); |
| |
| // Expr::Range |
| f(Expr::Range(ExprRange { |
| // `..` |
| attrs: Vec::new(), |
| start: None, |
| limits: RangeLimits::HalfOpen(Token), |
| end: None, |
| })); |
| iter(depth, &mut |expr| { |
| f(Expr::Range(ExprRange { |
| // `..$expr` |
| attrs: Vec::new(), |
| start: None, |
| limits: RangeLimits::HalfOpen(Token), |
| end: Some(Box::new(expr.clone())), |
| })); |
| f(Expr::Range(ExprRange { |
| // `$expr..` |
| attrs: Vec::new(), |
| start: Some(Box::new(expr)), |
| limits: RangeLimits::HalfOpen(Token), |
| end: None, |
| })); |
| }); |
| |
| // Expr::Reference |
| iter(depth, &mut |expr| { |
| f(Expr::Reference(ExprReference { |
| // `&$expr` |
| attrs: Vec::new(), |
| and_token: Token, |
| mutability: None, |
| expr: Box::new(expr), |
| })); |
| }); |
| |
| // Expr::Return |
| f(Expr::Return(ExprReturn { |
| // `return` |
| attrs: Vec::new(), |
| return_token: Token, |
| expr: None, |
| })); |
| iter(depth, &mut |expr| { |
| f(Expr::Return(ExprReturn { |
| // `return $expr` |
| attrs: Vec::new(), |
| return_token: Token, |
| expr: Some(Box::new(expr)), |
| })); |
| }); |
| |
| // Expr::Try |
| iter(depth, &mut |expr| { |
| f(Expr::Try(ExprTry { |
| // `$expr?` |
| attrs: Vec::new(), |
| expr: Box::new(expr), |
| question_token: Token, |
| })); |
| }); |
| |
| // Expr::Unary |
| iter(depth, &mut |expr| { |
| for op in [ |
| UnOp::Deref(Token), |
| //UnOp::Not(Token), |
| //UnOp::Neg(Token), |
| ] { |
| f(Expr::Unary(ExprUnary { |
| // `*$expr` |
| attrs: Vec::new(), |
| op, |
| expr: Box::new(expr.clone()), |
| })); |
| } |
| }); |
| |
| if false { |
| // Expr::Array |
| f(Expr::Array(ExprArray { |
| // `[]` |
| attrs: Vec::new(), |
| bracket_token: token::Bracket(span), |
| elems: Punctuated::new(), |
| })); |
| |
| // Expr::Async |
| f(Expr::Async(ExprAsync { |
| // `async {}` |
| attrs: Vec::new(), |
| async_token: Token, |
| capture: None, |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| |
| // Expr::Await |
| iter(depth, &mut |expr| { |
| f(Expr::Await(ExprAwait { |
| // `$expr.await` |
| attrs: Vec::new(), |
| base: Box::new(expr), |
| dot_token: Token, |
| await_token: Token, |
| })); |
| }); |
| |
| // Expr::Block |
| f(Expr::Block(ExprBlock { |
| // `'a: {}` |
| attrs: Vec::new(), |
| label: Some(Label { |
| name: Lifetime::new("'a", span), |
| colon_token: Token, |
| }), |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| iter(depth, &mut |expr| { |
| f(Expr::Block(ExprBlock { |
| // `{ $expr }` |
| attrs: Vec::new(), |
| label: None, |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::from([Stmt::Expr(expr.clone(), None)]), |
| }, |
| })); |
| f(Expr::Block(ExprBlock { |
| // `{ $expr; }` |
| attrs: Vec::new(), |
| label: None, |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::from([Stmt::Expr(expr, Some(Token))]), |
| }, |
| })); |
| }); |
| |
| // Expr::Break |
| f(Expr::Break(ExprBreak { |
| // `break 'a` |
| attrs: Vec::new(), |
| break_token: Token, |
| label: Some(Lifetime::new("'a", span)), |
| expr: None, |
| })); |
| iter(depth, &mut |expr| { |
| f(Expr::Break(ExprBreak { |
| // `break 'a $expr` |
| attrs: Vec::new(), |
| break_token: Token, |
| label: Some(Lifetime::new("'a", span)), |
| expr: Some(Box::new(expr)), |
| })); |
| }); |
| |
| // Expr::Closure |
| f(Expr::Closure(ExprClosure { |
| // `|| -> T {}` |
| attrs: Vec::new(), |
| lifetimes: None, |
| constness: None, |
| movability: None, |
| asyncness: None, |
| capture: None, |
| or1_token: Token, |
| inputs: Punctuated::new(), |
| or2_token: Token, |
| output: ReturnType::Type( |
| Token, |
| Box::new(Type::Path(TypePath { |
| qself: None, |
| path: Path::from(Ident::new("T", span)), |
| })), |
| ), |
| body: Box::new(Expr::Block(ExprBlock { |
| attrs: Vec::new(), |
| label: None, |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })), |
| })); |
| |
| // Expr::Const |
| f(Expr::Const(ExprConst { |
| // `const {}` |
| attrs: Vec::new(), |
| const_token: Token, |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| |
| // Expr::Continue |
| f(Expr::Continue(ExprContinue { |
| // `continue` |
| attrs: Vec::new(), |
| continue_token: Token, |
| label: None, |
| })); |
| f(Expr::Continue(ExprContinue { |
| // `continue 'a` |
| attrs: Vec::new(), |
| continue_token: Token, |
| label: Some(Lifetime::new("'a", span)), |
| })); |
| |
| // Expr::ForLoop |
| iter(depth, &mut |expr| { |
| f(Expr::ForLoop(ExprForLoop { |
| // `for _ in $expr {}` |
| attrs: Vec::new(), |
| label: None, |
| for_token: Token, |
| pat: Box::new(Pat::Wild(PatWild { |
| attrs: Vec::new(), |
| underscore_token: Token, |
| })), |
| in_token: Token, |
| expr: Box::new(expr.clone()), |
| body: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| f(Expr::ForLoop(ExprForLoop { |
| // `'a: for _ in $expr {}` |
| attrs: Vec::new(), |
| label: Some(Label { |
| name: Lifetime::new("'a", span), |
| colon_token: Token, |
| }), |
| for_token: Token, |
| pat: Box::new(Pat::Wild(PatWild { |
| attrs: Vec::new(), |
| underscore_token: Token, |
| })), |
| in_token: Token, |
| expr: Box::new(expr), |
| body: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| }); |
| |
| // Expr::Index |
| iter(depth, &mut |expr| { |
| f(Expr::Index(ExprIndex { |
| // `$expr[0]` |
| attrs: Vec::new(), |
| expr: Box::new(expr), |
| bracket_token: token::Bracket(span), |
| index: Box::new(Expr::Lit(ExprLit { |
| attrs: Vec::new(), |
| lit: Lit::Int(LitInt::new("0", span)), |
| })), |
| })); |
| }); |
| |
| // Expr::Loop |
| f(Expr::Loop(ExprLoop { |
| // `loop {}` |
| attrs: Vec::new(), |
| label: None, |
| loop_token: Token, |
| body: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| f(Expr::Loop(ExprLoop { |
| // `'a: loop {}` |
| attrs: Vec::new(), |
| label: Some(Label { |
| name: Lifetime::new("'a", span), |
| colon_token: Token, |
| }), |
| loop_token: Token, |
| body: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| |
| // Expr::Macro |
| f(Expr::Macro(ExprMacro { |
| // `m!()` |
| attrs: Vec::new(), |
| mac: Macro { |
| path: Path::from(Ident::new("m", span)), |
| bang_token: Token, |
| delimiter: MacroDelimiter::Paren(token::Paren(span)), |
| tokens: TokenStream::new(), |
| }, |
| })); |
| f(Expr::Macro(ExprMacro { |
| // `m! {}` |
| attrs: Vec::new(), |
| mac: Macro { |
| path: Path::from(Ident::new("m", span)), |
| bang_token: Token, |
| delimiter: MacroDelimiter::Brace(token::Brace(span)), |
| tokens: TokenStream::new(), |
| }, |
| })); |
| |
| // Expr::Match |
| iter(depth, &mut |expr| { |
| f(Expr::Match(ExprMatch { |
| // `match $expr {}` |
| attrs: Vec::new(), |
| match_token: Token, |
| expr: Box::new(expr.clone()), |
| brace_token: token::Brace(span), |
| arms: Vec::new(), |
| })); |
| f(Expr::Match(ExprMatch { |
| // `match x { _ => $expr }` |
| attrs: Vec::new(), |
| match_token: Token, |
| expr: Box::new(Expr::Path(ExprPath { |
| attrs: Vec::new(), |
| qself: None, |
| path: Path::from(Ident::new("x", span)), |
| })), |
| brace_token: token::Brace(span), |
| arms: Vec::from([Arm { |
| attrs: Vec::new(), |
| pat: Pat::Wild(PatWild { |
| attrs: Vec::new(), |
| underscore_token: Token, |
| }), |
| guard: None, |
| fat_arrow_token: Token, |
| body: Box::new(expr.clone()), |
| comma: None, |
| }]), |
| })); |
| f(Expr::Match(ExprMatch { |
| // `match x { _ if $expr => {} }` |
| attrs: Vec::new(), |
| match_token: Token, |
| expr: Box::new(Expr::Path(ExprPath { |
| attrs: Vec::new(), |
| qself: None, |
| path: Path::from(Ident::new("x", span)), |
| })), |
| brace_token: token::Brace(span), |
| arms: Vec::from([Arm { |
| attrs: Vec::new(), |
| pat: Pat::Wild(PatWild { |
| attrs: Vec::new(), |
| underscore_token: Token, |
| }), |
| guard: Some((Token, Box::new(expr))), |
| fat_arrow_token: Token, |
| body: Box::new(Expr::Block(ExprBlock { |
| attrs: Vec::new(), |
| label: None, |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })), |
| comma: None, |
| }]), |
| })); |
| }); |
| |
| // Expr::MethodCall |
| iter(depth, &mut |expr| { |
| f(Expr::MethodCall(ExprMethodCall { |
| // `$expr.method()` |
| attrs: Vec::new(), |
| receiver: Box::new(expr.clone()), |
| dot_token: Token, |
| method: Ident::new("method", span), |
| turbofish: None, |
| paren_token: token::Paren(span), |
| args: Punctuated::new(), |
| })); |
| f(Expr::MethodCall(ExprMethodCall { |
| // `$expr.method::<T>()` |
| attrs: Vec::new(), |
| receiver: Box::new(expr), |
| dot_token: Token, |
| method: Ident::new("method", span), |
| turbofish: Some(AngleBracketedGenericArguments { |
| colon2_token: Some(Token), |
| lt_token: Token, |
| args: Punctuated::from_iter([GenericArgument::Type(Type::Path( |
| TypePath { |
| qself: None, |
| path: Path::from(Ident::new("T", span)), |
| }, |
| ))]), |
| gt_token: Token, |
| }), |
| paren_token: token::Paren(span), |
| args: Punctuated::new(), |
| })); |
| }); |
| |
| // Expr::RawAddr |
| iter(depth, &mut |expr| { |
| f(Expr::RawAddr(ExprRawAddr { |
| // `&raw const $expr` |
| attrs: Vec::new(), |
| and_token: Token, |
| raw: Token, |
| mutability: PointerMutability::Const(Token), |
| expr: Box::new(expr), |
| })); |
| }); |
| |
| // Expr::Struct |
| f(Expr::Struct(ExprStruct { |
| // `Struct {}` |
| attrs: Vec::new(), |
| qself: None, |
| path: Path::from(Ident::new("Struct", span)), |
| brace_token: token::Brace(span), |
| fields: Punctuated::new(), |
| dot2_token: None, |
| rest: None, |
| })); |
| |
| // Expr::TryBlock |
| f(Expr::TryBlock(ExprTryBlock { |
| // `try {}` |
| attrs: Vec::new(), |
| try_token: Token, |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| |
| // Expr::Unsafe |
| f(Expr::Unsafe(ExprUnsafe { |
| // `unsafe {}` |
| attrs: Vec::new(), |
| unsafe_token: Token, |
| block: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| |
| // Expr::While |
| iter(depth, &mut |expr| { |
| f(Expr::While(ExprWhile { |
| // `while $expr {}` |
| attrs: Vec::new(), |
| label: None, |
| while_token: Token, |
| cond: Box::new(expr.clone()), |
| body: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| f(Expr::While(ExprWhile { |
| // `'a: while $expr {}` |
| attrs: Vec::new(), |
| label: Some(Label { |
| name: Lifetime::new("'a", span), |
| colon_token: Token, |
| }), |
| while_token: Token, |
| cond: Box::new(expr), |
| body: Block { |
| brace_token: token::Brace(span), |
| stmts: Vec::new(), |
| }, |
| })); |
| }); |
| |
| // Expr::Yield |
| f(Expr::Yield(ExprYield { |
| // `yield` |
| attrs: Vec::new(), |
| yield_token: Token, |
| expr: None, |
| })); |
| iter(depth, &mut |expr| { |
| f(Expr::Yield(ExprYield { |
| // `yield $expr` |
| attrs: Vec::new(), |
| yield_token: Token, |
| expr: Some(Box::new(expr)), |
| })); |
| }); |
| } |
| } |
| |
| let mut failures = 0; |
| macro_rules! fail { |
| ($($message:tt)*) => {{ |
| eprintln!($($message)*); |
| failures += 1; |
| return; |
| }}; |
| } |
| let mut assert = |mut original: Expr| { |
| let tokens = original.to_token_stream(); |
| let Ok(mut parsed) = syn::parse2::<Expr>(tokens.clone()) else { |
| fail!( |
| "failed to parse: {}\n{:#?}", |
| tokens, |
| crate::macros::debug::Lite(&original), |
| ); |
| }; |
| AsIfPrinted.visit_expr_mut(&mut original); |
| FlattenParens.visit_expr_mut(&mut parsed); |
| if original != parsed { |
| fail!( |
| "before: {}\n{:#?}\nafter: {}\n{:#?}", |
| tokens, |
| crate::macros::debug::Lite(&original), |
| parsed.to_token_stream(), |
| crate::macros::debug::Lite(&parsed), |
| ); |
| } |
| let mut tokens_no_paren = tokens.clone(); |
| FlattenParens::visit_token_stream_mut(&mut tokens_no_paren); |
| if tokens.to_string() != tokens_no_paren.to_string() { |
| if let Ok(mut parsed2) = syn::parse2::<Expr>(tokens_no_paren) { |
| FlattenParens.visit_expr_mut(&mut parsed2); |
| if original == parsed2 { |
| fail!("redundant parens: {}", tokens); |
| } |
| } |
| } |
| }; |
| |
| iter(4, &mut assert); |
| if failures > 0 { |
| eprintln!("FAILURES: {failures}"); |
| ExitCode::FAILURE |
| } else { |
| ExitCode::SUCCESS |
| } |
| } |