1#![cfg_attr(nightly, feature(proc_macro_span))]
2
3extern 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
32struct Args {
34 crate_name: syn::Path,
36
37 macro_name: syn::Expr,
39
40 expression: syn::Expr,
42
43 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
180fn reindent_predicates(predicates: &mut [Predicate], reindent: usize) -> bool {
189 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 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 i == 0 {
210 if total_newlines > 0 {
211 predicate.prefix = PredicatePrefix::Whitespace(Whitespace::new().with_lines(1).with_spaces(reindent));
212 }
213 } 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}