]>
Commit | Line | Data |
---|---|---|
923072b8 FG |
1 | mod context; |
2 | ||
a2a8927a | 3 | use crate::edition_panic::use_panic_2021; |
74b04a01 | 4 | use rustc_ast::ptr::P; |
fc512014 XL |
5 | use rustc_ast::token; |
6 | use rustc_ast::tokenstream::{DelimSpan, TokenStream}; | |
6522a427 | 7 | use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, MacDelimiter, Path, PathSegment, UnOp}; |
74b04a01 | 8 | use rustc_ast_pretty::pprust; |
5e7ed085 | 9 | use rustc_errors::{Applicability, PResult}; |
923072b8 | 10 | use rustc_expand::base::{DummyResult, ExtCtxt, MacEager, MacResult}; |
60c5eb7d | 11 | use rustc_parse::parser::Parser; |
f9f354fc | 12 | use rustc_span::symbol::{sym, Ident, Symbol}; |
dfeec247 | 13 | use rustc_span::{Span, DUMMY_SP}; |
0531ce1d XL |
14 | |
15 | pub fn expand_assert<'cx>( | |
9fa01778 | 16 | cx: &'cx mut ExtCtxt<'_>, |
5869c6ff | 17 | span: Span, |
e1599b0c | 18 | tts: TokenStream, |
8faf50e0 | 19 | ) -> Box<dyn MacResult + 'cx> { |
5869c6ff | 20 | let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) { |
0731742a XL |
21 | Ok(assert) => assert, |
22 | Err(mut err) => { | |
23 | err.emit(); | |
5869c6ff | 24 | return DummyResult::any(span); |
0531ce1d | 25 | } |
0531ce1d XL |
26 | }; |
27 | ||
e1599b0c XL |
28 | // `core::panic` and `std::panic` are different macros, so we use call-site |
29 | // context to pick up whichever is currently in scope. | |
923072b8 | 30 | let call_site_span = cx.with_call_site_ctxt(span); |
fc512014 | 31 | |
923072b8 FG |
32 | let panic_path = || { |
33 | if use_panic_2021(span) { | |
5869c6ff XL |
34 | // On edition 2021, we always call `$crate::panic::panic_2021!()`. |
35 | Path { | |
923072b8 | 36 | span: call_site_span, |
5869c6ff XL |
37 | segments: cx |
38 | .std_path(&[sym::panic, sym::panic_2021]) | |
39 | .into_iter() | |
40 | .map(|ident| PathSegment::from_ident(ident)) | |
41 | .collect(), | |
42 | tokens: None, | |
43 | } | |
44 | } else { | |
45 | // Before edition 2021, we call `panic!()` unqualified, | |
46 | // such that it calls either `std::panic!()` or `core::panic!()`. | |
923072b8 FG |
47 | Path::from_ident(Ident::new(sym::panic, call_site_span)) |
48 | } | |
49 | }; | |
50 | ||
51 | // Simply uses the user provided message instead of generating custom outputs | |
52 | let expr = if let Some(tokens) = custom_message { | |
53 | let then = cx.expr( | |
54 | call_site_span, | |
f2b60f7d | 55 | ExprKind::MacCall(P(MacCall { |
923072b8 | 56 | path: panic_path(), |
6522a427 EL |
57 | args: P(DelimArgs { |
58 | dspan: DelimSpan::from_single(call_site_span), | |
59 | delim: MacDelimiter::Parenthesis, | |
fc512014 | 60 | tokens, |
6522a427 | 61 | }), |
fc512014 | 62 | prior_type_ascription: None, |
f2b60f7d | 63 | })), |
923072b8 FG |
64 | ); |
65 | expr_if_not(cx, call_site_span, cond_expr, then, None) | |
66 | } | |
67 | // If `generic_assert` is enabled, generates rich captured outputs | |
68 | // | |
69 | // FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949 | |
70 | else if let Some(features) = cx.ecfg.features && features.generic_assert { | |
71 | context::Context::new(cx, call_site_span).build(cond_expr, panic_path()) | |
72 | } | |
73 | // If `generic_assert` is not enabled, only outputs a literal "assertion failed: ..." | |
74 | // string | |
75 | else { | |
fc512014 XL |
76 | // Pass our own message directly to $crate::panicking::panic(), |
77 | // because it might contain `{` and `}` that should always be | |
78 | // passed literally. | |
923072b8 FG |
79 | let then = cx.expr_call_global( |
80 | call_site_span, | |
fc512014 XL |
81 | cx.std_path(&[sym::panicking, sym::panic]), |
82 | vec![cx.expr_str( | |
83 | DUMMY_SP, | |
dfeec247 XL |
84 | Symbol::intern(&format!( |
85 | "assertion failed: {}", | |
86 | pprust::expr_to_string(&cond_expr).escape_debug() | |
87 | )), | |
fc512014 | 88 | )], |
923072b8 FG |
89 | ); |
90 | expr_if_not(cx, call_site_span, cond_expr, then, None) | |
0531ce1d | 91 | }; |
923072b8 FG |
92 | |
93 | MacEager::expr(expr) | |
0531ce1d | 94 | } |
0731742a XL |
95 | |
96 | struct Assert { | |
923072b8 | 97 | cond_expr: P<Expr>, |
0731742a XL |
98 | custom_message: Option<TokenStream>, |
99 | } | |
100 | ||
923072b8 FG |
101 | // if !{ ... } { ... } else { ... } |
102 | fn expr_if_not( | |
103 | cx: &ExtCtxt<'_>, | |
104 | span: Span, | |
105 | cond: P<Expr>, | |
106 | then: P<Expr>, | |
107 | els: Option<P<Expr>>, | |
108 | ) -> P<Expr> { | |
109 | cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els) | |
110 | } | |
111 | ||
5e7ed085 | 112 | fn parse_assert<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> { |
e1599b0c | 113 | let mut parser = cx.new_parser_from_tts(stream); |
0731742a XL |
114 | |
115 | if parser.token == token::Eof { | |
116 | let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument"); | |
117 | err.span_label(sp, "boolean expression required"); | |
118 | return Err(err); | |
119 | } | |
120 | ||
48663c56 XL |
121 | let cond_expr = parser.parse_expr()?; |
122 | ||
123 | // Some crates use the `assert!` macro in the following form (note extra semicolon): | |
124 | // | |
125 | // assert!( | |
126 | // my_function(); | |
127 | // ); | |
128 | // | |
74b04a01 | 129 | // Emit an error about semicolon and suggest removing it. |
48663c56 | 130 | if parser.token == token::Semi { |
74b04a01 | 131 | let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument"); |
48663c56 | 132 | err.span_suggestion( |
dc9dc135 | 133 | parser.token.span, |
48663c56 | 134 | "try removing semicolon", |
923072b8 | 135 | "", |
dfeec247 | 136 | Applicability::MaybeIncorrect, |
48663c56 | 137 | ); |
48663c56 XL |
138 | err.emit(); |
139 | ||
140 | parser.bump(); | |
141 | } | |
142 | ||
143 | // Some crates use the `assert!` macro in the following form (note missing comma before | |
144 | // message): | |
145 | // | |
146 | // assert!(true "error message"); | |
147 | // | |
74b04a01 | 148 | // Emit an error and suggest inserting a comma. |
dfeec247 XL |
149 | let custom_message = |
150 | if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind { | |
74b04a01 XL |
151 | let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal"); |
152 | let comma_span = parser.prev_token.span.shrink_to_hi(); | |
dfeec247 XL |
153 | err.span_suggestion_short( |
154 | comma_span, | |
155 | "try adding a comma", | |
923072b8 | 156 | ", ", |
dfeec247 XL |
157 | Applicability::MaybeIncorrect, |
158 | ); | |
dfeec247 | 159 | err.emit(); |
48663c56 | 160 | |
dfeec247 XL |
161 | parse_custom_message(&mut parser) |
162 | } else if parser.eat(&token::Comma) { | |
163 | parse_custom_message(&mut parser) | |
164 | } else { | |
165 | None | |
166 | }; | |
48663c56 XL |
167 | |
168 | if parser.token != token::Eof { | |
29967ef6 | 169 | return parser.unexpected(); |
48663c56 XL |
170 | } |
171 | ||
172 | Ok(Assert { cond_expr, custom_message }) | |
173 | } | |
174 | ||
416331ca | 175 | fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> { |
48663c56 | 176 | let ts = parser.parse_tokens(); |
dfeec247 | 177 | if !ts.is_empty() { Some(ts) } else { None } |
0731742a | 178 | } |