Skip to content

Implement RFC-2011 (Nicer assert! messages) #96496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 105 additions & 77 deletions compiler/rustc_builtin_macros/src/assert.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
mod context;

use crate::edition_panic::use_panic_2021;
use rustc_ast::ptr::P;
use rustc_ast::token;
use rustc_ast::tokenstream::{DelimSpan, TokenStream};
use rustc_ast::{self as ast, *};
use rustc_ast::{Expr, ExprKind, MacArgs, MacCall, MacDelimiter, Path, PathSegment, UnOp};
use rustc_ast_pretty::pprust;
use rustc_errors::{Applicability, PResult};
use rustc_expand::base::*;
use rustc_expand::base::{DummyResult, ExtCtxt, MacEager, MacResult};
use rustc_parse::parser::Parser;
use rustc_span::symbol::{sym, Ident, Symbol};
use rustc_span::{Span, DUMMY_SP};
@@ -15,7 +17,7 @@ pub fn expand_assert<'cx>(
span: Span,
tts: TokenStream,
) -> Box<dyn MacResult + 'cx> {
let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
let Assert { cond_expr, custom_message } = match Assert::parse(cx, span, tts) {
Ok(assert) => assert,
Err(mut err) => {
err.emit();
@@ -25,13 +27,13 @@ pub fn expand_assert<'cx>(

// `core::panic` and `std::panic` are different macros, so we use call-site
// context to pick up whichever is currently in scope.
let sp = cx.with_call_site_ctxt(span);
let call_site_span = cx.with_call_site_ctxt(span);

let panic_call = if let Some(tokens) = custom_message {
let path = if use_panic_2021(span) {
let panic_path = || {
if use_panic_2021(span) {
// On edition 2021, we always call `$crate::panic::panic_2021!()`.
Path {
span: sp,
span: call_site_span,
segments: cx
.std_path(&[sym::panic, sym::panic_2021])
.into_iter()
@@ -42,27 +44,39 @@ pub fn expand_assert<'cx>(
} else {
// Before edition 2021, we call `panic!()` unqualified,
// such that it calls either `std::panic!()` or `core::panic!()`.
Path::from_ident(Ident::new(sym::panic, sp))
};
// Pass the custom message to panic!().
cx.expr(
sp,
Path::from_ident(Ident::new(sym::panic, call_site_span))
}
};

// Simply uses the user provided message instead of generating custom outputs
let expr = if let Some(tokens) = custom_message {
let then = cx.expr(
call_site_span,
ExprKind::MacCall(MacCall {
path,
path: panic_path(),
args: P(MacArgs::Delimited(
DelimSpan::from_single(sp),
DelimSpan::from_single(call_site_span),
MacDelimiter::Parenthesis,
tokens,
)),
prior_type_ascription: None,
}),
)
} else {
);
expr_if_not(cx, call_site_span, cond_expr, then, None)
}
// If `generic_assert` is enabled, generates rich captured outputs
//
// FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949
else if let Some(features) = cx.ecfg.features && features.generic_assert {
context::Context::new(cx, call_site_span).build(cond_expr, panic_path())
}
// If `generic_assert` is not enabled, outputs "assertion failed: ..."
else {
// Pass our own message directly to $crate::panicking::panic(),
// because it might contain `{` and `}` that should always be
// passed literally.
cx.expr_call_global(
sp,
let then = cx.expr_call_global(
call_site_span,
cx.std_path(&[sym::panicking, sym::panic]),
vec![cx.expr_str(
DUMMY_SP,
@@ -71,82 +85,96 @@ pub fn expand_assert<'cx>(
pprust::expr_to_string(&cond_expr).escape_debug()
)),
)],
)
);
expr_if_not(cx, call_site_span, cond_expr, then, None)
};
let if_expr =
cx.expr_if(sp, cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)), panic_call, None);
MacEager::expr(if_expr)

MacEager::expr(expr)
}

struct Assert {
cond_expr: P<ast::Expr>,
cond_expr: P<Expr>,
custom_message: Option<TokenStream>,
}

