| use crate::assert::expr_if_not; |
| use rustc_ast::{ |
| attr, |
| ptr::P, |
| token, |
| tokenstream::{DelimSpan, TokenStream, TokenTree}, |
| BorrowKind, Expr, ExprKind, ItemKind, MacArgs, MacCall, MacDelimiter, Mutability, Path, |
| PathSegment, Stmt, StructRest, UseTree, UseTreeKind, DUMMY_NODE_ID, |
| }; |
| use rustc_ast_pretty::pprust; |
| use rustc_data_structures::fx::FxHashSet; |
| use rustc_expand::base::ExtCtxt; |
| use rustc_span::{ |
| symbol::{sym, Ident, Symbol}, |
| Span, |
| }; |
| |
| pub(super) struct Context<'cx, 'a> { |
| // Top-level `let captureN = Capture::new()` statements |
| capture_decls: Vec<Capture>, |
| cx: &'cx ExtCtxt<'a>, |
| // Formatting string used for debugging |
| fmt_string: String, |
| // Top-level `let __local_bindN = &expr` statements |
| local_bind_decls: Vec<Stmt>, |
| // Used to avoid capturing duplicated paths |
| // |
| // ```rust |
| // let a = 1i32; |
| // assert!(add(a, a) == 3); |
| // ``` |
| paths: FxHashSet<Ident>, |
| span: Span, |
| } |
| |
| impl<'cx, 'a> Context<'cx, 'a> { |
| pub(super) fn new(cx: &'cx ExtCtxt<'a>, span: Span) -> Self { |
| Self { |
| capture_decls: <_>::default(), |
| cx, |
| fmt_string: <_>::default(), |
| local_bind_decls: <_>::default(), |
| paths: <_>::default(), |
| span, |
| } |
| } |
| |
| /// Builds the whole `assert!` expression. For example, `let elem = 1; assert!(elem == 1);` expands to: |
| /// |
| /// ```rust |
| /// let elem = 1; |
| /// { |
| /// #[allow(unused_imports)] |
| /// use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable}; |
| /// let mut __capture0 = ::core::asserting::Capture::new(); |
| /// let __local_bind0 = &elem; |
| /// if !( |
| /// *{ |
| /// (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); |
| /// __local_bind0 |
| /// } == 1 |
| /// ) { |
| /// panic!("Assertion failed: elem == 1\nWith captures:\n elem = {}", __capture0) |
| /// } |
| /// } |
| /// ``` |
| pub(super) fn build(mut self, mut cond_expr: P<Expr>, panic_path: Path) -> P<Expr> { |
| let expr_str = pprust::expr_to_string(&cond_expr); |
| self.manage_cond_expr(&mut cond_expr); |
| let initial_imports = self.build_initial_imports(); |
| let panic = self.build_panic(&expr_str, panic_path); |
| |
| let Self { capture_decls, cx, local_bind_decls, span, .. } = self; |
| |
| let mut stmts = Vec::with_capacity(4); |
| stmts.push(initial_imports); |
| stmts.extend(capture_decls.into_iter().map(|c| c.decl)); |
| stmts.extend(local_bind_decls); |
| stmts.push(cx.stmt_expr(expr_if_not(cx, span, cond_expr, panic, None))); |
| cx.expr_block(cx.block(span, stmts)) |
| } |
| |
| /// Initial **trait** imports |
| /// |
| /// use ::core::asserting::{ ... }; |
| fn build_initial_imports(&self) -> Stmt { |
| let nested_tree = |this: &Self, sym| { |
| ( |
| UseTree { |
| prefix: this.cx.path(this.span, vec![Ident::with_dummy_span(sym)]), |
| kind: UseTreeKind::Simple(None, DUMMY_NODE_ID, DUMMY_NODE_ID), |
| span: this.span, |
| }, |
| DUMMY_NODE_ID, |
| ) |
| }; |
| self.cx.stmt_item( |
| self.span, |
| self.cx.item( |
| self.span, |
| Ident::empty(), |
| vec![self.cx.attribute(attr::mk_list_item( |
| Ident::new(sym::allow, self.span), |
| vec![attr::mk_nested_word_item(Ident::new(sym::unused_imports, self.span))], |
| ))], |
| ItemKind::Use(UseTree { |
| prefix: self.cx.path(self.span, self.cx.std_path(&[sym::asserting])), |
| kind: UseTreeKind::Nested(vec![ |
| nested_tree(self, sym::TryCaptureGeneric), |
| nested_tree(self, sym::TryCapturePrintable), |
| ]), |
| span: self.span, |
| }), |
| ), |
| ) |
| } |
| |
| /// The necessary custom `panic!(...)` expression. |
| /// |
| /// panic!( |
| /// "Assertion failed: ... \n With expansion: ...", |
| /// __capture0, |
| /// ... |
| /// ); |
| fn build_panic(&self, expr_str: &str, panic_path: Path) -> P<Expr> { |
| let escaped_expr_str = escape_to_fmt(expr_str); |
| let initial = [ |
| TokenTree::token( |
| token::Literal(token::Lit { |
| kind: token::LitKind::Str, |
| symbol: Symbol::intern(&if self.fmt_string.is_empty() { |
| format!("Assertion failed: {escaped_expr_str}") |
| } else { |
| format!( |
| "Assertion failed: {escaped_expr_str}\nWith captures:\n{}", |
| &self.fmt_string |
| ) |
| }), |
| suffix: None, |
| }), |
| self.span, |
| ), |
| TokenTree::token(token::Comma, self.span), |
| ]; |
| let captures = self.capture_decls.iter().flat_map(|cap| { |
| [ |
| TokenTree::token(token::Ident(cap.ident.name, false), cap.ident.span), |
| TokenTree::token(token::Comma, self.span), |
| ] |
| }); |
| self.cx.expr( |
| self.span, |
| ExprKind::MacCall(MacCall { |
| path: panic_path, |
| args: P(MacArgs::Delimited( |
| DelimSpan::from_single(self.span), |
| MacDelimiter::Parenthesis, |
| initial.into_iter().chain(captures).collect::<TokenStream>(), |
| )), |
| prior_type_ascription: None, |
| }), |
| ) |
| } |
| |
| /// Recursive function called until `cond_expr` and `fmt_str` are fully modified. |
| /// |
| /// See [Self::manage_initial_capture] and [Self::manage_try_capture] |
| fn manage_cond_expr(&mut self, expr: &mut P<Expr>) { |
| match (*expr).kind { |
| ExprKind::AddrOf(_, _, ref mut local_expr) => { |
| self.manage_cond_expr(local_expr); |
| } |
| ExprKind::Array(ref mut local_exprs) => { |
| for local_expr in local_exprs { |
| self.manage_cond_expr(local_expr); |
| } |
| } |
| ExprKind::Binary(_, ref mut lhs, ref mut rhs) => { |
| self.manage_cond_expr(lhs); |
| self.manage_cond_expr(rhs); |
| } |
| ExprKind::Call(_, ref mut local_exprs) => { |
| for local_expr in local_exprs { |
| self.manage_cond_expr(local_expr); |
| } |
| } |
| ExprKind::Cast(ref mut local_expr, _) => { |
| self.manage_cond_expr(local_expr); |
| } |
| ExprKind::Index(ref mut prefix, ref mut suffix) => { |
| self.manage_cond_expr(prefix); |
| self.manage_cond_expr(suffix); |
| } |
| ExprKind::MethodCall(_, ref mut local_exprs, _) => { |
| for local_expr in local_exprs.iter_mut().skip(1) { |
| self.manage_cond_expr(local_expr); |
| } |
| } |
| ExprKind::Path(_, Path { ref segments, .. }) if let &[ref path_segment] = &segments[..] => { |
| let path_ident = path_segment.ident; |
| self.manage_initial_capture(expr, path_ident); |
| } |
| ExprKind::Paren(ref mut local_expr) => { |
| self.manage_cond_expr(local_expr); |
| } |
| ExprKind::Range(ref mut prefix, ref mut suffix, _) => { |
| if let Some(ref mut elem) = prefix { |
| self.manage_cond_expr(elem); |
| } |
| if let Some(ref mut elem) = suffix { |
| self.manage_cond_expr(elem); |
| } |
| } |
| ExprKind::Repeat(ref mut local_expr, ref mut elem) => { |
| self.manage_cond_expr(local_expr); |
| self.manage_cond_expr(&mut elem.value); |
| } |
| ExprKind::Struct(ref mut elem) => { |
| for field in &mut elem.fields { |
| self.manage_cond_expr(&mut field.expr); |
| } |
| if let StructRest::Base(ref mut local_expr) = elem.rest { |
| self.manage_cond_expr(local_expr); |
| } |
| } |
| ExprKind::Tup(ref mut local_exprs) => { |
| for local_expr in local_exprs { |
| self.manage_cond_expr(local_expr); |
| } |
| } |
| ExprKind::Unary(_, ref mut local_expr) => { |
| self.manage_cond_expr(local_expr); |
| } |
| // Expressions that are not worth or can not be captured. |
| // |
| // Full list instead of `_` to catch possible future inclusions and to |
| // sync with the `rfc-2011-nicer-assert-messages/all-expr-kinds.rs` test. |
| ExprKind::Assign(_, _, _) |
| | ExprKind::AssignOp(_, _, _) |
| | ExprKind::Async(_, _, _) |
| | ExprKind::Await(_) |
| | ExprKind::Block(_, _) |
| | ExprKind::Box(_) |
| | ExprKind::Break(_, _) |
| | ExprKind::Closure(_, _, _, _, _, _) |
| | ExprKind::ConstBlock(_) |
| | ExprKind::Continue(_) |
| | ExprKind::Err |
| | ExprKind::Field(_, _) |
| | ExprKind::ForLoop(_, _, _, _) |
| | ExprKind::If(_, _, _) |
| | ExprKind::InlineAsm(_) |
| | ExprKind::Let(_, _, _) |
| | ExprKind::Lit(_) |
| | ExprKind::Loop(_, _) |
| | ExprKind::MacCall(_) |
| | ExprKind::Match(_, _) |
| | ExprKind::Path(_, _) |
| | ExprKind::Ret(_) |
| | ExprKind::Try(_) |
| | ExprKind::TryBlock(_) |
| | ExprKind::Type(_, _) |
| | ExprKind::Underscore |
| | ExprKind::While(_, _, _) |
| | ExprKind::Yeet(_) |
| | ExprKind::Yield(_) => {} |
| } |
| } |
| |
| /// Pushes the top-level declarations and modifies `expr` to try capturing variables. |
| /// |
| /// `fmt_str`, the formatting string used for debugging, is constructed to show possible |
| /// captured variables. |
| fn manage_initial_capture(&mut self, expr: &mut P<Expr>, path_ident: Ident) { |
| if self.paths.contains(&path_ident) { |
| return; |
| } else { |
| self.fmt_string.push_str(" "); |
| self.fmt_string.push_str(path_ident.as_str()); |
| self.fmt_string.push_str(" = {:?}\n"); |
| let _ = self.paths.insert(path_ident); |
| } |
| let curr_capture_idx = self.capture_decls.len(); |
| let capture_string = format!("__capture{curr_capture_idx}"); |
| let ident = Ident::new(Symbol::intern(&capture_string), self.span); |
| let init_std_path = self.cx.std_path(&[sym::asserting, sym::Capture, sym::new]); |
| let init = self.cx.expr_call( |
| self.span, |
| self.cx.expr_path(self.cx.path(self.span, init_std_path)), |
| vec![], |
| ); |
| let capture = Capture { decl: self.cx.stmt_let(self.span, true, ident, init), ident }; |
| self.capture_decls.push(capture); |
| self.manage_try_capture(ident, curr_capture_idx, expr); |
| } |
| |
| /// Tries to copy `__local_bindN` into `__captureN`. |
| /// |
| /// *{ |
| /// (&Wrapper(__local_bindN)).try_capture(&mut __captureN); |
| /// __local_bindN |
| /// } |
| fn manage_try_capture(&mut self, capture: Ident, curr_capture_idx: usize, expr: &mut P<Expr>) { |
| let local_bind_string = format!("__local_bind{curr_capture_idx}"); |
| let local_bind = Ident::new(Symbol::intern(&local_bind_string), self.span); |
| self.local_bind_decls.push(self.cx.stmt_let( |
| self.span, |
| false, |
| local_bind, |
| self.cx.expr_addr_of(self.span, expr.clone()), |
| )); |
| let wrapper = self.cx.expr_call( |
| self.span, |
| self.cx.expr_path( |
| self.cx.path(self.span, self.cx.std_path(&[sym::asserting, sym::Wrapper])), |
| ), |
| vec![self.cx.expr_path(Path::from_ident(local_bind))], |
| ); |
| let try_capture_call = self |
| .cx |
| .stmt_expr(expr_method_call( |
| self.cx, |
| PathSegment { |
| args: None, |
| id: DUMMY_NODE_ID, |
| ident: Ident::new(sym::try_capture, self.span), |
| }, |
| vec![ |
| expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)), |
| expr_addr_of_mut( |
| self.cx, |
| self.span, |
| self.cx.expr_path(Path::from_ident(capture)), |
| ), |
| ], |
| self.span, |
| )) |
| .add_trailing_semicolon(); |
| let local_bind_path = self.cx.expr_path(Path::from_ident(local_bind)); |
| let ret = self.cx.stmt_expr(local_bind_path); |
| let block = self.cx.expr_block(self.cx.block(self.span, vec![try_capture_call, ret])); |
| *expr = self.cx.expr_deref(self.span, block); |
| } |
| } |
| |
| /// Information about a captured element. |
| #[derive(Debug)] |
| struct Capture { |
| // Generated indexed `Capture` statement. |
| // |
| // `let __capture{} = Capture::new();` |
| decl: Stmt, |
| // The name of the generated indexed `Capture` variable. |
| // |
| // `__capture{}` |
| ident: Ident, |
| } |
| |
| /// Escapes to use as a formatting string. |
| fn escape_to_fmt(s: &str) -> String { |
| let mut rslt = String::with_capacity(s.len()); |
| for c in s.chars() { |
| rslt.extend(c.escape_debug()); |
| match c { |
| '{' | '}' => rslt.push(c), |
| _ => {} |
| } |
| } |
| rslt |
| } |
| |
| fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> { |
| cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e)) |
| } |
| |
| fn expr_method_call( |
| cx: &ExtCtxt<'_>, |
| path: PathSegment, |
| args: Vec<P<Expr>>, |
| span: Span, |
| ) -> P<Expr> { |
| cx.expr(span, ExprKind::MethodCall(path, args, span)) |
| } |
| |
| fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> { |
| cx.expr(sp, ExprKind::Paren(e)) |
| } |