about summary refs log tree commit diff
diff options
context:
space:
mode:
authorCaio <c410.f3r@gmail.com>2022-06-15 07:37:40 -0300
committerCaio <c410.f3r@gmail.com>2022-06-15 07:37:40 -0300
commit605c64a91e5a748b29224887a63e6220ebac91f9 (patch)
tree31d743adb2aa3ce5098308fa9d439b3f1aebfe0e
parent389352c4bbd56024eefd1566e06dcba04d223f92 (diff)
downloadrust-605c64a91e5a748b29224887a63e6220ebac91f9.tar.gz
rust-605c64a91e5a748b29224887a63e6220ebac91f9.zip
[RFC 2011] Minimal initial implementation
-rw-r--r--compiler/rustc_builtin_macros/src/assert/context.rs307
-rw-r--r--compiler/rustc_builtin_macros/src/lib.rs1
-rw-r--r--compiler/rustc_span/src/symbol.rs7
-rw-r--r--src/test/ui/macros/assert-trailing-junk.rs3
-rw-r--r--src/test/ui/macros/assert-trailing-junk.with-generic-asset.stderr (renamed from src/test/ui/macros/assert-trailing-junk.stderr)14
-rw-r--r--src/test/ui/macros/assert-trailing-junk.without-generic-asset.stderr54
-rw-r--r--src/test/ui/macros/assert.rs3
-rw-r--r--src/test/ui/macros/assert.with-generic-asset.stderr (renamed from src/test/ui/macros/assert.stderr)8
-rw-r--r--src/test/ui/macros/assert.without-generic-asset.stderr28
-rw-r--r--src/test/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs143
-rw-r--r--src/test/ui/macros/rfc-2011-nicer-assert-messages/all-not-available-cases.rs43
-rw-r--r--src/test/ui/macros/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs13
-rw-r--r--src/test/ui/macros/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs14
-rw-r--r--src/test/ui/macros/rfc-2011-nicer-assert-messages/auxiliary/common.rs25
-rw-r--r--src/test/ui/macros/rfc-2011-nicer-assert-messages/codegen.rs9
-rw-r--r--src/test/ui/macros/rfc-2011-nicer-assert-messages/codegen.stdout29
-rw-r--r--src/test/ui/macros/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs8
17 files changed, 668 insertions, 41 deletions
diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs
index 8d187a4be8a..37a4bf5fdca 100644
--- a/compiler/rustc_builtin_macros/src/assert/context.rs
+++ b/compiler/rustc_builtin_macros/src/assert/context.rs
@@ -1,44 +1,299 @@
-use rustc_ast::{ptr::P, Expr, Path};
+use crate::assert::expr_if_not;
+use rustc_ast::{
+    attr,
+    ptr::P,
+    token,
+    tokenstream::{DelimSpan, TokenStream, TokenTree},
+    BorrowKind, Expr, ExprKind, ItemKind, MacArgs, MacCall, MacDelimiter, Mutability, Path,
+    PathSegment, Stmt, UseTree, UseTreeKind, DUMMY_NODE_ID,
+};
+use rustc_ast_pretty::pprust;
+use rustc_data_structures::fx::FxHashSet;
 use rustc_expand::base::ExtCtxt;
