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};
10
11use crate::whitespace::{OperatorWithSpacing, Whitespace};
12
13type FormatArgs = syn::punctuated::Punctuated<syn::Expr, syn::token::Comma>;
14
15mod assert;
16mod check;
17mod hygiene_bug;
18mod whitespace;
19
20#[doc(hidden)]
21#[proc_macro]
22pub fn assert_impl(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
23	hygiene_bug::fix(assert::assert(syn::parse_macro_input!(tokens)).into())
24}
25
26#[doc(hidden)]
27#[proc_macro]
28pub fn check_impl(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
29	hygiene_bug::fix(check::check(syn::parse_macro_input!(tokens)).into())
30}
31
32/// Parsed arguments for the `check` or `assert` macro.
33struct Args {
34	/// The path of the `assert2` crate.
35	crate_name: syn::Path,
36
37	/// The name of the macro being called.
38	macro_name: syn::Expr,
39
40	/// The expression passed to the macro,
41	expression: syn::Expr,
42
43	/// Optional extra message (all arguments forwarded to format_args!()).
44	format_args: Option<FormatArgs>,
45}
46
47impl syn::parse::Parse for Args {
48	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
49		let crate_name = input.parse()?;
50		let _comma: syn::token::Comma = input.parse()?;
51		let macro_name = input.parse()?;
52		let _comma: syn::token::Comma = input.parse()?;
53		let expression = syn::Expr::parse_without_eager_brace(input)?;
54		let format_args = if input.is_empty() {
55			FormatArgs::new()
56		} else {
57			input.parse::<syn::token::Comma>()?;
58			FormatArgs::parse_terminated(input)?
59		};
60
61		let format_args = Some(format_args).filter(|x| !x.is_empty());
62		Ok(Self {
63			crate_name,
64			macro_name,
65			expression,
66			format_args,
67		})
68	}
69}
70
71impl Args {
72	fn into_context(self) -> Context {
73		let mut predicates = split_predicates(self.expression);
74		let multiline = reindent_predicates(&mut predicates, 4);
75		let mut fragments = Fragments::new();
76		let print_predicates = printable_predicates(&self.crate_name, &predicates, &mut fragments);
77
78		let custom_msg = match self.format_args {
79			Some(x) => quote!(::core::option::Option::Some(::core::format_args!(#x))),
80			None => quote!(::core::option::Option::None),
81		};
82
83		Context {
84			crate_name: self.crate_name,
85			macro_name: self.macro_name,
86			predicates,
87			print_predicates,
88			multiline,
89			fragments,
90			custom_msg,
91		}
92	}
93}
94
95struct Context {
96	crate_name: syn::Path,
97	macro_name: syn::Expr,
98	predicates: Vec<Predicate>,
99	print_predicates: TokenStream,
100	multiline: bool,
101	fragments: Fragments,
102	custom_msg: TokenStream,
103}
104
105struct Predicate {
106	prefix: PredicatePrefix,
107	expr: syn::Expr,
108}
109
110enum PredicatePrefix {
111	None,
112	Whitespace(Whitespace),
113	Operator(OperatorWithSpacing),
114}
115
116impl PredicatePrefix {
117	fn total_newlines(&self) -> usize {
118		match self {
119			Self::None => 0,
120			Self::Whitespace(x) => x.lines,
121			Self::Operator(x) => x.total_newlines(),
122		}
123	}
124
125	fn min_indent(&self) -> Option<usize> {
126		match self {
127			Self::None => None,
128			Self::Whitespace(x) => x.indentation(),
129			Self::Operator(x) => x.min_indent(),
130		}
131	}
132
133	fn adjust_indent(&mut self, adjust: isize) {
134		match self {
135			Self::None => (),
136			Self::Whitespace(x) => x.adjust_indent(adjust),
137			Self::Operator(x) => x.adjust_indent(adjust),
138		}
139	}
140}
141
142impl std::fmt::Display for PredicatePrefix {
143	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144		match self {
145			Self::None => Ok(()),
146			Self::Whitespace(x) => x.fmt(f),
147			Self::Operator(x) => x.fmt(f),
148		}
149	}
150}
151
152fn split_predicates(input: syn::Expr) -> Vec<Predicate> {
153	let mut output = Vec::new();
154	let mut remaining = vec![
155		Predicate {
156			prefix: PredicatePrefix::None,
157			expr: input,
158		}
159	];
160	while let Some(outer_predicate) = remaining.pop() {
161		match outer_predicate.expr {
162			syn::Expr::Binary(expr) if matches!(expr.op, syn::BinOp::And(_)) => {
163				let inner_operator = whitespace::operator_with_whitespace(&expr)
164					.unwrap_or(OperatorWithSpacing::new_logical_and());
165				remaining.push(Predicate {
166					prefix: PredicatePrefix::Operator(inner_operator),
167					expr: *expr.right,
168				});
169				remaining.push(Predicate {
170					prefix: outer_predicate.prefix,
171					expr: *expr.left,
172				});
173			},
174			_ => output.push(outer_predicate),
175		}
176	}
177	output
178}
179
180/// Change the initial indentation of the predicates.
181///
182/// All common leading spaces are removed and replaced with `reindent` spaces.
183///
184/// If the first predicate has no glue, and there is any glue with a newline,
185/// new glue is added to put it on a new line with indentation.
186///
187/// Returns `true` if the predicates contain at least one newline.
188fn reindent_predicates(predicates: &mut [Predicate], reindent: usize) -> bool {
189	// Count the total number of newlines and the minimum indentation of all lines.
190	let mut total_newlines = 0;
191	let mut min_indent = None;
192	for predicate in predicates.iter() {
193		total_newlines += predicate.prefix.total_newlines();
194		min_indent = [min_indent, predicate.prefix.min_indent()]
195			.into_iter()
196			.flatten()
197			.min()
198	}
199
200	// No newlines in input, leave it alone.
201	if total_newlines == 0 {
202		return false;
203	}
204
205	let min_indent = min_indent.unwrap_or(0);
206
207	for (i, predicate) in predicates.iter_mut().enumerate() {
208		// If there are newlines in the input, put the first predicate on a new line with indentation too.
209		if i == 0 {
210			if total_newlines > 0 {
211				predicate.prefix = PredicatePrefix::Whitespace(Whitespace::new().with_lines(1).with_spaces(reindent));
212			}
213		// Adjust the indentation of all predicates.
214		} else {
215			predicate.prefix.adjust_indent(reindent as isize - min_indent as isize);
216		}
217	}
218
219	true
220}
221
222fn printable_predicates(crate_name: &syn::Path, predicates: &[Predicate], fragments: &mut Fragments) -> TokenStream {
223	let mut printable_predicates = Vec::new();
224	for predicate in predicates {
225		let prefix = predicate.prefix.to_string();
226		let expression = match &predicate.expr {
227			syn::Expr::Let(expr) => {
228				let pattern = expression_to_string(crate_name, expr.pat.to_token_stream(), fragments);
229				let expression = expression_to_string(crate_name, expr.expr.to_token_stream(), fragments);
230				quote! {
231					(
232						#prefix,
233						#crate_name::__assert2_impl::print::Predicate::Let {
234							pattern: #pattern,
235							expression: #expression,
236						},
237					)
238				}
239			},
240			syn::Expr::Binary(expr) => {
241				let left = expression_to_string(crate_name, expr.left.to_token_stream(), fragments);
242				let operator = tokens_to_string(expr.op.to_token_stream(), fragments);
243				let right = expression_to_string(crate_name, expr.right.to_token_stream(), fragments);
244				quote! {
245					(
246						#prefix,
247						#crate_name::__assert2_impl::print::Predicate::Binary {
248							left: #left,
249							operator: #operator,
250							right: #right,
251						},
252					)
253				}
254			},
255			expr => {
256				let expression = expression_to_string(crate_name, expr.to_token_stream(), fragments);
257				quote! {
258					(
259						#prefix,
260						#crate_name::__assert2_impl::print::Predicate::Bool {
261							expression: #expression,
262						},
263					)
264				}
265			},
266		};
267		printable_predicates.push(expression);
268	}
269	quote!( &[#(#printable_predicates),*] )
270}
271
272fn tokens_to_string(tokens: TokenStream, fragments: &mut Fragments) -> TokenStream {
273	#[cfg(feature = "nightly")]
274	{
275		use syn::spanned::Spanned;
276		find_macro_fragments(tokens.clone(), fragments);
277		if let Some(s) = tokens.span().unwrap().source_text() {
278			return quote!(#s);
279		}
280	}
281
282	#[cfg(not(feature = "nightly"))]
283	{
284		let _ = fragments;
285	}
286
287	#[cfg(feature = "span-locations")]
288	{
289		let mut output = String::new();
290		let mut end = None;
291		let mut streams = vec![(tokens.into_iter(), None::<(char, Whitespace, proc_macro2::Span)>)];
292		while let Some((mut stream, delimiter)) = streams.pop() {
293			let tree = match stream.next() {
294				None => {
295					if let Some((delimiter, whitespace, delim_span)) = delimiter {
296						output.push_str(&whitespace.to_string());
297						output.push(delimiter);
298						end = Some(delim_span);
299					}
300					continue;
301				},
302				Some(tree) => tree,
303			};
304			streams.push((stream, delimiter));
305
306			if let Some(end) = end {
307				match whitespace::whitespace_between(end, tree.span()) {
308					Some(whitespace) => output.push_str(&whitespace.to_string()),
309					None => {
310						print!("Failed to determine whitespace before tree");
311						output.push(' ');
312					},
313				};
314			};
315
316			match tree {
317				proc_macro2::TokenTree::Ident(ident) => {
318					output.push_str(&ident.to_string());
319					end = Some(ident.span());
320				},
321				proc_macro2::TokenTree::Punct(punct) => {
322					output.push(punct.as_char());
323					end = match punct.spacing() {
324						proc_macro2::Spacing::Joint => None,
325						proc_macro2::Spacing::Alone => Some(punct.span()),
326					};
327				},
328				proc_macro2::TokenTree::Literal(literal) => {
329					output.push_str(&literal.to_string());
330					end = Some(literal.span());
331				},
332				proc_macro2::TokenTree::Group(group) => {
333					let (whitespace_open, whitespace_close) = whitespace::whitespace_inside(&group)
334						.unwrap_or((Whitespace::new(), Whitespace::new()));
335					let (open, close) = match group.delimiter() {
336						proc_macro2::Delimiter::None => ('(', ')'),
337						proc_macro2::Delimiter::Parenthesis => ('(', ')'),
338						proc_macro2::Delimiter::Brace => ('{', '}'),
339						proc_macro2::Delimiter::Bracket => ('[', ']'),
340					};
341					output.push(open);
342					output.push_str(&whitespace_open.to_string());
343					let stream = group.stream();
344					if !stream.is_empty() {
345						end = None;
346						streams.push((stream.into_iter(), Some((close, whitespace_close, group.span_close()))));
347					} else {
348						output.push_str(&whitespace_close.to_string());
349						output.push(close);
350						end = Some(group.span_close());
351					}
352				},
353			}
354
355		}
356
357		quote!(#output)
358	}
359
360	#[cfg(not(feature = "span-locations"))]
361	{
362		let tokens = tokens.to_string();
363		quote!(#tokens)
364	}
365}
366
367fn expression_to_string(crate_name: &syn::Path, tokens: TokenStream, fragments: &mut Fragments) -> TokenStream {
368	#[cfg(feature = "nightly")]
369	{
370		let _ = crate_name;
371		use syn::spanned::Spanned;
372		find_macro_fragments(tokens.clone(), fragments);
373		if let Some(s) = tokens.span().unwrap().source_text() {
374			return quote!(#s);
375		}
376	}
377
378	#[cfg(feature = "span-locations")]
379	{
380		let _ = crate_name;
381		tokens_to_string(tokens, fragments)
382	}
383
384	#[cfg(not(feature = "span-locations"))]
385	{
386		let _ = fragments;
387		quote!(#crate_name::__assert2_stringify!(#tokens))
388	}
389}
390
391#[cfg(feature = "nightly")]
392fn find_macro_fragments(tokens: TokenStream, f: &mut Fragments) {
393	use syn::spanned::Spanned;
394	use proc_macro2::{Delimiter, TokenTree};
395
396	for token in tokens {
397		if let TokenTree::Group(g) = token {
398			if g.delimiter() == Delimiter::None {
399				let name = g.span().unwrap().source_text().unwrap_or_else(|| "???".into());
400				let contents = g.stream();
401				let expansion = contents.span().unwrap().source_text().unwrap_or_else(|| contents.to_string());
402				if name != expansion {
403					let entry = (name, expansion);
404					if !f.list.contains(&entry) {
405						f.list.push(entry);
406					}
407				}
408			}
409			find_macro_fragments(g.stream(), f);
410		}
411	}
412}
413
414
415struct Fragments {
416	list: Vec<(String, String)>,
417}
418
419impl Fragments {
420	fn new() -> Self {
421		Self { list: Vec::new() }
422	}
423}
424
425impl quote::ToTokens for Fragments {
426	fn to_tokens(&self, tokens: &mut TokenStream) {
427		let mut t = TokenStream::new();
428		for (name, expansion) in &self.list {
429			t.extend(quote!((#name, #expansion),));
430		}
431		tokens.extend(quote!(&[#t]));
432	}
433}