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.