diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 8d187a4be8aee..37a4bf5fdcad7 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, cx: &'cx ExtCtxt<'a>, + // Formatting string used for debugging + fmt_string: String, + // Top-level `let __local_bindN = &expr` statements + local_bind_decls: Vec, + // Used to avoid capturing duplicated paths + // + // ```rust + // let a = 1i32; + // assert!(add(a, a) == 3); + // ``` + paths: FxHashSet, 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, panic_path: Path) -> P { + 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 { + 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::(), + )), + 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) { + 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, 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, _panic_path: Path) -> P { - 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) { + 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) -> P { + cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e)) +} + +fn expr_method_call( + cx: &ExtCtxt<'_>, + path: PathSegment, + args: Vec>, + span: Span, +) -> P { + cx.expr(span, ExprKind::MethodCall(path, args, span)) +} + +fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: P) -> P { + 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 124d0d18cdbe2..11565ba72d755 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 7b0fa65e8086b..2af43087e8de6 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -156,6 +156,7 @@ symbols! { C, CStr, CString, + Capture, Center, Clone, Continue, @@ -263,6 +264,8 @@ symbols! { ToOwned, ToString, Try, + TryCaptureGeneric, + TryCapturePrintable, TryFrom, TryInto, Ty, @@ -272,6 +275,7 @@ symbols! { UnsafeArg, Vec, VecDeque, + Wrapper, Yield, _DECLS, _Self, @@ -354,6 +358,7 @@ symbols! { assert_receiver_is_total_eq, assert_uninit_valid, assert_zero_valid, + asserting, associated_const_equality, associated_consts, associated_type_bounds, @@ -1432,6 +1437,7 @@ symbols! { truncf32, truncf64, try_blocks, + try_capture, try_from, try_into, try_trait_v2, @@ -1494,6 +1500,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 cd7faf9bf8bfb..da725e19e2ada 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 similarity index 86% rename from src/test/ui/macros/assert-trailing-junk.stderr rename to src/test/ui/macros/assert-trailing-junk.with-generic-asset.stderr index eb001429c5522..09dd16a0b0d89 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 0000000000000..09dd16a0b0d89 --- /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 71b0dbb19e262..a314db907b8a2 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 similarity index 87% rename from src/test/ui/macros/assert.stderr rename to src/test/ui/macros/assert.with-generic-asset.stderr index 57e5c77a56692..51d8f28a35c39 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 0000000000000..51d8f28a35c39 --- /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/rfc-2011-nicer-assert-messages/all-expr-kinds.rs b/src/test/ui/rfc-2011-nicer-assert-messages/all-expr-kinds.rs new file mode 100644 index 0000000000000..14533ec2481d5 --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/all-expr-kinds.rs @@ -0,0 +1,142 @@ +// edition:2021 +// ignore-tidy-linelength +// 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::() { + 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/rfc-2011-nicer-assert-messages/all-not-available-cases.rs b/src/test/ui/rfc-2011-nicer-assert-messages/all-not-available-cases.rs new file mode 100644 index 0000000000000..ecbba8c443ac7 --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/all-not-available-cases.rs @@ -0,0 +1,42 @@ +// aux-build:common.rs +// ignore-tidy-linelength +// 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/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs b/src/test/ui/rfc-2011-nicer-assert-messages/assert-with-custom-errors-does-not-create-unnecessary-code.rs new file mode 100644 index 0000000000000..6a1435f792bf4 --- /dev/null +++ b/src/test/ui/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/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs b/src/test/ui/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs new file mode 100644 index 0000000000000..d4c1df6b94a81 --- /dev/null +++ b/src/test/ui/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs @@ -0,0 +1,13 @@ +// aux-build:common.rs +// 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/rfc-2011-nicer-assert-messages/auxiliary/common.rs b/src/test/ui/rfc-2011-nicer-assert-messages/auxiliary/common.rs new file mode 100644 index 0000000000000..903ed507c2e51 --- /dev/null +++ b/src/test/ui/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::() { + 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/rfc-2011-nicer-assert-messages/codegen.rs b/src/test/ui/rfc-2011-nicer-assert-messages/codegen.rs new file mode 100644 index 0000000000000..1db9d33c72aee --- /dev/null +++ b/src/test/ui/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/rfc-2011-nicer-assert-messages/codegen.stdout b/src/test/ui/rfc-2011-nicer-assert-messages/codegen.stdout new file mode 100644 index 0000000000000..a590eb3223254 --- /dev/null +++ b/src/test/ui/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/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs b/src/test/ui/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs index f70ca87e304a9..01860adaac250 100644 --- a/src/test/ui/rfc-2011-nicer-assert-messages/feature-gate-generic_assert.rs +++ b/src/test/ui/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() {