rustc_builtin_macros/
assert.rs

1mod context;
2
3use rustc_ast::ptr::P;
4use rustc_ast::token::Delimiter;
5use rustc_ast::tokenstream::{DelimSpan, TokenStream};
6use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment, UnOp, token};
7use rustc_ast_pretty::pprust;
8use rustc_errors::PResult;
9use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
10use rustc_parse::exp;
11use rustc_parse::parser::Parser;
12use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
13use thin_vec::thin_vec;
14
15use crate::edition_panic::use_panic_2021;
16use crate::errors;
17
18pub(crate) fn expand_assert<'cx>(
19    cx: &'cx mut ExtCtxt<'_>,
20    span: Span,
21    tts: TokenStream,
22) -> MacroExpanderResult<'cx> {
23    let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
24        Ok(assert) => assert,
25        Err(err) => {
26            let guar = err.emit();
27            return ExpandResult::Ready(DummyResult::any(span, guar));
28        }
29    };
30
31    // `core::panic` and `std::panic` are different macros, so we use call-site
32    // context to pick up whichever is currently in scope.
33    let call_site_span = cx.with_call_site_ctxt(span);
34
35    let panic_path = || {
36        if use_panic_2021(span) {
37            // On edition 2021, we always call `$crate::panic::panic_2021!()`.
38            Path {
39                span: call_site_span,
40                segments: cx
41                    .std_path(&[sym::panic, sym::panic_2021])
42                    .into_iter()
43                    .map(|ident| PathSegment::from_ident(ident))
44                    .collect(),
45                tokens: None,
46            }
47        } else {
48            // Before edition 2021, we call `panic!()` unqualified,
49            // such that it calls either `std::panic!()` or `core::panic!()`.
50            Path::from_ident(Ident::new(sym::panic, call_site_span))
51        }
52    };
53
54    // Simply uses the user provided message instead of generating custom outputs
55    let expr = if let Some(tokens) = custom_message {
56        let then = cx.expr(
57            call_site_span,
58            ExprKind::MacCall(P(MacCall {
59                path: panic_path(),
60                args: P(DelimArgs {
61                    dspan: DelimSpan::from_single(call_site_span),
62                    delim: Delimiter::Parenthesis,
63                    tokens,
64                }),
65            })),
66        );
67        expr_if_not(cx, call_site_span, cond_expr, then, None)
68    }
69    // If `generic_assert` is enabled, generates rich captured outputs
70    //
71    // FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949
72    else if cx.ecfg.features.generic_assert() {
73        context::Context::new(cx, call_site_span).build(cond_expr, panic_path())
74    }
75    // If `generic_assert` is not enabled, only outputs a literal "assertion failed: ..."
76    // string
77    else {
78        // Pass our own message directly to $crate::panicking::panic(),
79        // because it might contain `{` and `}` that should always be
80        // passed literally.
81        let then = cx.expr_call_global(
82            call_site_span,
83            cx.std_path(&[sym::panicking, sym::panic]),
84            thin_vec![cx.expr_str(
85                DUMMY_SP,
86                Symbol::intern(&format!(
87                    "assertion failed: {}",
88                    pprust::expr_to_string(&cond_expr)
89                )),
90            )],
91        );
92        expr_if_not(cx, call_site_span, cond_expr, then, None)
93    };
94
95    ExpandResult::Ready(MacEager::expr(expr))
96}
97
98struct Assert {
99    cond_expr: P<Expr>,
100    custom_message: Option<TokenStream>,
101}
102
103// if !{ ... } { ... } else { ... }
104fn expr_if_not(
105    cx: &ExtCtxt<'_>,
106    span: Span,
107    cond: P<Expr>,
108    then: P<Expr>,
109    els: Option<P<Expr>>,
110) -> P<Expr> {
111    cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
112}
113
114fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
115    let mut parser = cx.new_parser_from_tts(stream);
116
117    if parser.token == token::Eof {
118        return Err(cx.dcx().create_err(errors::AssertRequiresBoolean { span: sp }));
119    }
120
121    let cond_expr = parser.parse_expr()?;
122
123    // Some crates use the `assert!` macro in the following form (note extra semicolon):
124    //
125    // assert!(
126    //     my_function();
127    // );
128    //
129    // Emit an error about semicolon and suggest removing it.
130    if parser.token == token::Semi {
131        cx.dcx().emit_err(errors::AssertRequiresExpression { span: sp, token: parser.token.span });
132        parser.bump();
133    }
134
135    // Some crates use the `assert!` macro in the following form (note missing comma before
136    // message):
137    //
138    // assert!(true "error message");
139    //
140    // Emit an error and suggest inserting a comma.
141    let custom_message =
142        if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
143            let comma = parser.prev_token.span.shrink_to_hi();
144            cx.dcx().emit_err(errors::AssertMissingComma { span: parser.token.span, comma });
145
146            parse_custom_message(&mut parser)
147        } else if parser.eat(exp!(Comma)) {
148            parse_custom_message(&mut parser)
149        } else {
150            None
151        };
152
153    if parser.token != token::Eof {
154        parser.unexpected()?;
155    }
156
157    Ok(Assert { cond_expr, custom_message })
158}
159
160fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
161    let ts = parser.parse_tokens();
162    if !ts.is_empty() { Some(ts) } else { None }
163}