From eb18746bc6c6c5c710ad674873438cbad5894f06 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Mar 2021 17:55:23 +0100 Subject: [PATCH 1/6] 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. From cfce60ea3760acf8537d882fbae4fd1086e2b332 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Mar 2021 18:07:26 +0100 Subject: [PATCH 2/6] Allow for multiple patterns and a guard in assert_matches. --- library/core/src/macros/mod.rs | 46 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 3e31a9e8910e0..9bde2207fe194 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -110,7 +110,10 @@ macro_rules! assert_ne { }); } -/// Asserts that an expression matches a pattern. +/// Asserts that an expression matches any of the given patterns. +/// +/// Like in a `match` expression, the pattern can be optionally followed by `if` +/// and a guard expression that has access to names bound by the pattern. /// /// On panic, this macro will print the value of the expression with its /// debug representation. @@ -125,38 +128,35 @@ macro_rules! assert_ne { /// let b = 1u32.checked_sub(2); /// assert_matches!(a, Some(_)); /// assert_matches!(b, None); +/// +/// let c = Ok("abc".to_string()); +/// assert_matches!(a, Ok(x) | Err(x) if x.len() < 100); /// ``` #[macro_export] #[unstable(feature = "assert_matches", issue = "none")] #[allow_internal_unstable(core_panic)] macro_rules! assert_matches { - ($left:expr, $right:pat $(,)?) => ({ - match &$left { + ($left:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => ({ + match $left { + $( $pattern )|+ $( if $guard )? => {} left_val => { - if let $right = left_val { - // OK - } else { - $crate::panicking::assert_matches_failed( - &*left_val, - $crate::stringify!($right), - $crate::option::Option::None - ); - } + $crate::panicking::assert_matches_failed( + &left_val, + $crate::stringify!($($pattern)|+ $(if $guard)?), + $crate::option::Option::None + ); } } }); - ($left:expr, $right:expr, $($arg:tt)+) => ({ - match &$left { + ($left:expr, $( $pattern:pat )|+ $( if $guard: expr )?, $($arg:tt)+) => ({ + match $left { + $( $pattern )|+ $( if $guard )? => {} 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)+)) - ); - } + $crate::panicking::assert_matches_failed( + &left_val, + $crate::stringify!($($pattern)|+ $(if $guard)?), + $crate::option::Option::Some($crate::format_args!($($arg)+)) + ); } } }); From 0a8e401188062f0c60c989978352663b1e25e70e Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Mar 2021 18:12:33 +0100 Subject: [PATCH 3/6] Add debug_assert_matches macro. --- library/core/src/macros/mod.rs | 34 ++++++++++++++++++++++++++++++++++ library/std/src/lib.rs | 4 ++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 9bde2207fe194..1d41d7f64bbfb 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -260,6 +260,40 @@ macro_rules! debug_assert_ne { ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_ne!($($arg)*); }) } +/// Asserts that an expression matches any of the given patterns. +/// +/// Like in a `match` expression, the pattern can be optionally followed by `if` +/// and a guard expression that has access to names bound by the pattern. +/// +/// On panic, this macro will print the value of the expression with its +/// debug representation. +/// +/// Unlike [`assert_matches!`], `debug_assert_matches!` statements are only +/// enabled in non optimized builds by default. An optimized build will not +/// execute `debug_assert_matches!` statements unless `-C debug-assertions` is +/// passed to the compiler. This makes `debug_assert_matches!` useful for +/// checks that are too expensive to be present in a release build but may be +/// helpful during development. The result of expanding `debug_assert_matches!` +/// is always type checked. +/// +/// # Examples +/// +/// ``` +/// let a = 1u32.checked_add(2); +/// let b = 1u32.checked_sub(2); +/// debug_assert_matches!(a, Some(_)); +/// debug_assert_matches!(b, None); +/// +/// let c = Ok("abc".to_string()); +/// debug_assert_matches!(a, Ok(x) | Err(x) if x.len() < 100); +/// ``` +#[macro_export] +#[unstable(feature = "assert_matches", issue = "none")] +#[allow_internal_unstable(assert_matches)] +macro_rules! debug_assert_matches { + ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_matches!($($arg)*); }) +} + /// Returns whether the given expression matches any of the given patterns. /// /// Like in a `match` expression, the pattern can be optionally followed by `if` diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index ddceb492765da..467f69d45b5d1 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -551,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_matches, 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_matches, + debug_assert_ne, matches, r#try, todo, unimplemented, unreachable, write, writeln, }; // Re-export built-in macros defined through libcore. From 5bd1204fc2a3147d425bc98fc0fefb00c6b4702d Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Mar 2021 18:41:43 +0100 Subject: [PATCH 4/6] Fix assert_matches doc examples. --- library/core/src/macros/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 1d41d7f64bbfb..b0b35c6915f99 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -124,13 +124,15 @@ macro_rules! assert_ne { /// # Examples /// /// ``` +/// #![feature(assert_matches)] +/// /// let a = 1u32.checked_add(2); /// let b = 1u32.checked_sub(2); /// assert_matches!(a, Some(_)); /// assert_matches!(b, None); /// /// let c = Ok("abc".to_string()); -/// assert_matches!(a, Ok(x) | Err(x) if x.len() < 100); +/// assert_matches!(c, Ok(x) | Err(x) if x.len() < 100); /// ``` #[macro_export] #[unstable(feature = "assert_matches", issue = "none")] @@ -279,13 +281,15 @@ macro_rules! debug_assert_ne { /// # Examples /// /// ``` +/// #![feature(assert_matches)] +/// /// let a = 1u32.checked_add(2); /// let b = 1u32.checked_sub(2); /// debug_assert_matches!(a, Some(_)); /// debug_assert_matches!(b, None); /// /// let c = Ok("abc".to_string()); -/// debug_assert_matches!(a, Ok(x) | Err(x) if x.len() < 100); +/// debug_assert_matches!(c, Ok(x) | Err(x) if x.len() < 100); /// ``` #[macro_export] #[unstable(feature = "assert_matches", issue = "none")] From f223affd7ae383816a038700d591d78bebd98d46 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Mar 2021 19:36:36 +0100 Subject: [PATCH 5/6] Don't consume the expression in assert_matches!()'s failure case. --- library/core/src/macros/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index b0b35c6915f99..fb43433a79465 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -141,9 +141,9 @@ macro_rules! assert_matches { ($left:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => ({ match $left { $( $pattern )|+ $( if $guard )? => {} - left_val => { + ref left_val => { $crate::panicking::assert_matches_failed( - &left_val, + left_val, $crate::stringify!($($pattern)|+ $(if $guard)?), $crate::option::Option::None ); @@ -153,9 +153,9 @@ macro_rules! assert_matches { ($left:expr, $( $pattern:pat )|+ $( if $guard: expr )?, $($arg:tt)+) => ({ match $left { $( $pattern )|+ $( if $guard )? => {} - left_val => { + ref left_val => { $crate::panicking::assert_matches_failed( - &left_val, + left_val, $crate::stringify!($($pattern)|+ $(if $guard)?), $crate::option::Option::Some($crate::format_args!($($arg)+)) ); From 80fcdef3b53c43f3d7bace1db3e3ef9ffebd757e Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 4 Mar 2021 21:33:31 +0100 Subject: [PATCH 6/6] Add tracking issue for assert_matches. --- library/core/src/macros/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index fb43433a79465..3e70ba81d4997 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -135,7 +135,7 @@ macro_rules! assert_ne { /// assert_matches!(c, Ok(x) | Err(x) if x.len() < 100); /// ``` #[macro_export] -#[unstable(feature = "assert_matches", issue = "none")] +#[unstable(feature = "assert_matches", issue = "82775")] #[allow_internal_unstable(core_panic)] macro_rules! assert_matches { ($left:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => ({ @@ -292,7 +292,7 @@ macro_rules! debug_assert_ne { /// debug_assert_matches!(c, Ok(x) | Err(x) if x.len() < 100); /// ``` #[macro_export] -#[unstable(feature = "assert_matches", issue = "none")] +#[unstable(feature = "assert_matches", issue = "82775")] #[allow_internal_unstable(assert_matches)] macro_rules! debug_assert_matches { ($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_matches!($($arg)*); })