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