Skip to content

Commit a910b04

Browse files
authored
Rollup merge of rust-lang#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 rust-lang#62633 (comment) 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`
2 parents 22de90c + 80fcdef commit a910b04

File tree

3 files changed

+139
-25
lines changed

3 files changed

+139
-25
lines changed

library/core/src/macros/mod.rs

+90
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,60 @@ macro_rules! assert_ne {
110110
});
111111
}
112112

113+
/// Asserts that an expression matches any of the given patterns.
114+
///
115+
/// Like in a `match` expression, the pattern can be optionally followed by `if`
116+
/// and a guard expression that has access to names bound by the pattern.
117+
///
118+
/// On panic, this macro will print the value of the expression with its
119+
/// debug representation.
120+
///
121+
/// Like [`assert!`], this macro has a second form, where a custom
122+
/// panic message can be provided.
123+
///
124+
/// # Examples
125+
///
126+
/// ```
127+
/// #![feature(assert_matches)]
128+
///
129+
/// let a = 1u32.checked_add(2);
130+
/// let b = 1u32.checked_sub(2);
131+
/// assert_matches!(a, Some(_));
132+
/// assert_matches!(b, None);
133+
///
134+
/// let c = Ok("abc".to_string());
135+
/// assert_matches!(c, Ok(x) | Err(x) if x.len() < 100);
136+
/// ```
137+
#[macro_export]
138+
#[unstable(feature = "assert_matches", issue = "82775")]
139+
#[allow_internal_unstable(core_panic)]
140+
macro_rules! assert_matches {
141+
($left:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => ({
142+
match $left {
143+
$( $pattern )|+ $( if $guard )? => {}
144+
ref left_val => {
145+
$crate::panicking::assert_matches_failed(
146+
left_val,
147+
$crate::stringify!($($pattern)|+ $(if $guard)?),
148+
$crate::option::Option::None
149+
);
150+
}
151+
}
152+
});
153+
($left:expr, $( $pattern:pat )|+ $( if $guard: expr )?, $($arg:tt)+) => ({
154+
match $left {
155+
$( $pattern )|+ $( if $guard )? => {}
156+
ref left_val => {
157+
$crate::panicking::assert_matches_failed(
158+
left_val,
159+
$crate::stringify!($($pattern)|+ $(if $guard)?),
160+
$crate::option::Option::Some($crate::format_args!($($arg)+))
161+
);
162+
}
163+
}
164+
});
165+
}
166+
113167
/// Asserts that a boolean expression is `true` at runtime.
114168
///
115169
/// This will invoke the [`panic!`] macro if the provided expression cannot be
@@ -208,6 +262,42 @@ macro_rules! debug_assert_ne {
208262
($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_ne!($($arg)*); })
209263
}
210264

265+
/// Asserts that an expression matches any of the given patterns.
266+
///
267+
/// Like in a `match` expression, the pattern can be optionally followed by `if`
268+
/// and a guard expression that has access to names bound by the pattern.
269+
///
270+
/// On panic, this macro will print the value of the expression with its
271+
/// debug representation.
272+
///
273+
/// Unlike [`assert_matches!`], `debug_assert_matches!` statements are only
274+
/// enabled in non optimized builds by default. An optimized build will not
275+
/// execute `debug_assert_matches!` statements unless `-C debug-assertions` is
276+
/// passed to the compiler. This makes `debug_assert_matches!` useful for
277+
/// checks that are too expensive to be present in a release build but may be
278+
/// helpful during development. The result of expanding `debug_assert_matches!`
279+
/// is always type checked.
280+
///
281+
/// # Examples
282+
///
283+
/// ```
284+
/// #![feature(assert_matches)]
285+
///
286+
/// let a = 1u32.checked_add(2);
287+
/// let b = 1u32.checked_sub(2);
288+
/// debug_assert_matches!(a, Some(_));
289+
/// debug_assert_matches!(b, None);
290+
///
291+
/// let c = Ok("abc".to_string());
292+
/// debug_assert_matches!(c, Ok(x) | Err(x) if x.len() < 100);
293+
/// ```
294+
#[macro_export]
295+
#[unstable(feature = "assert_matches", issue = "82775")]
296+
#[allow_internal_unstable(assert_matches)]
297+
macro_rules! debug_assert_matches {
298+
($($arg:tt)*) => (if $crate::cfg!(debug_assertions) { $crate::assert_matches!($($arg)*); })
299+
}
300+
211301
/// Returns whether the given expression matches any of the given patterns.
212302
///
213303
/// Like in a `match` expression, the pattern can be optionally followed by `if`

