Skip to content
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

Make assert a built-in procedural macro #48813

Merged
merged 3 commits into from
Mar 16, 2018
Merged
Show file tree
Hide file tree
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
15 changes: 15 additions & 0 deletions src/libcore/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ macro_rules! panic {
/// ```
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg(stage0)]
macro_rules! assert {
($cond:expr) => (
if !$cond {
Expand Down Expand Up @@ -784,4 +785,18 @@ mod builtin {
($file:expr) => ({ /* compiler built-in */ });
($file:expr,) => ({ /* compiler built-in */ });
}

/// Ensure that a boolean expression is `true` at runtime.
///
/// For more information, see the documentation for [`std::assert!`].
///
/// [`std::assert!`]: ../std/macro.assert.html
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg(dox)]
macro_rules! assert {
($cond:expr) => ({ /* compiler built-in */ });
($cond:expr,) => ({ /* compiler built-in */ });
($cond:expr, $($arg:tt)+) => ({ /* compiler built-in */ });
}
}
3 changes: 2 additions & 1 deletion src/libstd/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,9 @@ use prelude::v1::*;
// We want to re-export a few macros from core but libcore has already been
// imported by the compiler (via our #[no_std] attribute) In this case we just
// add a new crate name so we can attach the re-exports to it.
#[macro_reexport(assert, assert_eq, assert_ne, debug_assert, debug_assert_eq,
#[macro_reexport(assert_eq, assert_ne, debug_assert, debug_assert_eq,
debug_assert_ne, unreachable, unimplemented, write, writeln, try)]
#[cfg_attr(stage0, macro_reexport(assert))]
extern crate core as __core;

#[macro_use]
Expand Down
54 changes: 54 additions & 0 deletions src/libstd/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,60 @@ pub mod builtin {
($file:expr) => ({ /* compiler built-in */ });
($file:expr,) => ({ /* compiler built-in */ });
}

/// Ensure that a boolean expression is `true` at runtime.
///
/// This will invoke the [`panic!`] macro if the provided expression cannot be
/// evaluated to `true` at runtime.
///
/// # Uses
///
/// Assertions are always checked in both debug and release builds, and cannot
/// be disabled. See [`debug_assert!`] for assertions that are not enabled in
/// release builds by default.
///
/// Unsafe code relies on `assert!` to enforce run-time invariants that, if
/// violated could lead to unsafety.
///
/// Other use-cases of `assert!` include [testing] and enforcing run-time
/// invariants in safe code (whose violation cannot result in unsafety).
///
/// # Custom Messages
///
/// This macro has a second form, where a custom panic message can
/// be provided with or without arguments for formatting. See [`std::fmt`]
/// for syntax for this form.
///
/// [`panic!`]: macro.panic.html
/// [`debug_assert!`]: macro.debug_assert.html
/// [testing]: ../book/second-edition/ch11-01-writing-tests.html#checking-results-with-the-assert-macro
/// [`std::fmt`]: ../std/fmt/index.html
///
/// # Examples
///
/// ```
/// // the panic message for these assertions is the stringified value of the
/// // expression given.
/// assert!(true);
///
/// fn some_computation() -> bool { true } // a very simple function
///
/// assert!(some_computation());
///
/// // assert with a custom message
/// let x = true;
/// assert!(x, "x wasn't true!");
///
/// let a = 3; let b = 27;
/// assert!(a + b == 30, "a = {}, b = {}", a, b);
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[macro_export]
macro_rules! assert {
($cond:expr) => ({ /* compiler built-in */ });
($cond:expr,) => ({ /* compiler built-in */ });
($cond:expr, $($arg:tt)+) => ({ /* compiler built-in */ });
}
}

/// A macro for defining #[cfg] if-else statements.
Expand Down
122 changes: 122 additions & 0 deletions src/libsyntax_ext/assert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use syntax::ast::*;
use syntax::codemap::Spanned;
use syntax::ext::base::*;
use syntax::ext::build::AstBuilder;
use syntax::parse::token;
use syntax::print::pprust;
use syntax::tokenstream::{TokenStream, TokenTree};
use syntax_pos::{Span, DUMMY_SP};

