Skip to main content

rustc_builtin_macros/assert/
context.rs

1use rustc_ast::token::{self, Delimiter, IdentIsRaw};
2use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree};
3use rustc_ast::{
4    BinOpKind, BorrowKind, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, ItemKind, MacCall, MethodCall,
5    Mutability, Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeKind,
6};
7use rustc_ast_pretty::pprust;
8use rustc_data_structures::fx::FxHashSet;
9use rustc_expand::base::ExtCtxt;
10use rustc_span::{Ident, Span, Symbol, sym};
11use thin_vec::{ThinVec, thin_vec};
12
13pub(super) struct Context<'cx, 'a> {
14    // An optimization.
15    //
16    // Elements that aren't consumed (PartialEq, PartialOrd, ...) can be copied **after** the
17    // `assert!` expression fails rather than copied on-the-fly.
18    best_case_captures: Vec<Stmt>,
19    // Top-level `let captureN = Capture::new()` statements
20    capture_decls: Vec<Capture>,
21    cx: &'cx ExtCtxt<'a>,
22    // Formatting string used for debugging
23    fmt_string: String,
24    // If the current expression being visited consumes itself. Used to construct
25    // `best_case_captures`.
26    is_consumed: bool,
27    // Top-level `let __local_bindN = &expr` statements
28    local_bind_decls: Vec<Stmt>,
29    // Used to avoid capturing duplicated paths
30    //
31    // ```rust
32    // let a = 1i32;
33    // assert!(add(a, a) == 3);
34    // ```
35    paths: FxHashSet<Ident>,
36    span: Span,
37}
38
39impl<'cx, 'a> Context<'cx, 'a> {
40    pub(super) fn new(cx: &'cx ExtCtxt<'a>, span: Span) -> Self {
41        Self {
42            best_case_captures: <_>::default(),
43            capture_decls: <_>::default(),
44            cx,
45            fmt_string: <_>::default(),
46            is_consumed: true,
47            local_bind_decls: <_>::default(),
48            paths: <_>::default(),
49            span,
50        }
51    }
52
53    /// Builds the whole `assert!` expression. For example, `let elem = 1; assert!(elem == 1);` expands to:
54    ///
55    /// ```rust
56    /// let elem = 1;
57    /// {
58    ///   #[allow(unused_imports)]
59    ///   use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
60    ///   let mut __capture0 = ::core::asserting::Capture::new();
61    ///   let __local_bind0 = &elem;
62    ///   if !(
63    ///     *{
64    ///       (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
65    ///       __local_bind0
66    ///     } == 1
67    ///   ) {
68    ///     panic!("Assertion failed: elem == 1\nWith captures:\n  elem = {:?}", __capture0)
69    ///   }
70    /// }
71    /// ```
72    pub(super) fn build(mut self, mut cond_expr: Box<Expr>, panic_path: Path) -> Box<Expr> {
73        let expr_str = pprust::expr_to_string(&cond_expr);
74        self.manage_cond_expr(&mut cond_expr);
75        let initial_imports = self.build_initial_imports();
76        let panic = self.build_panic(&expr_str, panic_path);
77        let cond_expr_with_unlikely = self.build_unlikely(cond_expr);
78
79        let Self { best_case_captures, capture_decls, cx, local_bind_decls, span, .. } = self;
80
81        let mut assert_then_stmts = ThinVec::with_capacity(2);
82        assert_then_stmts.extend(best_case_captures);
83        assert_then_stmts.push(self.cx.stmt_expr(panic));
84        let assert_then = self.cx.block(span, assert_then_stmts);
85
86        let mut stmts = ThinVec::with_capacity(4);
87        stmts.push(initial_imports);
88        stmts.extend(capture_decls.into_iter().map(|c| c.decl));
89        stmts.extend(local_bind_decls);
90        stmts.push(
91            cx.stmt_expr(cx.expr(span, ExprKind::If(cond_expr_with_unlikely, assert_then, None))),
92        );
93        cx.expr_block(cx.block(span, stmts))
94    }
95
96    /// Initial **trait** imports
97    ///
98    /// use ::core::asserting::{ ... };
99    fn build_initial_imports(&self) -> Stmt {
100        let nested_tree = |this: &Self, sym| {
101            (
102                UseTree {
103                    prefix: this.cx.path(this.span, ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Ident::with_dummy_span(sym)]))vec![Ident::with_dummy_span(sym)]),
104                    kind: UseTreeKind::Simple(None),
105                },
106                DUMMY_NODE_ID,
107            )
108        };
109        self.cx.stmt_item(
110            self.span,
111            self.cx.item(
112                self.span,
113                {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(self.cx.attr_nested_word(sym::allow, sym::unused_imports,
            self.span));
    vec
}thin_vec![self.cx.attr_nested_word(sym::allow, sym::unused_imports, self.span)],
114                ItemKind::Use(UseTree {
115                    prefix: self.cx.path(self.span, self.cx.std_path(&[sym::asserting])),
116                    kind: UseTreeKind::Nested {
117                        items: {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(nested_tree(self, sym::TryCaptureGeneric));
    vec.push(nested_tree(self, sym::TryCapturePrintable));
    vec
}thin_vec![
118                            nested_tree(self, sym::TryCaptureGeneric),
119                            nested_tree(self, sym::TryCapturePrintable),
120                        ],
121                        span: self.span,
122                    },
123                }),
124            ),
125        )
126    }
127
128    /// Takes the conditional expression of `assert!` and then wraps it inside `unlikely`
129    fn build_unlikely(&self, cond_expr: Box<Expr>) -> Box<Expr> {
130        let unlikely_path = self.cx.std_path(&[sym::intrinsics, sym::unlikely]);
131        self.cx.expr_call(
132            self.span,
133            self.cx.expr_path(self.cx.path(self.span, unlikely_path)),
134            {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(self.cx.expr(self.span, ExprKind::Unary(UnOp::Not, cond_expr)));
    vec
}thin_vec![self.cx.expr(self.span, ExprKind::Unary(UnOp::Not, cond_expr))],
135        )
136    }
137
138    /// The necessary custom `panic!(...)` expression.
139    ///
140    /// panic!(
141    ///     "Assertion failed: ... \n With expansion: ...",
142    ///     __capture0,
143    ///     ...
144    /// );
145    fn build_panic(&self, expr_str: &str, panic_path: Path) -> Box<Expr> {
146        let escaped_expr_str = escape_to_fmt(expr_str);
147        let initial = [
148            TokenTree::token_joint(
149                token::Literal(token::Lit {
150                    kind: token::LitKind::Str,
151                    symbol: Symbol::intern(&if self.fmt_string.is_empty() {
152                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Assertion failed: {0}",
                escaped_expr_str))
    })format!("Assertion failed: {escaped_expr_str}")
153                    } else {
154                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Assertion failed: {1}\nWith captures:\n{0}",
                &self.fmt_string, escaped_expr_str))
    })format!(
155                            "Assertion failed: {escaped_expr_str}\nWith captures:\n{}",
156                            &self.fmt_string
157                        )
158                    }),
159                    suffix: None,
160                }),
161                self.span,
162            ),
163            TokenTree::token_alone(token::Comma, self.span),
164        ];
165        let captures = self.capture_decls.iter().flat_map(|cap| {
166            [
167                TokenTree::token_joint(
168                    token::Ident(cap.ident.name, IdentIsRaw::No),
169                    cap.ident.span,
170                ),
171                TokenTree::token_alone(token::Comma, self.span),
172            ]
173        });
174        self.cx.expr(
175            self.span,
176            ExprKind::MacCall(Box::new(MacCall {
177                path: panic_path,
178                args: Box::new(DelimArgs {
179                    dspan: DelimSpan::from_single(self.span),
180                    delim: Delimiter::Parenthesis,
181                    tokens: initial.into_iter().chain(captures).collect::<TokenStream>(),
182                }),
183            })),
184        )
185    }
186
187    /// Recursive function called until `cond_expr` and `fmt_str` are fully modified.
188    ///
189    /// See [Self::manage_initial_capture] and [Self::manage_try_capture]
190    fn manage_cond_expr(&mut self, expr: &mut Box<Expr>) {
191        match &mut expr.kind {
192            ExprKind::AddrOf(_, mutability, local_expr) => {
193                self.with_is_consumed_management(#[allow(non_exhaustive_omitted_patterns)] match mutability {
    Mutability::Mut => true,
    _ => false,
}matches!(mutability, Mutability::Mut), |this| {
194                    this.manage_cond_expr(local_expr)
195                });
196            }
197            ExprKind::Array(local_exprs) => {
198                for local_expr in local_exprs {
199                    self.manage_cond_expr(local_expr);
200                }
201            }
202            ExprKind::Binary(op, lhs, rhs) => {
203                self.with_is_consumed_management(
204                    #[allow(non_exhaustive_omitted_patterns)] match op.node {
    BinOpKind::Add | BinOpKind::And | BinOpKind::BitAnd | BinOpKind::BitOr |
        BinOpKind::BitXor | BinOpKind::Div | BinOpKind::Mul | BinOpKind::Or |
        BinOpKind::Rem | BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub =>
        true,
    _ => false,
}matches!(
205                        op.node,
206                        BinOpKind::Add
207                            | BinOpKind::And
208                            | BinOpKind::BitAnd
209                            | BinOpKind::BitOr
210                            | BinOpKind::BitXor
211                            | BinOpKind::Div
212                            | BinOpKind::Mul
213                            | BinOpKind::Or
214                            | BinOpKind::Rem
215                            | BinOpKind::Shl
216                            | BinOpKind::Shr
217                            | BinOpKind::Sub
218                    ),
219                    |this| {
220                        this.manage_cond_expr(lhs);
221                        this.manage_cond_expr(rhs);
222                    },
223                );
224            }
225            ExprKind::Call(_, local_exprs) => {
226                for local_expr in local_exprs {
227                    self.manage_cond_expr(local_expr);
228                }
229            }
230            ExprKind::Cast(local_expr, _) => {
231                self.manage_cond_expr(local_expr);
232            }
233            ExprKind::If(local_expr, _, _) => {
234                self.manage_cond_expr(local_expr);
235            }
236            ExprKind::Index(prefix, suffix, _) => {
237                self.manage_cond_expr(prefix);
238                self.manage_cond_expr(suffix);
239            }
240            ExprKind::Let(_, local_expr, _, _) => {
241                self.manage_cond_expr(local_expr);
242            }
243            ExprKind::Match(local_expr, ..) => {
244                self.manage_cond_expr(local_expr);
245            }
246            ExprKind::MethodCall(call) => {
247                for arg in &mut call.args {
248                    self.manage_cond_expr(arg);
249                }
250            }
251            ExprKind::Path(_, Path { segments, .. }) if let [path_segment] = &segments[..] => {
252                let path_ident = path_segment.ident;
253                self.manage_initial_capture(expr, path_ident);
254            }
255            ExprKind::Paren(local_expr) => {
256                self.manage_cond_expr(local_expr);
257            }
258            ExprKind::Range(prefix, suffix, _) => {
259                if let Some(elem) = prefix {
260                    self.manage_cond_expr(elem);
261                }
262                if let Some(elem) = suffix {
263                    self.manage_cond_expr(elem);
264                }
265            }
266            ExprKind::Repeat(local_expr, elem) => {
267                self.manage_cond_expr(local_expr);
268                self.manage_cond_expr(&mut elem.value);
269            }
270            ExprKind::Struct(elem) => {
271                for field in &mut elem.fields {
272                    self.manage_cond_expr(&mut field.expr);
273                }
274                if let StructRest::Base(local_expr) = &mut elem.rest {
275                    self.manage_cond_expr(local_expr);
276                }
277            }
278            ExprKind::Tup(local_exprs) => {
279                for local_expr in local_exprs {
280                    self.manage_cond_expr(local_expr);
281                }
282            }
283            ExprKind::Unary(un_op, local_expr) => {
284                self.with_is_consumed_management(#[allow(non_exhaustive_omitted_patterns)] match un_op {
    UnOp::Neg | UnOp::Not => true,
    _ => false,
}matches!(un_op, UnOp::Neg | UnOp::Not), |this| {
285                    this.manage_cond_expr(local_expr)
286                });
287            }
288            // Expressions that are not worth or can not be captured.
289            //
290            // Full list instead of `_` to catch possible future inclusions and to
291            // sync with the `rfc-2011-nicer-assert-messages/all-expr-kinds.rs` test.
292            ExprKind::Assign(_, _, _)
293            | ExprKind::AssignOp(_, _, _)
294            | ExprKind::Gen(_, _, _, _)
295            | ExprKind::Await(_, _)
296            | ExprKind::Use(_, _)
297            | ExprKind::Block(_, _)
298            | ExprKind::Break(_, _)
299            | ExprKind::Closure(_)
300            | ExprKind::ConstBlock(_)
301            | ExprKind::Continue(_)
302            | ExprKind::Dummy
303            | ExprKind::Err(_)
304            | ExprKind::Field(_, _)
305            | ExprKind::ForLoop { .. }
306            | ExprKind::FormatArgs(_)
307            | ExprKind::IncludedBytes(..)
308            | ExprKind::InlineAsm(_)
309            | ExprKind::Lit(_)
310            | ExprKind::Loop(_, _, _)
311            | ExprKind::MacCall(_)
312            | ExprKind::OffsetOf(_, _)
313            | ExprKind::Path(_, _)
314            | ExprKind::Ret(_)
315            | ExprKind::Try(_)
316            | ExprKind::TryBlock(_, _)
317            | ExprKind::Type(_, _)
318            | ExprKind::Underscore
319            | ExprKind::While(_, _, _)
320            | ExprKind::Yeet(_)
321            | ExprKind::Become(_)
322            | ExprKind::Yield(_)
323            | ExprKind::UnsafeBinderCast(..) => {}
324        }
325    }
326
327    /// Pushes the top-level declarations and modifies `expr` to try capturing variables.
328    ///
329    /// `fmt_str`, the formatting string used for debugging, is constructed to show possible
330    /// captured variables.
331    fn manage_initial_capture(&mut self, expr: &mut Box<Expr>, path_ident: Ident) {
332        if self.paths.contains(&path_ident) {
333            return;
334        } else {
335            self.fmt_string.push_str("  ");
336            self.fmt_string.push_str(path_ident.as_str());
337            self.fmt_string.push_str(" = {:?}\n");
338            let _ = self.paths.insert(path_ident);
339        }
340        let curr_capture_idx = self.capture_decls.len();
341        let capture_string = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__capture{0}", curr_capture_idx))
    })format!("__capture{curr_capture_idx}");
