1 use rustc_errors::{Applicability, DiagnosticBuilder};
5 use rustc_ast::tokenstream::{DelimSpan, TokenStream};
6 use rustc_ast::{self as ast, *};
7 use rustc_ast_pretty::pprust;
8 use rustc_expand::base::*;
9 use rustc_parse::parser::Parser;
10 use rustc_span::symbol::{sym, Ident, Symbol};
11 use rustc_span::{Span, DUMMY_SP};
13 pub fn expand_assert<'cx>(
14 cx: &'cx mut ExtCtxt<'_>,
17 ) -> Box<dyn MacResult + 'cx> {
18 let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
22 return DummyResult::any(span);
26 // `core::panic` and `std::panic` are different macros, so we use call-site
27 // context to pick up whichever is currently in scope.
28 let sp = cx.with_call_site_ctxt(span);
30 let panic_call = if let Some(tokens) = custom_message {
31 let path = if span.rust_2021() {
32 // On edition 2021, we always call `$crate::panic::panic_2021!()`.
36 .std_path(&[sym::panic, sym::panic_2021])
38 .map(|ident| PathSegment::from_ident(ident))
43 // Before edition 2021, we call `panic!()` unqualified,
44 // such that it calls either `std::panic!()` or `core::panic!()`.
45 Path::from_ident(Ident::new(sym::panic, sp))
47 // Pass the custom message to panic!().
50 ExprKind::MacCall(MacCall {
52 args: P(MacArgs::Delimited(
53 DelimSpan::from_single(sp),
54 MacDelimiter::Parenthesis,
57 prior_type_ascription: None,
61 // Pass our own message directly to $crate::panicking::panic(),
62 // because it might contain `{` and `}` that should always be
66 cx.std_path(&[sym::panicking, sym::panic]),
69 Symbol::intern(&format!(
70 "assertion failed: {}",
71 pprust::expr_to_string(&cond_expr).escape_debug()
77 cx.expr_if(sp, cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)), panic_call, None);
78 MacEager::expr(if_expr)
82 cond_expr: P<ast::Expr>,
83 custom_message: Option<TokenStream>,
90 ) -> Result<Assert, DiagnosticBuilder<'a>> {
91 let mut parser = cx.new_parser_from_tts(stream);
93 if parser.token == token::Eof {
94 let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument");
95 err.span_label(sp, "boolean expression required");
99 let cond_expr = parser.parse_expr()?;
101 // Some crates use the `assert!` macro in the following form (note extra semicolon):
107 // Emit an error about semicolon and suggest removing it.
108 if parser.token == token::Semi {
109 let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
112 "try removing semicolon",
114 Applicability::MaybeIncorrect,
121 // Some crates use the `assert!` macro in the following form (note missing comma before
124 // assert!(true "error message");
126 // Emit an error and suggest inserting a comma.
128 if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
129 let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal");
130 let comma_span = parser.prev_token.span.shrink_to_hi();
131 err.span_suggestion_short(
133 "try adding a comma",
135 Applicability::MaybeIncorrect,
139 parse_custom_message(&mut parser)
140 } else if parser.eat(&token::Comma) {
141 parse_custom_message(&mut parser)
146 if parser.token != token::Eof {
147 return parser.unexpected();
150 Ok(Assert { cond_expr, custom_message })
153 fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
154 let ts = parser.parse_tokens();
155 if !ts.is_empty() { Some(ts) } else { None }