Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,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
Expand Down
77 changes: 56 additions & 21 deletions library/std/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 */);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this actually help avoid tearing?

$ cat t.rs
fn main() {
        let a = 1;
        let b = 1;
        eprintln!("a {} b {}", a, b);
}
$ rustc t.rs && strace -o strace.log ./t 2>/dev/null; rg write strace.log
65:write(2, "a ", 2)                       = 2
66:write(2, "1", 1)                        = 1
67:write(2, " b ", 3)                      = 3
68:write(2, "1", 1)                        = 1
69:write(2, "\n", 1)                       = 1

So we should expect this to lower to a series of writes to stderr, i.e., it will in fact tear if there's concurrency. We'd need to add a buffer somewhere to avoid that.

Copy link
Member

@Mark-Simulacrum Mark-Simulacrum Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see, we're taking a lock in fmt::Write for Stderr before calling write_fmt.

Should we avoid the matches and just make dbg! expand to write!(std::io::stderr().lock(), ...) then? That seems simpler and achieves the same goal, right? I guess it'd need to inline the capturing (print_to_buffer_if_capture_used)...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mark-Simulacrum What would you propose would go in that ...? That's precisely what the recursive matches generate.

/// (tmp_1, tmp_2)
/// }
/// }
/// }
/// ```
///
/// This is necessary so that `dbg!` outputs don't get torn, see #136703.
#[doc(hidden)]
#[rustc_macro_transparency = "semitransparent"]
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)),+,)
};
},
}
80 changes: 52 additions & 28 deletions src/tools/clippy/clippy_lints/src/dbg_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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!(),
};
Expand Down Expand Up @@ -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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether this is really necessary, I just copied this from above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed here. You can probably use match arm.body.kind { and add this extra test line to src/tools/clippy/tests/ui/dbg_macro/dbg_macro.rs (and bless the results with ./x test clippy --bless):

        takes_async_fn(async move |val| { dbg!(val, val + 1); val });
        //~^ dbg_macro

ExprKind::Block(..) => return vals,
ExprKind::Match(val, a, _) => {
vals.push(val);
arms = a;
}
_ => unreachable!("dbg! macro expansion only results in block or match expressions"),
}
}
}
Loading