342        let ident = Ident::new(Symbol::intern(&capture_string), self.span);
343        let init_std_path = self.cx.std_path(&[sym::asserting, sym::Capture, sym::new]);
344        let init = self.cx.expr_call(
345            self.span,
346            self.cx.expr_path(self.cx.path(self.span, init_std_path)),
347            ThinVec::new(),
348        );
349        let capture = Capture { decl: self.cx.stmt_let(self.span, true, ident, init), ident };
350        self.capture_decls.push(capture);
351        self.manage_try_capture(ident, curr_capture_idx, expr);
352    }
353
354    /// Tries to copy `__local_bindN` into `__captureN`.
355    ///
356    /// *{
357    ///    (&Wrapper(__local_bindN)).try_capture(&mut __captureN);
358    ///    __local_bindN
359    /// }
360    fn manage_try_capture(
361        &mut self,
362        capture: Ident,
363        curr_capture_idx: usize,
364        expr: &mut Box<Expr>,
365    ) {
366        let local_bind_string = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__local_bind{0}",
                curr_capture_idx))
    })format!("__local_bind{curr_capture_idx}");
367        let local_bind = Ident::new(Symbol::intern(&local_bind_string), self.span);
368        self.local_bind_decls.push(self.cx.stmt_let(
369            self.span,
370            false,
371            local_bind,
372            self.cx.expr_addr_of(self.span, expr.clone()),
373        ));
374        let wrapper = self.cx.expr_call(
375            self.span,
376            self.cx.expr_path(
377                self.cx.path(self.span, self.cx.std_path(&[sym::asserting, sym::Wrapper])),
378            ),
379            {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(self.cx.expr_path(Path::from_ident(local_bind)));
    vec
}thin_vec![self.cx.expr_path(Path::from_ident(local_bind))],
380        );
381        let try_capture_call = self
382            .cx
383            .stmt_expr(expr_method_call(
384                self.cx,
385                PathSegment {
386                    args: None,
387                    id: DUMMY_NODE_ID,
388                    ident: Ident::new(sym::try_capture, self.span),
389                },
390                expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)),
391                {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(expr_addr_of_mut(self.cx, self.span,
            self.cx.expr_path(Path::from_ident(capture))));
    vec
}thin_vec![expr_addr_of_mut(
392                    self.cx,
393                    self.span,
394                    self.cx.expr_path(Path::from_ident(capture)),
395                )],
396                self.span,
397            ))
398            .add_trailing_semicolon();
399        let local_bind_path = self.cx.expr_path(Path::from_ident(local_bind));
400        let rslt = if self.is_consumed {
401            let ret = self.cx.stmt_expr(local_bind_path);
402            self.cx.expr_block(self.cx.block(self.span, {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(try_capture_call);
    vec.push(ret);
    vec
}thin_vec![try_capture_call, ret]))
403        } else {
404            self.best_case_captures.push(try_capture_call);
405            local_bind_path
406        };
407        *expr = self.cx.expr_deref(self.span, rslt);
408    }
409
410    // Calls `f` with the internal `is_consumed` set to `curr_is_consumed` and then
411    // sets the internal `is_consumed` back to its original value.
412    fn with_is_consumed_management(&mut self, curr_is_consumed: bool, f: impl FnOnce(&mut Self)) {
413        let prev_is_consumed = self.is_consumed;
414        self.is_consumed = curr_is_consumed;
415        f(self);
416        self.is_consumed = prev_is_consumed;
417    }
418}
419
420/// Information about a captured element.
421#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Capture {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "Capture",
            "decl", &self.decl, "ident", &&self.ident)
    }
}Debug)]
422struct Capture {
423    // Generated indexed `Capture` statement.
424    //
425    // `let __capture{} = Capture::new();`
426    decl: Stmt,
427    // The name of the generated indexed `Capture` variable.
428    //
429    // `__capture{}`
430    ident: Ident,
431}
432
433/// Escapes to use as a formatting string.
434fn escape_to_fmt(s: &str) -> String {
435    let mut rslt = String::with_capacity(s.len());
436    for c in s.chars() {
437        rslt.extend(c.escape_debug());
438        match c {
439            '{' | '}' => rslt.push(c),
440            _ => {}
441        }
442    }
443    rslt
444}
445
446fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: Box<Expr>) -> Box<Expr> {
447    cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e))
448}
449
450fn expr_method_call(
451    cx: &ExtCtxt<'_>,
452    seg: PathSegment,
453    receiver: Box<Expr>,
454    args: ThinVec<Box<Expr>>,
455    span: Span,
456) -> Box<Expr> {
457    cx.expr(span, ExprKind::MethodCall(Box::new(MethodCall { seg, receiver, args, span })))
458}
459
460fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: Box<Expr>) -> Box<Expr> {
461    cx.expr(sp, ExprKind::Paren(e))
462}