Skip to main content

rustc_builtin_macros/
assert.rs

1mod context;
2
3use rustc_ast::token::Delimiter;
4use rustc_ast::tokenstream::{DelimSpan, TokenStream};
5use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment, UnOp, token};
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::diagnostics;
15use crate::edition_panic::use_panic_2021;
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(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            }
45        } else {
46            // Before edition 2021, we call `panic!()` unqualified,
47            // such that it calls either `std::panic!()` or `core::panic!()`.
48            Path::from_ident(Ident::new(sym::panic, call_site_span))
49        }
50    };
51
52    // Simply uses the user provided message instead of generating custom outputs
53    let expr = if let Some(tokens) = custom_message {
54        let then = cx.expr(
55            call_site_span,
56            ExprKind::MacCall(Box::new(MacCall {
57                path: panic_path(),
58                args: Box::new(DelimArgs {
59                    dspan: DelimSpan::from_single(call_site_span),
60                    delim: Delimiter::Parenthesis,
61                    tokens,
62                }),
63            })),
64        );
65        expr_if_not(cx, call_site_span, cond_expr, then, None)
66    }
67    // If `generic_assert` is enabled, generates rich captured outputs
68    //
69    // FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949
70    else if cx.ecfg.features.generic_assert() {
71        context::Context::new(cx, call_site_span).build(cond_expr, panic_path())
72    }
73    // If `generic_assert` is not enabled, only outputs a literal "assertion failed: ..."
74    // string
75    else {
76        // Pass our own message directly to $crate::panicking::panic(),
77        // because it might contain `{` and `}` that should always be
78        // passed literally.
79        let then = cx.expr_call_global(
80            call_site_span,
81            cx.std_path(&[sym::panicking, sym::panic]),
82            {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(cx.expr_str(DUMMY_SP,
            Symbol::intern(&::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!("assertion failed: {0}",
                                    pprust::expr_to_string(&cond_expr)))
                        }))));
    vec
}thin_vec![cx.expr_str(
83                DUMMY_SP,
84                Symbol::intern(&format!(
85                    "assertion failed: {}",
86                    pprust::expr_to_string(&cond_expr)
87                )),
88            )],
89        );
90        expr_if_not(cx, call_site_span, cond_expr, then, None)
91    };
92
93    ExpandResult::Ready(MacEager::expr(expr))
94}
95
96struct Assert {
97    cond_expr: Box<Expr>,
98    custom_message: Option<TokenStream>,
99}
100
101// if !{ ... } { ... } else { ... }
102fn expr_if_not(
103    cx: &ExtCtxt<'_>,
104    span: Span,
105    cond: Box<Expr>,
106    then: Box<Expr>,
107    els: Option<Box<Expr>>,
108) -> Box<Expr> {
109    cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
110}
111
112fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
113    let mut parser = cx.new_parser_from_tts(stream);
114
115    if parser.token == token::Eof {
116        return Err(cx.dcx().create_err(diagnostics::AssertRequiresBoolean { span: sp }));
117    }
118
119    let cond_expr = parser.parse_expr()?;
120
121    // Some crates use the `assert!` macro in the following form (note extra semicolon):
122    //
123    // assert!(
124    //     my_function();
125    // );
126    //
127    // Emit an error about semicolon and suggest removing it.
128    if parser.token == token::Semi {
129        cx.dcx()
130            .emit_err(diagnostics::AssertRequiresExpression { span: sp, token: parser.token.span });
131        parser.bump();
132    }
133
134    // Some crates use the `assert!` macro in the following form (note missing comma before
135    // message):
136    //
137    // assert!(true "error message");
138    //
139    // Emit an error and suggest inserting a comma.
140    let custom_message =
141        if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
142            let comma = parser.prev_token.span.shrink_to_hi();
143            cx.dcx().emit_err(diagnostics::AssertMissingComma { span: parser.token.span, comma });
144
145            parse_custom_message(&mut parser)
146        } else if parser.eat(::rustc_parse::parser::token_type::ExpTokenPair {
    tok: rustc_ast::token::Comma,
    token_type: ::rustc_parse::parser::token_type::TokenType::Comma,
}exp!(Comma)) {
147            parse_custom_message(&mut parser)
148        } else {
149            None
150        };
151
152    if parser.token != token::Eof {
153        parser.unexpected()?;
154    }
155
156    Ok(Assert { cond_expr, custom_message })
157}
158
159fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
160    let ts = parser.parse_tokens();
161    if !ts.is_empty() { Some(ts) } else { None }
162}