From 72811e7d01e0d51599cf2b3e9ba8ae1f6ab538fc Mon Sep 17 00:00:00 2001 From: joboet Date: Thu, 11 Dec 2025 00:31:46 +0100 Subject: [PATCH 1/8] std: avoid tearing `dbg!` prints (cherry picked from commit bed40af305e84b422c68caa2b5828547ce459e4a) --- library/std/src/lib.rs | 4 +- library/std/src/macros.rs | 77 ++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index b3425e4969ac0..cf444aeaf2bfb 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..0bb14552432d5 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 = "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)),+,) - }; + }, } From 0ab7a1f5ce4d3cd47c5482dc9c7912b761af782a Mon Sep 17 00:00:00 2001 From: joboet Date: Fri, 12 Dec 2025 13:23:48 +0100 Subject: [PATCH 2/8] update `dbg!` clippy lint (cherry picked from commit f80c137f7b518ce9973a509e25a39520a6c9ec89) --- .../clippy/clippy_lints/src/dbg_macro.rs | 84 +++++++++++++------ 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/dbg_macro.rs b/src/tools/clippy/clippy_lints/src/dbg_macro.rs index 8d86a9f346314..10d72a1fe3bc1 100644 --- a/src/tools/clippy/clippy_lints/src/dbg_macro.rs +++ b/src/tools/clippy/clippy_lints/src/dbg_macro.rs @@ -1,11 +1,11 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{is_in_test, sym}; use clippy_utils::macros::{MacroCall, macro_backtrace}; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{is_in_test, sym}; 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}; @@ -92,33 +92,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, ..), - .. + 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() }, - .., - Expr { - kind: ExprKind::Match(last, ..), - .. + // 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})") }, - ], - ) => { - let snippet = snippet_with_applicability( - cx, - first.span.source_callsite().to(last.span.source_callsite()), - "..", - &mut applicability, - ); - (macro_call.span, format!("({snippet})")) + _ => unreachable!(), + }; + (macro_call.span, suggestion) }, _ => unreachable!(), }; @@ -171,3 +165,39 @@ 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"), + } + } +} From d2ef1c90b10b5857ec462c6505e9e86d7112f407 Mon Sep 17 00:00:00 2001 From: dianne Date: Wed, 18 Mar 2026 20:06:02 -0700 Subject: [PATCH 3/8] don't drop arguments' temporaries in `dbg!` (cherry picked from commit 51816efcc02d36d6f4c1a9149f655869ee6bc58d) --- library/std/src/macros.rs | 82 ++++++++++--------- library/std/src/macros/tests.rs | 13 +++ .../clippy/clippy_lints/src/dbg_macro.rs | 47 ++--------- .../dangling_primitive.stderr | 2 +- .../return_pointer_on_unwind.stderr | 2 +- tests/ui/borrowck/dbg-issue-120327.stderr | 40 ++++----- tests/ui/liveness/liveness-upvars.rs | 2 +- tests/ui/liveness/liveness-upvars.stderr | 10 ++- .../dbg-macro-move-semantics.stderr | 16 ++-- 9 files changed, 103 insertions(+), 111 deletions(-) create mode 100644 library/std/src/macros/tests.rs diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index 0bb14552432d5..5f9b383c2da37 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -5,6 +5,9 @@ //! library. // ignore-tidy-dbg +#[cfg(test)] +mod tests; + #[doc = include_str!("../../core/src/macros/panic.md")] #[macro_export] #[rustc_builtin_macro(std_panic)] @@ -359,19 +362,16 @@ macro_rules! dbg { }; } -/// 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. +/// Internal macro that processes a list of expressions, binds their results +/// with `match`, calls `eprint!` with the collected information, and returns +/// 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) -/// } +/// match (1, 2) { +/// (tmp_1, tmp_2) => { +/// eprint!("...", &tmp_1, &tmp_2, /* some other arguments */); +/// (tmp_1, tmp_2) /// } /// } /// ``` @@ -380,37 +380,41 @@ macro_rules! dbg { #[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)*)) => { + (($($piece:literal),+) ($($processed:expr => $bound:ident),+) ()) => { // Use of `match` here is intentional because it affects the lifetimes // of temporaries - https://stackoverflow.com/a/48732525/1063961 - match $val { - tmp => $crate::macros::dbg_internal!( - ($($piece,)* "[{file}:{line}:{column}] {} = {:#?}\n") - ($($processed => $bound,)* $val => tmp) - ($($rest),*) - ), + // Always put the arguments in a tuple to avoid an unused parens lint on the pattern. + match ($($processed,)+) { + ($($bound,)+) => { + $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:ident),*) ($val:expr $(,$rest:expr)*)) => { + $crate::macros::dbg_internal!( + ($($piece,)* "[{file}:{line}:{column}] {} = {:#?}\n") + ($($processed => $bound,)* $val => tmp) + ($($rest),*) + ) + }, } diff --git a/library/std/src/macros/tests.rs b/library/std/src/macros/tests.rs new file mode 100644 index 0000000000000..db2be925ff30a --- /dev/null +++ b/library/std/src/macros/tests.rs @@ -0,0 +1,13 @@ +// ignore-tidy-dbg + +/// Test for : +/// `dbg!` shouldn't drop arguments' temporaries. +#[test] +fn no_dropping_temps() { + fn temp() {} + + *dbg!(&temp()); + *dbg!(&temp(), 1).0; + *dbg!(0, &temp()).1; + *dbg!(0, &temp(), 2).1; +} diff --git a/src/tools/clippy/clippy_lints/src/dbg_macro.rs b/src/tools/clippy/clippy_lints/src/dbg_macro.rs index 10d72a1fe3bc1..6a270c0d703af 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::source::snippet_with_applicability; use clippy_utils::{is_in_test, sym}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::{Arm, Closure, ClosureKind, CoroutineKind, Expr, ExprKind, LetStmt, LocalSource, Node, Stmt, StmtKind}; +use rustc_hir::{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}; @@ -92,16 +92,15 @@ impl LateLintPass<'_> for DbgMacro { (macro_call.span, String::from("()")) } }, - ExprKind::Match(first, arms, _) => { - let vals = collect_vals(first, arms); - let suggestion = match vals.as_slice() { + ExprKind::Match(args, _, _) => { + let suggestion = match args.kind { // dbg!(1) => 1 - &[val] => { + ExprKind::Tup([val]) => { snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability) .to_string() }, // dbg!(2, 3) => (2, 3) - &[first, .., last] => { + ExprKind::Tup([first, .., last]) => { let snippet = snippet_with_applicability( cx, first.span.source_callsite().to(last.span.source_callsite()), @@ -165,39 +164,3 @@ 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"), - } - } -} diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_primitive.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_primitive.stderr index afc2cb214842e..4f06a1afa50fe 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_primitive.stderr +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_primitive.stderr @@ -2,7 +2,7 @@ error: Undefined Behavior: memory access failed: ALLOC has been freed, so this p --> tests/fail/dangling_pointers/dangling_primitive.rs:LL:CC | LL | dbg!(*ptr); - | ^^^^^^^^^^ Undefined Behavior occurred here + | ^^^^ Undefined Behavior occurred here | = 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 diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_on_unwind.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_on_unwind.stderr index 364a4b4a74418..88d5694c4736d 100644 --- a/src/tools/miri/tests/fail/function_calls/return_pointer_on_unwind.stderr +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_on_unwind.stderr @@ -7,7 +7,7 @@ error: Undefined Behavior: reading memory at ALLOC[0x0..0x4], but memory is unin --> tests/fail/function_calls/return_pointer_on_unwind.rs:LL:CC | LL | dbg!(x.0); - | ^^^^^^^^^ Undefined Behavior occurred here + | ^^^ Undefined Behavior occurred here | = 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 diff --git a/tests/ui/borrowck/dbg-issue-120327.stderr b/tests/ui/borrowck/dbg-issue-120327.stderr index efacc0c3f1341..685a530a1fe5b 100644 --- a/tests/ui/borrowck/dbg-issue-120327.stderr +++ b/tests/ui/borrowck/dbg-issue-120327.stderr @@ -4,14 +4,14 @@ error[E0382]: use of moved value: `a` LL | let a = String::new(); | - move occurs because `a` has type `String`, which does not implement the `Copy` trait LL | dbg!(a); - | ------- value moved here + | - value moved here LL | return a; | ^ value used here after move | -help: consider borrowing instead of transferring ownership +help: consider cloning the value if the performance cost is acceptable | -LL | dbg!(&a); - | + +LL | dbg!(a.clone()); + | ++++++++ error[E0382]: use of moved value: `a` --> $DIR/dbg-issue-120327.rs:10:12 @@ -19,14 +19,14 @@ error[E0382]: use of moved value: `a` LL | let a = String::new(); | - move occurs because `a` has type `String`, which does not implement the `Copy` trait LL | dbg!(1, 2, a, 1, 2); - | ------------------- value moved here + | - value moved here LL | return a; | ^ value used here after move | -help: consider borrowing instead of transferring ownership +help: consider cloning the value if the performance cost is acceptable | -LL | dbg!(1, 2, &a, 1, 2); - | + +LL | dbg!(1, 2, a.clone(), 1, 2); + | ++++++++ error[E0382]: use of moved value: `b` --> $DIR/dbg-issue-120327.rs:16:12 @@ -34,14 +34,14 @@ error[E0382]: use of moved value: `b` LL | let b: String = "".to_string(); | - move occurs because `b` has type `String`, which does not implement the `Copy` trait LL | dbg!(a, b); - | ---------- value moved here + | - value moved here LL | return b; | ^ value used here after move | -help: consider borrowing instead of transferring ownership +help: consider cloning the value if the performance cost is acceptable | -LL | dbg!(a, &b); - | + +LL | dbg!(a, b.clone()); + | ++++++++ error[E0382]: use of moved value: `a` --> $DIR/dbg-issue-120327.rs:22:12 @@ -50,14 +50,14 @@ LL | fn x(a: String) -> String { | - move occurs because `a` has type `String`, which does not implement the `Copy` trait LL | let b: String = "".to_string(); LL | dbg!(a, b); - | ---------- value moved here + | - value moved here LL | return a; | ^ value used here after move | -help: consider borrowing instead of transferring ownership +help: consider cloning the value if the performance cost is acceptable | -LL | dbg!(&a, b); - | + +LL | dbg!(a.clone(), b); + | ++++++++ error[E0382]: use of moved value: `b` --> $DIR/dbg-issue-120327.rs:46:12 @@ -103,14 +103,14 @@ error[E0382]: borrow of moved value: `a` LL | let a: String = "".to_string(); | - move occurs because `a` has type `String`, which does not implement the `Copy` trait LL | let _res = get_expr(dbg!(a)); - | ------- value moved here + | - value moved here LL | let _l = a.len(); | ^ value borrowed here after move | -help: consider borrowing instead of transferring ownership +help: consider cloning the value if the performance cost is acceptable | -LL | let _res = get_expr(dbg!(&a)); - | + +LL | let _res = get_expr(dbg!(a.clone())); + | ++++++++ error: aborting due to 7 previous errors diff --git a/tests/ui/liveness/liveness-upvars.rs b/tests/ui/liveness/liveness-upvars.rs index be58b48a40576..0e198f1dea10b 100644 --- a/tests/ui/liveness/liveness-upvars.rs +++ b/tests/ui/liveness/liveness-upvars.rs @@ -98,7 +98,7 @@ pub fn g(mut v: T) { } pub fn h() { - let mut z = T::default(); + let mut z = T::default(); //~ WARN unused variable: `z` let _ = move |b| { loop { if b { diff --git a/tests/ui/liveness/liveness-upvars.stderr b/tests/ui/liveness/liveness-upvars.stderr index cfed2830164ad..0bb5786ab3e2e 100644 --- a/tests/ui/liveness/liveness-upvars.stderr +++ b/tests/ui/liveness/liveness-upvars.stderr @@ -156,6 +156,14 @@ LL | z = T::default(); | = help: maybe it is overwritten before being read? +warning: unused variable: `z` + --> $DIR/liveness-upvars.rs:101:9 + | +LL | let mut z = T::default(); + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_z` + | + = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` + warning: value captured by `state` is never read --> $DIR/liveness-upvars.rs:131:9 | @@ -196,5 +204,5 @@ LL | s = yield (); | = help: maybe it is overwritten before being read? -warning: 24 warnings emitted +warning: 25 warnings emitted diff --git a/tests/ui/rfcs/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr b/tests/ui/rfcs/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr index f8ef315b9cc78..76b2d1be27795 100644 --- a/tests/ui/rfcs/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr +++ b/tests/ui/rfcs/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr @@ -1,17 +1,21 @@ error[E0382]: use of moved value: `a` - --> $DIR/dbg-macro-move-semantics.rs:9:13 + --> $DIR/dbg-macro-move-semantics.rs:9:18 | LL | let a = NoCopy(0); | - move occurs because `a` has type `NoCopy`, which does not implement the `Copy` trait LL | let _ = dbg!(a); - | ------- value moved here + | - value moved here LL | let _ = dbg!(a); - | ^^^^^^^ value used here after move + | ^ value used here after move | -help: consider borrowing instead of transferring ownership +note: if `NoCopy` implemented `Clone`, you could clone the value + --> $DIR/dbg-macro-move-semantics.rs:4:1 | -LL | let _ = dbg!(&a); - | + +LL | struct NoCopy(usize); + | ^^^^^^^^^^^^^ consider implementing `Clone` for this type +... +LL | let _ = dbg!(a); + | - you could clone this value error: aborting due to 1 previous error From 9b99f7ff466de0f87a33848afbac782261644808 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 29 Mar 2026 08:08:51 -0700 Subject: [PATCH 4/8] update diagnostic for variables moved by `dbg!` (cherry picked from commit 7d1b41cbb386fef7763885f738f7f89b48de10e2) --- .../src/diagnostics/conflict_errors.rs | 40 +++++---- compiler/rustc_span/src/symbol.rs | 1 + src/tools/clippy/clippy_utils/src/sym.rs | 1 - tests/ui/borrowck/dbg-issue-120327.rs | 53 ++++-------- tests/ui/borrowck/dbg-issue-120327.stderr | 81 ++++++++++++------- .../dbg-macro-move-semantics.stderr | 4 + 6 files changed, 96 insertions(+), 84 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index c2c07614bc0dd..3e34f2e5896ee 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -545,8 +545,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } // for dbg!(x) which may take ownership, suggest dbg!(&x) instead - // but here we actually do not check whether the macro name is `dbg!` - // so that we may extend the scope a bit larger to cover more cases fn suggest_ref_for_dbg_args( &self, body: &hir::Expr<'_>, @@ -560,29 +558,41 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { }); let Some(var_info) = var_info else { return }; let arg_name = var_info.name; - struct MatchArgFinder { - expr_span: Span, - match_arg_span: Option, + struct MatchArgFinder<'tcx> { + tcx: TyCtxt<'tcx>, + move_span: Span, arg_name: Symbol, + match_arg_span: Option = None, } - impl Visitor<'_> for MatchArgFinder { + impl Visitor<'_> for MatchArgFinder<'_> { fn visit_expr(&mut self, e: &hir::Expr<'_>) { // dbg! is expanded into a match pattern, we need to find the right argument span - if let hir::ExprKind::Match(expr, ..) = &e.kind - && let hir::ExprKind::Path(hir::QPath::Resolved( - _, - path @ Path { segments: [seg], .. }, - )) = &expr.kind - && seg.ident.name == self.arg_name - && self.expr_span.source_callsite().contains(expr.span) + if let hir::ExprKind::Match(scrutinee, ..) = &e.kind + && let hir::ExprKind::Tup(args) = scrutinee.kind + && e.span.macro_backtrace().any(|expn| { + expn.macro_def_id.is_some_and(|macro_def_id| { + self.tcx.is_diagnostic_item(sym::dbg_macro, macro_def_id) + }) + }) { - self.match_arg_span = Some(path.span); + for arg in args { + if let hir::ExprKind::Path(hir::QPath::Resolved( + _, + path @ Path { segments: [seg], .. }, + )) = &arg.kind + && seg.ident.name == self.arg_name + && self.move_span.source_equal(arg.span) + { + self.match_arg_span = Some(path.span); + return; + } + } } hir::intravisit::walk_expr(self, e); } } - let mut finder = MatchArgFinder { expr_span: move_span, match_arg_span: None, arg_name }; + let mut finder = MatchArgFinder { tcx: self.infcx.tcx, move_span, arg_name, .. }; finder.visit_expr(body); if let Some(macro_arg_span) = finder.match_arg_span { err.span_suggestion_verbose( diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 731a838530729..5b079bcaf0db8 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -763,6 +763,7 @@ symbols! { custom_test_frameworks, d, d32, + dbg_macro, dead_code, dealloc, debug, diff --git a/src/tools/clippy/clippy_utils/src/sym.rs b/src/tools/clippy/clippy_utils/src/sym.rs index 250e73afd4f00..a7171cff66f90 100644 --- a/src/tools/clippy/clippy_utils/src/sym.rs +++ b/src/tools/clippy/clippy_utils/src/sym.rs @@ -199,7 +199,6 @@ generate! { cx, cycle, cyclomatic_complexity, - dbg_macro, de, debug_struct, deprecated_in_future, diff --git a/tests/ui/borrowck/dbg-issue-120327.rs b/tests/ui/borrowck/dbg-issue-120327.rs index 2de43f634877a..45605acc34334 100644 --- a/tests/ui/borrowck/dbg-issue-120327.rs +++ b/tests/ui/borrowck/dbg-issue-120327.rs @@ -1,67 +1,44 @@ +//! Diagnostic test for : suggest borrowing +//! variables passed to `dbg!` that are later used. +//@ dont-require-annotations: HELP + fn s() -> String { let a = String::new(); - dbg!(a); + dbg!(a); //~ HELP consider borrowing instead of transferring ownership return a; //~ ERROR use of moved value: } fn m() -> String { let a = String::new(); - dbg!(1, 2, a, 1, 2); + dbg!(1, 2, a, 1, 2); //~ HELP consider borrowing instead of transferring ownership return a; //~ ERROR use of moved value: } fn t(a: String) -> String { let b: String = "".to_string(); - dbg!(a, b); + dbg!(a, b); //~ HELP consider borrowing instead of transferring ownership return b; //~ ERROR use of moved value: } fn x(a: String) -> String { let b: String = "".to_string(); - dbg!(a, b); + dbg!(a, b); //~ HELP consider borrowing instead of transferring ownership return a; //~ ERROR use of moved value: } -macro_rules! my_dbg { - () => { - eprintln!("[{}:{}:{}]", file!(), line!(), column!()) - }; - ($val:expr $(,)?) => { - match $val { - tmp => { - eprintln!("[{}:{}:{}] {} = {:#?}", - file!(), line!(), column!(), stringify!($val), &tmp); - tmp - } - } - }; - ($($val:expr),+ $(,)?) => { - ($(my_dbg!($val)),+,) - }; -} - -fn test_my_dbg() -> String { - let b: String = "".to_string(); - my_dbg!(b, 1); - return b; //~ ERROR use of moved value: -} - -fn test_not_macro() -> String { - let a = String::new(); - let _b = match a { - tmp => { - eprintln!("dbg: {}", tmp); - tmp - } - }; - return a; //~ ERROR use of moved value: +fn two_of_them(a: String) -> String { + dbg!(a, a); //~ ERROR use of moved value + //~| HELP consider borrowing instead of transferring ownership + //~| HELP consider borrowing instead of transferring ownership + return a; //~ ERROR use of moved value } fn get_expr(_s: String) {} +// The suggestion is purely syntactic; applying it here will result in a type error. fn test() { let a: String = "".to_string(); - let _res = get_expr(dbg!(a)); + let _res = get_expr(dbg!(a)); //~ HELP consider borrowing instead of transferring ownership let _l = a.len(); //~ ERROR borrow of moved value } diff --git a/tests/ui/borrowck/dbg-issue-120327.stderr b/tests/ui/borrowck/dbg-issue-120327.stderr index 685a530a1fe5b..e7a7151e541dd 100644 --- a/tests/ui/borrowck/dbg-issue-120327.stderr +++ b/tests/ui/borrowck/dbg-issue-120327.stderr @@ -1,5 +1,5 @@ error[E0382]: use of moved value: `a` - --> $DIR/dbg-issue-120327.rs:4:12 + --> $DIR/dbg-issue-120327.rs:8:12 | LL | let a = String::new(); | - move occurs because `a` has type `String`, which does not implement the `Copy` trait @@ -12,9 +12,13 @@ help: consider cloning the value if the performance cost is acceptable | LL | dbg!(a.clone()); | ++++++++ +help: consider borrowing instead of transferring ownership + | +LL | dbg!(&a); + | + error[E0382]: use of moved value: `a` - --> $DIR/dbg-issue-120327.rs:10:12 + --> $DIR/dbg-issue-120327.rs:14:12 | LL | let a = String::new(); | - move occurs because `a` has type `String`, which does not implement the `Copy` trait @@ -27,9 +31,13 @@ help: consider cloning the value if the performance cost is acceptable | LL | dbg!(1, 2, a.clone(), 1, 2); | ++++++++ +help: consider borrowing instead of transferring ownership + | +LL | dbg!(1, 2, &a, 1, 2); + | + error[E0382]: use of moved value: `b` - --> $DIR/dbg-issue-120327.rs:16:12 + --> $DIR/dbg-issue-120327.rs:20:12 | LL | let b: String = "".to_string(); | - move occurs because `b` has type `String`, which does not implement the `Copy` trait @@ -42,9 +50,13 @@ help: consider cloning the value if the performance cost is acceptable | LL | dbg!(a, b.clone()); | ++++++++ +help: consider borrowing instead of transferring ownership + | +LL | dbg!(a, &b); + | + error[E0382]: use of moved value: `a` - --> $DIR/dbg-issue-120327.rs:22:12 + --> $DIR/dbg-issue-120327.rs:26:12 | LL | fn x(a: String) -> String { | - move occurs because `a` has type `String`, which does not implement the `Copy` trait @@ -58,47 +70,52 @@ help: consider cloning the value if the performance cost is acceptable | LL | dbg!(a.clone(), b); | ++++++++ +help: consider borrowing instead of transferring ownership + | +LL | dbg!(&a, b); + | + -error[E0382]: use of moved value: `b` - --> $DIR/dbg-issue-120327.rs:46:12 +error[E0382]: use of moved value: `a` + --> $DIR/dbg-issue-120327.rs:30:13 | -LL | tmp => { - | --- value moved here -... -LL | let b: String = "".to_string(); - | - move occurs because `b` has type `String`, which does not implement the `Copy` trait -LL | my_dbg!(b, 1); -LL | return b; - | ^ value used here after move +LL | fn two_of_them(a: String) -> String { + | - move occurs because `a` has type `String`, which does not implement the `Copy` trait +LL | dbg!(a, a); + | - ^ value used here after move + | | + | value moved here | -help: consider borrowing instead of transferring ownership +help: consider cloning the value if the performance cost is acceptable | -LL | my_dbg!(&b, 1); - | + -help: borrow this binding in the pattern to avoid moving the value +LL | dbg!(a.clone(), a); + | ++++++++ +help: consider borrowing instead of transferring ownership | -LL | ref tmp => { - | +++ +LL | dbg!(&a, a); + | + error[E0382]: use of moved value: `a` - --> $DIR/dbg-issue-120327.rs:57:12 + --> $DIR/dbg-issue-120327.rs:33:12 | -LL | let a = String::new(); - | - move occurs because `a` has type `String`, which does not implement the `Copy` trait -LL | let _b = match a { -LL | tmp => { - | --- value moved here +LL | fn two_of_them(a: String) -> String { + | - move occurs because `a` has type `String`, which does not implement the `Copy` trait +LL | dbg!(a, a); + | - value moved here ... LL | return a; | ^ value used here after move | -help: borrow this binding in the pattern to avoid moving the value +help: consider cloning the value if the performance cost is acceptable + | +LL | dbg!(a, a.clone()); + | ++++++++ +help: consider borrowing instead of transferring ownership | -LL | ref tmp => { - | +++ +LL | dbg!(a, &a); + | + error[E0382]: borrow of moved value: `a` - --> $DIR/dbg-issue-120327.rs:65:14 + --> $DIR/dbg-issue-120327.rs:42:14 | LL | let a: String = "".to_string(); | - move occurs because `a` has type `String`, which does not implement the `Copy` trait @@ -111,6 +128,10 @@ help: consider cloning the value if the performance cost is acceptable | LL | let _res = get_expr(dbg!(a.clone())); | ++++++++ +help: consider borrowing instead of transferring ownership + | +LL | let _res = get_expr(dbg!(&a)); + | + error: aborting due to 7 previous errors diff --git a/tests/ui/rfcs/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr b/tests/ui/rfcs/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr index 76b2d1be27795..7ce5ebf81e310 100644 --- a/tests/ui/rfcs/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr +++ b/tests/ui/rfcs/rfc-2361-dbg-macro/dbg-macro-move-semantics.stderr @@ -16,6 +16,10 @@ LL | struct NoCopy(usize); ... LL | let _ = dbg!(a); | - you could clone this value +help: consider borrowing instead of transferring ownership + | +LL | let _ = dbg!(&a); + | + error: aborting due to 1 previous error From c2ea77e058846de4fcd98d7b21389622d711f81d Mon Sep 17 00:00:00 2001 From: dianne Date: Wed, 8 Apr 2026 08:42:07 -0700 Subject: [PATCH 5/8] don't leak internal temporaries from `dbg!` (cherry picked from commit 35be9f22089d49b265c4c95d5c8665a748eeb462) --- library/std/src/macros.rs | 7 +++++-- library/std/src/macros/tests.rs | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index 5f9b383c2da37..f3f79610fbdca 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -369,7 +369,8 @@ macro_rules! dbg { /// E.g. `dbg_internal!(() () (1, 2))` expands into /// ```rust, ignore /// match (1, 2) { -/// (tmp_1, tmp_2) => { +/// args => { +/// let (tmp_1, tmp_2) = args; /// eprint!("...", &tmp_1, &tmp_2, /* some other arguments */); /// (tmp_1, tmp_2) /// } @@ -385,7 +386,9 @@ pub macro dbg_internal { // of temporaries - https://stackoverflow.com/a/48732525/1063961 // Always put the arguments in a tuple to avoid an unused parens lint on the pattern. match ($($processed,)+) { - ($($bound,)+) => { + // Move the entire tuple so it doesn't stick around as a temporary (#154988). + args => { + let ($($bound,)+) = args; $crate::eprint!( $crate::concat!($($piece),+), $( diff --git a/library/std/src/macros/tests.rs b/library/std/src/macros/tests.rs index db2be925ff30a..230bfdf3c9836 100644 --- a/library/std/src/macros/tests.rs +++ b/library/std/src/macros/tests.rs @@ -1,5 +1,7 @@ // ignore-tidy-dbg +use core::fmt::Debug; + /// Test for : /// `dbg!` shouldn't drop arguments' temporaries. #[test] @@ -11,3 +13,25 @@ fn no_dropping_temps() { *dbg!(0, &temp()).1; *dbg!(0, &temp(), 2).1; } + +/// Test for : +/// `dbg!` shouldn't create a temporary that lives past its invocation. +#[test] +fn no_leaking_internal_temps_from_dbg() { + #[derive(Debug)] + struct Foo; + + #[derive(Debug)] + struct Bar<'a>(#[allow(unused)] &'a Foo); + impl Drop for Bar<'_> { + fn drop(&mut self) {} + } + + let foo = Foo; + let bar = Bar(&foo); + // If `dbg!` creates a `(Bar<'_>,)` temporary that lasts past its expansion, this will fail + // to compile, because it will be dropped after `foo`, which it borrows from. The tuple + // mimics the drop order of block tail expressions before Rust 2024: first the result of `dbg!` + // is dropped, then `foo`, then any temporaries left over from `dbg!` are dropped, if present. + (drop(dbg!(bar)), drop(foo)); +} From aae280909ba61e92899d14ef12cb56344591f367 Mon Sep 17 00:00:00 2001 From: dianne Date: Tue, 28 Apr 2026 02:15:53 -0700 Subject: [PATCH 6/8] de-stabilize `std::macros::dbg_internal!` --- library/std/src/macros.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index f3f79610fbdca..a8b10220b39a0 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -379,7 +379,9 @@ macro_rules! dbg { /// /// This is necessary so that `dbg!` outputs don't get torn, see #136703. #[doc(hidden)] +#[allow_internal_unstable(std_internals)] #[rustc_macro_transparency = "semiopaque"] +#[unstable(feature = "std_internals", issue = "none")] pub macro dbg_internal { (($($piece:literal),+) ($($processed:expr => $bound:ident),+) ()) => { // Use of `match` here is intentional because it affects the lifetimes From 67f8de0e553395727bb8ca7fa0d0b73a8594119a Mon Sep 17 00:00:00 2001 From: dianne Date: Tue, 28 Apr 2026 03:44:58 -0700 Subject: [PATCH 7/8] reimplement `dbg!` with `super let` --- .../src/diagnostics/conflict_errors.rs | 35 ++++---- library/std/src/lib.rs | 5 +- library/std/src/macros.rs | 81 +++++++++---------- library/std/src/macros/tests.rs | 30 +++++++ .../clippy/clippy_lints/src/dbg_macro.rs | 67 +++++++++------ .../mismatched-types-issue-126222.fixed | 4 +- .../mismatched-types-issue-126222.stderr | 38 ++++++--- 7 files changed, 157 insertions(+), 103 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 3e34f2e5896ee..8cecbc09b1f59 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -558,43 +558,38 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { }); let Some(var_info) = var_info else { return }; let arg_name = var_info.name; - struct MatchArgFinder<'tcx> { + struct DbgArgFinder<'tcx> { tcx: TyCtxt<'tcx>, move_span: Span, arg_name: Symbol, - match_arg_span: Option = None, + dbg_arg_span: Option = None, } - impl Visitor<'_> for MatchArgFinder<'_> { + impl Visitor<'_> for DbgArgFinder<'_> { fn visit_expr(&mut self, e: &hir::Expr<'_>) { - // dbg! is expanded into a match pattern, we need to find the right argument span - if let hir::ExprKind::Match(scrutinee, ..) = &e.kind - && let hir::ExprKind::Tup(args) = scrutinee.kind + // dbg! is expanded into assignments, we need to find the right argument span + if let hir::ExprKind::Assign(_, arg, _) = &e.kind + && let hir::ExprKind::Path(hir::QPath::Resolved( + _, + path @ Path { segments: [seg], .. }, + )) = &arg.kind + && seg.ident.name == self.arg_name + && self.move_span.source_equal(arg.span) && e.span.macro_backtrace().any(|expn| { expn.macro_def_id.is_some_and(|macro_def_id| { self.tcx.is_diagnostic_item(sym::dbg_macro, macro_def_id) }) }) { - for arg in args { - if let hir::ExprKind::Path(hir::QPath::Resolved( - _, - path @ Path { segments: [seg], .. }, - )) = &arg.kind - && seg.ident.name == self.arg_name - && self.move_span.source_equal(arg.span) - { - self.match_arg_span = Some(path.span); - return; - } - } + self.dbg_arg_span = Some(path.span); + return; } hir::intravisit::walk_expr(self, e); } } - let mut finder = MatchArgFinder { tcx: self.infcx.tcx, move_span, arg_name, .. }; + let mut finder = DbgArgFinder { tcx: self.infcx.tcx, move_span, arg_name, .. }; finder.visit_expr(body); - if let Some(macro_arg_span) = finder.match_arg_span { + if let Some(macro_arg_span) = finder.dbg_arg_span { err.span_suggestion_verbose( macro_arg_span.shrink_to_lo(), "consider borrowing instead of transferring ownership", diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index cf444aeaf2bfb..ddf8c4956ce29 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -255,7 +255,10 @@ #![allow(unused_features)] // // Features: -#![cfg_attr(test, feature(internal_output_capture, print_internals, update_panic_count, rt))] +#![cfg_attr( + test, + feature(internal_output_capture, print_internals, super_let, update_panic_count, rt) +)] #![cfg_attr( all(target_vendor = "fortanix", target_env = "sgx"), feature(slice_index_methods, coerce_unsized, sgx_platform) diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index a8b10220b39a0..39428d26f687a 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -362,59 +362,56 @@ macro_rules! dbg { }; } -/// Internal macro that processes a list of expressions, binds their results -/// with `match`, calls `eprint!` with the collected information, and returns -/// all the evaluated expressions in a tuple. +/// Internal macro that processes a list of expressions, binds their results, calls `eprint!` with +/// the collected information, and returns all the evaluated expressions in a tuple. /// /// E.g. `dbg_internal!(() () (1, 2))` expands into /// ```rust, ignore -/// match (1, 2) { -/// args => { -/// let (tmp_1, tmp_2) = args; -/// eprint!("...", &tmp_1, &tmp_2, /* some other arguments */); -/// (tmp_1, tmp_2) -/// } +/// { +/// let tmp_1; +/// let tmp_2; +/// super let _ = (tmp_1 = 1); +/// super let _ = (tmp_2 = 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. +/// `super let` is used to avoid creating a temporary scope around `dbg!`'s arguments. Nested +/// `match` is insufficient because match arms introduce temporary scopes (#153850) and using a +/// single match on a tuple containing all the arguments is insufficient because the borrow checker +/// thinks that tuple can outlive the `dbg!` invocation if dropping the temporary places the tuple's +/// elements were moved out of panics (not actually possible; they've been moved from). See #155902. #[doc(hidden)] -#[allow_internal_unstable(std_internals)] +#[allow_internal_unstable(std_internals, super_let)] #[rustc_macro_transparency = "semiopaque"] #[unstable(feature = "std_internals", issue = "none")] pub macro dbg_internal { - (($($piece:literal),+) ($($processed:expr => $bound:ident),+) ()) => { - // Use of `match` here is intentional because it affects the lifetimes - // of temporaries - https://stackoverflow.com/a/48732525/1063961 - // Always put the arguments in a tuple to avoid an unused parens lint on the pattern. - match ($($processed,)+) { - // Move the entire tuple so it doesn't stick around as a temporary (#154988). - args => { - let ($($bound,)+) = args; - $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:ident),+) ()) => {{ + $(let $bound);+; + $(super let _ = ($bound = $processed));+; + $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:ident),*) ($val:expr $(,$rest:expr)*)) => { $crate::macros::dbg_internal!( ($($piece,)* "[{file}:{line}:{column}] {} = {:#?}\n") diff --git a/library/std/src/macros/tests.rs b/library/std/src/macros/tests.rs index 230bfdf3c9836..1ad4cf8c4c039 100644 --- a/library/std/src/macros/tests.rs +++ b/library/std/src/macros/tests.rs @@ -35,3 +35,33 @@ fn no_leaking_internal_temps_from_dbg() { // is dropped, then `foo`, then any temporaries left over from `dbg!` are dropped, if present. (drop(dbg!(bar)), drop(foo)); } + +/// Test for : +/// `dbg!` shouldn't create a temporary that borrowck thinks can live past its invocation on a false +/// unwind path. +#[test] +fn no_leaking_internal_temps_from_dbg_even_on_false_unwind() { + #[derive(Debug)] + struct Foo; + impl Drop for Foo { + fn drop(&mut self) {} + } + + #[derive(Debug)] + struct Bar<'a>(#[allow(unused)] &'a Foo); + impl Drop for Bar<'_> { + fn drop(&mut self) {} + } + + { + let foo = Foo; + let bar = Bar(&foo); + // The temporaries of this `super let`'s scrutinee will outlive `bar` and `foo`, emulating + // the drop order of block tail expressions before Rust 2024. If borrowck thinks that a + // panic from moving `bar` is possible and that a `Bar<'_>`-containing temporary lives past + // the end of the block because of that, this will fail to compile. Because `Foo` implements + // `Drop`, it's an error for `foo` to be dropped before such a temporary when unwinding; + // otherwise, `foo` would just live to the end of the stack frame when unwinding. + super let _ = drop(dbg!(bar)); + } +} diff --git a/src/tools/clippy/clippy_lints/src/dbg_macro.rs b/src/tools/clippy/clippy_lints/src/dbg_macro.rs index 6a270c0d703af..7889322c18765 100644 --- a/src/tools/clippy/clippy_lints/src/dbg_macro.rs +++ b/src/tools/clippy/clippy_lints/src/dbg_macro.rs @@ -75,13 +75,33 @@ impl LateLintPass<'_> for DbgMacro { "the `dbg!` macro is intended as a debugging tool", |diag| { let mut applicability = Applicability::MachineApplicable; - let (sugg_span, suggestion) = match is_async_move_desugar(expr) - .unwrap_or(expr) - .peel_drop_temps() - .kind - { + let dbg_expn = is_async_move_desugar(expr).unwrap_or(expr).peel_drop_temps(); + // `dbg!` always expands to a block. If it was given arguments, it assigns names to them + // using `super let _ = (tmp = $arg);` statements. + let ExprKind::Block(block, _) = dbg_expn.kind else { + unreachable!() + }; + let args: Vec<_> = block + .stmts + .iter() + .filter_map(|stmt| { + if let StmtKind::Let(LetStmt { + super_: Some(_), + init: Some(init), + .. + }) = stmt.kind + && let ExprKind::Assign(_, arg, _) = init.kind + { + Some(arg) + } else { + None + } + }) + .collect(); + + let (sugg_span, suggestion) = match args.as_slice() { // 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) @@ -92,28 +112,23 @@ impl LateLintPass<'_> for DbgMacro { (macro_call.span, String::from("()")) } }, - ExprKind::Match(args, _, _) => { - let suggestion = match args.kind { - // dbg!(1) => 1 - ExprKind::Tup([val]) => { - snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability) - .to_string() - }, - // dbg!(2, 3) => (2, 3) - ExprKind::Tup([first, .., last]) => { - let snippet = snippet_with_applicability( - cx, - first.span.source_callsite().to(last.span.source_callsite()), - "..", - &mut applicability, - ); - format!("({snippet})") - }, - _ => unreachable!(), - }; + // dbg!(1) => 1 + [val] => { + let suggestion = + snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability) + .to_string(); (macro_call.span, suggestion) }, - _ => unreachable!(), + // dbg!(2, 3) => (2, 3) + [first, .., last] => { + let snippet = snippet_with_applicability( + cx, + first.span.source_callsite().to(last.span.source_callsite()), + "..", + &mut applicability, + ); + (macro_call.span, format!("({snippet})")) + }, }; diag.span_suggestion( diff --git a/tests/ui/mismatched_types/mismatched-types-issue-126222.fixed b/tests/ui/mismatched_types/mismatched-types-issue-126222.fixed index 30fd0028f198f..d857a0f6c7761 100644 --- a/tests/ui/mismatched_types/mismatched-types-issue-126222.fixed +++ b/tests/ui/mismatched_types/mismatched-types-issue-126222.fixed @@ -12,7 +12,7 @@ fn main() { fn mismatch_types2() -> i32 { match 2 { x => { - return dbg!(x) //~ ERROR mismatched types + return dbg!(x); //~ ERROR mismatched types } } todo!() @@ -27,7 +27,7 @@ fn main() { fn mismatch_types4() -> i32 { match 1 { - _ => {return dbg!(1)} //~ ERROR mismatched types + _ => {return dbg!(1);} //~ ERROR mismatched types } todo!() } diff --git a/tests/ui/mismatched_types/mismatched-types-issue-126222.stderr b/tests/ui/mismatched_types/mismatched-types-issue-126222.stderr index 09ba15253dc58..ca38d8324c989 100644 --- a/tests/ui/mismatched_types/mismatched-types-issue-126222.stderr +++ b/tests/ui/mismatched_types/mismatched-types-issue-126222.stderr @@ -1,8 +1,11 @@ error[E0308]: mismatched types --> $DIR/mismatched-types-issue-126222.rs:7:18 | -LL | x => dbg!(x), - | ^^^^^^^ expected `()`, found integer +LL | / match 1 { +LL | | x => dbg!(x), + | | ^^^^^^^ expected `()`, found integer +LL | | } + | |_________- expected this to be `()` | help: you might have meant to return this value | @@ -12,19 +15,27 @@ LL | x => return dbg!(x), error[E0308]: mismatched types --> $DIR/mismatched-types-issue-126222.rs:15:17 | -LL | dbg!(x) - | ^^^^^^^ expected `()`, found integer +LL | / match 2 { +LL | | x => { +LL | | dbg!(x) + | | ^^^^^^^ expected `()`, found integer +LL | | } +LL | | } + | |_________- expected this to be `()` | help: you might have meant to return this value | -LL | return dbg!(x) - | ++++++ +LL | return dbg!(x); + | ++++++ + error[E0308]: mismatched types --> $DIR/mismatched-types-issue-126222.rs:23:18 | -LL | _ => dbg!(1) - | ^^^^^^^ expected `()`, found integer +LL | / match 1 { +LL | | _ => dbg!(1) + | | ^^^^^^^ expected `()`, found integer +LL | | } + | |_________- expected this to be `()` | help: you might have meant to return this value | @@ -34,13 +45,16 @@ LL | _ => return dbg!(1) error[E0308]: mismatched types --> $DIR/mismatched-types-issue-126222.rs:30:19 | -LL | _ => {dbg!(1)} - | ^^^^^^^ expected `()`, found integer +LL | / match 1 { +LL | | _ => {dbg!(1)} + | | ^^^^^^^ expected `()`, found integer +LL | | } + | |_________- expected this to be `()` | help: you might have meant to return this value | -LL | _ => {return dbg!(1)} - | ++++++ +LL | _ => {return dbg!(1);} + | ++++++ + error: aborting due to 4 previous errors From bb13d18572bc9092a318fb348d2fb760148e934a Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 3 May 2026 06:09:21 -0700 Subject: [PATCH 8/8] pretend we're on the beta branch for ci --- src/ci/channel | 2 +- src/version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ci/channel b/src/ci/channel index 2bf5ad0447d33..65b2df87f7df3 100644 --- a/src/ci/channel +++ b/src/ci/channel @@ -1 +1 @@ -stable +beta diff --git a/src/version b/src/version index 55f6ae93382d1..9141007a55821 100644 --- a/src/version +++ b/src/version @@ -1 +1 @@ -1.95.0 +1.96.0