-use rustc_span::Span;
+use rustc_span::{
+    symbol::{sym, Ident, Symbol},
+    Span,
+};
 
 pub(super) struct Context<'cx, 'a> {
+    // Top-level `let captureN = Capture::new()` statements
+    capture_decls: Vec<Capture>,
     cx: &'cx ExtCtxt<'a>,
+    // Formatting string used for debugging
+    fmt_string: String,
+    // Top-level `let __local_bindN = &expr` statements
+    local_bind_decls: Vec<Stmt>,
+    // Used to avoid capturing duplicated paths
+    //
+    // ```rust
+    // let a = 1i32;
+    // assert!(add(a, a) == 3);
+    // ```
+    paths: FxHashSet<Ident>,
     span: Span,
 }
 
 impl<'cx, 'a> Context<'cx, 'a> {
     pub(super) fn new(cx: &'cx ExtCtxt<'a>, span: Span) -> Self {
-        Self { cx, span }
+        Self {
+            capture_decls: <_>::default(),
+            cx,
+            fmt_string: <_>::default(),
+            local_bind_decls: <_>::default(),
+            paths: <_>::default(),
+            span,
+        }
     }
 
-    /// Builds the whole `assert!` expression.
+    /// Builds the whole `assert!` expression. For example, `let elem = 1; assert!(elem == 1);` expands to:
     ///
+    /// ```rust
+    /// let elem = 1;
     /// {
-    ///    use ::core::asserting::{ ... };
+    ///   #[allow(unused_imports)]
+    ///   use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
+    ///   let mut __capture0 = ::core::asserting::Capture::new();
+    ///   let __local_bind0 = &elem;
+    ///   if !(
+    ///     *{
+    ///       (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
+    ///       __local_bind0
+    ///     } == 1
+    ///   ) {
+    ///     panic!("Assertion failed: elem == 1\nWith captures:\n  elem = {}", __capture0)
+    ///   }
+    /// }
+    /// ```
+    pub(super) fn build(mut self, mut cond_expr: P<Expr>, panic_path: Path) -> P<Expr> {
+        let expr_str = pprust::expr_to_string(&cond_expr);
+        self.manage_cond_expr(&mut cond_expr);
+        let initial_imports = self.build_initial_imports();
+        let panic = self.build_panic(&expr_str, panic_path);
+
+        let Self { capture_decls, cx, local_bind_decls, span, .. } = self;
+
+        let mut stmts = Vec::with_capacity(4);
+        stmts.push(initial_imports);
+        stmts.extend(capture_decls.into_iter().map(|c| c.decl));
+        stmts.extend(local_bind_decls);
+        stmts.push(cx.stmt_expr(expr_if_not(cx, span, cond_expr, panic, None)));
+        cx.expr_block(cx.block(span, stmts))
+    }
+
+    /// Initial **trait** imports
+    ///
+    /// use ::core::asserting::{ ... };
+    fn build_initial_imports(&self) -> Stmt {
+        let nested_tree = |this: &Self, sym| {
+            (
+                UseTree {
+                    prefix: this.cx.path(this.span, vec![Ident::with_dummy_span(sym)]),
+                    kind: UseTreeKind::Simple(None, DUMMY_NODE_ID, DUMMY_NODE_ID),
+                    span: this.span,
+                },
+                DUMMY_NODE_ID,
+            )
+        };
+        self.cx.stmt_item(
+            self.span,
+            self.cx.item(
+                self.span,
+                Ident::empty(),
+                vec![self.cx.attribute(attr::mk_list_item(
+                    Ident::new(sym::allow, self.span),
+                    vec![attr::mk_nested_word_item(Ident::new(sym::unused_imports, self.span))],
+                ))],
+                ItemKind::Use(UseTree {
+                    prefix: self.cx.path(self.span, self.cx.std_path(&[sym::asserting])),
+                    kind: UseTreeKind::Nested(vec![
+                        nested_tree(self, sym::TryCaptureGeneric),
+                        nested_tree(self, sym::TryCapturePrintable),
+                    ]),
+                    span: self.span,
+                }),
+            ),
+        )
+    }
+
+    /// The necessary custom `panic!(...)` expression.
+    ///
+    /// panic!(
+    ///     "Assertion failed: ... \n With expansion: ...",
+    ///     __capture0,
+    ///     ...
+    /// );
+    fn build_panic(&self, expr_str: &str, panic_path: Path) -> P<Expr> {
+        let escaped_expr_str = escape_to_fmt(expr_str);
+        let initial = [
+            TokenTree::token(
+                token::Literal(token::Lit {
+                    kind: token::LitKind::Str,
+                    symbol: Symbol::intern(&if self.fmt_string.is_empty() {
+                        format!("Assertion failed: {escaped_expr_str}")
+                    } else {
+                        format!(
+                            "Assertion failed: {escaped_expr_str}\nWith captures:\n{}",
+                            &self.fmt_string
+                        )
+                    }),
+                    suffix: None,
+                }),
+                self.span,
+            ),
+            TokenTree::token(token::Comma, self.span),
+        ];
+        let captures = self.capture_decls.iter().flat_map(|cap| {
+            [
+                TokenTree::token(token::Ident(cap.ident.name, false), cap.ident.span),
+                TokenTree::token(token::Comma, self.span),
+            ]
+        });
+        self.cx.expr(
+            self.span,
+            ExprKind::MacCall(MacCall {
+                path: panic_path,
+                args: P(MacArgs::Delimited(
+                    DelimSpan::from_single(self.span),
+                    MacDelimiter::Parenthesis,
+                    initial.into_iter().chain(captures).collect::<TokenStream>(),
+                )),
+                prior_type_ascription: None,
+            }),
+        )
+    }
+
+    /// Recursive function called until `cond_expr` and `fmt_str` are fully modified.
+    ///
+    /// See [Self::manage_initial_capture] and [Self::manage_try_capture]
+    fn manage_cond_expr(&mut self, expr: &mut P<Expr>) {
+        match (*expr).kind {
+            ExprKind::Binary(_, ref mut lhs, ref mut rhs) => {
+                self.manage_cond_expr(lhs);
+                self.manage_cond_expr(rhs);
+            }
+            ExprKind::Path(_, Path { ref segments, .. }) if let &[ref path_segment] = &segments[..] => {
+                let path_ident = path_segment.ident;
+                self.manage_initial_capture(expr, path_ident);
+            }
+            _ => {}
+        }
+    }
+
+    /// Pushes the top-level declarations and modifies `expr` to try capturing variables.
     ///
-    ///    let mut __capture0 = Capture::new();
-    ///    ...
-    ///    ...
-    ///    ...
+    /// `fmt_str`, the formatting string used for debugging, is constructed to show possible
+    /// captured variables.
+    fn manage_initial_capture(&mut self, expr: &mut P<Expr>, path_ident: Ident) {
+        if self.paths.contains(&path_ident) {
+            return;
+        } else {
+            self.fmt_string.push_str("  ");
+            self.fmt_string.push_str(path_ident.as_str());
+            self.fmt_string.push_str(" = {:?}\n");
+            let _ = self.paths.insert(path_ident);
+        }
+        let curr_capture_idx = self.capture_decls.len();
+        let capture_string = format!("__capture{curr_capture_idx}");
+        let ident = Ident::new(Symbol::intern(&capture_string), self.span);
+        let init_std_path = self.cx.std_path(&[sym::asserting, sym::Capture, sym::new]);
+        let init = self.cx.expr_call(
+            self.span,
+            self.cx.expr_path(self.cx.path(self.span, init_std_path)),
+            vec![],
+        );
+        let capture = Capture { decl: self.cx.stmt_let(self.span, true, ident, init), ident };
+        self.capture_decls.push(capture);
+        self.manage_try_capture(ident, curr_capture_idx, expr);
+    }
+
+    /// Tries to copy `__local_bindN` into `__captureN`.
     ///
-    ///    if !{
-    ///       ...
-    ///       ...
-    ///       ...
-    ///    } {
-    ///        panic!(
-    ///            "Assertion failed: ... \n With expansion: ...",
-    ///            __capture0,
-    ///            ...
-    ///            ...
-    ///            ...
-    ///        );
-    ///    }
+    /// *{
+    ///    (&Wrapper(__local_bindN)).try_capture(&mut __captureN);
+    ///    __local_bindN
     /// }
-    pub(super) fn build(self, _cond_expr: P<Expr>, _panic_path: Path) -> P<Expr> {
-        let Self { cx, span, .. } = self;
-        let stmts = Vec::new();
-        cx.expr_block(cx.block(span, stmts))
+    fn manage_try_capture(&mut self, capture: Ident, curr_capture_idx: usize, expr: &mut P<Expr>) {
+        let local_bind_string = format!("__local_bind{curr_capture_idx}");
+        let local_bind = Ident::new(Symbol::intern(&local_bind_string), self.span);
+        self.local_bind_decls.push(self.cx.stmt_let(
+            self.span,
+            false,
+            local_bind,
+            self.cx.expr_addr_of(self.span, expr.clone()),
+        ));
+        let wrapper = self.cx.expr_call(
+            self.span,
+            self.cx.expr_path(
+                self.cx.path(self.span, self.cx.std_path(&[sym::asserting, sym::Wrapper])),
+            ),
+            vec![self.cx.expr_path(Path::from_ident(local_bind))],
+        );
+        let try_capture_call = self
+            .cx
+            .stmt_expr(expr_method_call(
+                self.cx,
+                PathSegment {
+                    args: None,
+                    id: DUMMY_NODE_ID,
+                    ident: Ident::new(sym::try_capture, self.span),
+                },
+                vec![
+                    expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)),
+                    expr_addr_of_mut(
+                        self.cx,
+                        self.span,
+                        self.cx.expr_path(Path::from_ident(capture)),
+                    ),
+                ],
+                self.span,
+            ))
+            .add_trailing_semicolon();
+        let local_bind_path = self.cx.expr_path(Path::from_ident(local_bind));
+        let ret = self.cx.stmt_expr(local_bind_path);
+        let block = self.cx.expr_block(self.cx.block(self.span, vec![try_capture_call, ret]));
+        *expr = self.cx.expr_deref(self.span, block);
+    }
+}
+
+/// Information about a captured element.
+#[derive(Debug)]
+struct Capture {
+    // Generated indexed `Capture` statement.
+    //
+    // `let __capture{} = Capture::new();`
+    decl: Stmt,
+    // The name of the generated indexed `Capture` variable.
+    //
+    // `__capture{}`
+    ident: Ident,
+}
+
+/// Escapes to use as a formatting string.
+fn escape_to_fmt(s: &str) -> String {
+    let mut rslt = String::with_capacity(s.len());
+    for c in s.chars() {
+        rslt.extend(c.escape_debug());
+        match c {
+            '{' | '}' => rslt.push(c),
+            _ => {}
+        }
     }
+    rslt
+}
+
+fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> {
+    cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e))
+}
+
+fn expr_method_call(
+    cx: &ExtCtxt<'_>,
+    path: PathSegment,
+    args: Vec<P<Expr>>,
+    span: Span,
+) -> P<Expr> {
+    cx.expr(span, ExprKind::MethodCall(path, args, span))
+}
+
+fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> {
+    cx.expr(sp, ExprKind::Paren(e))
 }
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
index 124d0d18cdb..11565ba72d7 100644
--- a/compiler/rustc_builtin_macros/src/lib.rs
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -6,6 +6,7 @@
 #![feature(array_windows)]
 #![feature(box_patterns)]
 #![feature(decl_macro)]
