one_assert/
lib.rs

1#![deny(
2    missing_docs,
3    missing_debug_implementations,
4    trivial_casts,
5    trivial_numeric_casts,
6    unsafe_code,
7    unstable_features,
8    unused_import_braces,
9    unused_qualifications,
10    rustdoc::broken_intra_doc_links,
11    rustdoc::private_intra_doc_links,
12    rustdoc::missing_crate_level_docs,
13    rustdoc::invalid_codeblock_attributes,
14    rustdoc::bare_urls
15)]
16
17//! ### TL;DR
18//! Why have separate macros for `assert_eq` and `assert_ne` (and `assert_gt` etc. with other crates) when you can
19//! just get the same output with `assert!(a == b)` (or `assert!(a != b)`, `assert!(a > b)`, …)? This crate provides a
20//! single `assert!` macro that analyzes the expression to provide more detailed output on failure.
21//!
22//! ### Introduction
23//! Rust's standard library provides the [`assert`], [`assert_eq`] and [`assert_ne`] macros. There are however some
24//! inconveniences with these, like how there are no specialization for other inequalities, like `assert_ge` for `>=`
25//! etc, or how the names only differ in one or two letters (`assert_eq`, `assert_ne`, `assert_ge`, `assert_gt`, …)
26//! and are thus easy to mix up at a glance.
27//!
28//! [`assert`]:    https://doc.rust-lang.org/std/macro.assert.html
29//! [`assert_eq`]: https://doc.rust-lang.org/std/macro.assert_eq.html
30//! [`assert_ne`]: https://doc.rust-lang.org/std/macro.assert_ne.html
31//!
32//! The main reason for not adding more macros is that they can be represented just fine with `assert!(a >= b)`, so
33//! there is no need for a separate macro for every use case.
34//!
35//! But that begs the question: Why do we have `assert_eq` and `assert_ne` in the first place?
36//!
37//! The practical reason: `assert_eq!(a, b)` provides better output than `assert!(a == b)`:
38//!
39//! ```
40//! # macro_rules! catch_panic {
41//! #     ($block: block) => {{
42//! #         let error = std::panic::catch_unwind(move || $block).unwrap_err();
43//! #         error.downcast_ref::<&'static str>().map(|s| s.to_string())
44//! #              .unwrap_or_else(|| *error.downcast::<String>().unwrap())
45//! #     }};
46//! # }
47//! let x = 1;
48//! let msg = catch_panic!({ assert!(x == 2); });
49//! assert_eq!(msg, "assertion failed: x == 2");
50//!
51//! # if rustc_version::version().unwrap() > rustc_version::Version::new(1, 70, 0) { // Output of assert_eq changed since MSRV...
52//! let msg = catch_panic!({ assert_eq!(x, 2); });
53//! assert_eq!(msg, "assertion `left == right` failed
54//!   left: 1
55//!  right: 2"
56//! );
57//! # }
58//! ```
59//!
60//! As you can see, `assert_eq` is able to provide detailed info on what the individual values were.\
61//! But: That doesn’t have to be the case. Rust has fancy-pants macros, so we can just
62//! **make `assert!(a == b)` work the same as `assert_eq!(a, b)`:**
63//!
64//! ```
65//! # macro_rules! catch_panic {
66//! #     ($block: block) => {{
67//! #         let error = std::panic::catch_unwind(move || $block).unwrap_err();
68//! #         error.downcast_ref::<&'static str>().map(|s| s.to_string())
69//! #              .unwrap_or_else(|| *error.downcast::<String>().unwrap())
70//! #     }};
71//! # }
72//! let x = 1;
73//! let msg = catch_panic!({ one_assert::assert!(x == 2); });
74//! assert_eq!(msg, "assertion `x == 2` failed
75//!      left: 1
76//!     right: 2"
77//! );
78//! ```
79//!
80//! And now we can expand this to as many operators (and even expressions!) as we want.
81//!
82//! ### Examples
83//!
84//! ```
85//! # macro_rules! catch_panic {
86//! #     ($block: block) => {{
87//! #         let error = std::panic::catch_unwind(move || $block).unwrap_err();
88//! #         error.downcast_ref::<&'static str>().map(|s| s.to_string())
89//! #              .unwrap_or_else(|| *error.downcast::<String>().unwrap())
90//! #     }};
91//! # }
92//! let x = 1;
93//! let msg = catch_panic!({ one_assert::assert!(x > 2); });
94//! assert_eq!(msg, "assertion `x > 2` failed
95//!      left: 1
96//!     right: 2"
97//! );
98//!
99//! let msg = catch_panic!({ one_assert::assert!(10 <= x); });
100//! assert_eq!(msg, "assertion `10 <= x` failed
101//!      left: 10
102//!     right: 1"
103//! );
104//!
105//! let msg = catch_panic!({ one_assert::assert!(x != 1, "x ({}) should not be 1", x); });
106//! assert_eq!(msg, "assertion `x != 1` failed: x (1) should not be 1
107//!      left: 1
108//!     right: 1"
109//! );
110//!
111//! let s = "Hello World";
112//! let msg = catch_panic!({ one_assert::assert!(s.starts_with("hello")); });
113//! assert_eq!(msg, r#"assertion `s.starts_with("hello")` failed
114//!      self: "Hello World"
115//!     arg 0: "hello""#
116//! );
117//! ```
118//!
119//! ### Limitations
120//! - **Several Components need to implement [`Debug`]**
121//!   - The macro will take whatever part of the expression is considered useful and debug print it. This means that
122//!     those parts need to implement [`Debug`].
123//!   - What is printed as part of any given expression type is subject to change, so it is recommended to only use
124//!     this in code where pretty much everything implements [`Debug`].
125//! - **[`Debug`] printing might happen even if the assertion passes**
126//!   - Because this macro prints more than just the two sides of an `==` or `!=` comparison, it has to deal with the
127//!     fact that some values might be moved during the evaluation of the expression. This means that the values have
128//!     to be printed in advance.
129//!   - Specifically, comparisons work as usual, but every other operator that has special output (e.g. `a+b`,
130//!     `foo(a,b)`, `arr[a]`, ...) has its arguments debug-printed in advance.
131//!   - Consequence: **You might not want to use this macro in performance-critical code.**
132//!   - Note however, that the expression and each part of it is only **evaluated** once, and the fail-fast behavior
133//!     of `&&` and `||` is preserved.
134//!
135//! ### Changelog
136//! See [Changelog.md](https://github.com/mich101mich/one_assert/blob/master/Changelog.md)
137//!
138//! ### License
139//! Licensed under either of [Apache License, Version 2.0] or [MIT license] at your option.
140//!
141//! [Apache License, Version 2.0]: https://github.com/mich101mich/one_assert/blob/master/LICENSE-APACHE
142//! [MIT license]: https://github.com/mich101mich/one_assert/blob/master/LICENSE-MIT
143
144use std::{
145    borrow::Borrow,
146    fmt::{Display, Write},
147};
148
149use proc_macro::TokenStream as TokenStream1;
150use proc_macro2::{Span, TokenStream};
151use quote::{quote, ToTokens};
152
153mod error;
154mod format_message;
155mod not;
156mod utils;
157mod variables;
158
159use error::*;
160use format_message::*;
161use utils::*;
162use variables::*;
163
164/// Parsed arguments for the `assert` macro
165struct Args {
166    /// condition to evaluate
167    expr: syn::Expr,
168    /// optional message to display if the condition is false
169    format: TokenStream,
170}
171
172impl syn::parse::Parse for Args {
173    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
174        if input.is_empty() {
175            let msg = "missing condition to check";
176            return Err(syn::Error::new(Span::call_site(), msg)); // checked in tests/fail/missing_params.rs
177        }
178        let span_source: TokenStream = input.fork().parse().unwrap(); // unwrap: parsing a TokenStream can't fail
179        let expr = match input.parse() {
180            Ok(expr) => expr,
181            Err(e) => {
182                let err = if input.is_empty() {
183                    // syn's error would use call_site instead of pointing at the broken expression
184                    let msg = format!("incomplete expression: {e}");
185                    syn::Error::new_spanned(span_source, msg) // checked in tests/fail/malformed_expr.rs
186                } else if let Ok(comma) = input.parse::<syn::Token![,]>() {
187                    // syn's error would point at the ',' saying "expected an expression"
188                    let msg = format!("Expression before the comma is incomplete: {e}");
189                    syn::Error::new_spanned(comma, msg) // checked in tests/fail/malformed_expr.rs
190                } else {
191                    e
192                };
193                return Err(err);
194            }
195        };
196
197        let format;
198        if input.is_empty() {
199            format = TokenStream::new();
200        } else if let Err(e) = input.parse::<syn::Token![,]>() {
201            let msg = "condition has to be followed by a comma, if a message is provided";
202            return Err(syn::Error::new(e.span(), msg)); // checked in tests/fail/malformed_parameters.rs
203        } else {
204            format = input.parse()?;
205        }
206
207        Ok(Args { expr, format })
208    }
209}
210
211/// The main macro that is used to check a condition and panic if it is false.
212///
213/// # Syntax
214/// ```text
215/// assert!(condition: expression);
216/// assert!(condition: expression, message: format_string, args...: format_args);
217/// ```
218/// Parameters:
219/// - `condition`: The condition that should be checked. If it evaluates to `false`, the assertion fails.
220///   Can be any expression that evaluates to `bool`.
221/// - `message`: An optional message that is displayed if the assertion fails. This message can contain `{}`
222///   placeholders for dynamic arguments. See [`format_args`] for more information.
223/// - `args`: Arguments that are only evaluated if the assertion fails. These arguments are passed to
224///   `format_args` to replace the `{}` placeholders in the message.
225///
226/// # Examples
227/// See the crate-level documentation for examples.
228#[proc_macro]
229pub fn assert(input: TokenStream1) -> TokenStream1 {
230    let input = syn::parse_macro_input!(input as Args);
231    match assert_internal(input) {
232        Ok(tokens) => tokens.into(),
233        Err(err) => err.into(),
234    }
235}
236
237fn assert_internal(input: Args) -> Result<TokenStream> {
238    let Args { expr, format } = input;
239
240    let expr_str = printable_expr_string(&expr);
241
242    if expr_str == "true" {
243        return Ok(assert_true_flavor());
244    } else if expr_str == "false" {
245        return Ok(quote! {
246            ::std::panic!("surprisingly, `false` did not evaluate to true")
247        });
248    }
249
250    let mut setup = TokenStream::new();
251    let mut format_message = FormatMessage::new();
252    // A wrapper type to create multi-token variables for span manipulation
253    setup.extend(quote! { struct __OneAssertWrapper<T>(T); });
254    format_message.add_text(format!("assertion `{expr_str}` failed"));
255
256    if !format.is_empty() {
257        format_message.add_placeholder(": {}", quote! { ::std::format_args!(#format) });
258    }
259
260    let output = eval_expr(expr, setup, format_message)?;
261    // println!("\n\n\n{}\n\n\n", output);
262    Ok(output)
263}
264
265#[allow(clippy::match_same_arms)] // every arm needs its own reasoning and consideration
266fn eval_expr(
267    mut e: syn::Expr,
268    mut setup: TokenStream,
269    mut format_message: FormatMessage,
270) -> Result<TokenStream> {
271    let mut assert_condition = e.to_token_stream();
272    let mut variables = Variables::new();
273
274    // inline any parentheses and invisible groups as they are not necessary
275    while let syn::Expr::Paren(syn::ExprParen { expr: inner, .. })
276    | syn::Expr::Group(syn::ExprGroup { expr: inner, .. }) = e
277    {
278        e = *inner;
279    }
280
281    match e {
282        // [a, b, c, d]
283        syn::Expr::Array(_) => {} // let the compiler generate the error
284
285        // a = b
286        syn::Expr::Assign(syn::ExprAssign { eq_token, .. }) => {
287            let msg = "Expected a boolean expression, found an assignment. Did you intend to compare with `==`?";
288            return Error::err_spanned(eq_token, msg); // checked in tests/fail/expr/assign.rs
289        }
290
291        // async { ... }
292        syn::Expr::Async(_) => {
293            let msg = "Expected a boolean expression, found an async block. Did you intend to await a future?";
294            return Error::err_spanned(e, msg); // checked in tests/fail/expr/async.rs
295        }
296
297        // future.await
298        syn::Expr::Await(_) => {} // might work if the future resolves to a boolean and the assert is in an async context
299
300        // left <op> right
301        syn::Expr::Binary(syn::ExprBinary {
302            left,
303            op,
304            right,
305            attrs,
306        }) => {
307            match op {
308                // logic operators => preserve fail-fast behavior where expressions like `!vec.empty() && vec[0].ok()` work
309                syn::BinOp::And(_) => return Ok(resolve_and(setup, format_message, left, right)),
310                syn::BinOp::Or(_) => return Ok(resolve_or(setup, format_message, left, right)),
311
312                // comparison operators, handle as expected
313                syn::BinOp::Eq(_)
314                | syn::BinOp::Lt(_)
315                | syn::BinOp::Le(_)
316                | syn::BinOp::Ne(_)
317                | syn::BinOp::Ge(_)
318                | syn::BinOp::Gt(_) => {
319                    let lhs = variables.add_borrowed_var(left, "lhs", "left");
320                    let rhs = variables.add_borrowed_var(right, "rhs", "right");
321                    assert_condition = quote! { #(#attrs)* #lhs #op #rhs };
322                }
323
324                // operators that might return a bool, but might move the inputs
325                syn::BinOp::Add(_)
326                | syn::BinOp::Sub(_)
327                | syn::BinOp::Mul(_)
328                | syn::BinOp::Div(_)
329                | syn::BinOp::Rem(_)
330                | syn::BinOp::BitXor(_)
331                | syn::BinOp::BitAnd(_)
332                | syn::BinOp::BitOr(_)
333                | syn::BinOp::Shl(_)
334                | syn::BinOp::Shr(_) => {
335                    let lhs = variables.add_moving_var(left, "lhs", "left");
336                    let rhs = variables.add_moving_var(right, "rhs", "right");
337                    assert_condition = quote! { #(#attrs)* #lhs #op #rhs };
338                }
339
340                // operators that don't return anything
341                syn::BinOp::AddAssign(_)
342                | syn::BinOp::SubAssign(_)
343                | syn::BinOp::MulAssign(_)
344                | syn::BinOp::DivAssign(_)
345                | syn::BinOp::RemAssign(_)
346                | syn::BinOp::BitXorAssign(_)
347                | syn::BinOp::BitAndAssign(_)
348                | syn::BinOp::BitOrAssign(_)
349                | syn::BinOp::ShlAssign(_)
350                | syn::BinOp::ShrAssign(_) => {
351                    // we generate our own error, because the compiler just says "expected bool, found ()"
352                    let msg = "Expected a boolean expression, found an assignment";
353                    return Error::err_spanned(op, msg); // checked in tests/fail/expr/binary.rs
354                }
355
356                // unknown operator, keep as-is
357                _ => {}
358            }
359        }
360
361        // { ... }
362        syn::Expr::Block(_) => {} // We could check the condition at the end of the block, but really, this shouldn't be done in the assert
363
364        // break
365        syn::Expr::Break(_) => {
366            // we need to generate our own error, because break returns `!` so it compiles, but the assertion makes no sense
367            let msg = "Expected a boolean expression, found a break statement";
368            return Error::err_spanned(e, msg); // checked in tests/fail/expr/break.rs
369        }
370
371        // function(args...)
372        syn::Expr::Call(syn::ExprCall {
373            args,
374            func,
375            paren_token,
376            attrs,
377        }) if !args.is_empty() => {
378            let index_len = (args.len() - 1).to_string().len();
379            let out_args = args.iter().enumerate().map(|(i, arg)| {
380                variables.add_moving_var(
381                    arg,
382                    format_args!("arg{i}"),
383                    format_args!("arg {i:>index_len$}"),
384                )
385            });
386
387            // output: `quote! { #(#attrs)* #func ( #(#out_args),* ) }` except we want to use the original parentheses for span purposes
388            assert_condition = quote! { #(#attrs)* #func };
389            paren_token.surround(&mut assert_condition, |out| {
390                out.extend(quote! { #(#out_args),* })
391            });
392        }
393        // function() // no args
394        syn::Expr::Call(_) => {} // just a plain function call that returns a boolean or not. Nothing more to add here
395
396        // expr as ty
397        syn::Expr::Cast(_) => {} // let the compiler generate the error.
398        // Might work if expr is `true as bool`, which would actually be a workaround for the `assert!(true)` case
399
400        // |args| { ... }
401        syn::Expr::Closure(_) => {} // let the compiler generate the error
402
403        // const { ... }
404        syn::Expr::Const(_) => {} // same as Expr::Block
405
406        // continue
407        syn::Expr::Continue(_) => {
408            // we need to generate our own error, because continue returns `!` so it compiles, but the assertion makes no sense
409            let msg = "Expected a boolean expression, found a continue statement";
410            return Error::err_spanned(e, msg); // checked in tests/fail/expr/continue.rs
411        }
412
413        // obj.field
414        syn::Expr::Field(_) => {} // might work if the field is a boolean
415        // It would be possible to print the object that the field is accessed on, but that won't provide much value.
416        // The only part of the object that is interesting is the field, and that is already evaluated as the assertion.
417
418        // for pat in { ... }
419        syn::Expr::ForLoop(_) => {
420            // we generate our own error, because the compiler just says "expected bool, found ()"
421            let msg = "Expected a boolean expression, found a for loop";
422            return Error::err_spanned(e, msg); // checked in tests/fail/expr/forloop.rs
423        }
424
425        // group with invisible delimiters
426        syn::Expr::Group(_) => unreachable!(), // inlined at the start of the function
427
428        // if cond { ... } else { ... }
429        syn::Expr::If(expr_if) => return resolve_if(setup, format_message, expr_if),
430
431        // expr[index]
432        syn::Expr::Index(syn::ExprIndex {
433            index,
434            expr,
435            attrs,
436            bracket_token,
437        }) => {
438            if !matches!(*index, syn::Expr::Lit(_)) {
439                let index = variables.add_moving_var(index, "index", "index");
440                // output: `quote! { #(#attrs)* #expr [#index] }` except we want to use the original brackets for span purposes
441                assert_condition = quote! { #(#attrs)* #expr };
442                bracket_token.surround(&mut assert_condition, |out| index.to_tokens(out));
443            }
444            // not printing literals, because their value is already known.
445
446            // not printing the indexed object, because the output could be huge.
447            // If we knew the object was a form of array, then we could would slice the range around the index,
448            // but it could also be a HashMap or a custom type, so we can't do that.
449        }
450
451        // _
452        syn::Expr::Infer(_) => {} // let the compiler generate the error
453
454        // let pat = expr
455        syn::Expr::Let(_) => {
456            // we have to generate our own error, because the produced code is `if #expression`, which would become `if let ...` 😂
457            let msg = "Expected a boolean expression, found a let statement";
458            return Error::err_spanned(e, msg); // checked in tests/fail/expr/let.rs
459        }
460
461        // lit
462        syn::Expr::Lit(_) => {} // might work if the literal is a boolean
463        // The base case for `assert!(true)` and `assert!(false)` was already caught in the initial
464        // setup. This is the case where a recursive call contained a plain `true` or `false`, so we
465        // shall accept them without printing weird messages
466
467        // loop { ... }
468        syn::Expr::Loop(_) => {} // might work if the loop breaks with a boolean
469        // If somebody has too much free time on their hands they can go ahead and write some recursive
470        // block parsing code to find all the `break` statements so that the error message can say
471        // which one was triggered. This would be really useful info for the user, but it's a lot of effort
472        // for something that probably nobody will ever see.
473        // Side note: Finding a `break` would actually help with the case where there are no breaks, because
474        // then the loop would just never return (`!`), so the compiler doesn't complain but the assertion
475        // makes no sense.
476
477        // some_macro!(...)
478        syn::Expr::Macro(_) => {} // not touching this
479
480        // match expr { ... }
481        syn::Expr::Match(_) => {} // we could check which variant matched etc. etc., but that is excessive for an assert
482
483        // receiver.method(args...)
484        syn::Expr::MethodCall(syn::ExprMethodCall {
485            receiver,
486            method,
487            turbofish,
488            args,
489            attrs,
490            dot_token,
491            paren_token,
492        }) => {
493            let obj = variables.add_moving_var(receiver, "object", "self");
494            let index_len = (args.len().saturating_sub(1)).to_string().len();
495            let out_args = args.iter().enumerate().map(|(i, arg)| {
496                variables.add_moving_var(
497                    arg,
498                    format_args!("arg{i}"),
499                    format_args!("arg {i:>index_len$}"),
500                )
501            });
502
503            // output: `quote! { #(attrs)* #obj #dot_token #method #turbofish ( #(#out_args),* ) }` except we want to use the original parentheses for span purposes
504            assert_condition = quote! { #(#attrs)* #obj #dot_token #method #turbofish };
505            paren_token.surround(&mut assert_condition, |out| {
506                out.extend(quote! { #(#out_args),* })
507            });
508        }
509
510        // (expr)
511        syn::Expr::Paren(_) => unreachable!(), // inlined at the start of the function
512
513        // some::path::<of>::stuff
514        syn::Expr::Path(_) => {} // might be a constant of type bool, otherwise let the compiler generate the error
515
516        // a..b
517        syn::Expr::Range(_) => {} // let the compiler generate the error
518
519        // &expr
520        syn::Expr::Reference(_) => {} // let the compiler generate the error
521
522        // [x; n]
523        syn::Expr::Repeat(_) => {} // let the compiler generate the error
524
525        // return expr
526        syn::Expr::Return(_) => {
527            // we need to generate our own error, because return returns `!` so it compiles, but the assertion makes no sense
528            let msg = "Expected a boolean expression, found a return statement";
529            return Error::err_spanned(e, msg); // checked in tests/fail/expr/return.rs
530        }
531
532        // MyStruct { field: value }
533        syn::Expr::Struct(_) => {
534            // we generate our own error, because the compiler will suggest adding parentheses around the struct literal
535            let msg = "Expected a boolean expression, found a struct literal";
536            return Error::err_spanned(e, msg);
537        }
538
539        // expr?
540        syn::Expr::Try(_) => {} // might work if expr is a Result<bool> or similar, otherwise let the compiler generate the error
541
542        // (a, b, c)
543        syn::Expr::Tuple(_) => {} // let the compiler generate the error
544
545        // !expr
546        syn::Expr::Unary(syn::ExprUnary {
547            expr,
548            op: syn::UnOp::Not(not_token),
549            attrs,
550        }) => {
551            return not::eval_not_expr(*expr, setup, format_message, not_token, attrs);
552        }
553        // op expr
554        syn::Expr::Unary(_) => {} // just leave it as-is
555
556        // unsafe { ... }
557        syn::Expr::Unsafe(_) => {} // Same as Expr::Block
558
559        // something
560        syn::Expr::Verbatim(_) => {} // even syn doesn't know what this is, so we can't do anything with it
561
562        // while cond { ... }
563        syn::Expr::While(_) => {
564            // we generate our own error, because the compiler just says "expected bool, found ()"
565            let msg = "Expected a boolean expression, found a while loop";
566            return Error::err_spanned(e, msg);
567        }
568
569        _ => {} // we don't know what this is, so we can't do anything with it
570                // this includes unstable syntax that is already contained in syn, like
571                // syn::Expr::TryBlock
572                // syn::Expr::Yield
573    }
574
575    variables.resolve_variables(&mut setup, &mut format_message);
576
577    Ok(quote! { #[allow(unreachable_code)] {
578        #setup
579        if #assert_condition {
580            // using an empty if instead of `!(#expression)` to avoid messing with the spans in `expression`.
581            // And to produce a better error: "expected bool, found <type>"
582            // instead of: "no unary operator '!' implemented for <type>"
583        } else {
584            ::std::panic!(#format_message);
585        }
586    }})
587}
588
589fn resolve_and(
590    setup: TokenStream,
591    format_message: FormatMessage,
592    left: impl Borrow<syn::Expr>,
593    right: impl Borrow<syn::Expr>,
594) -> TokenStream {
595    let left = left.borrow();
596    let right = right.borrow();
597
598    let mut message_if_left_false = format_message.clone();
599    message_if_left_false.add_cause("left side of `&&` evaluated to false");
600
601    let mut message_if_right_false = format_message;
602    message_if_right_false
603        .add_cause("left side of `&&` evaluated to true, but right side evaluated to false");
604
605    // `&&` logic: if first is true, evaluate second. Otherwise skip second
606    quote! { #[allow(unreachable_code)] {
607        #setup
608        if #left {
609            if #right {
610                // both sides true
611            } else {
612                ::std::panic!(#message_if_right_false);
613            }
614        } else {
615            ::std::panic!(#message_if_left_false);
616        }
617    }}
618}
619
620fn resolve_or(
621    setup: TokenStream,
622    mut format_message: FormatMessage,
623    left: impl Borrow<syn::Expr>,
624    right: impl Borrow<syn::Expr>,
625) -> TokenStream {
626    let left = left.borrow();
627    let right = right.borrow();
628
629    format_message.add_cause("both sides of `||` evaluated to false");
630
631    // `||` logic: if first is true, entire expression is true. Otherwise evaluate second
632    quote! { #[allow(unreachable_code)] {
633        #setup
634        if #left {
635            // left side true => entire expression true
636        } else {
637            if #right {
638                // right side true => entire expression true
639            } else {
640                // both sides false
641                ::std::panic!(#format_message);
642            }
643        }
644    }}
645}
646
647fn resolve_if(
648    setup: TokenStream,
649    format_message: FormatMessage,
650    expr_if: syn::ExprIf,
651) -> Result<TokenStream> {
652    let syn::ExprIf {
653        if_token,
654        cond,
655        then_branch,
656        mut else_branch,
657        ..
658    } = expr_if;
659
660    let mut format_cond = format_message.clone();
661    format_cond.add_cause(format_args!(
662        "
663  - if condition `{}` was true
664    - then-block `{}` evaluated to false",
665        printable_expr_string(&cond),
666        printable_expr_string(&then_branch)
667    ));
668
669    let mut out = quote! {
670        if #cond {
671            if #then_branch { /* assertion passed */ } else { ::std::panic!(#format_cond); }
672        }
673    };
674
675    let mut cause_message = format!(
676        "
677  - if condition `{}` was false",
678        printable_expr_string(&cond)
679    );
680
681    loop {
682        let Some((_, else_expr)) = else_branch else {
683            let msg = "if-expression is missing a final else-block to handle the case where all conditions are false.
684If you want a conditional assert, put the assert! inside the if block.";
685            return Err(Error::new_spanned(if_token, msg));
686        };
687
688        match *else_expr {
689            syn::Expr::If(nested_if) => {
690                let syn::ExprIf {
691                    cond,
692                    then_branch,
693                    else_branch: inner_else_branch,
694                    ..
695                } = nested_if;
696
697                let mut format_cond = format_message.clone();
698                format_cond.add_cause(format_args!(
699                    "{}
700  - else-if condition `{}` was true
701    - then-block `{}` evaluated to false",
702                    cause_message,
703                    printable_expr_string(&cond),
704                    printable_expr_string(&then_branch)
705                ));
706
707                out.extend(quote! {
708                    else if #cond {
709                        if #then_branch { /* assertion passed */ } else { ::std::panic!(#format_cond); }
710                    }
711                });
712
713                cause_message = format!(
714                    "{}
715  - else-if condition `{}` was false",
716                    cause_message,
717                    printable_expr_string(&cond)
718                );
719
720                else_branch = inner_else_branch;
721            }
722            else_block => {
723                let mut format_else = format_message;
724                format_else.add_cause(format_args!(
725                    "{}
726  - else-block `{}` evaluated to false",
727                    cause_message,
728                    printable_expr_string(&else_block)
729                ));
730
731                out.extend(quote! {
732                    else {
733                        if #else_block { /* assertion passed */ } else { ::std::panic!(#format_else); }
734                    }
735                });
736
737                break;
738            }
739        }
740    }
741
742    // we could analyze the blocks as well, but that is a bit excessive.
743    // If you want better output, put the assert in the if and not the other way around.
744
745    Ok(quote! { #[allow(unreachable_code, unused_braces)] {
746        #setup
747        #out
748    }})
749}
750
751fn assert_true_flavor() -> TokenStream {
752    quote! {
753        let line = ::std::line!();
754        if line % 100 == 69 {
755            ::std::panic!("You actually used `assert!(true)`? Nice.");
756        } else if line % 100 == 0 {
757            ::std::panic!("Congratulations! You are the {}th person to use `assert!(true)`! You win a free panic!", line);
758        } else if line % 10 == 0 {
759            // Have the assertion randomly pass
760        } else {
761            const MESSAGES: &[&'static ::std::primitive::str] = &[
762                "Ha! Did you think `assert!(true)` would do nothing? Fool!",
763                "assertion `true` failed:\n  left: tr\n right: ue",
764                "assertion `true` failed: `true` did not evaluate to true",
765                "assertion `true` failed: `true` did not evaluate to true...? Huh? What? 🤔",
766                "Undefined reference to `true`. Did you mean `false`?",
767                "assertion `true` failed: `true` did not evaluate to true. What a surprise!",
768            ];
769            let msg = MESSAGES[line as usize % MESSAGES.len()];
770            ::std::panic!("{}", msg);
771        }
772    }
773}
774
775// # Span manipulation workaround:
776// Spans cannot be manipulated on stable rust right now (see <https://github.com/rust-lang/rust/issues/54725>).
777// This also applies to getting the full span of an expression, which requires joining the spans of the individual
778// tokens. On stable, .span() will just return the first token, meaning that if you have an expression like
779// `1 + 2` and a compiler error should be printed on the entire expression, it will instead only underline
780// the first token, the `1` in this case.
781// To work around this, the common approach (see syn::Error::new_spanned) is to bind the first and last token
782// of your code to the first and last individual span of the input, so that when the rust compiler wants to
783// underline the "entire" span, it will join the spans for us and underline the entire expression.
784// This requires that the code that should be underlined has more than one token, so that more than one span
785// can be bound to it. This function should create variable names, which are only one token long, so we need
786// to artificially create a multi-token variable. This is the point of the __OneAssertWrapper struct. It simply
787// contains the value of the variable, and any access will be written as `var.0` instead of `var`, giving us
788// the multi-token variable we need.
789//
790// ## Simplified but full example
791//
792// ### Without the span manipulation
793// Input: `assert!(1 + 2);`
794//
795// Output:
796// ```
797// let var = 1 + 2;
798// if var {} else { panic!("assertion failed"); }
799// ```
800//
801// This code would produce a compiler error like this:
802// ```
803// error: mismatched types
804//  1 | assert!(1 + 2);
805//              ^ expected bool, found {integer}
806// ```
807// which is not very helpful, because the error message only points at the first token of the expression.
808//
809// ### With the span manipulation
810// Input: `assert!(1 + 2);`
811//
812// Output:
813// ```
814// let var = __OneAssertWrapper(1 + 2);
815// if var.0 {} else { panic!("assertion failed"); }
816// ```
817// Note that the token-span assignment of the usage of `var.0` is as follows:
818// - `var` is assigned the span of the `1` from the input
819// - `.0` is assigned the span of the `2` from the input
820//
821// Produced error:
822// ```
823// error: mismatched types
824//  1 | assert!(1 + 2);
825//              ^^^^^ expected bool, found {integer}
826// ```
827// As you can see, the compiler wants to underline the full `var.0`, meaning it will end up underlining
828// everything between the original `1` and `2` tokens, which is exactly what we want.