]> git.proxmox.com Git - rustc.git/blob - src/librustc_builtin_macros/assert.rs
New upstream version 1.44.1+dfsg1
[rustc.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, 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 }