Skip to main content

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(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 => output.push(' '),
310				};
311			};
312
313			match tree {
314				proc_macro2::TokenTree::Ident(ident) => {
315					output.push_str(&ident.to_string());
316					end = Some(ident.span());
317				},
318				proc_macro2::TokenTree::Punct(punct) => {
319					output.push(punct.as_char());
320					end = match punct.spacing() {
321						proc_macro2::Spacing::Joint => None,
322						proc_macro2::Spacing::Alone => Some(punct.span()),
323					};
324				},
325				proc_macro2::TokenTree::Literal(literal) => {
326					output.push_str(&literal.to_string());
327					end = Some(literal.span());
328				},
329				proc_macro2::TokenTree::Group(group) => {
330					let (whitespace_open, whitespace_close) = whitespace::whitespace_inside(&group)
331						.unwrap_or((Whitespace::new(), Whitespace::new()));
332					let (open, close) = match group.delimiter() {
333						proc_macro2::Delimiter::None => ('(', ')'),
334						proc_macro2::Delimiter::Parenthesis => ('(', ')'),
335						proc_macro2::Delimiter::Brace => ('{', '}'),
336						proc_macro2::Delimiter::Bracket => ('[', ']'),
337					};
338					output.push(open);
339					output.push_str(&whitespace_open.to_string());
340					let stream = group.stream();
341					if !stream.is_empty() {
342						end = None;
343						streams.push((stream.into_iter(), Some((close, whitespace_close, group.span_close()))));
344					} else {
345						output.push_str(&whitespace_close.to_string());
346						output.push(close);
347						end = Some(group.span_close());
348					}
349				},
350			}
351
352		}
353
354		quote!(#output)
355	}
356
357	#[cfg(not(feature = "span-locations"))]
358	{
359		let tokens = tokens.to_string();
360		quote!(#tokens)
361	}
362}
363
364fn expression_to_string(crate_name: &syn::Path, tokens: TokenStream, fragments: &mut Fragments) -> TokenStream {
365	#[cfg(feature = "nightly")]
366	{
367		let _ = crate_name;
368		use syn::spanned::Spanned;
369		find_macro_fragments(tokens.clone(), fragments);
370		if let Some(s) = tokens.span().unwrap().source_text() {
371			return quote!(#s);
372		}
373	}
374
375	#[cfg(feature = "span-locations")]
376	{
377		let _ = crate_name;
378		tokens_to_string(tokens, fragments)
379	}
380
381	#[cfg(not(feature = "span-locations"))]
382	{
383		let _ = fragments;
384		quote!(#crate_name::__assert2_stringify!(#tokens))
385	}
386}
387
388#[cfg(feature = "nightly")]
389fn find_macro_fragments(tokens: TokenStream, f: &mut Fragments) {
390	use syn::spanned::Spanned;
391	use proc_macro2::{Delimiter, TokenTree};
392
393	for token in tokens {
394		if let TokenTree::Group(g) = token {
395			if g.delimiter() == Delimiter::None {
396				let name = g.span().unwrap().source_text().unwrap_or_else(|| "???".into());
397				let contents = g.stream();
398				let expansion = contents.span().unwrap().source_text().unwrap_or_else(|| contents.to_string());
399				if name != expansion {
400					let entry = (name, expansion);
401					if !f.list.contains(&entry) {
402						f.list.push(entry);
403					}
404				}
405			}
406			find_macro_fragments(g.stream(), f);
407		}
408	}
409}
410
411
412struct Fragments {
413	list: Vec<(String, String)>,
414}
415
416impl Fragments {
417	fn new() -> Self {
418		Self { list: Vec::new() }
419	}
420}
421
422impl quote::ToTokens for Fragments {
423	fn to_tokens(&self, tokens: &mut TokenStream) {
424		let mut t = TokenStream::new();
425		for (name, expansion) in &self.list {
426			t.extend(quote!((#name, #expansion),));
427		}
428		tokens.extend(quote!(&[#t]));
429	}
430}