about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMara <m-ou.se@m-ou.se>2021-03-05 10:57:23 +0100
committerGitHub <noreply@github.com>2021-03-05 10:57:23 +0100
commit04045cc83f59e08be5d12d7ca4ac4d6364a02ff1 (patch)
treea344474d23628d27137c40905cb00c62d5108e4e
parent232caad39581118619c5168284b15457d1c72900 (diff)
parent80fcdef3b53c43f3d7bace1db3e3ef9ffebd757e (diff)
downloadrust-04045cc83f59e08be5d12d7ca4ac4d6364a02ff1.tar.gz
rust-04045cc83f59e08be5d12d7ca4ac4d6364a02ff1.zip
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```
-rw-r--r--library/core/src/macros/mod.rs90
-rw-r--r--library/core/src/panicking.rs69
-rw-r--r--library/std/src/lib.rs5
3 files changed, 139 insertions, 25 deletions
diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs
index 9a54921f07b..3e70ba81d49 100644
--- a/library/core/src/macros/mod.rs
+++ b/library/core/src/macros/mod.rs
@@ -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.
 ///
 /// 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)*); })
 }
 
+/// 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.
 ///
 /// Like in a `match` expression, the pattern can be optionally followed by `if`
diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs
index af8a6101392..12acf5b4329 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<fmt::Arguments<'_>>,
-    ) -> ! {
-        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<T: fmt::Debug + ?Sized>(
+    left: &T,
+    right: &str,
+    args: Option<fmt::Arguments<'_>>,
+) -> ! {
+    // 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<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)`
+            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 99ab3888d96..72b86338d2c 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)]
@@ -552,8 +553,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_matches,
+    debug_assert_ne, matches, r#try, todo, unimplemented, unreachable, write, writeln,
 };
 
 // Re-export built-in macros defined through libcore.