fn parse_assert<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
let mut parser = cx.new_parser_from_tts(stream);
impl Assert {
fn parse<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
let mut parser = cx.new_parser_from_tts(stream);

if parser.token == token::Eof {
let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument");
err.span_label(sp, "boolean expression required");
return Err(err);
}

let cond_expr = parser.parse_expr()?;

// Some crates use the `assert!` macro in the following form (note extra semicolon):
//
// assert!(
// my_function();
// );
//
// Emit an error about semicolon and suggest removing it.
if parser.token == token::Semi {
let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
err.span_suggestion(
parser.token.span,
"try removing semicolon",
String::new(),
Applicability::MaybeIncorrect,
);
err.emit();
if parser.token == token::Eof {
let mut err =
cx.struct_span_err(sp, "macro requires a boolean expression as an argument");
err.span_label(sp, "boolean expression required");
return Err(err);
}

parser.bump();
}
let cond_expr = parser.parse_expr()?;

// Some crates use the `assert!` macro in the following form (note missing comma before
// message):
//
// assert!(true "error message");
//
// Emit an error and suggest inserting a comma.
let custom_message =
if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal");
let comma_span = parser.prev_token.span.shrink_to_hi();
err.span_suggestion_short(
comma_span,
"try adding a comma",
", ".to_string(),
// Some crates use the `assert!` macro in the following form (note extra semicolon):
//
// assert!(
// my_function();
// );
//
// Emit an error about semicolon and suggest removing it.
if parser.token == token::Semi {
let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
err.span_suggestion(
parser.token.span,
"try removing semicolon",
String::new(),
Applicability::MaybeIncorrect,
);
err.emit();

parse_custom_message(&mut parser)
} else if parser.eat(&token::Comma) {
parse_custom_message(&mut parser)
} else {
None
};
parser.bump();
}

// Some crates use the `assert!` macro in the following form (note missing comma before
// message):
//
// assert!(true "error message");
//
// Emit an error and suggest inserting a comma.
let custom_message =
if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal");
let comma_span = parser.prev_token.span.shrink_to_hi();
err.span_suggestion_short(
comma_span,
"try adding a comma",
", ".to_string(),
Applicability::MaybeIncorrect,
);
err.emit();

if parser.token != token::Eof {
return parser.unexpected();
Self::parse_custom_message(&mut parser)
} else if parser.eat(&token::Comma) {
Self::parse_custom_message(&mut parser)
} else {
None
};

if parser.token != token::Eof {
return parser.unexpected();
}

Ok(Assert { cond_expr, custom_message })
}

Ok(Assert { cond_expr, custom_message })
fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
let ts = parser.parse_tokens();
if !ts.is_empty() { Some(ts) } else { None }
}
}

fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
let ts = parser.parse_tokens();
if !ts.is_empty() { Some(ts) } else { None }
// if !{ ... } { ... } else { ... }
fn expr_if_not(
cx: &ExtCtxt<'_>,
span: Span,
cond: P<Expr>,
then: P<Expr>,
els: Option<P<Expr>>,
) -> P<Expr> {
cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
}
453 changes: 453 additions & 0 deletions compiler/rustc_builtin_macros/src/assert/context.rs

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions compiler/rustc_builtin_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
//! This crate contains implementations of built-in macros and other code generating facilities
//! injecting code into the crate before it is lowered to HIR.
#![allow(rustc::potential_query_instability)]
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![feature(array_windows)]
#![feature(box_patterns)]
#![feature(decl_macro)]
#![feature(is_sorted)]
#![feature(nll)]
#![feature(let_chains)]
#![feature(let_else)]
#![feature(nll)]
#![feature(proc_macro_internals)]
#![feature(proc_macro_quote)]
#![recursion_limit = "256"]
#![allow(rustc::potential_query_instability)]

extern crate proc_macro;

6 changes: 5 additions & 1 deletion compiler/rustc_expand/src/build.rs
Original file line number Diff line number Diff line change
@@ -160,7 +160,7 @@ impl<'a> ExtCtxt<'a> {
attrs: AttrVec::new(),
tokens: None,
});
ast::Stmt { id: ast::DUMMY_NODE_ID, kind: ast::StmtKind::Local(local), span: sp }
self.stmt_local(local, sp)
}

// Generates `let _: Type;`, which is usually used for type assertions.
@@ -174,6 +174,10 @@ impl<'a> ExtCtxt<'a> {
attrs: AttrVec::new(),
tokens: None,
});
self.stmt_local(local, span)
}

pub fn stmt_local(&self, local: P<ast::Local>, span: Span) -> ast::Stmt {
ast::Stmt { id: ast::DUMMY_NODE_ID, kind: ast::StmtKind::Local(local), span }
}

