1use crate::complex_expr::ComplexTestCase;
2use crate::modifier::{parse_kws, Modifier};
3use crate::utils::fmt_syn;
4use crate::TokenStream2;
5use quote::ToTokens;
6use std::collections::HashSet;
7use std::fmt::{Display, Formatter};
8use syn::parse::{Parse, ParseStream};
9use syn::token::If;
10use syn::{parse_quote, Attribute, Expr, Pat, Token};
11
12pub mod kw {
13 syn::custom_keyword!(matches);
14 syn::custom_keyword!(using);
15 syn::custom_keyword!(with);
16 syn::custom_keyword!(it);
17 syn::custom_keyword!(is);
18 syn::custom_keyword!(panics);
19}
20
21#[derive(Clone, Debug)]
22pub struct TestCaseExpression {
23 _token: Token![=>],
24 pub extra_keywords: HashSet<Modifier>,
25 pub result: TestCaseResult,
26}
27
28#[derive(Clone, Debug)]
29pub enum TestCaseResult {
30 Empty,
32 Simple(Expr),
34 Matching(Pat, Option<Box<Expr>>),
36 Panicking(Option<Expr>),
38 With(Expr),
40 UseFn(Expr),
42 Complex(ComplexTestCase),
44}
45
46impl Parse for TestCaseExpression {
47 fn parse(input: ParseStream) -> syn::Result<Self> {
48 let token: Token![=>] = input.parse()?;
49 let extra_keywords = parse_kws(input);
50
51 if input.parse::<kw::matches>().is_ok() {
52 let pattern = Pat::parse_single(input)?;
53 let guard = if input.peek(If) {
54 let _if_kw: If = input.parse()?;
55 let guard: Box<Expr> = input.parse()?;
56 Some(guard)
57 } else {
58 None
59 };
60
61 Ok(TestCaseExpression {
62 _token: token,
63 extra_keywords,
64 result: TestCaseResult::Matching(pattern, guard),
65 })
66 } else if input.parse::<kw::it>().is_ok() || input.parse::<kw::is>().is_ok() {
67 parse_with_keyword::<_, _>(input, token, extra_keywords, TestCaseResult::Complex)
68 } else if input.parse::<kw::using>().is_ok() {
69 parse_with_keyword::<_, _>(input, token, extra_keywords, TestCaseResult::UseFn)
70 } else if input.parse::<kw::with>().is_ok() {
71 parse_with_keyword::<_, _>(input, token, extra_keywords, TestCaseResult::With)
72 } else if input.parse::<kw::panics>().is_ok() {
73 parse_with_keyword_ok::<_, _>(input, token, extra_keywords, TestCaseResult::Panicking)
74 } else {
75 let result = match input.parse::<Expr>() {
76 Ok(expr) => TestCaseResult::Simple(expr),
77 Err(_) if !extra_keywords.is_empty() => TestCaseResult::Empty,
78 Err(e) => return Err(e),
79 };
80
81 Ok(Self {
82 _token: token,
83 extra_keywords,
84 result,
85 })
86 }
87 }
88}
89
90impl Display for TestCaseExpression {
91 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
92 for kw in &self.extra_keywords {
93 write!(f, "{kw:?}")?;
94 }
95 write!(f, "{}", self.result)
96 }
97}
98
99impl Display for TestCaseResult {
100 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101 match self {
102 TestCaseResult::Simple(expr) => write!(f, "{}", fmt_syn(expr)),
103 TestCaseResult::Matching(pat, expr) => {
104 write!(f, "matching {} {}", fmt_syn(pat), fmt_syn(expr))
105 }
106 TestCaseResult::Panicking(expr) => write!(
107 f,
108 "panicking {:?}",
109 expr.as_ref().map(|inner| fmt_syn(&inner))
110 ),
111 TestCaseResult::With(expr) => write!(f, "with {}", fmt_syn(expr)),
112 TestCaseResult::UseFn(expr) => write!(f, "use {}", fmt_syn(expr)),
113 TestCaseResult::Complex(complex) => write!(f, "complex {complex}"),
114 TestCaseResult::Empty => write!(f, "empty"),
115 }
116 }
117}
118
119impl TestCaseExpression {
120 pub fn assertion(&self) -> TokenStream2 {
121 match &self.result {
122 TestCaseResult::Simple(expr) => parse_quote! { assert_eq!(#expr, _result) },
123 TestCaseResult::Matching(pat, guard) => {
124 let pat_str = pat.to_token_stream().to_string();
125
126 if let Some(guard) = guard {
127 let guard_str = guard.to_token_stream().to_string();
128
129 parse_quote! {
130 match _result {
131 #pat if #guard => (),
132 e => panic!("Expected `{} if {}` found {:?}", #pat_str, #guard_str, e)
133 }
134 }
135 } else {
136 parse_quote! {
137 match _result {
138 #pat => (),
139 e => panic!("Expected `{}` found {:?}", #pat_str, e)
140 }
141 }
142 }
143 }
144 TestCaseResult::Panicking(_) => TokenStream2::new(),
145 TestCaseResult::With(expr) => parse_quote! { let fun = #expr; fun(_result) },
146 TestCaseResult::UseFn(path) => parse_quote! { #path(_result) },
147 TestCaseResult::Complex(complex) => complex.assertion(),
148 TestCaseResult::Empty => TokenStream2::new(),
149 }
150 }
151
152 pub fn attributes(&self) -> Vec<Attribute> {
153 let mut attrs: Vec<Attribute> = self
154 .extra_keywords
155 .iter()
156 .map(|modifier| modifier.attribute())
157 .collect();
158 if let TestCaseResult::Panicking(opt) = &self.result {
159 if let Some(expr) = opt {
160 attrs.push(parse_quote! { #[should_panic(expected = #expr)] })
161 } else {
162 attrs.push(parse_quote! { #[should_panic] })
163 }
164 }
165 attrs
166 }
167}
168
169fn parse_with_keyword<Inner, Mapping>(
170 input: ParseStream,
171 token: Token![=>],
172 extra_keywords: HashSet<Modifier>,
173 mapping: Mapping,
174) -> syn::Result<TestCaseExpression>
175where
176 Mapping: FnOnce(Inner) -> TestCaseResult,
177 Inner: Parse,
178{
179 Ok(TestCaseExpression {
180 _token: token,
181 extra_keywords,
182 result: mapping(input.parse()?),
183 })
184}
185
186fn parse_with_keyword_ok<Inner, Mapping>(
187 input: ParseStream,
188 token: Token![=>],
189 extra_keywords: HashSet<Modifier>,
190 mapping: Mapping,
191) -> syn::Result<TestCaseExpression>
192where
193 Mapping: FnOnce(Option<Inner>) -> TestCaseResult,
194 Inner: Parse,
195{
196 let result = (!input.is_empty()).then(|| input.parse()).transpose()?;
197 Ok(TestCaseExpression {
198 _token: token,
199 extra_keywords,
200 result: mapping(result),
201 })
202}