diff options
Diffstat (limited to 'vendor/syn/tests/test_expr.rs')
| -rw-r--r-- | vendor/syn/tests/test_expr.rs | 1699 |
1 files changed, 1699 insertions, 0 deletions
diff --git a/vendor/syn/tests/test_expr.rs b/vendor/syn/tests/test_expr.rs new file mode 100644 index 00000000..e23d0bd9 --- /dev/null +++ b/vendor/syn/tests/test_expr.rs @@ -0,0 +1,1699 @@ +#![cfg(not(miri))] +#![recursion_limit = "1024"] +#![feature(rustc_private)] +#![allow( + clippy::elidable_lifetime_names, + 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_range_attrs() { + // Attributes are not allowed on range expressions starting with `..` + syn::parse_str::<Expr>("#[allow()] ..").unwrap_err(); + syn::parse_str::<Expr>("#[allow()] .. hi").unwrap_err(); + + snapshot!("#[allow()] lo .. hi" as Expr, @r#" + Expr::Range { + start: Some(Expr::Path { + attrs: [ + Attribute { + style: AttrStyle::Outer, + meta: Meta::List { + path: Path { + segments: [ + PathSegment { + ident: "allow", + }, + ], + }, + delimiter: MacroDelimiter::Paren, + tokens: TokenStream(``), + }, + }, + ], + path: Path { + segments: [ + PathSegment { + ident: "lo", + }, + ], + }, + }), + limits: RangeLimits::HalfOpen, + end: Some(Expr::Path { + path: Path { + segments: [ + PathSegment { + ident: "hi", + }, + ], + }, + }), + } + "#); +} + +#[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! { #[attr] (1 + 1) }, + quote! { #[attr] (x = 1) }, + quote! { #[attr] (x += 1) }, + quote! { #[attr] (1 as T) }, + quote! { (return #[attr] (x + ..)).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::combine_attrs().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::combine_attrs().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::combine_attrs().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 + } +} |
