]>
Commit | Line | Data |
---|---|---|
a2a8927a | 1 | use crate::edition_panic::use_panic_2021; |
74b04a01 | 2 | use rustc_ast::ptr::P; |
fc512014 XL |
3 | use rustc_ast::token; |
4 | use rustc_ast::tokenstream::{DelimSpan, TokenStream}; | |
3dfed10e | 5 | use rustc_ast::{self as ast, *}; |
74b04a01 | 6 | use rustc_ast_pretty::pprust; |
5e7ed085 | 7 | use rustc_errors::{Applicability, PResult}; |
dfeec247 | 8 | use rustc_expand::base::*; |
60c5eb7d | 9 | use rustc_parse::parser::Parser; |
f9f354fc | 10 | use rustc_span::symbol::{sym, Ident, Symbol}; |
dfeec247 | 11 | use rustc_span::{Span, DUMMY_SP}; |
0531ce1d XL |
12 | |
13 | pub fn expand_assert<'cx>( | |
9fa01778 | 14 | cx: &'cx mut ExtCtxt<'_>, |
5869c6ff | 15 | span: Span, |
e1599b0c | 16 | tts: TokenStream, |
8faf50e0 | 17 | ) -> Box<dyn MacResult + 'cx> { |
5869c6ff | 18 | let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) { |
0731742a XL |
19 | Ok(assert) => assert, |
20 | Err(mut err) => { | |
21 | err.emit(); | |
5869c6ff | 22 | return DummyResult::any(span); |
0531ce1d | 23 | } |
0531ce1d XL |
24 | }; |
25 | ||
e1599b0c XL |
26 | // `core::panic` and `std::panic` are different macros, so we use call-site |
27 | // context to pick up whichever is currently in scope. | |
5869c6ff | 28 | let sp = cx.with_call_site_ctxt(span); |
fc512014 XL |
29 | |
30 | let panic_call = if let Some(tokens) = custom_message { | |
dc3f5686 | 31 | let path = if use_panic_2021(span) { |
5869c6ff XL |
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 | }; | |
fc512014 XL |
47 | // Pass the custom message to panic!(). |
48 | cx.expr( | |
49 | sp, | |
50 | ExprKind::MacCall(MacCall { | |
5869c6ff | 51 | path, |
fc512014 XL |
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, | |
dfeec247 XL |
69 | Symbol::intern(&format!( |
70 | "assertion failed: {}", | |
71 | pprust::expr_to_string(&cond_expr).escape_debug() | |
72 | )), | |
fc512014 XL |
73 | )], |
74 | ) | |
0531ce1d | 75 | }; |
fc512014 XL |
76 | let if_expr = |
77 | cx.expr_if(sp, cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)), panic_call, None); | |
0531ce1d XL |
78 | MacEager::expr(if_expr) |
79 | } | |
0731742a XL |
80 | |
81 | struct Assert { | |
82 | cond_expr: P<ast::Expr>, | |
83 | custom_message: Option<TokenStream>, | |
84 | } | |
85 | ||
5e7ed085 | 86 | fn parse_assert<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> { |
e1599b0c | 87 | let mut parser = cx.new_parser_from_tts(stream); |
0731742a XL |
88 | |
89 | if parser.token == token::Eof { | |
90 | let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument"); | |
91 | err.span_label(sp, "boolean expression required"); | |
92 | return Err(err); | |
93 | } | |
94 | ||
48663c56 XL |
95 | let cond_expr = parser.parse_expr()?; |
96 | ||
97 | // Some crates use the `assert!` macro in the following form (note extra semicolon): | |
98 | // | |
99 | // assert!( | |
100 | // my_function(); | |
101 | // ); | |
102 | // | |
74b04a01 | 103 | // Emit an error about semicolon and suggest removing it. |
48663c56 | 104 | if parser.token == token::Semi { |
74b04a01 | 105 | let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument"); |
48663c56 | 106 | err.span_suggestion( |
dc9dc135 | 107 | parser.token.span, |
48663c56 XL |
108 | "try removing semicolon", |
109 | String::new(), | |
dfeec247 | 110 | Applicability::MaybeIncorrect, |
48663c56 | 111 | ); |
48663c56 XL |
112 | err.emit(); |
113 | ||
114 | parser.bump(); | |
115 | } | |
116 | ||
117 | // Some crates use the `assert!` macro in the following form (note missing comma before | |
118 | // message): | |
119 | // | |
120 | // assert!(true "error message"); | |
121 | // | |
74b04a01 | 122 | // Emit an error and suggest inserting a comma. |
dfeec247 XL |
123 | let custom_message = |
124 | if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind { | |
74b04a01 XL |
125 | let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal"); |
126 | let comma_span = parser.prev_token.span.shrink_to_hi(); | |
dfeec247 XL |
127 | err.span_suggestion_short( |
128 | comma_span, | |
129 | "try adding a comma", | |
130 | ", ".to_string(), | |
131 | Applicability::MaybeIncorrect, | |
132 | ); | |
dfeec247 | 133 | err.emit(); |
48663c56 | 134 | |
dfeec247 XL |
135 | parse_custom_message(&mut parser) |
136 | } else if parser.eat(&token::Comma) { | |
137 | parse_custom_message(&mut parser) | |
138 | } else { | |
139 | None | |
140 | }; | |
48663c56 XL |
141 | |
142 | if parser.token != token::Eof { | |
29967ef6 | 143 | return parser.unexpected(); |
48663c56 XL |
144 | } |
145 | ||
146 | Ok(Assert { cond_expr, custom_message }) | |
147 | } | |
148 | ||
416331ca | 149 | fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> { |
48663c56 | 150 | let ts = parser.parse_tokens(); |
dfeec247 | 151 | if !ts.is_empty() { Some(ts) } else { None } |
0731742a | 152 | } |