From eb18746bc6c6c5c710ad674873438cbad5894f06 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Mar 2021 17:55:23 +0100 Subject: [PATCH] Add assert_matches!(expr, pat). --- library/core/src/macros/mod.rs | 52 +++++++++++++++++++++++++ library/core/src/panicking.rs | 69 ++++++++++++++++++++++------------ library/std/src/lib.rs | 5 ++- 3 files changed, 101 insertions(+), 25 deletions(-) diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 9a54921f07b49..3e31a9e8910e0 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -110,6 +110,58 @@ macro_rules! assert_ne { }); } +/// Asserts that an expression matches a pattern. +/// +/// On panic, this macro will print the value of the expression with its +/// debug representation. +/// +/// Like [`assert!`], this macro has a second form, where a custom +/// panic message can be provided. +/// +/// # Examples +/// +/// ``` +/// let a = 1u32.checked_add(2); +/// let b = 1u32.checked_sub(2); +/// assert_matches!(a, Some(_)); +/// assert_matches!(b, None); +/// ``` +#[macro_export] +#[unstable(feature = "assert_matches", issue = "none")] +#[allow_internal_unstable(core_panic)] +macro_rules! assert_matches { + ($left:expr, $right:pat $(,)?) => ({ + match &$left { + left_val => { + if let $right = left_val { + // OK + } else { + $crate::panicking::assert_matches_failed( + &*left_val, + $crate::stringify!($right), + $crate::option::Option::None + ); + } + } + } + }); + ($left:expr, $right:expr, $($arg:tt)+) => ({ + match &$left { + left_val => { + if let $right = left_val { + // OK + } else { + $crate::panicking::assert_matches_failed( + &*left_val, + $crate::stringify!($right), + $crate::option::Option::Some($crate::format_args!($($arg)+)) + ); + } + } + } + }); +} + /// Asserts that a boolean expression is `true` at runtime. /// /// This will invoke the [`panic!`] macro if the provided expression cannot be diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index af8a6101392a4..12acf5b4329db 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -97,6 +97,7 @@ pub fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! { pub enum AssertKind { Eq, Ne, + Match, } /// Internal function for `assert_eq!` and `assert_ne!` macros @@ -113,32 +114,54 @@ where T: fmt::Debug + ?Sized, U: fmt::Debug + ?Sized, { - #[track_caller] - fn inner( - kind: AssertKind, - left: &dyn fmt::Debug, - right: &dyn fmt::Debug, - args: Option>, - ) -> ! { - let op = match kind { - AssertKind::Eq => "==", - AssertKind::Ne => "!=", - }; - - match args { - Some(args) => panic!( - r#"assertion failed: `(left {} right)` + assert_failed_inner(kind, &left, &right, args) +} + +/// Internal function for `assert_match!` +#[cold] +#[track_caller] +#[doc(hidden)] +pub fn assert_matches_failed( + left: &T, + right: &str, + args: Option>, +) -> ! { + // Use the Display implementation to display the pattern. + struct Pattern<'a>(&'a str); + impl fmt::Debug for Pattern<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.0, f) + } + } + assert_failed_inner(AssertKind::Match, &left, &Pattern(right), args); +} + +/// Non-generic version of the above functions, to avoid code bloat. +#[track_caller] +fn assert_failed_inner( + kind: AssertKind, + left: &dyn fmt::Debug, + right: &dyn fmt::Debug, + args: Option>, +) -> ! { + let op = match kind { + AssertKind::Eq => "==", + AssertKind::Ne => "!=", + AssertKind::Match => "matches", + }; + + match args { + Some(args) => panic!( + r#"assertion failed: `(left {} right)` left: `{:?}`, right: `{:?}: {}`"#, - op, left, right, args - ), - None => panic!( - r#"assertion failed: `(left {} right)` + op, left, right, args + ), + None => panic!( + r#"assertion failed: `(left {} right)` left: `{:?}`, right: `{:?}`"#, - op, left, right, - ), - } + op, left, right, + ), } - inner(kind, &left, &right, args) } diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index ba49dee38e642..ddceb492765da 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -228,6 +228,7 @@ #![feature(arbitrary_self_types)] #![feature(array_error_internals)] #![feature(asm)] +#![feature(assert_matches)] #![feature(associated_type_bounds)] #![feature(atomic_mut_ptr)] #![feature(box_syntax)] @@ -550,8 +551,8 @@ pub use std_detect::detect; #[stable(feature = "rust1", since = "1.0.0")] #[allow(deprecated, deprecated_in_future)] pub use core::{ - assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, matches, r#try, todo, - unimplemented, unreachable, write, writeln, + assert_eq, assert_matches, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, matches, + r#try, todo, unimplemented, unreachable, write, writeln, }; // Re-export built-in macros defined through libcore.