assert2_macros/
lib.rs

1#![cfg_attr(nightly, feature(proc_macro_span))]
2
3//! This macro contains only private procedural macros.
4//! See the documentation for [`assert2`](https://docs.rs/assert2/) for the public API.
5
6extern crate proc_macro;
7
8use proc_macro2::TokenStream;
9use quote::{ToTokens, quote};
10use syn::punctuated::Punctuated;
11
12type FormatArgs = Punctuated<syn::Expr, syn::token::Comma>;
13
14#[doc(hidden)]
15#[proc_macro]
16pub fn check_impl(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
17	hygiene_bug::fix(check_or_assert_impl(syn::parse_macro_input!(tokens)).into())
18}
19
20mod hygiene_bug;
21mod let_assert;
22
23#[doc(hidden)]
24#[proc_macro]
25pub fn let_assert_impl(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
26	hygiene_bug::fix(let_assert::let_assert_impl(syn::parse_macro_input!(tokens)).into())
27}
28
29/// Real implementation for assert!() and check!().
30fn check_or_assert_impl(args: Args) -> TokenStream {
31	match args.expr {
32		syn::Expr::Binary(expr) => check_binary_op(args.crate_name, args.macro_name, expr, args.format_args),
33		syn::Expr::Let(expr) => check_let_expr(args.crate_name, args.macro_name, expr, args.format_args),
34		expr => check_bool_expr(args.crate_name, args.macro_name, expr, args.format_args),
35	}
36}
37
38fn check_binary_op(crate_name: syn::Path, macro_name: syn::Expr, expr: syn::ExprBinary, format_args: Option<FormatArgs>) -> TokenStream {
39	match expr.op {
40		syn::BinOp::Eq(_) => (),
41		syn::BinOp::Lt(_) => (),
42		syn::BinOp::Le(_) => (),
43		syn::BinOp::Ne(_) => (),
44		syn::BinOp::Ge(_) => (),
45		syn::BinOp::Gt(_) => (),
46		_ => return check_bool_expr(crate_name, macro_name, syn::Expr::Binary(expr), format_args),
47	};
48
49	let syn::ExprBinary { left, right, op, .. } = &expr;
50	let mut fragments = Fragments::new();
51	let left_expr = expression_to_string(&crate_name, left.to_token_stream(), &mut fragments);
52	let right_expr = expression_to_string(&crate_name, right.to_token_stream(), &mut fragments);
53	let op_str = tokens_to_string(op.to_token_stream(), &mut fragments);
54
55	let custom_msg = match format_args {
56		Some(x) => quote!(Some(format_args!(#x))),
57		None => quote!(None),
58	};
59
60	quote! {
61		match (&(#left), &(#right)) {
62			(left, right) if !(left #op right) => {
63				use #crate_name::__assert2_impl::maybe_debug::{IsDebug, IsMaybeNotDebug};
64				let left = (&&#crate_name::__assert2_impl::maybe_debug::Wrap(left)).__assert2_maybe_debug().wrap(left);
65				let right = (&&#crate_name::__assert2_impl::maybe_debug::Wrap(right)).__assert2_maybe_debug().wrap(right);
66				#crate_name::__assert2_impl::print::FailedCheck {
67					macro_name: #macro_name,
68					file: file!(),
69					line: line!(),
70					column: column!(),
71					custom_msg: #custom_msg,
72					expression: #crate_name::__assert2_impl::print::BinaryOp {
73						left: &left,
74						right: &right,
75						operator: #op_str,
76						left_expr: #left_expr,
77						right_expr: #right_expr,
78					},
79					fragments: #fragments,
80				}.print();
81				Err(())
82			}
83			_ => Ok(()),
84		}
85	}
86}
87
88fn check_bool_expr(crate_name: syn::Path, macro_name: syn::Expr, expr: syn::Expr, format_args: Option<FormatArgs>) -> TokenStream {
89	let mut fragments = Fragments::new();
90	let expr_str = expression_to_string(&crate_name, expr.to_token_stream(), &mut fragments);
91
92	let custom_msg = match format_args {
93		Some(x) => quote!(Some(format_args!(#x))),
94		None => quote!(None),
95	};
96
97	quote! {
98		match #expr {
99			false => {
100				#crate_name::__assert2_impl::print::FailedCheck {
101					macro_name: #macro_name,
102					file: file!(),
103					line: line!(),
104					column: column!(),
105					custom_msg: #custom_msg,
106					expression: #crate_name::__assert2_impl::print::BooleanExpr {
107						expression: #expr_str,
108					},
109					fragments: #fragments,
110				}.print();
111				Err(())
112			}
113			true => Ok(()),
114		}
115	}
116}
117
118fn check_let_expr(crate_name: syn::Path, macro_name: syn::Expr, expr: syn::ExprLet, format_args: Option<FormatArgs>) -> TokenStream {
119	let syn::ExprLet {
120		pat,
121		expr,
122		..
123	} = expr;
124
125	let mut fragments = Fragments::new();
126	let pat_str = tokens_to_string(pat.to_token_stream(), &mut fragments);
127	let expr_str = expression_to_string(&crate_name, expr.to_token_stream(), &mut fragments);
128
129	let custom_msg = match format_args {
130		Some(x) => quote!(Some(format_args!(#x))),
131		None => quote!(None),
132	};
133
134	quote! {
135		match &(#expr) {
136			#pat => Ok(()),
137			value => {
138				use #crate_name::__assert2_impl::maybe_debug::{IsDebug, IsMaybeNotDebug};
139				let value = (&&#crate_name::__assert2_impl::maybe_debug::Wrap(value)).__assert2_maybe_debug().wrap(value);
140				#crate_name::__assert2_impl::print::FailedCheck {
141					macro_name: #macro_name,
142					file: file!(),
143					line: line!(),
144					column: column!(),
145					custom_msg: #custom_msg,
146					expression: #crate_name::__assert2_impl::print::MatchExpr {
147						print_let: true,
148						value: &value,
149						pattern: #pat_str,
150						expression: #expr_str,
151					},
152					fragments: #fragments,
153				}.print();
154				Err(())
155			}
156		}
157	}
158}
159
160fn tokens_to_string(ts: TokenStream, fragments: &mut Fragments) -> TokenStream {
161	#[cfg(nightly)]
162	{
163		use syn::spanned::Spanned;
164		find_macro_fragments(ts.clone(), fragments);
165		if let Some(s) = ts.span().unwrap().source_text() {
166			return quote!(#s);
167		}
168	}
169
170	let _ = fragments;
171
172	let tokens = ts.to_string();
173	quote!(#tokens)
174}
175
176fn expression_to_string(crate_name: &syn::Path, ts: TokenStream, fragments: &mut Fragments) -> TokenStream {
177	#[cfg(nightly)]
178	{
179		use syn::spanned::Spanned;
180		find_macro_fragments(ts.clone(), fragments);
181		if let Some(s) = ts.span().unwrap().source_text() {
182			return quote!(#s);
183		}
184	}
185
186	let _ = fragments;
187
188	quote!(#crate_name::__assert2_stringify!(#ts))
189}
190
191#[cfg(nightly)]
192fn find_macro_fragments(ts: TokenStream, f: &mut Fragments) {
193	use syn::spanned::Spanned;
194	use proc_macro2::{Delimiter, TokenTree};
195
196	for token in ts {
197		if let TokenTree::Group(g) = token {
198			if g.delimiter() == Delimiter::None {
199				let name = g.span().unwrap().source_text().unwrap_or_else(|| "???".into());
200				let contents = g.stream();
201				let expansion = contents.span().unwrap().source_text().unwrap_or_else(|| contents.to_string());
202				if name != expansion {
203					let entry = (name, expansion);
204					if !f.list.contains(&entry) {
205						f.list.push(entry);
206					}
207				}
208			}
209			find_macro_fragments(g.stream(), f);
210		}
211	}
212}
213
214struct Fragments {
215	list: Vec<(String, String)>,
216}
217
218impl Fragments {
219	fn new() -> Self {
220		Self { list: Vec::new() }
221	}
222}
223
224impl quote::ToTokens for Fragments {
225	fn to_tokens(&self, tokens: &mut TokenStream) {
226		let mut t = TokenStream::new();
227		for (name, expansion) in &self.list {
228			t.extend(quote!((#name, #expansion),));
229		}
230		tokens.extend(quote!(&[#t]));
231	}
232}
233
234struct Args {
235	crate_name: syn::Path,
236	macro_name: syn::Expr,
237	expr: syn::Expr,
238	format_args: Option<FormatArgs>,
239}
240
241impl syn::parse::Parse for Args {
242	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
243		let crate_name = input.parse()?;
244		let _comma: syn::token::Comma = input.parse()?;
245		let macro_name = input.parse()?;
246		let _comma: syn::token::Comma = input.parse()?;
247		let expr = input.parse()?;
248		let format_args = if input.is_empty() {
249			FormatArgs::new()
250		} else {
251			input.parse::<syn::token::Comma>()?;
252			FormatArgs::parse_terminated(input)?
253		};
254
255		let format_args = Some(format_args).filter(|x| !x.is_empty());
256		Ok(Self {
257			crate_name,
258			macro_name,
259			expr,
260			format_args,
261		})
262	}
263}