]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/assert.rs
Auto merge of #104673 - matthiaskrgr:rollup-85f65ov, r=matthiaskrgr
[rust.git] / compiler / rustc_builtin_macros / src / assert.rs
1 mod context;
2
3 use crate::edition_panic::use_panic_2021;
4 use rustc_ast::ptr::P;
5 use rustc_ast::token;
6 use rustc_ast::tokenstream::{DelimSpan, TokenStream};
7 use rustc_ast::{Expr, ExprKind, MacArgs, 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};
14
15 pub fn expand_assert<'cx>(
16     cx: &'cx mut ExtCtxt<'_>,
17     span: Span,
18     tts: TokenStream,
19 ) -> Box<dyn MacResult + 'cx> {
20     let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
21         Ok(assert) => assert,
22         Err(mut err) => {
23             err.emit();
24             return DummyResult::any(span);
25         }
26     };
27
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);
31
32     let panic_path = || {
33         if use_panic_2021(span) {
34             // On edition 2021, we always call `$crate::panic::panic_2021!()`.
35             Path {
36                 span: call_site_span,
37                 segments: cx
38                     .std_path(&[sym::panic, sym::panic_2021])
39                     .into_iter()
40                     .map(|ident| PathSegment::from_ident(ident))
41                     .collect(),
42                 tokens: None,
43             }
44         } else {
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))
48         }
49     };
50
51     // Simply uses the user provided message instead of generating custom outputs
52     let expr = if let Some(tokens) = custom_message {
53         let then = cx.expr(
54             call_site_span,
55             ExprKind::MacCall(P(MacCall {
56                 path: panic_path(),
57                 args: P(MacArgs::Delimited(
58                     DelimSpan::from_single(call_site_span),
59                     MacDelimiter::Parenthesis,
60                     tokens,
61                 )),
62                 prior_type_ascription: None,
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 let Some(features) = cx.ecfg.features && 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             vec![cx.expr_str(
83                 DUMMY_SP,
84                 Symbol::intern(&format!(
85                     "assertion failed: {}",
86                     pprust::expr_to_string(&cond_expr).escape_debug()
87                 )),
88             )],
89         );
90         expr_if_not(cx, call_site_span, cond_expr, then, None)
91     };
92
93     MacEager::expr(expr)
94 }
95
96 struct Assert {
97     cond_expr: P<Expr>,
98     custom_message: Option<TokenStream>,
99 }
100
101 // if !{ ... } { ... } else { ... }
102 fn expr_if_not(
103     cx: &ExtCtxt<'_>,
104     span: Span,
105     cond: P<Expr>,
106     then: P<Expr>,
107     els: Option<P<Expr>>,
108 ) -> P<Expr> {
109     cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
110 }
111
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);
114
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");
118         return Err(err);
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         let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
132         err.span_suggestion(
133             parser.token.span,
134             "try removing semicolon",
135             "",
136             Applicability::MaybeIncorrect,
137         );
138         err.emit();
139
140         parser.bump();
141     }
142
143     // Some crates use the `assert!` macro in the following form (note missing comma before
144     // message):
145     //
146     // assert!(true "error message");
147     //
148     // Emit an error and suggest inserting a comma.
149     let custom_message =
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(
154                 comma_span,
155                 "try adding a comma",
156                 ", ",
157                 Applicability::MaybeIncorrect,
158             );
159             err.emit();
160
161             parse_custom_message(&mut parser)
162         } else if parser.eat(&token::Comma) {
163             parse_custom_message(&mut parser)
164         } else {
165             None
166         };
167
168     if parser.token != token::Eof {
169         return parser.unexpected();
170     }
171
172     Ok(Assert { cond_expr, custom_message })
173 }
174
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 }
178 }