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