]> git.lizzy.rs Git - rust.git/blob - src/librustc_builtin_macros/assert.rs
Rollup merge of #74102 - oli-obk:const_prop_icde, r=wesleywiser
[rust.git] / src / librustc_builtin_macros / assert.rs
1 use rustc_errors::{Applicability, DiagnosticBuilder};
2
3 use rustc_ast::ast::{self, *};
4 use rustc_ast::ptr::P;
5 use rustc_ast::token::{self, TokenKind};
6 use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree};
7 use rustc_ast_pretty::pprust;
8 use rustc_expand::base::*;
9 use rustc_parse::parser::Parser;
10 use rustc_span::symbol::{sym, Ident, Symbol};
11 use rustc_span::{Span, DUMMY_SP};
12
13 pub fn expand_assert<'cx>(
14     cx: &'cx mut ExtCtxt<'_>,
15     sp: Span,
16     tts: TokenStream,
17 ) -> Box<dyn MacResult + 'cx> {
18     let Assert { cond_expr, custom_message } = match parse_assert(cx, sp, tts) {
19         Ok(assert) => assert,
20         Err(mut err) => {
21             err.emit();
22             return DummyResult::any(sp);
23         }
24     };
25
26     // `core::panic` and `std::panic` are different macros, so we use call-site
27     // context to pick up whichever is currently in scope.
28     let sp = cx.with_call_site_ctxt(sp);
29     let tokens = custom_message.unwrap_or_else(|| {
30         TokenStream::from(TokenTree::token(
31             TokenKind::lit(
32                 token::Str,
33                 Symbol::intern(&format!(
34                     "assertion failed: {}",
35                     pprust::expr_to_string(&cond_expr).escape_debug()
36                 )),
37                 None,
38             ),
39             DUMMY_SP,
40         ))
41     });
42     let args = P(MacArgs::Delimited(DelimSpan::from_single(sp), MacDelimiter::Parenthesis, tokens));
43     let panic_call = MacCall {
44         path: Path::from_ident(Ident::new(sym::panic, sp)),
45         args,
46         prior_type_ascription: None,
47     };
48     let if_expr = cx.expr_if(
49         sp,
50         cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)),
51         cx.expr(sp, ExprKind::MacCall(panic_call)),
52         None,
53     );
54     MacEager::expr(if_expr)
55 }
56
57 struct Assert {
58     cond_expr: P<ast::Expr>,
59     custom_message: Option<TokenStream>,
60 }
61
62 fn parse_assert<'a>(
63     cx: &mut ExtCtxt<'a>,
64     sp: Span,
65     stream: TokenStream,
66 ) -> Result<Assert, DiagnosticBuilder<'a>> {
67     let mut parser = cx.new_parser_from_tts(stream);
68
69     if parser.token == token::Eof {
70         let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument");
71         err.span_label(sp, "boolean expression required");
72         return Err(err);
73     }
74
75     let cond_expr = parser.parse_expr()?;
76
77     // Some crates use the `assert!` macro in the following form (note extra semicolon):
78     //
79     // assert!(
80     //     my_function();
81     // );
82     //
83     // Emit an error about semicolon and suggest removing it.
84     if parser.token == token::Semi {
85         let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
86         err.span_suggestion(
87             parser.token.span,
88             "try removing semicolon",
89             String::new(),
90             Applicability::MaybeIncorrect,
91         );
92         err.emit();
93
94         parser.bump();
95     }
96
97     // Some crates use the `assert!` macro in the following form (note missing comma before
98     // message):
99     //
100     // assert!(true "error message");
101     //
102     // Emit an error and suggest inserting a comma.
103     let custom_message =
104         if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
105             let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal");
106             let comma_span = parser.prev_token.span.shrink_to_hi();
107             err.span_suggestion_short(
108                 comma_span,
109                 "try adding a comma",
110                 ", ".to_string(),
111                 Applicability::MaybeIncorrect,
112             );
113             err.emit();
114
115             parse_custom_message(&mut parser)
116         } else if parser.eat(&token::Comma) {
117             parse_custom_message(&mut parser)
118         } else {
119             None
120         };
121
122     if parser.token != token::Eof {
123         parser.expect_one_of(&[], &[])?;
124         unreachable!();
125     }
126
127     Ok(Assert { cond_expr, custom_message })
128 }
129
130 fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
131     let ts = parser.parse_tokens();
132     if !ts.is_empty() { Some(ts) } else { None }
133 }