diff --git a/tests/test_expr.rs b/tests/test_expr.rs index 6d5d4f8729..0784c94263 100644 --- a/tests/test_expr.rs +++ b/tests/test_expr.rs @@ -7,12 +7,19 @@ #[macro_use] mod macros; -use proc_macro2::{Delimiter, Group}; +use proc_macro2::{Delimiter, Group, Span}; use quote::{quote, ToTokens as _}; use std::mem; +use std::process::ExitCode; use syn::punctuated::Punctuated; use syn::visit_mut::{self, VisitMut}; -use syn::{parse_quote, token, Expr, ExprRange, ExprTuple, Stmt, Token}; +use syn::{ + parse_quote, token, Arm, BinOp, Block, Expr, ExprAssign, ExprAwait, ExprBinary, ExprBlock, + ExprBreak, ExprCall, ExprCast, ExprClosure, ExprField, ExprForLoop, ExprIf, ExprIndex, + ExprMatch, ExprMethodCall, ExprRange, ExprRawAddr, ExprReference, ExprReturn, ExprTry, + ExprTuple, ExprUnary, ExprWhile, ExprYield, PointerMutability, RangeLimits, ReturnType, Stmt, + Token, UnOp, +}; #[test] fn test_expr_parse() { @@ -724,3 +731,468 @@ fn test_fixup() { ); } } + +#[test] +fn test_permutations() -> ExitCode { + fn iter(depth: usize, f: &mut dyn FnMut(Expr)) { + // Expr::Path + f(parse_quote!(x)); + f(parse_quote!(x::)); + f(parse_quote!(::CONST)); + + let Some(depth) = depth.checked_sub(1) else { + return; + }; + + // Expr::Array + f(parse_quote!([])); + + // Expr::Assign + iter(depth, &mut |expr| { + iter(0, &mut |simple| { + f(Expr::Assign(ExprAssign { + attrs: Vec::new(), + left: Box::new(simple.clone()), + eq_token: Token![=](Span::call_site()), + right: Box::new(expr.clone()), + })); + f(Expr::Assign(ExprAssign { + attrs: Vec::new(), + left: Box::new(expr.clone()), + eq_token: Token![=](Span::call_site()), + right: Box::new(simple), + })); + }); + }); + + // Expr::Async + f(parse_quote!(async {})); + + // Expr::Await + iter(depth, &mut |base| { + f(Expr::Await(ExprAwait { + attrs: Vec::new(), + base: Box::new(base), + dot_token: Token![.](Span::call_site()), + await_token: Token![await](Span::call_site()), + })); + }); + + // Expr::Binary + iter(depth, &mut |expr| { + iter(0, &mut |simple| { + for op in [ + BinOp::Add(Token![+](Span::call_site())), + BinOp::Sub(Token![-](Span::call_site())), + BinOp::Mul(Token![*](Span::call_site())), + BinOp::Div(Token![/](Span::call_site())), + BinOp::Rem(Token![%](Span::call_site())), + BinOp::And(Token![&&](Span::call_site())), + BinOp::Or(Token![||](Span::call_site())), + BinOp::BitXor(Token![^](Span::call_site())), + BinOp::BitAnd(Token![&](Span::call_site())), + BinOp::BitOr(Token![|](Span::call_site())), + BinOp::Shl(Token![<<](Span::call_site())), + BinOp::Shr(Token![>>](Span::call_site())), + BinOp::Eq(Token![==](Span::call_site())), + BinOp::Lt(Token![<](Span::call_site())), + BinOp::Le(Token![<=](Span::call_site())), + BinOp::Ne(Token![!=](Span::call_site())), + BinOp::Ge(Token![>=](Span::call_site())), + BinOp::Gt(Token![>](Span::call_site())), + BinOp::AddAssign(Token![+=](Span::call_site())), + BinOp::SubAssign(Token![-=](Span::call_site())), + BinOp::MulAssign(Token![*=](Span::call_site())), + BinOp::DivAssign(Token![/=](Span::call_site())), + BinOp::RemAssign(Token![%=](Span::call_site())), + BinOp::BitXorAssign(Token![^=](Span::call_site())), + BinOp::BitAndAssign(Token![&=](Span::call_site())), + BinOp::BitOrAssign(Token![|=](Span::call_site())), + BinOp::ShlAssign(Token![<<=](Span::call_site())), + BinOp::ShrAssign(Token![>>=](Span::call_site())), + ] { + f(Expr::Binary(ExprBinary { + attrs: Vec::new(), + left: Box::new(simple.clone()), + op, + right: Box::new(expr.clone()), + })); + f(Expr::Binary(ExprBinary { + attrs: Vec::new(), + left: Box::new(expr.clone()), + op, + right: Box::new(simple.clone()), + })); + } + }); + }); + + // Expr::Block + f(parse_quote!('a: {})); + iter(depth, &mut |expr| { + f(Expr::Block(ExprBlock { + attrs: Vec::new(), + label: None, + block: Block { + brace_token: token::Brace(Span::call_site()), + stmts: Vec::from([Stmt::Expr(expr.clone(), None)]), + }, + })); + f(Expr::Block(ExprBlock { + attrs: Vec::new(), + label: None, + block: Block { + brace_token: token::Brace(Span::call_site()), + stmts: Vec::from([Stmt::Expr( + expr.clone(), + Some(Token![;](Span::call_site())), + )]), + }, + })); + }); + + // Expr::Break + f(parse_quote!(break)); + f(parse_quote!(break 'a)); + iter(depth, &mut |expr| { + f(Expr::Break(ExprBreak { + attrs: Vec::new(), + break_token: Token![break](Span::call_site()), + label: None, + expr: Some(Box::new(expr.clone())), + })); + f(Expr::Break(ExprBreak { + attrs: Vec::new(), + break_token: Token![break](Span::call_site()), + label: Some(parse_quote!('a)), + expr: Some(Box::new(expr)), + })); + }); + + // Expr::Call + iter(depth, &mut |expr| { + f(Expr::Call(ExprCall { + attrs: Vec::new(), + func: Box::new(expr), + paren_token: token::Paren(Span::call_site()), + args: Punctuated::new(), + })); + }); + + // Expr::Cast + iter(depth, &mut |expr| { + f(Expr::Cast(ExprCast { + attrs: Vec::new(), + expr: Box::new(expr.clone()), + as_token: Token![as](Span::call_site()), + ty: parse_quote!(T), + })); + f(Expr::Cast(ExprCast { + attrs: Vec::new(), + expr: Box::new(expr), + as_token: Token![as](Span::call_site()), + ty: parse_quote!(Thing), + })); + }); + + // Expr::Closure + iter(depth, &mut |expr| { + f(Expr::Closure(ExprClosure { + attrs: Vec::new(), + lifetimes: None, + constness: None, + movability: None, + asyncness: None, + capture: None, + or1_token: Token![|](Span::call_site()), + inputs: Punctuated::new(), + or2_token: Token![|](Span::call_site()), + output: ReturnType::Default, + body: Box::new(expr.clone()), + })); + f(Expr::Closure(ExprClosure { + attrs: Vec::new(), + lifetimes: None, + constness: None, + movability: None, + asyncness: None, + capture: None, + or1_token: Token![|](Span::call_site()), + inputs: Punctuated::new(), + or2_token: Token![|](Span::call_site()), + output: ReturnType::Type(Token![->](Span::call_site()), parse_quote!(T)), + body: Box::new(expr), + })); + }); + + // Expr::Const + f(parse_quote!(const {})); + + // Expr::Continue + f(parse_quote!(continue)); + f(parse_quote!(continue 'a)); + + // Expr::Field + iter(depth, &mut |expr| { + f(Expr::Field(ExprField { + attrs: Vec::new(), + base: Box::new(expr.clone()), + dot_token: Token![.](Span::call_site()), + member: parse_quote!(field), + })); + f(Expr::Field(ExprField { + attrs: Vec::new(), + base: Box::new(expr), + dot_token: Token![.](Span::call_site()), + member: parse_quote!(0), + })); + }); + + // Expr::ForLoop + iter(depth, &mut |expr| { + f(Expr::ForLoop(ExprForLoop { + attrs: Vec::new(), + label: None, + for_token: Token![for](Span::call_site()), + pat: parse_quote!(_), + in_token: Token![in](Span::call_site()), + expr: Box::new(expr.clone()), + body: parse_quote!({}), + })); + f(Expr::ForLoop(ExprForLoop { + attrs: Vec::new(), + label: Some(parse_quote!('a:)), + for_token: Token![for](Span::call_site()), + pat: parse_quote!(_), + in_token: Token![in](Span::call_site()), + expr: Box::new(expr), + body: parse_quote!({}), + })); + }); + + // Expr::If + iter(depth, &mut |expr| { + f(Expr::If(ExprIf { + attrs: Vec::new(), + if_token: Token![if](Span::call_site()), + cond: Box::new(expr), + then_branch: parse_quote!({}), + else_branch: None, + })); + }); + + // Expr::Index + iter(depth, &mut |expr| { + f(Expr::Index(ExprIndex { + attrs: Vec::new(), + expr: Box::new(expr), + bracket_token: token::Bracket(Span::call_site()), + index: parse_quote!(0), + })); + }); + + // Expr::Loop + f(parse_quote!(loop {})); + f(parse_quote!('a: loop {})); + + // Expr::Macro + f(parse_quote!(m!())); + f(parse_quote!(m! {})); + + // Expr::Match + iter(depth, &mut |expr| { + f(Expr::Match(ExprMatch { + attrs: Vec::new(), + match_token: Token![match](Span::call_site()), + expr: Box::new(expr.clone()), + brace_token: token::Brace(Span::call_site()), + arms: Vec::new(), + })); + f(Expr::Match(ExprMatch { + attrs: Vec::new(), + match_token: Token![match](Span::call_site()), + expr: parse_quote!(x), + brace_token: token::Brace(Span::call_site()), + arms: Vec::from([Arm { + attrs: Vec::new(), + pat: parse_quote!(_), + guard: None, + fat_arrow_token: Token![=>](Span::call_site()), + body: Box::new(expr.clone()), + comma: None, + }]), + })); + f(Expr::Match(ExprMatch { + attrs: Vec::new(), + match_token: Token![match](Span::call_site()), + expr: parse_quote!(x), + brace_token: token::Brace(Span::call_site()), + arms: Vec::from([Arm { + attrs: Vec::new(), + pat: parse_quote!(_), + guard: Some((Token![if](Span::call_site()), Box::new(expr))), + fat_arrow_token: Token![=>](Span::call_site()), + body: parse_quote!({}), + comma: None, + }]), + })); + }); + + // Expr::MethodCall + iter(depth, &mut |expr| { + f(Expr::MethodCall(ExprMethodCall { + attrs: Vec::new(), + receiver: Box::new(expr.clone()), + dot_token: Token![.](Span::call_site()), + method: parse_quote!(method), + turbofish: None, + paren_token: token::Paren(Span::call_site()), + args: Punctuated::new(), + })); + f(Expr::MethodCall(ExprMethodCall { + attrs: Vec::new(), + receiver: Box::new(expr), + dot_token: Token![.](Span::call_site()), + method: parse_quote!(method), + turbofish: Some(parse_quote!(::)), + paren_token: token::Paren(Span::call_site()), + args: Punctuated::new(), + })); + }); + + // Expr::Range + f(parse_quote!(..)); + f(parse_quote!(0..)); + f(parse_quote!(..0)); + iter(depth, &mut |expr| { + f(Expr::Range(ExprRange { + attrs: Vec::new(), + start: None, + limits: RangeLimits::HalfOpen(Token![..](Span::call_site())), + end: Some(Box::new(expr.clone())), + })); + f(Expr::Range(ExprRange { + attrs: Vec::new(), + start: Some(Box::new(expr.clone())), + limits: RangeLimits::HalfOpen(Token![..](Span::call_site())), + end: None, + })); + }); + + // Expr::RawAddr + iter(depth, &mut |expr| { + f(Expr::RawAddr(ExprRawAddr { + attrs: Vec::new(), + and_token: Token![&](Span::call_site()), + raw: Token![raw](Span::call_site()), + mutability: PointerMutability::Const(Token![const](Span::call_site())), + expr: Box::new(expr), + })); + }); + + // Expr::Reference + iter(depth, &mut |expr| { + f(Expr::Reference(ExprReference { + attrs: Vec::new(), + and_token: Token![&](Span::call_site()), + mutability: None, + expr: Box::new(expr.clone()), + })); + f(Expr::Reference(ExprReference { + attrs: Vec::new(), + and_token: Token![&](Span::call_site()), + mutability: Some(Token![mut](Span::call_site())), + expr: Box::new(expr), + })); + }); + + // Expr::Return + f(parse_quote!(return)); + iter(depth, &mut |expr| { + f(Expr::Return(ExprReturn { + attrs: Vec::new(), + return_token: Token![return](Span::call_site()), + expr: Some(Box::new(expr)), + })); + }); + + // Expr::Struct + f(parse_quote!(Struct {})); + + // Expr::Try + iter(depth, &mut |expr| { + f(Expr::Try(ExprTry { + attrs: Vec::new(), + expr: Box::new(expr), + question_token: Token![?](Span::call_site()), + })); + }); + + // Expr::TryBlock + f(parse_quote!(try {})); + + // Expr::Unary + iter(depth, &mut |expr| { + for op in [ + UnOp::Deref(Token![*](Span::call_site())), + UnOp::Not(Token![!](Span::call_site())), + UnOp::Neg(Token![-](Span::call_site())), + ] { + f(Expr::Unary(ExprUnary { + attrs: Vec::new(), + op, + expr: Box::new(expr.clone()), + })); + } + }); + + // Expr::Unsafe + f(parse_quote!(unsafe {})); + + // Expr::While + iter(depth, &mut |expr| { + f(Expr::While(ExprWhile { + attrs: Vec::new(), + label: None, + while_token: Token![while](Span::call_site()), + cond: Box::new(expr.clone()), + body: parse_quote!({}), + })); + f(Expr::While(ExprWhile { + attrs: Vec::new(), + label: Some(parse_quote!('a:)), + while_token: Token![while](Span::call_site()), + cond: Box::new(expr), + body: parse_quote!({}), + })); + }); + + // Expr::Yield + f(parse_quote!(yield)); + iter(depth, &mut |expr| { + f(Expr::Yield(ExprYield { + attrs: Vec::new(), + yield_token: Token![yield](Span::call_site()), + expr: Some(Box::new(expr)), + })); + }); + } + + let mut status = ExitCode::SUCCESS; + macro_rules! fail { + ($($message:tt)*) => {{ + eprintln!($($message)*); + status = ExitCode::FAILURE; + return; + }}; + } + let mut assert = |expr: Expr| { + let tokens = expr.to_token_stream(); + if syn::parse2::(tokens.clone()).is_err() { + fail!("failed to parse: {}", tokens); + } + }; + + iter(2, &mut assert); + status +}