+#![feature(if_let_guard)]
 #![feature(is_sorted)]
 #![feature(let_chains)]
 #![feature(let_else)]
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 6547ec493c8..c35a11ff761 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -157,6 +157,7 @@ symbols! {
         C,
         CStr,
         CString,
+        Capture,
         Center,
         Clone,
         Continue,
@@ -267,6 +268,8 @@ symbols! {
         ToOwned,
         ToString,
         Try,
+        TryCaptureGeneric,
+        TryCapturePrintable,
         TryFrom,
         TryInto,
         Ty,
@@ -276,6 +279,7 @@ symbols! {
         UnsafeArg,
         Vec,
         VecDeque,
+        Wrapper,
         Yield,
         _DECLS,
         _Self,
@@ -358,6 +362,7 @@ symbols! {
         assert_receiver_is_total_eq,
         assert_uninit_valid,
         assert_zero_valid,
+        asserting,
         associated_const_equality,
         associated_consts,
         associated_type_bounds,
@@ -1437,6 +1442,7 @@ symbols! {
         truncf32,
         truncf64,
         try_blocks,
+        try_capture,
         try_from,
         try_into,
         try_trait_v2,
@@ -1499,6 +1505,7 @@ symbols! {
         unsized_tuple_coercion,
         unstable,
         untagged_unions,
+        unused_imports,
         unused_qualifications,
         unwind,
         unwind_attributes,
diff --git a/src/test/ui/macros/assert-trailing-junk.rs b/src/test/ui/macros/assert-trailing-junk.rs
index cd7faf9bf8b..da725e19e2a 100644
--- a/src/test/ui/macros/assert-trailing-junk.rs
+++ b/src/test/ui/macros/assert-trailing-junk.rs
@@ -1,3 +1,6 @@
+// revisions: with-generic-asset without-generic-asset
+// [with-generic-asset] compile-flags: --cfg feature="generic_assert"
+
 // Ensure assert macro does not ignore trailing garbage.
 //
 // See https://github.com/rust-lang/rust/issues/60024 for details.
diff --git a/src/test/ui/macros/assert-trailing-junk.stderr b/src/test/ui/macros/assert-trailing-junk.with-generic-asset.stderr
index eb001429c55..09dd16a0b0d 100644
--- a/src/test/ui/macros/assert-trailing-junk.stderr
+++ b/src/test/ui/macros/assert-trailing-junk.with-generic-asset.stderr
@@ -1,17 +1,17 @@
 error: expected one of `,`, `.`, `?`, or an operator, found `some`
-  --> $DIR/assert-trailing-junk.rs:6:18
+  --> $DIR/assert-trailing-junk.rs:9:18
    |
 LL |     assert!(true some extra junk, "whatever");
    |                  ^^^^ expected one of `,`, `.`, `?`, or an operator
 
 error: expected one of `,`, `.`, `?`, or an operator, found `some`
-  --> $DIR/assert-trailing-junk.rs:9:18
+  --> $DIR/assert-trailing-junk.rs:12:18
    |
 LL |     assert!(true some extra junk);
    |                  ^^^^ expected one of `,`, `.`, `?`, or an operator
 
 error: no rules expected the token `blah`
-  --> $DIR/assert-trailing-junk.rs:12:30
+  --> $DIR/assert-trailing-junk.rs:15:30
    |
 LL |     assert!(true, "whatever" blah);
    |                             -^^^^ no rules expected this token in macro call
@@ -19,7 +19,7 @@ LL |     assert!(true, "whatever" blah);
    |                             help: missing comma here
 
 error: unexpected string literal
-  --> $DIR/assert-trailing-junk.rs:15:18
+  --> $DIR/assert-trailing-junk.rs:18:18
    |
 LL |     assert!(true "whatever" blah);
    |                 -^^^^^^^^^^
@@ -27,7 +27,7 @@ LL |     assert!(true "whatever" blah);
    |                 help: try adding a comma
 
 error: no rules expected the token `blah`
-  --> $DIR/assert-trailing-junk.rs:15:29
+  --> $DIR/assert-trailing-junk.rs:18:29
    |
 LL |     assert!(true "whatever" blah);
    |                            -^^^^ no rules expected this token in macro call
@@ -35,7 +35,7 @@ LL |     assert!(true "whatever" blah);
    |                            help: missing comma here
 
 error: macro requires an expression as an argument
-  --> $DIR/assert-trailing-junk.rs:19:5
+  --> $DIR/assert-trailing-junk.rs:22:5
    |
 LL |     assert!(true;);
    |     ^^^^^^^^^^^^-^
@@ -43,7 +43,7 @@ LL |     assert!(true;);
    |                 help: try removing semicolon
 
 error: unexpected string literal
-  --> $DIR/assert-trailing-junk.rs:22:27
+  --> $DIR/assert-trailing-junk.rs:25:27
    |
 LL |     assert!(false || true "error message");
    |                          -^^^^^^^^^^^^^^^
diff --git a/src/test/ui/macros/assert-trailing-junk.without-generic-asset.stderr b/src/test/ui/macros/assert-trailing-junk.without-generic-asset.stderr
new file mode 100644
index 00000000000..09dd16a0b0d
--- /dev/null
+++ b/src/test/ui/macros/assert-trailing-junk.without-generic-asset.stderr
@@ -0,0 +1,54 @@
+error: expected one of `,`, `.`, `?`, or an operator, found `some`
+  --> $DIR/assert-trailing-junk.rs:9:18
+   |
+LL |     assert!(true some extra junk, "whatever");
+   |                  ^^^^ expected one of `,`, `.`, `?`, or an operator
+
+error: expected one of `,`, `.`, `?`, or an operator, found `some`
+  --> $DIR/assert-trailing-junk.rs:12:18
+   |
+LL |     assert!(true some extra junk);
+   |                  ^^^^ expected one of `,`, `.`, `?`, or an operator
+
+error: no rules expected the token `blah`
+  --> $DIR/assert-trailing-junk.rs:15:30
+   |
+LL |     assert!(true, "whatever" blah);
+   |                             -^^^^ no rules expected this token in macro call
+   |                             |
+   |                             help: missing comma here
+
+error: unexpected string literal
+  --> $DIR/assert-trailing-junk.rs:18:18
+   |
+LL |     assert!(true "whatever" blah);
+   |                 -^^^^^^^^^^
+   |                 |
+   |                 help: try adding a comma
+
+error: no rules expected the token `blah`
+  --> $DIR/assert-trailing-junk.rs:18:29
+   |
+LL |     assert!(true "whatever" blah);
+   |                            -^^^^ no rules expected this token in macro call
+   |                            |
+   |                            help: missing comma here
+
+error: macro requires an expression as an argument
+  --> $DIR/assert-trailing-junk.rs:22:5
+   |
+LL |     assert!(true;);
+   |     ^^^^^^^^^^^^-^
+   |                 |
+   |                 help: try removing semicolon
+
+error: unexpected string literal
+  --> $DIR/assert-trailing-junk.rs:25:27
+   |
+LL |     assert!(false || true "error message");
+   |                          -^^^^^^^^^^^^^^^
+   |                          |
+   |                          help: try adding a comma
+
+error: aborting due to 7 previous errors
+
diff --git a/src/test/ui/macros/assert.rs b/src/test/ui/macros/assert.rs
index 71b0dbb19e2..a314db907b8 100644
--- a/src/test/ui/macros/assert.rs
+++ b/src/test/ui/macros/assert.rs
@@ -1,3 +1,6 @@
+// revisions: with-generic-asset without-generic-asset
+// [with-generic-asset] compile-flags: --cfg feature="generic_assert"
+
 fn main() {
     assert!();  //~ ERROR requires a boolean expression
     assert!(struct); //~ ERROR expected expression
diff --git a/src/test/ui/macros/assert.stderr b/src/test/ui/macros/assert.with-generic-asset.stderr
index 57e5c77a566..51d8f28a35c 100644
--- a/src/test/ui/macros/assert.stderr
+++ b/src/test/ui/macros/assert.with-generic-asset.stderr
@@ -1,17 +1,17 @@
 error: macro requires a boolean expression as an argument
-  --> $DIR/assert.rs:2:5
+  --> $DIR/assert.rs:5:5
    |
 LL |     assert!();
    |     ^^^^^^^^^ boolean expression required
 
 error: expected expression, found keyword `struct`
-  --> $DIR/assert.rs:3:13
+  --> $DIR/assert.rs:6:13
    |
 LL |     assert!(struct);
    |             ^^^^^^ expected expression
 
 error: macro requires a boolean expression as an argument
-  --> $DIR/assert.rs:4:5
+  --> $DIR/assert.rs:7:5
    |
 LL |     debug_assert!();
    |     ^^^^^^^^^^^^^^^ boolean expression required
@@ -19,7 +19,7 @@ LL |     debug_assert!();
    = note: this error originates in the macro `debug_assert` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: expected expression, found keyword `struct`
-  --> $DIR/assert.rs:5:19
+  --> $DIR/assert.rs:8:19
    |
 LL |     debug_assert!(struct);
    |                   ^^^^^^ expected expression
diff --git a/src/test/ui/macros/assert.without-generic-asset.stderr b/src/test/ui/macros/assert.without-generic-asset.stderr
new file mode 100644
index 00000000000..51d8f28a35c
--- /dev/null
+++ b/src/test/ui/macros/assert.without-generic-asset.stderr
@@ -0,0 +1,28 @@
+error: macro requires a boolean expression as an argument
+  --> $DIR/assert.rs:5:5
+   |
+LL |     assert!();
+   |     ^^^^^^^^^ boolean expression required
+
+error: expected expression, found keyword `struct`
+  --> $DIR/assert.rs:6:13
+   |
+LL |     assert!(struct);
+   |             ^^^^^^ expected expression
+
+error: macro requires a boolean expression as an argument
+  --> $DIR/assert.rs:7:5
+   |
+LL |     debug_assert!();
+   |     ^^^^^^^^^^^^^^^ boolean expression required
+   |
+   = note: this error originates in the macro `debug_assert` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: expected expression, found keyword `struct`
+  --> $DIR/assert.rs:8:19
+   |
+LL |     debug_assert!(struct);
+   |                   ^^^^^^ expected expression
+
+error: aborting due to 4 previous errors
+
diff --git a/src/test/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs b/src/test/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs
new file mode 100644
index 00000000000..c0e9f29fdbc
--- /dev/null
+++ b/src/test/ui/macros/rfc-2011-nicer-assert-messages/all-expr-kinds.rs
@@ -0,0 +1,143 @@
+// edition:2021
+// ignore-tidy-linelength
+// only-x86_64
+// run-pass
+
+#![allow(path_statements, unused_allocation)]
+#![feature(box_syntax, core_intrinsics, generic_assert, generic_assert_internals)]
+
+macro_rules! test {
+  (
+    let mut $elem_ident:ident = $elem_expr:expr;
+    [ $($assert:tt)* ] => $msg:literal
+  ) => {
+    {
+      #[allow(unused_assignments, unused_mut, unused_variables)]
+      let rslt = std::panic::catch_unwind(|| {
+        let mut $elem_ident = $elem_expr;
+        assert!($($assert)*);
+      });
+      let err = rslt.unwrap_err();
+      if let Some(elem) = err.downcast_ref::<String>() {
+        assert_eq!(elem, &$msg);
+      }
+      else if let Some(elem) = err.downcast_ref::<&str>() {
+        assert_eq!(elem, &$msg);
+      }
+      else {
+        panic!("assert!( ... ) should return a string");
+      }
+    }
+  }
+}
+
+macro_rules! tests {
+  (
+    let mut $elem_ident:ident = $elem_expr:expr;
+
+    $(
+      [ $($elem_assert:tt)* ] => $elem_msg:literal
+    )+
+  ) => {
+    $(
+      test!(
+        let mut $elem_ident = $elem_expr;
+        [ $($elem_assert)* ] => $elem_msg
+      );
+    )+
+  }
+}
+
+const FOO: Foo = Foo { bar: 1 };
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+struct Foo {
+  bar: i32
+}
+
+fn main() {
+  // ***** Allowed *****
+
+  tests!(
+    let mut elem = 1i32;
+
+    // binary
+    [ elem + 1 == 3 ] => "Assertion failed: elem + 1 == 3\nWith captures:\n  elem = 1\n"
+  );
+
+  // ***** Disallowed *****
+
+  tests!(
+    let mut elem = 1i32;
+
+    // assign
+    [ { let local = elem; local } == 3 ] => "Assertion failed: { let local = elem; local } == 3"
+
+    // assign op
+    [ { elem += 1; elem } == 3 ] => "Assertion failed: { elem += 1; elem } == 3"
+
+    // async
+    [ { let _ = async { elem }; elem } == 3 ] => "Assertion failed: { let _ = async { elem }; elem } == 3"
+
+    // await
+
+    // block
+    [ { elem } == 3 ] => "Assertion failed: { elem } == 3"
+
+    // box
+    [ box elem == box 3 ] => "Assertion failed: box elem == box 3"
+
+    // break
+    [ loop { break elem; } ==  3 ] => "Assertion failed: loop { break elem; } == 3"
+
+    // closure
+    [(|| elem)() ==  3 ] => "Assertion failed: (|| elem)() == 3"
+
+    // const block
+
+    // continue
+
+    // err
+
+    // field
+    [ FOO.bar ==  3 ] => "Assertion failed: FOO.bar == 3"
+
+    // for loop
+    [ { for _ in 0..elem { elem; } elem } ==  3 ] => "Assertion failed: { for _ in 0..elem { elem; } elem } == 3"
+
+    // if
+    [ if true { elem } else { elem } == 3 ] => "Assertion failed: if true { elem } else { elem } == 3"
+
+    // inline asm
+
+    // let
+    [ if let true = true { elem } else { elem } == 3 ] => "Assertion failed: if let true = true { elem } else { elem } == 3"
+
+    // lit
+
+    // loop
+    [ loop { elem; break elem; } == 3 ] => "Assertion failed: loop { elem; break elem; } == 3"
+
+    // mac call
+
+    // match
+    [ match elem { _ => elem } == 3 ] => "Assertion failed: match elem { _ => elem, } == 3"
+
+    // ret
+    [ (|| { return elem; })() == 3 ] => "Assertion failed: (|| { return elem; })() == 3"
+
+    // try
+    [ (|| { Some(Some(elem)?) })() == Some(3) ] => "Assertion failed: (|| { Some(Some(elem)?) })() == Some(3)"
+
+    // try block
+
+    // underscore
+
+    // while
+    [ { while false { elem; break; } elem } == 3 ] => "Assertion failed: { while false { elem; break; } elem } == 3"
+
+    // yeet
+
+    // yield
+  );
+}
diff --git a/src/test/ui/macros/rfc-2011-nicer-assert-messages/all-not-available-cases.rs b/src/test/ui/macros/rfc-2011-nicer-assert-messages/all-not-available-cases.rs
new file mode 100644
index 00000000000..86697c58fbc
--- /dev/null
+++ b/src/test/ui/macros/rfc-2011-nicer-assert-messages/all-not-available-cases.rs
@@ -0,0 +1,43 @@
+// aux-build:common.rs
+// ignore-tidy-linelength
+// only-x86_64
+// run-pass
+
+#![feature(core_intrinsics, generic_assert, generic_assert_internals)]
+
+extern crate common;
+
+#[derive(Clone, Copy, PartialEq)]
+struct CopyNoDebug(i32);
+
+#[derive(Debug, PartialEq)]
+struct NoCopyDebug(i32);
+
+#[derive(PartialEq)]
+struct NoCopyNoDebug(i32);
+
+fn main() {
+  // Has Copy but does not have Debug
+  common::test!(
+    let mut copy_no_debug = CopyNoDebug(1);
+    [ copy_no_debug == CopyNoDebug(3) ] => "Assertion failed: copy_no_debug == CopyNoDebug(3)\nWith captures:\n  copy_no_debug = N/A\n"
+  );
+
+  // Does not have Copy but has Debug
+  common::test!(
+    let mut no_copy_debug = NoCopyDebug(1);
+    [ no_copy_debug == NoCopyDebug(3) ] => "Assertion failed: no_copy_debug == NoCopyDebug(3)\nWith captures:\n  no_copy_debug = N/A\n"
+  );
+
+  // Does not have Copy and does not have Debug
+  common::test!(
+    let mut no_copy_no_debug = NoCopyNoDebug(1);
+    [ no_copy_no_debug == NoCopyNoDebug(3) ] => "Assertion failed: no_copy_no_debug == NoCopyNoDebug(3)\nWith captures:\n  no_copy_no_debug = N/A\n"
+  );
+
+  // Unevaluated (Expression short-circuited)
+  common::test!(
+    let mut elem = true;
+    [ false && elem ] => "Assertion failed: false && elem\nWith captures:\n  elem = N/A\n"
+  );
+}
diff --git a/src/test/ui/macros/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs b/src/test/ui/macros/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs
new file mode 100644
index 00000000000..6a1435f792b
--- /dev/null
+++ b/src/test/ui/macros/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs
@@ -0,0 +1,13 @@
+// compile-flags: --test
+// run-pass
+
+#![feature(core_intrinsics, generic_assert, generic_assert_internals)]
+
+#[should_panic(expected = "Custom user message")]
+#[test]
+fn test() {
+  assert!(1 == 3, "Custom user message");
+}
+
+fn main() {
+}
diff --git a/src/test/ui/macros/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs b/src/test/ui/macros/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs
new file mode 100644
index 00000000000..06c4993ec30
--- /dev/null
+++ b/src/test/ui/macros/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs
@@ -0,0 +1,14 @@
+// aux-build:common.rs
+// only-x86_64
+// run-pass
+
+#![feature(core_intrinsics, generic_assert, generic_assert_internals)]
+
+extern crate common;
+
+fn main() {
+  common::test!(
+    let mut _nothing = ();
+    [ 1 == 3 ] => "Assertion failed: 1 == 3"
+  );
+}
diff --git a/src/test/ui/macros/rfc-2011-nicer-assert-messages/auxiliary/common.rs b/src/test/ui/macros/rfc-2011-nicer-assert-messages/auxiliary/common.rs
new file mode 100644
index 00000000000..903ed507c2e
--- /dev/null
+++ b/src/test/ui/macros/rfc-2011-nicer-assert-messages/auxiliary/common.rs
@@ -0,0 +1,25 @@
+#[macro_export]
+macro_rules! test {
+  (
+    let mut $elem_ident:ident = $elem_expr:expr;
+    [ $($assert:tt)* ] => $msg:literal
+  ) => {
+    {
+      #[allow(unused_assignments, unused_mut, unused_variables)]
+      let rslt = std::panic::catch_unwind(|| {
+        let mut $elem_ident = $elem_expr;
+        assert!($($assert)*);
+      });
+      let err = rslt.unwrap_err();
+      if let Some(elem) = err.downcast_ref::<String>() {
+        assert_eq!(elem, &$msg);
+      }
+      else if let Some(elem) = err.downcast_ref::<&str>() {
+        assert_eq!(elem, &$msg);
+      }
+      else {
+        panic!("assert!( ... ) should return a string");
+      }
+    }
+  }
+}
diff --git a/src/test/ui/macros/rfc-2011-nicer-assert-messages/codegen.rs b/src/test/ui/macros/rfc-2011-nicer-assert-messages/codegen.rs
new file mode 100644
index 00000000000..1db9d33c72a
--- /dev/null
+++ b/src/test/ui/macros/rfc-2011-nicer-assert-messages/codegen.rs
@@ -0,0 +1,9 @@
+// check-pass
+// compile-flags: -Z unpretty=expanded
+
+#![feature(core_intrinsics, generic_assert, generic_assert_internals)]
+
+fn main() {
+    let elem = 1i32;
+    assert!(elem == 1);
+}
diff --git a/src/test/ui/macros/rfc-2011-nicer-assert-messages/codegen.stdout b/src/test/ui/macros/rfc-2011-nicer-assert-messages/codegen.stdout
new file mode 100644
index 00000000000..a590eb32232
--- /dev/null
+++ b/src/test/ui/macros/rfc-2011-nicer-assert-messages/codegen.stdout
@@ -0,0 +1,29 @@
+#![feature(prelude_import)]
+#![no_std]
+// check-pass
+// compile-flags: -Z unpretty=expanded
+
+#![feature(core_intrinsics, generic_assert, generic_assert_internals)]
+#[prelude_import]
+use ::std::prelude::rust_2015::*;
+#[macro_use]
+extern crate std;
+
+fn main() {
+    let elem = 1i32;
+    {
+        #[allow(unused_imports)]
+        use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
+        let mut __capture0 = ::core::asserting::Capture::new();
+        let __local_bind0 = &elem;
+        if !(*{
+                                (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
+                                __local_bind0
+                            } == 1) {
+                {
+                    ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem == 1\nWith captures:\n  elem = ",
+                                        "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
+                }
+            }
+    };
+}
diff --git a/src/test/ui/macros/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs b/src/test/ui/macros/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs
index f70ca87e304..01860adaac2 100644
--- a/src/test/ui/macros/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs
+++ b/src/test/ui/macros/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs
@@ -1,8 +1,7 @@
 // compile-flags: --test
+// ignore-tidy-linelength
 // run-pass
 
-// `generic_assert` is completely unimplemented and doesn't generate any logic, thus the
-// reason why this test currently passes
 #![feature(core_intrinsics, generic_assert, generic_assert_internals)]
 
 use std::fmt::{Debug, Formatter};
@@ -16,10 +15,11 @@ impl Debug for CopyDebug {
   }
 }
 
+#[should_panic(expected = "Assertion failed: copy_debug == CopyDebug(3)\nWith captures:\n  copy_debug = With great power comes great electricity bills\n")]
 #[test]
 fn test() {
-  let _copy_debug = CopyDebug(1);
-  assert!(_copy_debug == CopyDebug(3));
+  let copy_debug = CopyDebug(1);
+  assert!(copy_debug == CopyDebug(3));
 }
 
 fn main() {