rustc_builtin_macros/
assert.rs

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