library/core/src/panicking.rs

+46-23
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
9797
pub enum AssertKind {
9898
Eq,
9999
Ne,
100+
Match,
100101
}
101102

102103
/// Internal function for `assert_eq!` and `assert_ne!` macros
@@ -113,32 +114,54 @@ where
113114
T: fmt::Debug + ?Sized,
114115
U: fmt::Debug + ?Sized,
115116
{
116-
#[track_caller]
117-
fn inner(
118-
kind: AssertKind,
119-
left: &dyn fmt::Debug,
120-
right: &dyn fmt::Debug,
121-
args: Option<fmt::Arguments<'_>>,
122-
) -> ! {
123-
let op = match kind {
124-
AssertKind::Eq => "==",
125-
AssertKind::Ne => "!=",
126-
};
127-
128-
match args {
129-
Some(args) => panic!(
130-
r#"assertion failed: `(left {} right)`
117+
assert_failed_inner(kind, &left, &right, args)
118+
}
119+
120+
/// Internal function for `assert_match!`
121+
#[cold]
122+
#[track_caller]
123+
#[doc(hidden)]
124+
pub fn assert_matches_failed<T: fmt::Debug + ?Sized>(
125+
left: &T,
126+
right: &str,
127+
args: Option<fmt::Arguments<'_>>,
128+
) -> ! {
129+
// Use the Display implementation to display the pattern.
130+
struct Pattern<'a>(&'a str);
131+
impl fmt::Debug for Pattern<'_> {
132+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133+
fmt::Display::fmt(self.0, f)
134+
}
135+
}
136+
assert_failed_inner(AssertKind::Match, &left, &Pattern(right), args);
137+
}
138+
139+
/// Non-generic version of the above functions, to avoid code bloat.
140+
#[track_caller]
141+
fn assert_failed_inner(
142+
kind: AssertKind,
143+
left: &dyn fmt::Debug,
144+
right: &dyn fmt::Debug,
145+
args: Option<fmt::Arguments<'_>>,
146+
) -> ! {
147+
let op = match kind {
148+
AssertKind::Eq => "==",
149+
AssertKind::Ne => "!=",
150+
AssertKind::Match => "matches",
151+
};
152+
153+
match args {
154+
Some(args) => panic!(
155+
r#"assertion failed: `(left {} right)`
131156
left: `{:?}`,
132157
right: `{:?}: {}`"#,
133-
op, left, right, args
134-
),
135-
None => panic!(
136-
r#"assertion failed: `(left {} right)`
158+
op, left, right, args
159+
),
160+
None => panic!(
161+
r#"assertion failed: `(left {} right)`
137162
left: `{:?}`,
138163
right: `{:?}`"#,
139-
op, left, right,
140-
),
141-
}
164+
op, left, right,
165+
),
142166
}
143-
inner(kind, &left, &right, args)
144167
}

library/std/src/lib.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@
228228
#![feature(arbitrary_self_types)]
229229
#![feature(array_error_internals)]
230230
#![feature(asm)]
231+
#![feature(assert_matches)]
231232
#![feature(associated_type_bounds)]
232233
#![feature(atomic_mut_ptr)]
233234
#![feature(box_syntax)]
@@ -551,8 +552,8 @@ pub use std_detect::detect;
551552
#[stable(feature = "rust1", since = "1.0.0")]
552553
#[allow(deprecated, deprecated_in_future)]
553554
pub use core::{
554-
assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, matches, r#try, todo,
555-
unimplemented, unreachable, write, writeln,
555+
assert_eq, assert_matches, assert_ne, debug_assert, debug_assert_eq, debug_assert_matches,
556+
debug_assert_ne, matches, r#try, todo, unimplemented, unreachable, write, writeln,
556557
};
557558

558559
// Re-export built-in macros defined through libcore.

0 commit comments

Comments
 (0)