Skip to content

Commit 03cc60a

Browse files
committed
Implementation
1 parent fe9c64d commit 03cc60a

File tree

6 files changed

+576
-80
lines changed

6 files changed

+576
-80
lines changed
+105-77
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
mod context;
2+
13
use crate::edition_panic::use_panic_2021;
24
use rustc_ast::ptr::P;
35
use rustc_ast::token;
46
use rustc_ast::tokenstream::{DelimSpan, TokenStream};
5-
use rustc_ast::{self as ast, *};
7+
use rustc_ast::{Expr, ExprKind, MacArgs, MacCall, MacDelimiter, Path, PathSegment, UnOp};
68
use rustc_ast_pretty::pprust;
79
use rustc_errors::{Applicability, PResult};
8-
use rustc_expand::base::*;
10+
use rustc_expand::base::{DummyResult, ExtCtxt, MacEager, MacResult};
911
use rustc_parse::parser::Parser;
1012
use rustc_span::symbol::{sym, Ident, Symbol};
1113
use rustc_span::{Span, DUMMY_SP};
@@ -15,7 +17,7 @@ pub fn expand_assert<'cx>(
1517
span: Span,
1618
tts: TokenStream,
1719
) -> Box<dyn MacResult + 'cx> {
18-
let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
20+
let Assert { cond_expr, custom_message } = match Assert::parse(cx, span, tts) {
1921
Ok(assert) => assert,
2022
Err(mut err) => {
2123
err.emit();
@@ -25,13 +27,13 @@ pub fn expand_assert<'cx>(
2527

2628
// `core::panic` and `std::panic` are different macros, so we use call-site
2729
// context to pick up whichever is currently in scope.
28-
let sp = cx.with_call_site_ctxt(span);
30+
let call_site_span = cx.with_call_site_ctxt(span);
2931

30-
let panic_call = if let Some(tokens) = custom_message {
31-
let path = if use_panic_2021(span) {
32+
let panic_path = || {
33+
if use_panic_2021(span) {
3234
// On edition 2021, we always call `$crate::panic::panic_2021!()`.
3335
Path {
34-
span: sp,
36+
span: call_site_span,
3537
segments: cx
3638
.std_path(&[sym::panic, sym::panic_2021])
3739
.into_iter()
@@ -42,27 +44,39 @@ pub fn expand_assert<'cx>(
4244
} else {
4345
// Before edition 2021, we call `panic!()` unqualified,
4446
// 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,
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,
5055
ExprKind::MacCall(MacCall {
51-
path,
56+
path: panic_path(),
5257
args: P(MacArgs::Delimited(
53-
DelimSpan::from_single(sp),
58+
DelimSpan::from_single(call_site_span),
5459
MacDelimiter::Parenthesis,
5560
tokens,
5661
)),
5762
prior_type_ascription: None,
5863
}),
59-
)
60-
} else {
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, outputs "assertion failed: ..."
74+
else {
6175
// Pass our own message directly to $crate::panicking::panic(),
6276
// because it might contain `{` and `}` that should always be
6377
// passed literally.
64-
cx.expr_call_global(
65-
sp,
78+
let then = cx.expr_call_global(
79+
call_site_span,
6680
cx.std_path(&[sym::panicking, sym::panic]),
6781
vec![cx.expr_str(
6882
DUMMY_SP,
@@ -71,82 +85,96 @@ pub fn expand_assert<'cx>(
7185
pprust::expr_to_string(&cond_expr).escape_debug()
7286
)),
7387
)],
74-
)
88+
);
89+
expr_if_not(cx, call_site_span, cond_expr, then, None)
7590
};
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)
91+
92+
MacEager::expr(expr)
7993
}
8094

8195
struct Assert {
82-
cond_expr: P<ast::Expr>,
96+
cond_expr: P<Expr>,
8397
custom_message: Option<TokenStream>,
8498
}
8599

86-
fn parse_assert<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
87-
let mut parser = cx.new_parser_from_tts(stream);
100+
impl Assert {
101+
fn parse<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
102+
let mut parser = cx.new_parser_from_tts(stream);
88103

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-
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-
//
103-
// Emit an error about semicolon and suggest removing it.
104-
if parser.token == token::Semi {
105-
let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
106-
err.span_suggestion(
107-
parser.token.span,
108-
"try removing semicolon",
109-
String::new(),
110-
Applicability::MaybeIncorrect,
111-
);
112-
err.emit();
104+
if parser.token == token::Eof {
105+
let mut err =
106+
cx.struct_span_err(sp, "macro requires a boolean expression as an argument");
107+
err.span_label(sp, "boolean expression required");
108+
return Err(err);
109+
}
113110

114-
parser.bump();
115-
}
111+
let cond_expr = parser.parse_expr()?;
116112

117-
// Some crates use the `assert!` macro in the following form (note missing comma before
118-
// message):
119-
//
120-
// assert!(true "error message");
121-
//
122-
// Emit an error and suggest inserting a comma.
123-
let custom_message =
124-
if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
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();
127-
err.span_suggestion_short(
128-
comma_span,
129-
"try adding a comma",
130-
", ".to_string(),
113+
// Some crates use the `assert!` macro in the following form (note extra semicolon):
114+
//
115+
// assert!(
116+
// my_function();
117+
// );
118+
//
119+
// Emit an error about semicolon and suggest removing it.
120+
if parser.token == token::Semi {
121+
let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
122+
err.span_suggestion(
123+
parser.token.span,
124+
"try removing semicolon",
125+
String::new(),
131126
Applicability::MaybeIncorrect,
132127
);
133128
err.emit();
134129

135-
parse_custom_message(&mut parser)
136-
} else if parser.eat(&token::Comma) {
137-
parse_custom_message(&mut parser)
138-
} else {
139-
None
140-
};
130+
parser.bump();
131+
}
132+
133+
// Some crates use the `assert!` macro in the following form (note missing comma before
134+
// message):
135+
//
136+
// assert!(true "error message");
137+
//
138+
// Emit an error and suggest inserting a comma.
139+
let custom_message =
140+
if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
141+
let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal");
142+
let comma_span = parser.prev_token.span.shrink_to_hi();
143+
err.span_suggestion_short(
144+
comma_span,
145+
"try adding a comma",
146+
", ".to_string(),
147+
Applicability::MaybeIncorrect,
148+
);
149+
err.emit();
141150

142-
if parser.token != token::Eof {
143-
return parser.unexpected();
151+
Self::parse_custom_message(&mut parser)
152+
} else if parser.eat(&token::Comma) {
153+
Self::parse_custom_message(&mut parser)
154+
} else {
155+
None
156+
};
157+
158+
if parser.token != token::Eof {
159+
return parser.unexpected();
160+
}
161+
162+
Ok(Assert { cond_expr, custom_message })
144163
}
145164

146-
Ok(Assert { cond_expr, custom_message })
165+
fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
166+
let ts = parser.parse_tokens();
167+
if !ts.is_empty() { Some(ts) } else { None }
168+
}
147169
}
148170

149-
fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
150-
let ts = parser.parse_tokens();
151-
if !ts.is_empty() { Some(ts) } else { None }
171+
// if !{ ... } { ... } else { ... }
172+
fn expr_if_not(
173+
cx: &ExtCtxt<'_>,
174+
span: Span,
175+
cond: P<Expr>,
176+
then: P<Expr>,
177+
els: Option<P<Expr>>,
178+
) -> P<Expr> {
179+
cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
152180
}

0 commit comments

Comments
 (0)