]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/assert.rs
Rollup merge of #80573 - jyn514:tool-lints, r=GuillaumeGomez
[rust.git] / compiler / rustc_builtin_macros / src / assert.rs
1 use rustc_errors::{Applicability, DiagnosticBuilder};
2
3 use rustc_ast::ptr::P;
4 use rustc_ast::token;
5 use rustc_ast::tokenstream::{DelimSpan, TokenStream};
6 use rustc_ast::{self as ast, *};
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
30     let panic_call = if let Some(tokens) = custom_message {
31         // Pass the custom message to panic!().
32         cx.expr(
33             sp,
34             ExprKind::MacCall(MacCall {
35                 path: Path::from_ident(Ident::new(sym::panic, sp)),
36                 args: P(MacArgs::Delimited(
37                     DelimSpan::from_single(sp),
38                     MacDelimiter::Parenthesis,
39                     tokens,
40                 )),
41                 prior_type_ascription: None,
42             }),
43         )
44     } else {
45         // Pass our own message directly to $crate::panicking::panic(),
46         // because it might contain `{` and `}` that should always be
47         // passed literally.
48         cx.expr_call_global(
49             sp,
50             cx.std_path(&[sym::panicking, sym::panic]),
51             vec![cx.expr_str(
52                 DUMMY_SP,
53                 Symbol::intern(&format!(
54                     "assertion failed: {}",
55                     pprust::expr_to_string(&cond_expr).escape_debug()
56                 )),
57             )],
58         )
59     };
60     let if_expr =
61         cx.expr_if(sp, cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)), panic_call, None);
62     MacEager::expr(if_expr)
63 }
64
65 struct Assert {
66     cond_expr: P<ast::Expr>,
67     custom_message: Option<TokenStream>,
68 }
69
70 fn parse_assert<'a>(
71     cx: &mut ExtCtxt<'a>,
72     sp: Span,
73     stream: TokenStream,
74 ) -> Result<Assert, DiagnosticBuilder<'a>> {
75     let mut parser = cx.new_parser_from_tts(stream);
76
77     if parser.token == token::Eof {
78         let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument");
79         err.span_label(sp, "boolean expression required");
80         return Err(err);
81     }
82
83     let cond_expr = parser.parse_expr()?;
84
85     // Some crates use the `assert!` macro in the following form (note extra semicolon):
86     //
87     // assert!(
88     //     my_function();
89     // );
90     //
91     // Emit an error about semicolon and suggest removing it.
92     if parser.token == token::Semi {
93         let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
94         err.span_suggestion(
95             parser.token.span,
96             "try removing semicolon",
97             String::new(),
98             Applicability::MaybeIncorrect,
99         );
100         err.emit();
101
102         parser.bump();
103     }
104
105     // Some crates use the `assert!` macro in the following form (note missing comma before
106     // message):
107     //
108     // assert!(true "error message");
109     //
110     // Emit an error and suggest inserting a comma.
111     let custom_message =
112         if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
113             let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal");
114             let comma_span = parser.prev_token.span.shrink_to_hi();
115             err.span_suggestion_short(
116                 comma_span,
117                 "try adding a comma",
118                 ", ".to_string(),
119                 Applicability::MaybeIncorrect,
120             );
121             err.emit();
122
123             parse_custom_message(&mut parser)
124         } else if parser.eat(&token::Comma) {
125             parse_custom_message(&mut parser)
126         } else {
127             None
128         };
129
130     if parser.token != token::Eof {
131         return parser.unexpected();
132     }
133
134     Ok(Assert { cond_expr, custom_message })
135 }
136
137 fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
138     let ts = parser.parse_tokens();
139     if !ts.is_empty() { Some(ts) } else { None }
140 }