pub fn expand_assert<'cx>(
cx: &'cx mut ExtCtxt,
sp: Span,
tts: &[TokenTree],
) -> Box<MacResult + 'cx> {
let mut parser = cx.new_parser_from_tts(tts);
let cond_expr = panictry!(parser.parse_expr());
let custom_msg_args = if parser.eat(&token::Comma) {
let ts = parser.parse_tokens();
if !ts.is_empty() {
Some(ts)
} else {
None
}
} else {
None
};

let sp = sp.with_ctxt(sp.ctxt().apply_mark(cx.current_expansion.mark));
let panic_call = Mac_ {
path: Path::from_ident(sp, Ident::from_str("panic")),
tts: if let Some(ts) = custom_msg_args {
ts.into()
} else {
// `expr_to_string` escapes the string literals with `.escape_default()`
// which escapes all non-ASCII characters with `\u`.
let escaped_expr = escape_format_string(&unescape_printable_unicode(
&pprust::expr_to_string(&cond_expr),
));

TokenStream::from(TokenTree::Token(
DUMMY_SP,
token::Literal(
token::Lit::Str_(Name::intern(&format!("assertion failed: {}", escaped_expr))),
None,
),
)).into()
},
};
let if_expr = cx.expr_if(
sp,
cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)),
cx.expr(
sp,
ExprKind::Mac(Spanned {
span: sp,
node: panic_call,
}),
),
None,
);
MacEager::expr(if_expr)
}

/// Escapes a string for use as a formatting string.
fn escape_format_string(s: &str) -> String {
let mut res = String::with_capacity(s.len());
for c in s.chars() {
res.extend(c.escape_debug());
match c {
'{' | '}' => res.push(c),
_ => {}
}
}
res
}

#[test]
fn test_escape_format_string() {
assert!(escape_format_string(r"foo{}\") == r"foo{{}}\\");
}

/// Unescapes the escaped unicodes (`\u{...}`) that are printable.
fn unescape_printable_unicode(mut s: &str) -> String {
use std::{char, u32};

let mut res = String::with_capacity(s.len());

loop {
if let Some(start) = s.find(r"\u{") {
res.push_str(&s[0..start]);
s = &s[start..];
s.find('}')
.and_then(|end| {
let v = u32::from_str_radix(&s[3..end], 16).ok()?;
let c = char::from_u32(v)?;
// Escape unprintable characters.
res.extend(c.escape_debug());
s = &s[end + 1..];
Some(())
})
.expect("lexer should have rejected invalid escape sequences");
} else {
res.push_str(s);
return res;
}
}
}

#[test]
fn test_unescape_printable_unicode() {
assert!(unescape_printable_unicode(r"\u{2603}\n\u{0}") == r"☃\n\u{0}");
}
3 changes: 3 additions & 0 deletions src/libsyntax_ext/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#![feature(proc_macro_internals)]
#![feature(decl_macro)]
#![feature(str_escape)]

extern crate fmt_macros;
#[macro_use]
Expand All @@ -26,6 +27,7 @@ extern crate proc_macro;
extern crate rustc_data_structures;
extern crate rustc_errors as errors;

mod assert;
mod asm;
mod cfg;
mod compile_error;
Expand Down Expand Up @@ -111,6 +113,7 @@ pub fn register_builtins(resolver: &mut syntax::ext::base::Resolver,
log_syntax: log_syntax::expand_syntax_ext,
trace_macros: trace_macros::expand_trace_macros,
compile_error: compile_error::expand_compile_error,
assert: assert::expand_assert,
}

// format_args uses `unstable` things internally.
Expand Down
13 changes: 13 additions & 0 deletions src/test/run-pass/assert-escape.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

fn main() {
assert!(r#"☃\backslash"#.contains("\\"));
}
1 change: 1 addition & 0 deletions src/test/ui/codemap_tests/issue-28308.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@

fn main() {
assert!("foo");
//~^ ERROR cannot apply unary operator `!`
}
2 changes: 0 additions & 2 deletions src/test/ui/codemap_tests/issue-28308.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ error[E0600]: cannot apply unary operator `!` to type `&'static str`
|
LL | assert!("foo");
| ^^^^^^^^^^^^^^^
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error: aborting due to previous error

Expand Down