Rollup merge of #149869 - joboet:torn-dbg, r=Mark-Simulacrum

std: avoid tearing `dbg!` prints

Fixes https://github.com/rust-lang/rust/issues/136703.

This is an alternative to rust-lang/rust#149859. Instead of formatting everything into a string, this PR makes multi-expression `dbg!` expand into multiple nested matches, with the final match containing a single `eprint!`. By using macro recursion and relying on hygiene, this allows naming every bound value in that `eprint!`.

CC @orlp

r? libs
This commit is contained in:
Matthias Krüger 2026-01-25 07:42:58 +01:00 committed by GitHub
commit cc666ba8f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 125 additions and 64 deletions

View file

@ -468,7 +468,9 @@ extern crate std as realstd;
// The standard macros that are not built-in to the compiler.
#[macro_use]
mod macros;
#[doc(hidden)]
#[unstable(feature = "std_internals", issue = "none")]
pub mod macros;
// The runtime entry point and a few unstable public functions used by the
// compiler

View file

@ -347,35 +347,70 @@ macro_rules! eprintln {
/// [`debug!`]: https://docs.rs/log/*/log/macro.debug.html
/// [`log`]: https://crates.io/crates/log
#[macro_export]
#[allow_internal_unstable(std_internals)]
#[cfg_attr(not(test), rustc_diagnostic_item = "dbg_macro")]
#[stable(feature = "dbg_macro", since = "1.32.0")]
macro_rules! dbg {
// NOTE: We cannot use `concat!` to make a static string as a format argument
// of `eprintln!` because `file!` could contain a `{` or
// `$val` expression could be a block (`{ .. }`), in which case the `eprintln!`
// will be malformed.
() => {
$crate::eprintln!("[{}:{}:{}]", $crate::file!(), $crate::line!(), $crate::column!())
};
($val:expr $(,)?) => {
($($val:expr),+ $(,)?) => {
$crate::macros::dbg_internal!(() () ($($val),+))
};
}
/// Internal macro that processes a list of expressions and produces a chain of
/// nested `match`es, one for each expression, before finally calling `eprint!`
/// with the collected information and returning all the evaluated expressions
/// in a tuple.
///
/// E.g. `dbg_internal!(() () (1, 2))` expands into
/// ```rust, ignore
/// match 1 {
/// tmp_1 => match 2 {
/// tmp_2 => {
/// eprint!("...", &tmp_1, &tmp_2, /* some other arguments */);
/// (tmp_1, tmp_2)
/// }
/// }
/// }
/// ```
///
/// This is necessary so that `dbg!` outputs don't get torn, see #136703.
#[doc(hidden)]
#[rustc_macro_transparency = "semiopaque"]
pub macro dbg_internal {
(($($piece:literal),+) ($($processed:expr => $bound:expr),+) ()) => {{
$crate::eprint!(
$crate::concat!($($piece),+),
$(
$crate::stringify!($processed),
// The `&T: Debug` check happens here (not in the format literal desugaring)
// to avoid format literal related messages and suggestions.
&&$bound as &dyn $crate::fmt::Debug
),+,
// The location returned here is that of the macro invocation, so
// it will be the same for all expressions. Thus, label these
// arguments so that they can be reused in every piece of the
// formatting template.
file=$crate::file!(),
line=$crate::line!(),
column=$crate::column!()
);
// Comma separate the variables only when necessary so that this will
// not yield a tuple for a single expression, but rather just parenthesize
// the expression.
($($bound),+)
}},
(($($piece:literal),*) ($($processed:expr => $bound:expr),*) ($val:expr $(,$rest:expr)*)) => {
// Use of `match` here is intentional because it affects the lifetimes
// of temporaries - https://stackoverflow.com/a/48732525/1063961
match $val {
tmp => {
$crate::eprintln!("[{}:{}:{}] {} = {:#?}",
$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
}
tmp => $crate::macros::dbg_internal!(
($($piece,)* "[{file}:{line}:{column}] {} = {:#?}\n")
($($processed => $bound,)* $val => tmp)
($($rest),*)
),
}
};
($($val:expr),+ $(,)?) => {
($($crate::dbg!($val)),+,)
};
},
}

View file

@ -5,7 +5,7 @@ use clippy_utils::macros::{MacroCall, macro_backtrace};
use clippy_utils::source::snippet_with_applicability;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::{Closure, ClosureKind, CoroutineKind, Expr, ExprKind, LetStmt, LocalSource, Node, Stmt, StmtKind};
use rustc_hir::{Arm, Closure, ClosureKind, CoroutineKind, Expr, ExprKind, LetStmt, LocalSource, Node, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::{Span, SyntaxContext, sym};
@ -90,33 +90,27 @@ impl LateLintPass<'_> for DbgMacro {
(macro_call.span, String::from("()"))
}
},
// dbg!(1)
ExprKind::Match(val, ..) => (
macro_call.span,
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})"))
ExprKind::Match(first, arms, _) => {
let vals = collect_vals(first, arms);
let suggestion = match vals.as_slice() {
// dbg!(1) => 1
&[val] => {
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability)
.to_string()
}
// dbg!(2, 3) => (2, 3)
&[first, .., last] => {
let snippet = snippet_with_applicability(
cx,
first.span.source_callsite().to(last.span.source_callsite()),
"..",
&mut applicability,
);
format!("({snippet})")
}
_ => unreachable!(),
};
(macro_call.span, suggestion)
},
_ => unreachable!(),
};
@ -169,3 +163,33 @@ 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<MacroCall> {
macro_backtrace(span).find(|mc| cx.tcx.is_diagnostic_item(sym::dbg_macro, mc.def_id))
}
/// Extracts all value expressions from the `match`-tree generated by `dbg!`.
///
/// E.g. from
/// ```rust, ignore
/// match 1 {
/// tmp_1 => match 2 {
/// tmp_2 => {
/// /* printing */
/// (tmp_1, tmp_2)
/// }
/// }
/// }
/// ```
/// this extracts `1` and `2`.
fn collect_vals<'hir>(first: &'hir Expr<'hir>, mut arms: &'hir [Arm<'hir>]) -> Vec<&'hir Expr<'hir>> {
let mut vals = vec![first];
loop {
let [arm] = arms else { unreachable!("dbg! macro expansion only has single-arm matches") };
match is_async_move_desugar(arm.body).unwrap_or(arm.body).peel_drop_temps().kind {
ExprKind::Block(..) => return vals,
ExprKind::Match(val, a, _) => {
vals.push(val);
arms = a;
}
_ => unreachable!("dbg! macro expansion only results in block or match expressions"),
}
}
}

