Skip to content

Commit 5f3996c

Browse files
committed
Auto merge of #48813 - sinkuu:build_in_assert_macro, r=alexcrichton
Make `assert` a built-in procedural macro Makes `assert` macro a built-in one without touching its functionality. This is a prerequisite for RFC 2011 (#44838).
2 parents a7170b0 + 4a254c0 commit 5f3996c

File tree

8 files changed

+210
-3
lines changed

8 files changed

+210
-3
lines changed

src/libcore/macros.rs

+15
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ macro_rules! panic {
7676
/// ```
7777
#[macro_export]
7878
#[stable(feature = "rust1", since = "1.0.0")]
79+
#[cfg(stage0)]
7980
macro_rules! assert {
8081
($cond:expr) => (
8182
if !$cond {
@@ -784,4 +785,18 @@ mod builtin {
784785
($file:expr) => ({ /* compiler built-in */ });
785786
($file:expr,) => ({ /* compiler built-in */ });
786787
}
788+
789+
/// Ensure that a boolean expression is `true` at runtime.
790+
///
791+
/// For more information, see the documentation for [`std::assert!`].
792+
///
793+
/// [`std::assert!`]: ../std/macro.assert.html
794+
#[macro_export]
795+
#[stable(feature = "rust1", since = "1.0.0")]
796+
#[cfg(dox)]
797+
macro_rules! assert {
798+
($cond:expr) => ({ /* compiler built-in */ });
799+
($cond:expr,) => ({ /* compiler built-in */ });
800+
($cond:expr, $($arg:tt)+) => ({ /* compiler built-in */ });
801+
}
787802
}

src/libstd/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,9 @@ use prelude::v1::*;
355355
// We want to re-export a few macros from core but libcore has already been
356356
// imported by the compiler (via our #[no_std] attribute) In this case we just
357357
// add a new crate name so we can attach the re-exports to it.
358-
#[macro_reexport(assert, assert_eq, assert_ne, debug_assert, debug_assert_eq,
358+
#[macro_reexport(assert_eq, assert_ne, debug_assert, debug_assert_eq,
359359
debug_assert_ne, unreachable, unimplemented, write, writeln, try)]
360+
#[cfg_attr(stage0, macro_reexport(assert))]
360361
extern crate core as __core;
361362

362363
#[macro_use]

src/libstd/macros.rs

+54
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,60 @@ pub mod builtin {
719719
($file:expr) => ({ /* compiler built-in */ });
720720
($file:expr,) => ({ /* compiler built-in */ });
721721
}
722+
723+
/// Ensure that a boolean expression is `true` at runtime.
724+
///
725+
/// This will invoke the [`panic!`] macro if the provided expression cannot be
726+
/// evaluated to `true` at runtime.
727+
///
728+
/// # Uses
729+
///
730+
/// Assertions are always checked in both debug and release builds, and cannot
731+
/// be disabled. See [`debug_assert!`] for assertions that are not enabled in
732+
/// release builds by default.
733+
///
734+
/// Unsafe code relies on `assert!` to enforce run-time invariants that, if
735+
/// violated could lead to unsafety.
736+
///
737+
/// Other use-cases of `assert!` include [testing] and enforcing run-time
738+
/// invariants in safe code (whose violation cannot result in unsafety).
739+
///
740+
/// # Custom Messages
741+
///
742+
/// This macro has a second form, where a custom panic message can
743+
/// be provided with or without arguments for formatting. See [`std::fmt`]
744+
/// for syntax for this form.
745+
///
746+
/// [`panic!`]: macro.panic.html
747+
/// [`debug_assert!`]: macro.debug_assert.html
748+
/// [testing]: ../book/second-edition/ch11-01-writing-tests.html#checking-results-with-the-assert-macro
749+
/// [`std::fmt`]: ../std/fmt/index.html
750+
///
751+
/// # Examples
752+
///
753+
/// ```
754+
/// // the panic message for these assertions is the stringified value of the
755+
/// // expression given.
756+
/// assert!(true);
757+
///
758+
/// fn some_computation() -> bool { true } // a very simple function
759+
///
760+
/// assert!(some_computation());
761+
///
762+
/// // assert with a custom message
763+
/// let x = true;
764+
/// assert!(x, "x wasn't true!");
765+
///
766+
/// let a = 3; let b = 27;
767+
/// assert!(a + b == 30, "a = {}, b = {}", a, b);
768+
/// ```
769+
#[stable(feature = "rust1", since = "1.0.0")]
770+
#[macro_export]
771+
macro_rules! assert {
772+
($cond:expr) => ({ /* compiler built-in */ });
773+
($cond:expr,) => ({ /* compiler built-in */ });
774+
($cond:expr, $($arg:tt)+) => ({ /* compiler built-in */ });
775+
}
722776
}
723777

724778
/// A macro for defining #[cfg] if-else statements.

src/libsyntax_ext/assert.rs

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use syntax::ast::*;
12+
use syntax::codemap::Spanned;
13+
use syntax::ext::base::*;
14+
use syntax::ext::build::AstBuilder;
15+
use syntax::parse::token;
16+
use syntax::print::pprust;
17+
use syntax::tokenstream::{TokenStream, TokenTree};
18+
use syntax_pos::{Span, DUMMY_SP};
19+
20+
pub fn expand_assert<'cx>(
21+
cx: &'cx mut ExtCtxt,
22+
sp: Span,
23+
tts: &[TokenTree],
24+
) -> Box<MacResult + 'cx> {
25+
let mut parser = cx.new_parser_from_tts(tts);
26+
let cond_expr = panictry!(parser.parse_expr());
27+
let custom_msg_args = if parser.eat(&token::Comma) {
28+
let ts = parser.parse_tokens();
29+
if !ts.is_empty() {
30+
Some(ts)
31+
} else {
32+
None
33+
}
34+
} else {
35+
None
36+
};
37+
38+
let sp = sp.with_ctxt(sp.ctxt().apply_mark(cx.current_expansion.mark));
39+
let panic_call = Mac_ {
40+
path: Path::from_ident(sp, Ident::from_str("panic")),
41+
tts: if let Some(ts) = custom_msg_args {
42+
ts.into()
43+
} else {
44+
// `expr_to_string` escapes the string literals with `.escape_default()`
45+
// which escapes all non-ASCII characters with `\u`.
46+
let escaped_expr = escape_format_string(&unescape_printable_unicode(
47+
&pprust::expr_to_string(&cond_expr),
48+
));
49+
50+
TokenStream::from(TokenTree::Token(
51+
DUMMY_SP,
52+
token::Literal(
53+
token::Lit::Str_(Name::intern(&format!("assertion failed: {}", escaped_expr))),
54+
None,
55+
),
56+
)).into()
57+
},
58+
};
59+
let if_expr = cx.expr_if(
60+
sp,
61+
cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)),
62+
cx.expr(
63+
sp,
64+
ExprKind::Mac(Spanned {
65+
span: sp,
66+
node: panic_call,
67+
}),
68+
),
69+
None,
70+
);
71+
MacEager::expr(if_expr)
72+
}
73+
74+
/// Escapes a string for use as a formatting string.
75+
fn escape_format_string(s: &str) -> String {
76+
let mut res = String::with_capacity(s.len());
77+
for c in s.chars() {
78+
res.extend(c.escape_debug());
79+
match c {
80+
'{' | '}' => res.push(c),
81+
_ => {}
82+
}
83+
}
84+
res
85+
}
86+
87+
#[test]
88+
fn test_escape_format_string() {
89+
assert!(escape_format_string(r"foo{}\") == r"foo{{}}\\");
90+
}
91+
92+
/// Unescapes the escaped unicodes (`\u{...}`) that are printable.
93+
fn unescape_printable_unicode(mut s: &str) -> String {
94+
use std::{char, u32};
95+
96+
let mut res = String::with_capacity(s.len());
97+
98+
loop {
99+
if let Some(start) = s.find(r"\u{") {
100+
res.push_str(&s[0..start]);
101+
s = &s[start..];
102+
s.find('}')
103+
.and_then(|end| {
104+
let v = u32::from_str_radix(&s[3..end], 16).ok()?;
105+
let c = char::from_u32(v)?;
106+
// Escape unprintable characters.
107+
res.extend(c.escape_debug());
108+
s = &s[end + 1..];
109+
Some(())
110+
})
111+
.expect("lexer should have rejected invalid escape sequences");
112+
} else {
113+
res.push_str(s);
114+
return res;
115+
}
116+
}
117+
}
118+
119+
#[test]
120+
fn test_unescape_printable_unicode() {
121+
assert!(unescape_printable_unicode(r"\u{2603}\n\u{0}") == r"☃\n\u{0}");
122+
}

src/libsyntax_ext/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#![feature(proc_macro_internals)]
1919
#![feature(decl_macro)]
20+
#![feature(str_escape)]
2021

2122
extern crate fmt_macros;
2223
#[macro_use]
@@ -26,6 +27,7 @@ extern crate proc_macro;
2627
extern crate rustc_data_structures;
2728
extern crate rustc_errors as errors;
2829

30+
mod assert;
2931
mod asm;
3032
mod cfg;
3133
mod compile_error;
@@ -112,6 +114,7 @@ pub fn register_builtins(resolver: &mut syntax::ext::base::Resolver,
112114
log_syntax: log_syntax::expand_syntax_ext,
113115
trace_macros: trace_macros::expand_trace_macros,
114116
compile_error: compile_error::expand_compile_error,
117+
assert: assert::expand_assert,
115118
}
116119

117120
// format_args uses `unstable` things internally.

src/test/run-pass/assert-escape.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn main() {
12+
assert!(r#"☃\backslash"#.contains("\\"));
13+
}

src/test/ui/codemap_tests/issue-28308.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010

1111
fn main() {
1212
assert!("foo");
13+
//~^ ERROR cannot apply unary operator `!`
1314
}

src/test/ui/codemap_tests/issue-28308.stderr

-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ error[E0600]: cannot apply unary operator `!` to type `&'static str`
33
|
44
LL | assert!("foo");
55
| ^^^^^^^^^^^^^^^
6-
|
7-
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
86

97
error: aborting due to previous error
108

0 commit comments

Comments
 (0)