5 tokenstream
::{DelimSpan, TokenStream, TokenTree}
,
6 BinOpKind
, BorrowKind
, DelimArgs
, Expr
, ExprKind
, ItemKind
, MacCall
, MethodCall
, Mutability
,
7 Path
, PathSegment
, Stmt
, StructRest
, UnOp
, UseTree
, UseTreeKind
, DUMMY_NODE_ID
,
9 use rustc_ast_pretty
::pprust
;
10 use rustc_data_structures
::fx
::FxHashSet
;
11 use rustc_expand
::base
::ExtCtxt
;
13 symbol
::{sym, Ident, Symbol}
,
16 use thin_vec
::{thin_vec, ThinVec}
;
18 pub(super) struct Context
<'cx
, 'a
> {
21 // Elements that aren't consumed (PartialEq, PartialOrd, ...) can be copied **after** the
22 // `assert!` expression fails rather than copied on-the-fly.
23 best_case_captures
: Vec
<Stmt
>,
24 // Top-level `let captureN = Capture::new()` statements
25 capture_decls
: Vec
<Capture
>,
27 // Formatting string used for debugging
29 // If the current expression being visited consumes itself. Used to construct
30 // `best_case_captures`.
32 // Top-level `let __local_bindN = &expr` statements
33 local_bind_decls
: Vec
<Stmt
>,
34 // Used to avoid capturing duplicated paths
38 // assert!(add(a, a) == 3);
40 paths
: FxHashSet
<Ident
>,
44 impl<'cx
, 'a
> Context
<'cx
, 'a
> {
45 pub(super) fn new(cx
: &'cx ExtCtxt
<'a
>, span
: Span
) -> Self {
47 best_case_captures
: <_
>::default(),
48 capture_decls
: <_
>::default(),
50 fmt_string
: <_
>::default(),
52 local_bind_decls
: <_
>::default(),
53 paths
: <_
>::default(),
58 /// Builds the whole `assert!` expression. For example, `let elem = 1; assert!(elem == 1);` expands to:
61 /// #![feature(generic_assert_internals)]
64 /// #[allow(unused_imports)]
65 /// use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
66 /// let mut __capture0 = ::core::asserting::Capture::new();
67 /// let __local_bind0 = &elem;
70 /// (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
74 /// panic!("Assertion failed: elem == 1\nWith captures:\n elem = {:?}", __capture0)
78 pub(super) fn build(mut self, mut cond_expr
: P
<Expr
>, panic_path
: Path
) -> P
<Expr
> {
79 let expr_str
= pprust
::expr_to_string(&cond_expr
);
80 self.manage_cond_expr(&mut cond_expr
);
81 let initial_imports
= self.build_initial_imports();
82 let panic
= self.build_panic(&expr_str
, panic_path
);
83 let cond_expr_with_unlikely
= self.build_unlikely(cond_expr
);
85 let Self { best_case_captures, capture_decls, cx, local_bind_decls, span, .. }
= self;
87 let mut assert_then_stmts
= ThinVec
::with_capacity(2);
88 assert_then_stmts
.extend(best_case_captures
);
89 assert_then_stmts
.push(self.cx
.stmt_expr(panic
));
90 let assert_then
= self.cx
.block(span
, assert_then_stmts
);
92 let mut stmts
= ThinVec
::with_capacity(4);
93 stmts
.push(initial_imports
);
94 stmts
.extend(capture_decls
.into_iter().map(|c
| c
.decl
));
95 stmts
.extend(local_bind_decls
);
97 cx
.stmt_expr(cx
.expr(span
, ExprKind
::If(cond_expr_with_unlikely
, assert_then
, None
))),
99 cx
.expr_block(cx
.block(span
, stmts
))
102 /// Initial **trait** imports
104 /// use ::core::asserting::{ ... };
105 fn build_initial_imports(&self) -> Stmt
{
106 let nested_tree
= |this
: &Self, sym
| {
109 prefix
: this
.cx
.path(this
.span
, vec
![Ident
::with_dummy_span(sym
)]),
110 kind
: UseTreeKind
::Simple(None
),
121 thin_vec
![self.cx
.attr_nested_word(sym
::allow
, sym
::unused_imports
, self.span
)],
122 ItemKind
::Use(UseTree
{
123 prefix
: self.cx
.path(self.span
, self.cx
.std_path(&[sym
::asserting
])),
124 kind
: UseTreeKind
::Nested(thin_vec
![
125 nested_tree(self, sym
::TryCaptureGeneric
),
126 nested_tree(self, sym
::TryCapturePrintable
),
134 /// Takes the conditional expression of `assert!` and then wraps it inside `unlikely`
135 fn build_unlikely(&self, cond_expr
: P
<Expr
>) -> P
<Expr
> {
136 let unlikely_path
= self.cx
.std_path(&[sym
::intrinsics
, sym
::unlikely
]);
139 self.cx
.expr_path(self.cx
.path(self.span
, unlikely_path
)),
140 thin_vec
![self.cx
.expr(self.span
, ExprKind
::Unary(UnOp
::Not
, cond_expr
))],
144 /// The necessary custom `panic!(...)` expression.
147 /// "Assertion failed: ... \n With expansion: ...",
151 fn build_panic(&self, expr_str
: &str, panic_path
: Path
) -> P
<Expr
> {
152 let escaped_expr_str
= escape_to_fmt(expr_str
);
154 TokenTree
::token_alone(
155 token
::Literal(token
::Lit
{
156 kind
: token
::LitKind
::Str
,
157 symbol
: Symbol
::intern(&if self.fmt_string
.is_empty() {
158 format
!("Assertion failed: {escaped_expr_str}")
161 "Assertion failed: {escaped_expr_str}\nWith captures:\n{}",
169 TokenTree
::token_alone(token
::Comma
, self.span
),
171 let captures
= self.capture_decls
.iter().flat_map(|cap
| {
173 TokenTree
::token_alone(token
::Ident(cap
.ident
.name
, false), cap
.ident
.span
),
174 TokenTree
::token_alone(token
::Comma
, self.span
),
179 ExprKind
::MacCall(P(MacCall
{
182 dspan
: DelimSpan
::from_single(self.span
),
183 delim
: Delimiter
::Parenthesis
,
184 tokens
: initial
.into_iter().chain(captures
).collect
::<TokenStream
>(),
190 /// Recursive function called until `cond_expr` and `fmt_str` are fully modified.
192 /// See [Self::manage_initial_capture] and [Self::manage_try_capture]
193 fn manage_cond_expr(&mut self, expr
: &mut P
<Expr
>) {
194 match &mut expr
.kind
{
195 ExprKind
::AddrOf(_
, mutability
, local_expr
) => {
196 self.with_is_consumed_management(
197 matches
!(mutability
, Mutability
::Mut
),
198 |this
| this
.manage_cond_expr(local_expr
)
201 ExprKind
::Array(local_exprs
) => {
202 for local_expr
in local_exprs
{
203 self.manage_cond_expr(local_expr
);
206 ExprKind
::Binary(op
, lhs
, rhs
) => {
207 self.with_is_consumed_management(
224 this
.manage_cond_expr(lhs
);
225 this
.manage_cond_expr(rhs
);
229 ExprKind
::Call(_
, local_exprs
) => {
230 for local_expr
in local_exprs
{
231 self.manage_cond_expr(local_expr
);
234 ExprKind
::Cast(local_expr
, _
) => {
235 self.manage_cond_expr(local_expr
);
237 ExprKind
::If(local_expr
, _
, _
) => {
238 self.manage_cond_expr(local_expr
);
240 ExprKind
::Index(prefix
, suffix
, _
) => {
241 self.manage_cond_expr(prefix
);
242 self.manage_cond_expr(suffix
);
244 ExprKind
::Let(_
, local_expr
, _
) => {
245 self.manage_cond_expr(local_expr
);
247 ExprKind
::Match(local_expr
, _
) => {
248 self.manage_cond_expr(local_expr
);
250 ExprKind
::MethodCall(call
) => {
251 for arg
in &mut call
.args
{
252 self.manage_cond_expr(arg
);
255 ExprKind
::Path(_
, Path { segments, .. }
) if let [path_segment
] = &segments
[..] => {
256 let path_ident
= path_segment
.ident
;
257 self.manage_initial_capture(expr
, path_ident
);
259 ExprKind
::Paren(local_expr
) => {
260 self.manage_cond_expr(local_expr
);
262 ExprKind
::Range(prefix
, suffix
, _
) => {
263 if let Some(elem
) = prefix
{
264 self.manage_cond_expr(elem
);
266 if let Some(elem
) = suffix
{
267 self.manage_cond_expr(elem
);
270 ExprKind
::Repeat(local_expr
, elem
) => {
271 self.manage_cond_expr(local_expr
);
272 self.manage_cond_expr(&mut elem
.value
);
274 ExprKind
::Struct(elem
) => {
275 for field
in &mut elem
.fields
{
276 self.manage_cond_expr(&mut field
.expr
);
278 if let StructRest
::Base(local_expr
) = &mut elem
.rest
{
279 self.manage_cond_expr(local_expr
);
282 ExprKind
::Tup(local_exprs
) => {
283 for local_expr
in local_exprs
{
284 self.manage_cond_expr(local_expr
);
287 ExprKind
::Unary(un_op
, local_expr
) => {
288 self.with_is_consumed_management(
289 matches
!(un_op
, UnOp
::Neg
| UnOp
::Not
),
290 |this
| this
.manage_cond_expr(local_expr
)
293 // Expressions that are not worth or can not be captured.
295 // Full list instead of `_` to catch possible future inclusions and to
296 // sync with the `rfc-2011-nicer-assert-messages/all-expr-kinds.rs` test.
297 ExprKind
::Assign(_
, _
, _
)
298 | ExprKind
::AssignOp(_
, _
, _
)
299 | ExprKind
::Async(_
, _
)
300 | ExprKind
::Await(_
, _
)
301 | ExprKind
::Block(_
, _
)
302 | ExprKind
::Break(_
, _
)
303 | ExprKind
::Closure(_
)
304 | ExprKind
::ConstBlock(_
)
305 | ExprKind
::Continue(_
)
307 | ExprKind
::Field(_
, _
)
308 | ExprKind
::ForLoop(_
, _
, _
, _
)
309 | ExprKind
::FormatArgs(_
)
310 | ExprKind
::IncludedBytes(..)
311 | ExprKind
::InlineAsm(_
)
313 | ExprKind
::Loop(_
, _
, _
)
314 | ExprKind
::MacCall(_
)
315 | ExprKind
::OffsetOf(_
, _
)
316 | ExprKind
::Path(_
, _
)
319 | ExprKind
::TryBlock(_
)
320 | ExprKind
::Type(_
, _
)
321 | ExprKind
::Underscore
322 | ExprKind
::While(_
, _
, _
)
324 | ExprKind
::Become(_
)
325 | ExprKind
::Yield(_
) => {}
329 /// Pushes the top-level declarations and modifies `expr` to try capturing variables.
331 /// `fmt_str`, the formatting string used for debugging, is constructed to show possible
332 /// captured variables.
333 fn manage_initial_capture(&mut self, expr
: &mut P
<Expr
>, path_ident
: Ident
) {
334 if self.paths
.contains(&path_ident
) {
337 self.fmt_string
.push_str(" ");
338 self.fmt_string
.push_str(path_ident
.as_str());
339 self.fmt_string
.push_str(" = {:?}\n");
340 let _
= self.paths
.insert(path_ident
);
342 let curr_capture_idx
= self.capture_decls
.len();
343 let capture_string
= format
!("__capture{curr_capture_idx}");
344 let ident
= Ident
::new(Symbol
::intern(&capture_string
), self.span
);
345 let init_std_path
= self.cx
.std_path(&[sym
::asserting
, sym
::Capture
, sym
::new
]);
346 let init
= self.cx
.expr_call(
348 self.cx
.expr_path(self.cx
.path(self.span
, init_std_path
)),
351 let capture
= Capture { decl: self.cx.stmt_let(self.span, true, ident, init), ident }
;
352 self.capture_decls
.push(capture
);
353 self.manage_try_capture(ident
, curr_capture_idx
, expr
);
356 /// Tries to copy `__local_bindN` into `__captureN`.
359 /// (&Wrapper(__local_bindN)).try_capture(&mut __captureN);
362 fn manage_try_capture(&mut self, capture
: Ident
, curr_capture_idx
: usize, expr
: &mut P
<Expr
>) {
363 let local_bind_string
= format
!("__local_bind{curr_capture_idx}");
364 let local_bind
= Ident
::new(Symbol
::intern(&local_bind_string
), self.span
);
365 self.local_bind_decls
.push(self.cx
.stmt_let(
369 self.cx
.expr_addr_of(self.span
, expr
.clone()),
371 let wrapper
= self.cx
.expr_call(
374 self.cx
.path(self.span
, self.cx
.std_path(&[sym
::asserting
, sym
::Wrapper
])),
376 thin_vec
![self.cx
.expr_path(Path
::from_ident(local_bind
))],
378 let try_capture_call
= self
380 .stmt_expr(expr_method_call(
385 ident
: Ident
::new(sym
::try_capture
, self.span
),
387 expr_paren(self.cx
, self.span
, self.cx
.expr_addr_of(self.span
, wrapper
)),
388 thin_vec
![expr_addr_of_mut(
391 self.cx
.expr_path(Path
::from_ident(capture
)),
395 .add_trailing_semicolon();
396 let local_bind_path
= self.cx
.expr_path(Path
::from_ident(local_bind
));
397 let rslt
= if self.is_consumed
{
398 let ret
= self.cx
.stmt_expr(local_bind_path
);
399 self.cx
.expr_block(self.cx
.block(self.span
, thin_vec
![try_capture_call
, ret
]))
401 self.best_case_captures
.push(try_capture_call
);
404 *expr
= self.cx
.expr_deref(self.span
, rslt
);
407 // Calls `f` with the internal `is_consumed` set to `curr_is_consumed` and then
408 // sets the internal `is_consumed` back to its original value.
409 fn with_is_consumed_management(&mut self, curr_is_consumed
: bool
, f
: impl FnOnce(&mut Self)) {
410 let prev_is_consumed
= self.is_consumed
;
411 self.is_consumed
= curr_is_consumed
;
413 self.is_consumed
= prev_is_consumed
;
417 /// Information about a captured element.
420 // Generated indexed `Capture` statement.
422 // `let __capture{} = Capture::new();`
424 // The name of the generated indexed `Capture` variable.
430 /// Escapes to use as a formatting string.
431 fn escape_to_fmt(s
: &str) -> String
{
432 let mut rslt
= String
::with_capacity(s
.len());
434 rslt
.extend(c
.escape_debug());
436 '{' | '}'
=> rslt
.push(c
),
443 fn expr_addr_of_mut(cx
: &ExtCtxt
<'_
>, sp
: Span
, e
: P
<Expr
>) -> P
<Expr
> {
444 cx
.expr(sp
, ExprKind
::AddrOf(BorrowKind
::Ref
, Mutability
::Mut
, e
))
451 args
: ThinVec
<P
<Expr
>>,
454 cx
.expr(span
, ExprKind
::MethodCall(Box
::new(MethodCall { seg, receiver, args, span }
)))
457 fn expr_paren(cx
: &ExtCtxt
<'_
>, sp
: Span
, e
: P
<Expr
>) -> P
<Expr
> {
458 cx
.expr(sp
, ExprKind
::Paren(e
))