From a8717aba0fbda54711fab7b8c0e5c1a6d452f9bc Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 21 Nov 2021 16:22:46 -0800 Subject: [PATCH 1/5] Include a Debug rendering of lhs and rhs in `ensure!` messages --- src/ensure.rs | 471 +++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/macros.rs | 9 + tests/test_macros.rs | 2 +- tests/ui/empty-ensure.stderr | 6 +- 5 files changed, 487 insertions(+), 3 deletions(-) create mode 100644 src/ensure.rs diff --git a/src/ensure.rs b/src/ensure.rs new file mode 100644 index 0000000..fe6c486 --- /dev/null +++ b/src/ensure.rs @@ -0,0 +1,471 @@ +use crate::{anyhow, Error}; +use core::fmt::Debug; + +#[doc(hidden)] +pub trait BothDebug { + fn __dispatch_ensure(self, msg: &'static str) -> Error; +} + +impl BothDebug for (A, B) +where + A: Debug, + B: Debug, +{ + fn __dispatch_ensure(self, msg: &'static str) -> Error { + anyhow!("{} ({:?} vs {:?})", msg, self.0, self.1) + } +} + +#[doc(hidden)] +pub trait NotBothDebug { + fn __dispatch_ensure(self, msg: &'static str) -> Error; +} + +impl NotBothDebug for &(A, B) { + fn __dispatch_ensure(self, msg: &'static str) -> Error { + Error::msg(msg) + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __parse_ensure { + (atom () $bail:tt {($($rhs:tt)+) ($($lhs:tt)+) $op:tt} $(,)?) => { + $crate::__fancy_ensure!($($lhs)+, $op, $($rhs)+) + }; + + // low precedence control flow constructs + + (0 $stack:tt $bail:tt $parse:tt return $($rest:tt)*) => { + $crate::__fallback_ensure!($bail) + }; + + (0 $stack:tt $bail:tt $parse:tt break $($rest:tt)*) => { + $crate::__fallback_ensure!($bail) + }; + + (0 $stack:tt $bail:tt $parse:tt continue $($rest:tt)*) => { + $crate::__fallback_ensure!($bail) + }; + + (0 $stack:tt $bail:tt $parse:tt yield $($rest:tt)*) => { + $crate::__fallback_ensure!($bail) + }; + + (0 $stack:tt $bail:tt $parse:tt move $($rest:tt)*) => { + $crate::__fallback_ensure!($bail) + }; + + // unary operators + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} * $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* *) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} ! $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* !) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} - $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* -) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} let $(|)? $($pat:pat)|+ = $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* let $($pat)|+ =) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} $life:lifetime : $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* $life :) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} &mut $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* &mut) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} & $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* &) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} &&mut $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* &&mut) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} && $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* &&) $($parse)*} $($rest)*) + }; + + // control flow constructs + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} if $($rest:tt)*) => { + $crate::__parse_ensure!(0 (cond $stack) $bail {($($buf)* if) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} match $($rest:tt)*) => { + $crate::__parse_ensure!(0 (cond $stack) $bail {($($buf)* match) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} while $($rest:tt)*) => { + $crate::__parse_ensure!(0 (cond $stack) $bail {($($buf)* while) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} for $(|)? $($pat:pat)|+ in $($rest:tt)*) => { + $crate::__parse_ensure!(0 (cond $stack) $bail {($($buf)* for $($pat)|+ in) $($parse)*} $($rest)*) + }; + + (atom (cond $stack:tt) $bail:tt {($($buf:tt)*) $($parse:tt)*} {$($block:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(cond $stack $bail {($($buf)* {$($block)*}) $($parse)*} $($rest)*) + }; + + (cond $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} else if $($rest:tt)*) => { + $crate::__parse_ensure!(0 (cond $stack) $bail {($($buf)* else if) $($parse)*} $($rest)*) + }; + + (cond $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} else {$($block:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* else {$($block)*}) $($parse)*} $($rest)*) + }; + + (cond $stack:tt $bail:tt $parse:tt $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail $parse $($rest)*) + }; + + // atomic expressions + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} ($($paren:tt)*) $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* ($($paren)*)) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} [$($array:tt)*] $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* [$($array)*]) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} {$($block:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* {$($block)*}) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} loop {$($block:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* loop {$($block)*}) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} async {$($block:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* async {$($block)*}) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} async move {$($block:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* async move {$($block)*}) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} unsafe {$($block:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* unsafe {$($block)*}) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} $lit:literal $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* $lit) $($parse)*} $($rest)*) + }; + + // path expressions + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} :: $($rest:tt)*) => { + $crate::__parse_ensure!(path $stack $bail {($($buf)* ::) $($parse)*} $($rest)*) + }; + + (0 $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} $ident:ident $($rest:tt)*) => { + $crate::__parse_ensure!(component $stack $bail {($($buf)* $ident) $($parse)*} $($rest)*) + }; + + (path $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} $ident:ident $($rest:tt)*) => { + $crate::__parse_ensure!(component $stack $bail {($($buf)* $ident) $($parse)*} $($rest)*) + }; + + (component $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} :: < $($rest:tt)*) => { + $crate::__parse_ensure!(generic (component $stack) $bail {($($buf)* :: <) $($parse)*} $($rest)*) + }; + + (component $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} :: << $($rest:tt)*) => { + $crate::__parse_ensure!(generic (component $stack) $bail {($($buf)* :: <) $($parse)*} < $($rest)*) + }; + + (component $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} :: $($rest:tt)*) => { + $crate::__parse_ensure!(path $stack $bail {($($buf)* ::) $($parse)*} $($rest)*) + }; + + // macro invocations + + (component $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} ! ($($mac:tt)*) $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* ! ($($mac)*)) $($parse)*} $($rest)*) + }; + + (component $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} ! [$($mac:tt)*] $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* ! [$($mac)*]) $($parse)*} $($rest)*) + }; + + (component $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} ! {$($mac:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* ! {$($mac)*}) $($parse)*} $($rest)*) + }; + + (component $stack:tt $bail:tt $parse:tt $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail $parse $($rest)*) + }; + + // trailer expressions + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} ($($call:tt)*) $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* ($($call)*)) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} [$($index:tt)*] $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* [$($index)*]) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} {$($init:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* {$($init)*}) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} ? $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* ?) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} . $ident:ident :: < $($rest:tt)*) => { + $crate::__parse_ensure!(generic (atom $stack) $bail {($($buf)* . $ident :: <) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} . $ident:ident :: << $($rest:tt)*) => { + $crate::__parse_ensure!(generic (atom $stack) $bail {($($buf)* . $ident :: <) $($parse)*} < $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} . $ident:ident $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* . $ident) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} . $lit:tt $($rest:tt)*) => { + $crate::__parse_ensure!(atom $stack $bail {($($buf)* . $lit) $($parse)*} $($rest)*) + }; + + // angle bracketed generic arguments + + (generic ($pop:ident $stack:tt) $bail:tt {($($buf:tt)*) $($parse:tt)*} > $($rest:tt)*) => { + $crate::__parse_ensure!($pop $stack $bail {($($buf)* >) $($parse)*} $($rest)*) + }; + + (generic $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} $lit:literal $($rest:tt)*) => { + $crate::__parse_ensure!(arglist $stack $bail {($($buf)* $lit) $($parse)*} $($rest)*) + }; + + (generic $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} {$($block:tt)*} $($rest:tt)*) => { + $crate::__parse_ensure!(arglist $stack $bail {($($buf)* {$($block)*}) $($parse)*} $($rest)*) + }; + + (generic $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} $life:lifetime $($rest:tt)*) => { + $crate::__parse_ensure!(arglist $stack $bail {($($buf)* $life) $($parse)*} $($rest)*) + }; + + (generic $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} $ty:ty , $($rest:tt)*) => { + $crate::__parse_ensure!(generic $stack $bail {($($buf)* $ty ,) $($parse)*} $($rest)*) + }; + + (generic ($pop:ident $stack:tt) $bail:tt {($($buf:tt)*) $($parse:tt)*} $ty:ty > $($rest:tt)*) => { + $crate::__parse_ensure!($pop $stack $bail {($($buf)* $ty >) $($parse)*} $($rest)*) + }; + + (arglist $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} , $($rest:tt)*) => { + $crate::__parse_ensure!(generic $stack $bail {($($buf)* ,) $($parse)*} $($rest)*) + }; + + (arglist ($pop:ident $stack:tt) $bail:tt {($($buf:tt)*) $($parse:tt)*} > $($rest:tt)*) => { + $crate::__parse_ensure!($pop $stack $bail {($($buf)* >) $($parse)*} $($rest)*) + }; + + // high precedence binary operators + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} + $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* +) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} - $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* -) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} * $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* *) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} / $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* /) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} % $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* %) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} ^ $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* ^) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} & $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* &) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} | $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* |) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} << $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* <<) $($parse)*} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)*) $($parse:tt)*} >> $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* >>) $($parse)*} $($rest)*) + }; + + // comparison binary operators + + (atom () $bail:tt {($($buf:tt)*) $($parse:tt)*} == $($rest:tt)*) => { + $crate::__parse_ensure!(0 () $bail {() $($parse)* ($($buf)*) ==} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)+) $($parse:tt)*} == $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* ==) $($parse)*} $($rest)*) + }; + + (atom () $bail:tt {($($buf:tt)*) $($parse:tt)*} <= $($rest:tt)*) => { + $crate::__parse_ensure!(0 () $bail {() $($parse)* ($($buf)*) <=} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)+) $($parse:tt)*} <= $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* <=) $($parse)*} $($rest)*) + }; + + (atom () $bail:tt {($($buf:tt)*) $($parse:tt)*} < $($rest:tt)*) => { + $crate::__parse_ensure!(0 () $bail {() $($parse)* ($($buf)*) <} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)+) $($parse:tt)*} < $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* <) $($parse)*} $($rest)*) + }; + + (atom () $bail:tt {($($buf:tt)*) $($parse:tt)*} != $($rest:tt)*) => { + $crate::__parse_ensure!(0 () $bail {() $($parse)* ($($buf)*) !=} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)+) $($parse:tt)*} != $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* !=) $($parse)*} $($rest)*) + }; + + (atom () $bail:tt {($($buf:tt)*) $($parse:tt)*} >= $($rest:tt)*) => { + $crate::__parse_ensure!(0 () $bail {() $($parse)* ($($buf)*) >=} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)+) $($parse:tt)*} >= $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* >=) $($parse)*} $($rest)*) + }; + + (atom () $bail:tt {($($buf:tt)*) $($parse:tt)*} > $($rest:tt)*) => { + $crate::__parse_ensure!(0 () $bail {() $($parse)* ($($buf)*) >} $($rest)*) + }; + + (atom $stack:tt $bail:tt {($($buf:tt)+) $($parse:tt)*} > $($rest:tt)*) => { + $crate::__parse_ensure!(0 $stack $bail {($($buf)* >) $($parse)*} $($rest)*) + }; + + // low precedence binary operators + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} && $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* &&) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} || $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* ||) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} = $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* =) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} += $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* +=) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} -= $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* -=) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} *= $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* *=) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} /= $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* /=) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} %= $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* %=) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} ^= $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* ^=) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} &= $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* &=) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} |= $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* |=) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} <<= $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* <<=) $($parse)*} $($rest)*) + }; + + (atom ($($stack:tt)+) $bail:tt {($($buf:tt)*) $($parse:tt)*} >>= $($rest:tt)*) => { + $crate::__parse_ensure!(0 ($($stack)*) $bail {($($buf)* >>=) $($parse)*} $($rest)*) + }; + + // unrecognized expression + + ($state:tt $stack:tt $bail:tt $($rest:tt)*) => { + $crate::__fallback_ensure!($bail) + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __fancy_ensure { + ($lhs:expr, $op:tt, $rhs:expr) => { + match (&$lhs, &$rhs) { + (lhs, rhs) => { + if !(lhs $op rhs) { + #[allow(unused_imports)] + use $crate::private::{BothDebug, NotBothDebug}; + return Err((lhs, rhs).__dispatch_ensure(concat!("Condition failed: `", stringify!($lhs), " ", stringify!($op), " ", stringify!($rhs), "`"))); + } + } + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __fallback_ensure { + (($cond:expr $(,)?)) => { + if !$cond { + return $crate::private::Err($crate::Error::msg( + $crate::private::concat!("Condition failed: `", $crate::private::stringify!($cond), "`") + )); + } + }; + (($cond:expr, $msg:literal $(,)?)) => { + if !$cond { + return $crate::private::Err($crate::anyhow!($msg)); + } + }; + (($cond:expr, $err:expr $(,)?)) => { + if !$cond { + return $crate::private::Err($crate::anyhow!($err)); + } + }; + (($cond:expr, $fmt:expr, $($arg:tt)*)) => { + if !$cond { + return $crate::private::Err($crate::anyhow!($fmt, $($arg)*)); + } + }; +} diff --git a/src/lib.rs b/src/lib.rs index e1bd84c..3f5d34a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -237,6 +237,7 @@ extern crate alloc; mod backtrace; mod chain; mod context; +mod ensure; mod error; mod fmt; mod kind; @@ -608,6 +609,7 @@ pub mod private { use alloc::fmt; use core::fmt::Arguments; + pub use crate::ensure::{BothDebug, NotBothDebug}; pub use alloc::format; pub use core::result::Result::Err; pub use core::{concat, format_args, stringify}; diff --git a/src/macros.rs b/src/macros.rs index 450629d..643b2e5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -133,6 +133,7 @@ macro_rules! bail { /// # Ok(()) /// # } /// ``` +#[cfg(doc)] #[macro_export] macro_rules! ensure { ($cond:expr $(,)?) => { @@ -159,6 +160,14 @@ macro_rules! ensure { }; } +#[cfg(not(doc))] +#[macro_export] +macro_rules! ensure { + ($($tt:tt)*) => { + $crate::__parse_ensure!(0 () ($($tt)*) {()} $($tt)*) + }; +} + /// Construct an ad-hoc error from a string or existing non-`anyhow` error /// value. /// diff --git a/tests/test_macros.rs b/tests/test_macros.rs index a32e208..1ac3970 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -47,7 +47,7 @@ fn test_ensure() { }; assert_eq!( f().unwrap_err().to_string(), - "Condition failed: `v + v == 1`", + "Condition failed: `v + v == 1` (2 vs 1)", ); } diff --git a/tests/ui/empty-ensure.stderr b/tests/ui/empty-ensure.stderr index 9d47105..78875d6 100644 --- a/tests/ui/empty-ensure.stderr +++ b/tests/ui/empty-ensure.stderr @@ -1,5 +1,7 @@ -error: unexpected end of macro invocation +error: no rules expected the token `)` --> tests/ui/empty-ensure.rs:4:5 | 4 | ensure!(); - | ^^^^^^^^^ missing tokens in macro arguments + | ^^^^^^^^^ no rules expected this token in macro call + | + = note: this error originates in the macro `ensure` (in Nightly builds, run with -Z macro-backtrace for more info) From e6663605b8617f6fa34872788935f2f996abcb49 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 21 Nov 2021 18:57:18 -0800 Subject: [PATCH 2/5] Tests for ensure macro's parser --- tests/test_ensure.rs | 338 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 tests/test_ensure.rs diff --git a/tests/test_ensure.rs b/tests/test_ensure.rs new file mode 100644 index 0000000..84d3cef --- /dev/null +++ b/tests/test_ensure.rs @@ -0,0 +1,338 @@ +#![allow( + clippy::diverging_sub_expression, + clippy::let_and_return, + clippy::let_underscore_drop, + clippy::never_loop, + clippy::unit_arg +)] + +use anyhow::{anyhow, ensure, Chain, Error, Result}; +use std::fmt::Debug; +use std::iter; +use std::marker::PhantomData; +use std::ops::Add; + +struct S; + +impl Add for S { + type Output = bool; + fn add(self, rhs: T) -> Self::Output { + let _ = rhs; + false + } +} + +trait Trait: Sized { + fn t(self, i: i32) -> i32 { + i + } +} + +impl Trait for T {} + +#[track_caller] +fn assert_err(result: impl FnOnce() -> Result, expected: &'static str) { + assert_eq!(result().unwrap_err().to_string(), expected); +} + +#[test] +fn test_low_precedence_control_flow() { + #[allow(unreachable_code)] + let test = || { + let val = loop { + // Break has lower precedence than the comparison operators so the + // expression here is `S + (break (1 == 1))`. It would be bad if the + // ensure macro partitioned this input into `(S + break 1) == (1)` + // because that means a different thing than what was written. + ensure!(S + break 1 == 1); + }; + Ok(val) + }; + + assert!(test().unwrap()); +} + +#[test] +fn test_low_precedence_binary_operator() { + // Must not partition as `false == (true && false)`. + let test = || Ok(ensure!(false == true && false)); + assert_err(test, "Condition failed: `false == true && false`"); + + // But outside the root level, it is fine. + let test = || Ok(ensure!(while false == true && false {} < ())); + assert_err( + test, + "Condition failed: `while false == true && false { } < ()` (() vs ())", + ); +} + +#[test] +fn test_closure() { + // Must not partition as `(S + move) || (1 == 1)` by treating move as an + // identifier, nor as `(S + move || 1) == (1)` by misinterpreting the + // closure precedence. + let test = || Ok(ensure!(S + move || 1 == 1)); + assert_err(test, "Condition failed: `S + (move || 1 == 1)`"); + + let test = || Ok(ensure!(S + || 1 == 1)); + assert_err(test, "Condition failed: `S + (|| 1 == 1)`"); + + // Must not partition as `S + ((move | ()) | 1) == 1` by treating those + // pipes as bitwise-or. + let test = || Ok(ensure!(S + move |()| 1 == 1)); + assert_err(test, "Condition failed: `S + (move |()| 1 == 1)`"); + + let test = || Ok(ensure!(S + |()| 1 == 1)); + assert_err(test, "Condition failed: `S + (|()| 1 == 1)`"); +} + +#[test] +fn test_unary() { + let mut x = &1; + let test = || Ok(ensure!(*x == 2)); + assert_err(test, "Condition failed: `*x == 2` (1 vs 2)"); + + let test = || Ok(ensure!(!x == 1)); + assert_err(test, "Condition failed: `!x == 1` (-2 vs 1)"); + + let test = || Ok(ensure!(-x == 1)); + assert_err(test, "Condition failed: `-x == 1` (-1 vs 1)"); + + let test = || Ok(ensure!(&x == &&2)); + assert_err(test, "Condition failed: `&x == &&2` (1 vs 2)"); + + let test = || Ok(ensure!(&mut x == *&&mut &2)); + assert_err(test, "Condition failed: `&mut x == *&&mut &2` (1 vs 2)"); +} + +#[test] +fn test_if() { + #[rustfmt::skip] + let test = || Ok(ensure!(if false {}.t(1) == 2)); + assert_err(test, "Condition failed: `if false { }.t(1) == 2` (1 vs 2)"); + + #[rustfmt::skip] + let test = || Ok(ensure!(if false {} else {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `if false { } else { }.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(if false {} else if false {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `if false { } else if false { }.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(if let 1 = 2 {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `if let 1 = 2 { }.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(if let 1 | 2 = 2 {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `if let 1 | 2 = 2 { }.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(if let | 1 | 2 = 2 {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `if let 1 | 2 = 2 { }.t(1) == 2` (1 vs 2)", + ); +} + +#[test] +fn test_loop() { + #[rustfmt::skip] + let test = || Ok(ensure!(1 + loop { break 1 } == 1)); + assert_err( + test, + "Condition failed: `1 + loop { break 1 } == 1` (2 vs 1)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(1 + 'a: loop { break 'a 1 } == 1)); + assert_err( + test, + "Condition failed: `1 + 'a: loop { break 'a 1 } == 1` (2 vs 1)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(while false {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `while false { }.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(while let None = Some(1) {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `while let None = Some(1) { }.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(for _x in iter::once(0) {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `for _x in iter::once(0) { }.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(for | _x in iter::once(0) {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `for _x in iter::once(0) { }.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(for true | false in iter::empty() {}.t(1) == 2)); + assert_err( + test, + "Condition failed: `for true | false in iter::empty() { }.t(1) == 2` (1 vs 2)", + ); +} + +#[test] +fn test_match() { + #[rustfmt::skip] + let test = || Ok(ensure!(match 1 == 1 { true => 1, false => 0 } == 2)); + assert_err( + test, + "Condition failed: `match 1 == 1 { true => 1, false => 0, } == 2` (1 vs 2)", + ); +} + +#[test] +fn test_atom() { + let test = || Ok(ensure!([false, false].len() > 3)); + assert_err( + test, + "Condition failed: `[false, false].len() > 3` (2 vs 3)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!({ let x = 1; x } >= 3)); + assert_err(test, "Condition failed: `{ let x = 1; x } >= 3` (1 vs 3)"); + + let test = || Ok(ensure!(S + async { 1 } == true)); + assert_err( + test, + "Condition failed: `S + async { 1 } == true` (false vs true)", + ); + + let test = || Ok(ensure!(S + async move { 1 } == true)); + assert_err( + test, + "Condition failed: `S + async move { 1 } == true` (false vs true)", + ); + + let test = || Ok(ensure!(S + unsafe { 1 } == true)); + assert_err( + test, + "Condition failed: `S + unsafe { 1 } == true` (false vs true)", + ); +} + +#[test] +fn test_path() { + let test = || Ok(ensure!(crate::S.t(1) == 2)); + assert_err(test, "Condition failed: `crate::S.t(1) == 2` (1 vs 2)"); + + let test = || Ok(ensure!(::anyhow::Error::root_cause.t(1) == 2)); + assert_err( + test, + "Condition failed: `::anyhow::Error::root_cause.t(1) == 2` (1 vs 2)", + ); + + let test = || Ok(ensure!(Error::msg::<&str>.t(1) == 2)); + assert_err( + test, + "Condition failed: `Error::msg::<&str>.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(Error::msg::<&str,>.t(1) == 2)); + assert_err( + test, + "Condition failed: `Error::msg::<&str>.t(1) == 2` (1 vs 2)", + ); + + let test = || Ok(ensure!(Error::msg::<::Owned>.t(1) == 2)); + assert_err( + test, + "Condition failed: `Error::msg::<::Owned>.t(1) == 2` (1 vs 2)", + ); + + let test = || Ok(ensure!(Chain::<'static>::new.t(1) == 2)); + assert_err( + test, + "Condition failed: `Chain::<'static>::new.t(1) == 2` (1 vs 2)", + ); + + #[rustfmt::skip] + let test = || Ok(ensure!(Chain::<'static,>::new.t(1) == 2)); + assert_err( + test, + "Condition failed: `Chain::<'static>::new.t(1) == 2` (1 vs 2)", + ); +} + +#[test] +fn test_macro() { + let test = || Ok(ensure!(anyhow!("...").to_string().len() <= 1)); + assert_err( + test, + "Condition failed: `anyhow!(\"...\").to_string().len() <= 1` (3 vs 1)", + ); + + let test = || Ok(ensure!(vec![1].len() < 1)); + assert_err(test, "Condition failed: `vec![1].len() < 1` (1 vs 1)"); + + let test = || Ok(ensure!(stringify! {} != "")); + assert_err( + test, + "Condition failed: `stringify! { } != \"\"` (\"\" vs \"\")", + ); +} + +#[test] +fn test_trailer() { + let test = || Ok(ensure!((|| 1)() == 2)); + assert_err(test, "Condition failed: `(|| 1)() == 2` (1 vs 2)"); + + let test = || Ok(ensure!(b"hmm"[1] == b'c')); + assert_err(test, "Condition failed: `b\"hmm\"[1] == b'c'` (109 vs 99)"); + + let test = || Ok(ensure!(PhantomData:: {} != PhantomData)); + assert_err( + test, + "Condition failed: `PhantomData::{} != PhantomData` (PhantomData vs PhantomData)", + ); + + let result = Ok::<_, Error>(1); + let test = || Ok(ensure!(result? == 2)); + assert_err(test, "Condition failed: `result? == 2` (1 vs 2)"); + + let test = || Ok(ensure!((2, 3).1 == 2)); + assert_err(test, "Condition failed: `(2, 3).1 == 2` (3 vs 2)"); + + let err = anyhow!(""); + let test = || Ok(ensure!(err.is::<&str>() == false)); + assert_err( + test, + "Condition failed: `err.is::<&str>() == false` (true vs false)", + ); + + let test = || Ok(ensure!(err.is::<::Owned>() == true)); + assert_err( + test, + "Condition failed: `err.is::<::Owned>() == true` (false vs true)", + ); +} From 74e269313d9d394347676ccb4d22c8757ea56d1d Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 21 Nov 2021 22:06:08 -0800 Subject: [PATCH 3/5] Fix ensure ui test with empty args --- src/ensure.rs | 32 ++++++++++++++++---------------- tests/ui/empty-ensure.stderr | 6 +++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/ensure.rs b/src/ensure.rs index fe6c486..4e868ff 100644 --- a/src/ensure.rs +++ b/src/ensure.rs @@ -36,24 +36,24 @@ macro_rules! __parse_ensure { // low precedence control flow constructs - (0 $stack:tt $bail:tt $parse:tt return $($rest:tt)*) => { - $crate::__fallback_ensure!($bail) + (0 $stack:tt ($($bail:tt)*) $parse:tt return $($rest:tt)*) => { + $crate::__fallback_ensure!($($bail)*) }; - (0 $stack:tt $bail:tt $parse:tt break $($rest:tt)*) => { - $crate::__fallback_ensure!($bail) + (0 $stack:tt ($($bail:tt)*) $parse:tt break $($rest:tt)*) => { + $crate::__fallback_ensure!($($bail)*) }; - (0 $stack:tt $bail:tt $parse:tt continue $($rest:tt)*) => { - $crate::__fallback_ensure!($bail) + (0 $stack:tt ($($bail:tt)*) $parse:tt continue $($rest:tt)*) => { + $crate::__fallback_ensure!($($bail)*) }; - (0 $stack:tt $bail:tt $parse:tt yield $($rest:tt)*) => { - $crate::__fallback_ensure!($bail) + (0 $stack:tt ($($bail:tt)*) $parse:tt yield $($rest:tt)*) => { + $crate::__fallback_ensure!($($bail)*) }; - (0 $stack:tt $bail:tt $parse:tt move $($rest:tt)*) => { - $crate::__fallback_ensure!($bail) + (0 $stack:tt ($($bail:tt)*) $parse:tt move $($rest:tt)*) => { + $crate::__fallback_ensure!($($bail)*) }; // unary operators @@ -422,8 +422,8 @@ macro_rules! __parse_ensure { // unrecognized expression - ($state:tt $stack:tt $bail:tt $($rest:tt)*) => { - $crate::__fallback_ensure!($bail) + ($state:tt $stack:tt ($($bail:tt)*) $($rest:tt)*) => { + $crate::__fallback_ensure!($($bail)*) }; } @@ -446,24 +446,24 @@ macro_rules! __fancy_ensure { #[doc(hidden)] #[macro_export] macro_rules! __fallback_ensure { - (($cond:expr $(,)?)) => { + ($cond:expr $(,)?) => { if !$cond { return $crate::private::Err($crate::Error::msg( $crate::private::concat!("Condition failed: `", $crate::private::stringify!($cond), "`") )); } }; - (($cond:expr, $msg:literal $(,)?)) => { + ($cond:expr, $msg:literal $(,)?) => { if !$cond { return $crate::private::Err($crate::anyhow!($msg)); } }; - (($cond:expr, $err:expr $(,)?)) => { + ($cond:expr, $err:expr $(,)?) => { if !$cond { return $crate::private::Err($crate::anyhow!($err)); } }; - (($cond:expr, $fmt:expr, $($arg:tt)*)) => { + ($cond:expr, $fmt:expr, $($arg:tt)*) => { if !$cond { return $crate::private::Err($crate::anyhow!($fmt, $($arg)*)); } diff --git a/tests/ui/empty-ensure.stderr b/tests/ui/empty-ensure.stderr index 78875d6..b500de9 100644 --- a/tests/ui/empty-ensure.stderr +++ b/tests/ui/empty-ensure.stderr @@ -1,7 +1,7 @@ -error: no rules expected the token `)` +error: unexpected end of macro invocation --> tests/ui/empty-ensure.rs:4:5 | 4 | ensure!(); - | ^^^^^^^^^ no rules expected this token in macro call + | ^^^^^^^^^ missing tokens in macro arguments | - = note: this error originates in the macro `ensure` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::__parse_ensure` (in Nightly builds, run with -Z macro-backtrace for more info) From 74c153d42815cdadda53e524d86aa0a4619ba295 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 21 Nov 2021 22:12:20 -0800 Subject: [PATCH 4/5] Raise minimum tested version to rustc 1.53 On 1.52: error[E0658]: or-patterns syntax is experimental --> tests/test_ensure.rs:187:22 | 187 | let test = || Ok(ensure!(for true | false in iter::empty() {}.t(1) == 2)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 361d084..2faebf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - rust: [nightly, beta, stable, 1.52.0] + rust: [nightly, beta, stable, 1.53.0] steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@master @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - rust: [1.51.0, 1.50.0, 1.42.0, 1.38.0] + rust: [1.52.0, 1.51.0, 1.50.0, 1.42.0, 1.38.0] steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@master From 92824e29cd6e551658f7add91bf42f7b47bfbe6c Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 21 Nov 2021 22:20:19 -0800 Subject: [PATCH 5/5] Make ensure tests work on old rustc --- tests/test_ensure.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/test_ensure.rs b/tests/test_ensure.rs index 84d3cef..18c81b8 100644 --- a/tests/test_ensure.rs +++ b/tests/test_ensure.rs @@ -32,7 +32,15 @@ impl Trait for T {} #[track_caller] fn assert_err(result: impl FnOnce() -> Result, expected: &'static str) { - assert_eq!(result().unwrap_err().to_string(), expected); + let actual = result().unwrap_err().to_string(); + + let mut accepted_alternatives = expected.split('\n'); + let expected = accepted_alternatives.next_back().unwrap(); + if accepted_alternatives.any(|alternative| actual == alternative) { + return; + } + + assert_eq!(actual, expected); } #[test] @@ -153,14 +161,18 @@ fn test_loop() { let test = || Ok(ensure!(1 + loop { break 1 } == 1)); assert_err( test, - "Condition failed: `1 + loop { break 1 } == 1` (2 vs 1)", + // 1.54 puts a double space after loop + "Condition failed: `1 + loop { break 1 } == 1` (2 vs 1)\n\ + Condition failed: `1 + loop { break 1 } == 1` (2 vs 1)", ); #[rustfmt::skip] let test = || Ok(ensure!(1 + 'a: loop { break 'a 1 } == 1)); assert_err( test, - "Condition failed: `1 + 'a: loop { break 'a 1 } == 1` (2 vs 1)", + // 1.54 puts a double space after loop + "Condition failed: `1 + 'a: loop { break 'a 1 } == 1` (2 vs 1)\n\ + Condition failed: `1 + 'a: loop { break 'a 1 } == 1` (2 vs 1)", ); #[rustfmt::skip]