diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 0401e9b39ff49..8807e4d6ca059 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -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 diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index 25e2b7ea13703..c59d09a03493d 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -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 = "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)),+,) - }; + }, } diff --git a/src/tools/clippy/clippy_lints/src/dbg_macro.rs b/src/tools/clippy/clippy_lints/src/dbg_macro.rs index 152516baf7342..9197870cb6952 100644 --- a/src/tools/clippy/clippy_lints/src/dbg_macro.rs +++ b/src/tools/clippy/clippy_lints/src/dbg_macro.rs @@ -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 { 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"), + } + } +}