1
Fork 0

Rollup merge of #82770 - m-ou-se:assert-match, r=joshtriplett

Add assert_matches macro.

This adds `assert_matches!(expression, pattern)`.

Unlike the other asserts, this one ~~consumes the expression~~ may consume the expression, to be able to match the pattern. (It could add a `&` implicitly, but that's noticable in the pattern, and will make a consuming guard impossible.)

See https://github.com/rust-lang/rust/issues/62633#issuecomment-790737853

This re-uses the same `left: .. right: ..` output as the `assert_eq` and `assert_ne` macros, but with the pattern as the right part:

assert_eq:
```
assertion failed: `(left == right)`
  left: `Some("asdf")`,
 right: `None`
```
assert_matches:
```
assertion failed: `(left matches right)`
  left: `Ok("asdf")`,
 right: `Err(_)`
```

cc ```@cuviper```
This commit is contained in:
Mara 2021-03-05 10:57:23 +01:00 committed by GitHub
commit 04045cc83f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 27 deletions

View file

@ -110,6 +110,60 @@ macro_rules! assert_ne {
}); });
} }
/// 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.
///
/// Like [`assert!`], this macro has a second form, where a custom
/// panic message can be provided.
///
/// # 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!(c, Ok(x) | Err(x) if x.len() < 100);
/// ```
#[macro_export]
#[unstable(feature = "assert_matches", issue = "82775")]
#[allow_internal_unstable(core_panic)]
macro_rules! assert_matches {
($left:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => ({
match $left {
$( $pattern )|+ $( if $guard )? => {}
ref left_val => {
$crate::panicking::assert_matches_failed(
left_val,
$crate::stringify!($($pattern)|+ $(if $guard)?),
$crate::option::Option::None
);
}
}
});
($left:expr, $( $pattern:pat )|+ $( if $guard: expr )?, $($arg:tt)+) => ({
match $left {
$( $pattern )|+ $( if $guard )? => {}
ref left_val => {
$crate::panicking::assert_matches_failed(
left_val,
$crate::stringify!($($pattern)|+ $(if $guard)?),
$crate::option::Option::Some($crate::format_args!($($arg)+))
);
}
}
});
}
/// Asserts that a boolean expression is `true` at runtime. /// Asserts that a boolean expression is `true` at runtime.
/// ///
/// This will invoke the [`panic!`] macro if the provided expression cannot be /// This will invoke the [`panic!`] macro if the provided expression cannot be
@ -208,6 +262,42 @@ macro_rules! debug_assert_ne {
($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_ne!($($arg)*); }) ($($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
///
/// ```
/// #![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!(c, Ok(x) | Err(x) if x.len() < 100);
/// ```
#[macro_export]
#[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)*); })
}
/// Returns whether the given expression matches any of the given patterns. /// Returns whether the given expression matches any of the given patterns.
/// ///
/// Like in a `match` expression, the pattern can be optionally followed by `if` /// Like in a `match` expression, the pattern can be optionally followed by `if`

View file

@ -97,6 +97,7 @@ pub fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
pub enum AssertKind { pub enum AssertKind {
Eq, Eq,
Ne, Ne,
Match,
} }
/// Internal function for `assert_eq!` and `assert_ne!` macros /// Internal function for `assert_eq!` and `assert_ne!` macros
@ -113,32 +114,54 @@ where
T: fmt::Debug + ?Sized, T: fmt::Debug + ?Sized,
U: fmt::Debug + ?Sized, U: fmt::Debug + ?Sized,
{ {
#[track_caller] assert_failed_inner(kind, &left, &right, args)
fn inner( }
kind: AssertKind,
left: &dyn fmt::Debug,
right: &dyn fmt::Debug,
args: Option<fmt::Arguments<'_>>,
) -> ! {
let op = match kind {
AssertKind::Eq => "==",
AssertKind::Ne => "!=",
};
match args { /// Internal function for `assert_match!`
Some(args) => panic!( #[cold]
r#"assertion failed: `(left {} right)` #[track_caller]
left: `{:?}`, #[doc(hidden)]
right: `{:?}: {}`"#, pub fn assert_matches_failed<T: fmt::Debug + ?Sized>(
op, left, right, args left: &T,
), right: &str,
None => panic!( args: Option<fmt::Arguments<'_>>,
r#"assertion failed: `(left {} right)` ) -> ! {
left: `{:?}`, // Use the Display implementation to display the pattern.
right: `{:?}`"#, struct Pattern<'a>(&'a str);
op, left, right, impl fmt::Debug for Pattern<'_> {
), fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.0, f)
} }
} }
inner(kind, &left, &right, args) 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<fmt::Arguments<'_>>,
) -> ! {
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)`
left: `{:?}`,
right: `{:?}`"#,
op, left, right,
),
}
} }

View file

@ -228,6 +228,7 @@
#![feature(arbitrary_self_types)] #![feature(arbitrary_self_types)]
#![feature(array_error_internals)] #![feature(array_error_internals)]
#![feature(asm)] #![feature(asm)]
#![feature(assert_matches)]
#![feature(associated_type_bounds)] #![feature(associated_type_bounds)]
#![feature(atomic_mut_ptr)] #![feature(atomic_mut_ptr)]
#![feature(box_syntax)] #![feature(box_syntax)]
@ -552,8 +553,8 @@ pub use std_detect::detect;
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
#[allow(deprecated, deprecated_in_future)] #[allow(deprecated, deprecated_in_future)]
pub use core::{ pub use core::{
assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, matches, r#try, todo, assert_eq, assert_matches, assert_ne, debug_assert, debug_assert_eq, debug_assert_matches,
unimplemented, unreachable, write, writeln, debug_assert_ne, matches, r#try, todo, unimplemented, unreachable, write, writeln,
}; };
// Re-export built-in macros defined through libcore. // Re-export built-in macros defined through libcore.