3 use crate::edition_panic::use_panic_2021;
6 use rustc_ast::tokenstream::{DelimSpan, TokenStream};
7 use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, MacDelimiter, Path, PathSegment, UnOp};
8 use rustc_ast_pretty::pprust;
9 use rustc_errors::{Applicability, PResult};
10 use rustc_expand::base::{DummyResult, ExtCtxt, MacEager, MacResult};
11 use rustc_parse::parser::Parser;
12 use rustc_span::symbol::{sym, Ident, Symbol};
13 use rustc_span::{Span, DUMMY_SP};
15 pub fn expand_assert<'cx>(
16 cx: &'cx mut ExtCtxt<'_>,
19 ) -> Box<dyn MacResult + 'cx> {
20 let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
24 return DummyResult::any(span);
28 // `core::panic` and `std::panic` are different macros, so we use call-site
29 // context to pick up whichever is currently in scope.
30 let call_site_span = cx.with_call_site_ctxt(span);
33 if use_panic_2021(span) {
34 // On edition 2021, we always call `$crate::panic::panic_2021!()`.
38 .std_path(&[sym::panic, sym::panic_2021])
40 .map(|ident| PathSegment::from_ident(ident))
45 // Before edition 2021, we call `panic!()` unqualified,
46 // such that it calls either `std::panic!()` or `core::panic!()`.
47 Path::from_ident(Ident::new(sym::panic, call_site_span))
51 // Simply uses the user provided message instead of generating custom outputs
52 let expr = if let Some(tokens) = custom_message {
55 ExprKind::MacCall(P(MacCall {
58 dspan: DelimSpan::from_single(call_site_span),
59 delim: MacDelimiter::Parenthesis,
62 prior_type_ascription: None,
65 expr_if_not(cx, call_site_span, cond_expr, then, None)
67 // If `generic_assert` is enabled, generates rich captured outputs
69 // FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949
70 else if let Some(features) = cx.ecfg.features && features.generic_assert {
71 context::Context::new(cx, call_site_span).build(cond_expr, panic_path())
73 // If `generic_assert` is not enabled, only outputs a literal "assertion failed: ..."
76 // Pass our own message directly to $crate::panicking::panic(),
77 // because it might contain `{` and `}` that should always be
79 let then = cx.expr_call_global(
81 cx.std_path(&[sym::panicking, sym::panic]),
84 Symbol::intern(&format!(
85 "assertion failed: {}",
86 pprust::expr_to_string(&cond_expr).escape_debug()
90 expr_if_not(cx, call_site_span, cond_expr, then, None)
98 custom_message: Option<TokenStream>,
101 // if !{ ... } { ... } else { ... }
107 els: Option<P<Expr>>,
109 cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
112 fn parse_assert<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
113 let mut parser = cx.new_parser_from_tts(stream);
115 if parser.token == token::Eof {
116 let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument");
117 err.span_label(sp, "boolean expression required");
121 let cond_expr = parser.parse_expr()?;
123 // Some crates use the `assert!` macro in the following form (note extra semicolon):
129 // Emit an error about semicolon and suggest removing it.
130 if parser.token == token::Semi {
131 let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
134 "try removing semicolon",
136 Applicability::MaybeIncorrect,
143 // Some crates use the `assert!` macro in the following form (note missing comma before
146 // assert!(true "error message");
148 // Emit an error and suggest inserting a comma.
150 if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
151 let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal");
152 let comma_span = parser.prev_token.span.shrink_to_hi();
153 err.span_suggestion_short(
155 "try adding a comma",
157 Applicability::MaybeIncorrect,
161 parse_custom_message(&mut parser)
162 } else if parser.eat(&token::Comma) {
163 parse_custom_message(&mut parser)
168 if parser.token != token::Eof {
169 return parser.unexpected();
172 Ok(Assert { cond_expr, custom_message })
175 fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
176 let ts = parser.parse_tokens();
177 if !ts.is_empty() { Some(ts) } else { None }