2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/active.rs
Original file line number Diff line number Diff line change
@@ -150,6 +150,8 @@ declare_features! (
(active, allow_internal_unstable, "1.0.0", None, None),
/// Allows identifying the `compiler_builtins` crate.
(active, compiler_builtins, "1.13.0", None, None),
/// Outputs useful `assert!` messages
(active, generic_assert, "1.63.0", None, None),
/// Allows using the `rust-intrinsic`'s "ABI".
(active, intrinsics, "1.0.0", None, None),
/// Allows using `#[lang = ".."]` attribute for linking items to special compiler logic.
8 changes: 8 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
@@ -156,6 +156,7 @@ symbols! {
C,
CStr,
CString,
Capture,
Center,
Clone,
Continue,
@@ -262,6 +263,8 @@ symbols! {
ToOwned,
ToString,
Try,
TryCaptureGeneric,
TryCapturePrintable,
TryFrom,
TryInto,
Ty,
@@ -271,6 +274,7 @@ symbols! {
UnsafeArg,
Vec,
VecDeque,
Wrapper,
Yield,
_DECLS,
_Self,
@@ -353,6 +357,7 @@ symbols! {
assert_receiver_is_total_eq,
assert_uninit_valid,
assert_zero_valid,
asserting,
associated_const_equality,
associated_consts,
associated_type_bounds,
@@ -730,6 +735,7 @@ symbols! {
generator_state,
generators,
generic_arg_infer,
generic_assert,
generic_associated_types,
generic_associated_types_extended,
generic_const_exprs,
@@ -1426,6 +1432,7 @@ symbols! {
truncf32,
truncf64,
try_blocks,
try_capture,
try_from,
try_into,
try_trait_v2,
@@ -1488,6 +1495,7 @@ symbols! {
unsized_tuple_coercion,
unstable,
untagged_unions,
unused_imports,
unused_qualifications,
unwind,
unwind_attributes,
3 changes: 3 additions & 0 deletions src/test/ui/macros/assert-trailing-junk.rs
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -1,49 +1,49 @@
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
| |
| 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);
| -^^^^^^^^^^
| |
| 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
| |
| 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;);
| ^^^^^^^^^^^^-^
| |
| 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");
| -^^^^^^^^^^^^^^^
Original file line number Diff line number Diff line change
@@ -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

3 changes: 3 additions & 0 deletions src/test/ui/macros/assert.rs
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
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
|
= 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
28 changes: 28 additions & 0 deletions src/test/ui/macros/assert.without-generic-asset.stderr
Original file line number Diff line number Diff line change
@@ -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

188 changes: 188 additions & 0 deletions src/test/ui/rfc-2011-nicer-assert-messages/all-expr-kinds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// aux-build:common.rs
// edition:2021
// ignore-tidy-linelength
// run-pass

#![allow(path_statements, unused_allocation)]
#![feature(box_syntax, core_intrinsics, generic_assert, generic_assert_internals)]

extern crate common;

// Use common::test once https://github.com/rust-lang/rust/issues/96997 is fixed
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
}

impl Foo {
fn add(&self, a: i32, b: i32) -> i32 { a + b }
}

fn add(a: i32, b: i32) -> i32 { a + b }

fn main() {
// ***** Allowed *****

tests!(
let mut elem = 1i32;

// addr of
[ &elem == &3 ] => "Assertion failed: &elem == &3\nWith captures:\n elem = 1\n"

// array
[ [elem][0] == 3 ] => "Assertion failed: [elem][0] == 3\nWith captures:\n elem = 1\n"

// binary
[ elem + 1 == 3 ] => "Assertion failed: elem + 1 == 3\nWith captures:\n elem = 1\n"

// call
[ add(elem, elem) == 3 ] => "Assertion failed: add(elem, elem) == 3\nWith captures:\n elem = 1\n"

// cast
[ elem as i32 == 3 ] => "Assertion failed: elem as i32 == 3\nWith captures:\n elem = 1\n"

// index
[ [1i32, 1][elem as usize] == 3 ] => "Assertion failed: [1i32, 1][elem as usize] == 3\nWith captures:\n elem = 1\n"

// method call
[ FOO.add(elem, elem) == 3 ] => "Assertion failed: FOO.add(elem, elem) == 3\nWith captures:\n elem = 1\n"

// paren
[ (elem) == 3 ] => "Assertion failed: (elem) == 3\nWith captures:\n elem = 1\n"

// range
[ (0..elem) == (0..3) ] => "Assertion failed: (0..elem) == (0..3)\nWith captures:\n elem = 1\n"

// repeat
[ [elem; 1] == [3; 1] ] => "Assertion failed: [elem; 1] == [3; 1]\nWith captures:\n elem = 1\n"

// struct
[ Foo { bar: elem } == Foo { bar: 3 } ] => "Assertion failed: Foo { bar: elem } == Foo { bar: 3 }\nWith captures:\n elem = 1\n"

// tuple
[ (elem, 1) == (3, 3) ] => "Assertion failed: (elem, 1) == (3, 3)\nWith captures:\n elem = 1\n"

// unary
[ -elem == -3 ] => "Assertion failed: -elem == -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
);
}
Original file line number Diff line number Diff line change
@@ -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"
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// compile-flags: --test
// run-pass

#![feature(core_intrinsics, generic_assert, generic_assert_internals)]

#[should_panic(expected = "OMG!")]
#[test]
fn test() {
assert!(1 == 3, "OMG!");
}

fn main() {
}
Original file line number Diff line number Diff line change
@@ -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"
);
}
25 changes: 25 additions & 0 deletions src/test/ui/rfc-2011-nicer-assert-messages/auxiliary/common.rs
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// compile-flags: --test
// ignore-tidy-linelength
// run-pass

#![feature(core_intrinsics, generic_assert, generic_assert_internals)]

use std::fmt::{Debug, Formatter};

#[derive(Clone, Copy, PartialEq)]
struct CopyDebug(i32);

impl Debug for CopyDebug {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str("With great power comes great electricity bills")
}
}

#[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));
}

fn main() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// check-pass
// compile-flags: -Z unpretty=expanded

#![feature(core_intrinsics, generic_assert, generic_assert_internals)]

fn arbitrary_consuming_method_for_demonstration_purposes() {
let elem = 1i32;
assert!(elem as usize);
}

fn addr_of() {
let elem = 1i32;
assert!(&elem);
}

fn binary() {
let elem = 1i32;
assert!(elem == 1);
assert!(elem >= 1);
assert!(elem > 0);
assert!(elem < 3);
assert!(elem <= 3);
assert!(elem != 3);
}

fn unary() {
let elem = &1i32;
assert!(*elem);
}

fn main() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#![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 arbitrary_consuming_method_for_demonstration_purposes() {
let elem = 1i32;
{
#[allow(unused_imports)]
use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
let mut __capture0 = ::core::asserting::Capture::new();
let __local_bind0 = &elem;
if ::core::intrinsics::unlikely(!(*{
(&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
__local_bind0
} as usize)) {




{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem as usize\nWith captures:\n elem = ",
"\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
}
}
};
}
fn addr_of() {
let elem = 1i32;
{
#[allow(unused_imports)]
use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
let mut __capture0 = ::core::asserting::Capture::new();
let __local_bind0 = &elem;
if ::core::intrinsics::unlikely(!&*__local_bind0) {
(&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: &elem\nWith captures:\n elem = ",
"\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
}
}
};
}
fn binary() {
let elem = 1i32;
{
#[allow(unused_imports)]
use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
let mut __capture0 = ::core::asserting::Capture::new();
let __local_bind0 = &elem;
if ::core::intrinsics::unlikely(!(*__local_bind0 == 1)) {
(&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem == 1\nWith captures:\n elem = ",
"\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
}
}
};
{
#[allow(unused_imports)]
use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
let mut __capture0 = ::core::asserting::Capture::new();
let __local_bind0 = &elem;
if ::core::intrinsics::unlikely(!(*__local_bind0 >= 1)) {
(&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem >= 1\nWith captures:\n elem = ",
"\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
}
}
};
{
#[allow(unused_imports)]
use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
let mut __capture0 = ::core::asserting::Capture::new();
let __local_bind0 = &elem;
if ::core::intrinsics::unlikely(!(*__local_bind0 > 0)) {
(&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem > 0\nWith captures:\n elem = ",
"\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
}
}
};
{
#[allow(unused_imports)]
use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
let mut __capture0 = ::core::asserting::Capture::new();
let __local_bind0 = &elem;
if ::core::intrinsics::unlikely(!(*__local_bind0 < 3)) {
(&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem < 3\nWith captures:\n elem = ",
"\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
}
}
};
{
#[allow(unused_imports)]
use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
let mut __capture0 = ::core::asserting::Capture::new();
let __local_bind0 = &elem;
if ::core::intrinsics::unlikely(!(*__local_bind0 <= 3)) {
(&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem <= 3\nWith captures:\n elem = ",
"\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
}
}
};
{
#[allow(unused_imports)]
use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
let mut __capture0 = ::core::asserting::Capture::new();
let __local_bind0 = &elem;
if ::core::intrinsics::unlikely(!(*__local_bind0 != 3)) {
(&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem != 3\nWith captures:\n elem = ",
"\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
}
}
};
}
fn unary() {
let elem = &1i32;
{
#[allow(unused_imports)]
use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
let mut __capture0 = ::core::asserting::Capture::new();
let __local_bind0 = &elem;
if ::core::intrinsics::unlikely(!**__local_bind0) {
(&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
{
::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: *elem\nWith captures:\n elem = ",
"\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)]))
}
}
};
}
fn main() {}