From 39d7a21e433106a3039d369af2e07f53af770f1a Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Wed, 10 Dec 2025 18:44:04 +0100 Subject: [PATCH 1/3] Avoid tearing dbg! prints --- library/std/src/macros.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index 25e2b7ea13703..ba3d983398fdb 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -376,6 +376,38 @@ macro_rules! dbg { } }; ($($val:expr),+ $(,)?) => { - ($($crate::dbg!($val)),+,) + { + // Eagerly evaluate all arguments then print them all at once to + // avoid tearing prints. We can't simply hold the stderr lock while + // evaluating the arguments because that could lead to deadlocks if + // any of the expressions also attempt to write to stderr on another + // thread. + use $crate::fmt::Write; + let mut output = $crate::string::String::with_capacity( + // Make an educated guess about the final size to avoid + // multiple reallocations. + const { $(100 + $crate::file!().len() + $crate::stringify($val).len())++ }, + ); + let eager_eval = ($( + // Use of `match` here is intentional because it affects the lifetimes + // of temporaries - https://stackoverflow.com/a/48732525/1063961 + match $val { + tmp => { + let _ = $crate::writeln!(&mut output, "[{}:{}:{}] {} = {:#?}", + $crate::file!(), + $crate::line!(), + $crate::column!(), + $crate::stringify!($val), + // The `&T: Debug` check happens here (not in the format literal desugaring) + // to avoid format literal related messages and suggestions. + &&tmp as &dyn $crate::fmt::Debug, + ); + tmp + } + } + ),+,); + $crate::eprint!("{}", output); + eager_eval + } }; } From f8b7ca12aa923f11db82a77da2d036a5efcf450c Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Wed, 10 Dec 2025 19:54:28 +0100 Subject: [PATCH 2/3] Fix capacity expression --- library/std/src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index ba3d983398fdb..2e13f5beb3872 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -386,7 +386,7 @@ macro_rules! dbg { let mut output = $crate::string::String::with_capacity( // Make an educated guess about the final size to avoid // multiple reallocations. - const { $(100 + $crate::file!().len() + $crate::stringify($val).len())++ }, + const { 0 $(+ 100 + $crate::file!().len() + $crate::stringify!($val).len())+ }, ); let eager_eval = ($( // Use of `match` here is intentional because it affects the lifetimes From 114f91b69c7c1829ebd9e9f6f5fa482c61289cfa Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Wed, 10 Dec 2025 22:19:15 +0100 Subject: [PATCH 3/3] Fix clippy lint match --- .../clippy/clippy_lints/src/dbg_macro.rs | 87 ++++++++++++------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/dbg_macro.rs b/src/tools/clippy/clippy_lints/src/dbg_macro.rs index 152516baf7342..734a89c6aa94a 100644 --- a/src/tools/clippy/clippy_lints/src/dbg_macro.rs +++ b/src/tools/clippy/clippy_lints/src/dbg_macro.rs @@ -77,17 +77,28 @@ impl LateLintPass<'_> for DbgMacro { let mut applicability = Applicability::MachineApplicable; let (sugg_span, suggestion) = match is_async_move_desugar(expr).unwrap_or(expr).peel_drop_temps().kind { - // dbg!() - ExprKind::Block(..) => { - // If the `dbg!` macro is a "free" statement and not contained within other expressions, - // remove the whole statement. - if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id) - && let Some(semi_span) = - cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span) - { - (macro_call.span.to(semi_span), String::new()) + ExprKind::Block(blk, _label) => { + // dbg!(2, 3) + if let Some((first, last)) = find_multi_input_exprs(&blk.stmts) { + let snippet = snippet_with_applicability( + cx, + first.span.source_callsite().to(last.span.source_callsite()), + "..", + &mut applicability, + ); + (macro_call.span, format!("({snippet})")) + // dbg!() } else { - (macro_call.span, String::from("()")) + // If the `dbg!` macro is a "free" statement and not contained within other + // expressions, remove the whole statement. + if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id) + && let Some(semi_span) = + cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span) + { + (macro_call.span.to(semi_span), String::new()) + } else { + (macro_call.span, String::from("()")) + } } }, // dbg!(1) @@ -96,28 +107,6 @@ impl LateLintPass<'_> for DbgMacro { snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability) .to_string(), ), - // dbg!(2, 3) - ExprKind::Tup( - [ - Expr { - kind: ExprKind::Match(first, ..), - .. - }, - .., - Expr { - kind: ExprKind::Match(last, ..), - .. - }, - ], - ) => { - let snippet = snippet_with_applicability( - cx, - first.span.source_callsite().to(last.span.source_callsite()), - "..", - &mut applicability, - ); - (macro_call.span, format!("({snippet})")) - }, _ => unreachable!(), }; @@ -169,3 +158,37 @@ fn is_async_move_desugar<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx fn first_dbg_macro_in_expansion(cx: &LateContext<'_>, span: Span) -> Option { macro_backtrace(span).find(|mc| cx.tcx.is_diagnostic_item(sym::dbg_macro, mc.def_id)) } + +fn find_multi_input_exprs<'a>(statements: &'a [Stmt<'a>]) -> Option<(Expr<'a>, Expr<'a>)> { + // Multi-input dbg!(a, b, ...) macro contains a statement of the form: + // let eager_eval = ($( match $val { ... } ),*); + // which we're looking for here. + for stmt in statements { + if let StmtKind::Let(LetStmt { + init: + Some(Expr { + kind: + ExprKind::Tup( + [ + Expr { + kind: ExprKind::Match(first, ..), + .. + }, + .., + Expr { + kind: ExprKind::Match(last, ..), + .. + }, + ], + ), + .. + }), + .. + }) = stmt.kind + { + return Some((**first, **last)); + } + } + + None +}