View file

@ -16,7 +16,7 @@ help: ALLOC was deallocated here:
|
LL | };
| ^
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

View file

@ -11,7 +11,7 @@ LL | dbg!(x.0);
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
Uninitialized memory occurred at ALLOC[0x0..0x4], in this allocation:
ALLOC (stack variable, size: 132, align: 4) {

View file

@ -4,7 +4,7 @@ error[E0106]: missing lifetime specifier
LL | dbg!(b);
| ^^^^^^^ expected named lifetime parameter
|
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0425]: cannot find function `a` in this scope
--> $DIR/ice-line-bounds-issue-148732.rs:1:7
@ -37,7 +37,7 @@ LL | dbg!(b);
| ^^^^^^^ the trait `Debug` is not implemented for fn item `fn() {b}`
|
= help: use parentheses to call this function: `b()`
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 4 previous errors

View file

@ -4,7 +4,7 @@ error[E0308]: mismatched types
LL | x => dbg!(x),
| ^^^^^^^ expected `()`, found integer
|
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: you might have meant to return this value
|
LL | x => return dbg!(x),
@ -16,7 +16,7 @@ error[E0308]: mismatched types
LL | dbg!(x)
| ^^^^^^^ expected `()`, found integer
|
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: you might have meant to return this value
|
LL | return dbg!(x)
@ -28,7 +28,7 @@ error[E0308]: mismatched types
LL | _ => dbg!(1)
| ^^^^^^^ expected `()`, found integer
|
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: you might have meant to return this value
|
LL | _ => return dbg!(1)
@ -40,7 +40,7 @@ error[E0308]: mismatched types
LL | _ => {dbg!(1)}
| ^^^^^^^ expected `()`, found integer
|
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: you might have meant to return this value
|
LL | _ => {return dbg!(1)}

View file

@ -5,7 +5,7 @@ error[E0277]: `Dummy` doesn't implement `Debug`
| ^^^^^^^^^^^^^^^^ the trait `Debug` is not implemented for `Dummy`
|
= note: add `#[derive(Debug)]` to `Dummy` or manually `impl Debug for Dummy`
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `Dummy` with `#[derive(Debug)]`
--> $DIR/auxiliary/dummy_lib.rs:2:1
|

View file

@ -8,7 +8,7 @@ LL | let _ = dbg!(a);
LL | let _ = dbg!(a);
| ^^^^^^^ value used here after move
|
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider borrowing instead of transferring ownership
|
LL | let _ = dbg!(&a);

View file

@ -5,7 +5,7 @@ LL | let _: NotDebug = dbg!(NotDebug);
| ^^^^^^^^^^^^^^ the trait `Debug` is not implemented for `NotDebug`
|
= note: add `#[derive(Debug)]` to `NotDebug` or manually `impl Debug for NotDebug`
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider annotating `NotDebug` with `#[derive(Debug)]`
|
LL + #[derive(Debug)]

View file

@ -15,7 +15,7 @@ error[E0308]: mismatched types
LL | b"abc".iter().for_each(|x| dbg!(x));
| ^^^^^^^ expected `()`, found `&u8`
|
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0308]: mismatched types
--> $DIR/closure-ty-mismatch-issue-128561.rs:8:9

View file

@ -26,7 +26,7 @@ error[E0308]: mismatched types
LL | let c: S = dbg!(field);
| ^^^^^^^^^^^ expected `S`, found `&S`
|
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider using clone here
|
LL | let c: S = dbg!(field).clone();
@ -38,7 +38,7 @@ error[E0308]: mismatched types
LL | let c: S = dbg!(dbg!(field));
| ^^^^^^^^^^^^^^^^^ expected `S`, found `&S`
|
= note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `$crate::macros::dbg_internal` which comes from the expansion of the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider using clone here
|
LL | let c: S = dbg!(dbg!(field)).clone();