Skip to content

Commit fceb2de

Browse files
committed
Auto merge of #157576 - dianne:dbg-via-super-let-2, r=<try>
avoid tearing `dbg!` prints, implemented using `super let`
2 parents 43a4909 + 64b6b6b commit fceb2de

15 files changed

Lines changed: 258 additions & 170 deletions

File tree

compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -595,31 +595,38 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
595595
});
596596
let Some(var_info) = var_info else { return };
597597
let arg_name = var_info.name;
598-
struct MatchArgFinder {
599-
expr_span: Span,
600-
match_arg_span: Option<Span>,
598+
struct DbgArgFinder<'tcx> {
599+
tcx: TyCtxt<'tcx>,
600+
move_span: Span,
601601
arg_name: Symbol,
602+
dbg_arg_span: Option<Span> = None,
602603
}
603-
impl Visitor<'_> for MatchArgFinder {
604+
impl Visitor<'_> for DbgArgFinder<'_> {
604605
fn visit_expr(&mut self, e: &hir::Expr<'_>) {
605-
// dbg! is expanded into a match pattern, we need to find the right argument span
606-
if let hir::ExprKind::Match(expr, ..) = &e.kind
606+
// dbg! is expanded into assignments, we need to find the right argument span
607+
if let hir::ExprKind::Assign(_, arg, _) = &e.kind
607608
&& let hir::ExprKind::Path(hir::QPath::Resolved(
608609
_,
609610
path @ Path { segments: [seg], .. },
610-
)) = &expr.kind
611+
)) = &arg.kind
611612
&& seg.ident.name == self.arg_name
612-
&& self.expr_span.source_callsite().contains(expr.span)
613+
&& self.move_span.source_equal(arg.span)
614+
&& e.span.macro_backtrace().any(|expn| {
615+
expn.macro_def_id.is_some_and(|macro_def_id| {
616+
self.tcx.is_diagnostic_item(sym::dbg_macro, macro_def_id)
617+
})
618+
})
613619
{
614-
self.match_arg_span = Some(path.span);
620+
self.dbg_arg_span = Some(path.span);
621+
return;
615622
}
616623
hir::intravisit::walk_expr(self, e);
617624
}
618625
}
619626

620-
let mut finder = MatchArgFinder { expr_span: move_span, match_arg_span: None, arg_name };
627+
let mut finder = DbgArgFinder { tcx: self.infcx.tcx, move_span, arg_name, .. };
621628
finder.visit_expr(body);
622-
if let Some(macro_arg_span) = finder.match_arg_span {
629+
if let Some(macro_arg_span) = finder.dbg_arg_span {
623630
err.span_suggestion_verbose(
624631
macro_arg_span.shrink_to_lo(),
625632
"consider borrowing instead of transferring ownership",

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ symbols! {
753753
custom_mir,
754754
custom_test_frameworks,
755755
d32,
756+
dbg_macro,
756757
dead_code,
757758
dead_code_pub_in_binary,
758759
dealloc,

library/std/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,9 @@ extern crate std as realstd;
496496

497497
// The standard macros that are not built-in to the compiler.
498498
#[macro_use]
499-
mod macros;
499+
#[doc(hidden)]
500+
#[unstable(feature = "std_internals", issue = "none")]
501+
pub mod macros;
500502

501503
// The runtime entry point and a few unstable public functions used by the
502504
// compiler

library/std/src/macros.rs

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ macro_rules! eprintln {
350350
/// [`debug!`]: https://docs.rs/log/*/log/macro.debug.html
351351
/// [`log`]: https://crates.io/crates/log
352352
#[macro_export]
353+
#[allow_internal_unstable(std_internals)]
353354
#[cfg_attr(not(test), rustc_diagnostic_item = "dbg_macro")]
354355
#[stable(feature = "dbg_macro", since = "1.32.0")]
355356
macro_rules! dbg {
@@ -360,29 +361,69 @@ macro_rules! dbg {
360361
() => {
361362
$crate::eprintln!("[{}:{}:{}]", $crate::file!(), $crate::line!(), $crate::column!())
362363
};
363-
($val:expr $(,)?) => {
364-
// Use of `match` here is intentional because it affects the lifetimes
365-
// of temporaries - https://stackoverflow.com/a/48732525/1063961
366-
match $val {
367-
tmp => {
368-
$crate::eprintln!("[{}:{}:{}] {} = {:#?}",
369-
$crate::file!(),
370-
$crate::line!(),
371-
$crate::column!(),
372-
$crate::stringify!($val),
373-
// The `&T: Debug` check happens here (not in the format literal desugaring)
374-
// to avoid format literal related messages and suggestions.
375-
&&tmp as &dyn $crate::fmt::Debug,
376-
);
377-
tmp
378-
}
379-
}
380-
};
381364
($($val:expr),+ $(,)?) => {
382-
($($crate::dbg!($val)),+,)
365+
$crate::macros::dbg_internal!(() () ($($val),+))
383366
};
384367
}
385368

369+
/// Internal macro that processes a list of expressions, binds their results, calls `eprint!` with
370+
/// the collected information, and returns all the evaluated expressions in a tuple.
371+
///
372+
/// E.g. `dbg_internal!(() () (1, 2))` expands into
373+
/// ```rust, ignore
374+
/// {
375+
/// let tmp_1;
376+
/// let tmp_2;
377+
/// super let _ = (tmp_1 = 1);
378+
/// super let _ = (tmp_2 = 2);
379+
/// eprint!("...", &tmp_1, &tmp_2, /* some other arguments */);
380+
/// (tmp_1, tmp_2)
381+
/// }
382+
/// ```
383+
///
384+
/// This is necessary so that `dbg!` outputs don't get torn, see #136703.
385+
/// `super let` is used to avoid creating a temporary scope around `dbg!`'s arguments. Nested
386+
/// `match` is insufficient because match arms introduce temporary scopes (#153850) and using a
387+
/// single match on a tuple containing all the arguments is insufficient because of an imprecision
388+
/// in the borrow-checker (see #155902).
389+
#[doc(hidden)]
390+
#[allow_internal_unstable(std_internals, super_let)]
391+
#[rustc_macro_transparency = "semiopaque"]
392+
#[unstable(feature = "std_internals", issue = "none")]
393+
pub macro dbg_internal {
394+
(($($piece:literal),+) ($($processed:expr => $bound:ident),+) ()) => {{
395+
$(let $bound);+;
396+
$(super let _ = ($bound = $processed));+;
397+
$crate::eprint!(
398+
$crate::concat!($($piece),+),
399+
$(
400+
$crate::stringify!($processed),
401+
// The `&T: Debug` check happens here (not in the format literal desugaring)
402+
// to avoid format literal related messages and suggestions.
403+
&&$bound as &dyn $crate::fmt::Debug
404+
),+,
405+
// The location returned here is that of the macro invocation, so
406+
// it will be the same for all expressions. Thus, label these
407+
// arguments so that they can be reused in every piece of the
408+
// formatting template.
409+
file=$crate::file!(),
410+
line=$crate::line!(),
411+
column=$crate::column!()
412+
);
413+
// Comma separate the variables only when necessary so that this will
414+
// not yield a tuple for a single expression, but rather just parenthesize
415+
// the expression.
416+
($($bound),+)
417+
}},
418+
(($($piece:literal),*) ($($processed:expr => $bound:ident),*) ($val:expr $(,$rest:expr)*)) => {
419+
$crate::macros::dbg_internal!(
420+
($($piece,)* "[{file}:{line}:{column}] {} = {:#?}\n")
421+
($($processed => $bound,)* $val => tmp)
422+
($($rest),*)
423+
)
424+
},
425+
}
426+
386427
#[doc(hidden)]
387428
#[macro_export]
388429
#[allow_internal_unstable(hash_map_internals)]

src/tools/clippy/clippy_lints/src/dbg_macro.rs

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -75,51 +75,61 @@ impl LateLintPass<'_> for DbgMacro {
7575
"the `dbg!` macro is intended as a debugging tool",
7676
|diag| {
7777
let mut applicability = Applicability::MachineApplicable;
78-
let (sugg_span, suggestion) =
79-
match is_async_move_desugar(expr).unwrap_or(expr).peel_drop_temps().kind {
80-
// dbg!()
81-
ExprKind::Block(..) => {
82-
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
83-
// remove the whole statement.
84-
if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id)
85-
&& let Some(semi_span) =
86-
cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
87-
{
88-
(macro_call.span.to(semi_span), String::new())
89-
} else {
90-
(macro_call.span, String::from("()"))
91-
}
92-
},
93-
// dbg!(1)
94-
ExprKind::Match(val, ..) => (
95-
macro_call.span,
78+
let dbg_expn = is_async_move_desugar(expr).unwrap_or(expr).peel_drop_temps();
79+
// `dbg!` always expands to a block. If it was given arguments, it assigns names to them
80+
// using `super let _ = (tmp = $arg);` statements.
81+
let ExprKind::Block(block, _) = dbg_expn.kind else {
82+
unreachable!()
83+
};
84+
let args: Vec<_> = block
85+
.stmts
86+
.iter()
87+
.filter_map(|stmt| {
88+
if let StmtKind::Let(LetStmt {
89+
super_: Some(_),
90+
init: Some(init),
91+
..
92+
}) = stmt.kind
93+
&& let ExprKind::Assign(_, arg, _) = init.kind
94+
{
95+
Some(arg)
96+
} else {
97+
None
98+
}
99+
})
100+
.collect();
101+
102+
let (sugg_span, suggestion) = match args.as_slice() {
103+
// dbg!()
104+
[] => {
105+
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
106+
// remove the whole statement.
107+
if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id)
108+
&& let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
109+
{
110+
(macro_call.span.to(semi_span), String::new())
111+
} else {
112+
(macro_call.span, String::from("()"))
113+
}
114+
},
115+
// dbg!(1) => 1
116+
[val] => {
117+
let suggestion =
96118
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability)
97-
.to_string(),
98-
),
99-
// dbg!(2, 3)
100-
ExprKind::Tup(
101-
[
102-
Expr {
103-
kind: ExprKind::Match(first, ..),
104-
..
105-
},
106-
..,
107-
Expr {
108-
kind: ExprKind::Match(last, ..),
109-
..
110-
},
111-
],
112-
) => {
113-
let snippet = snippet_with_applicability(
114-
cx,
115-
first.span.source_callsite().to(last.span.source_callsite()),
116-
"..",
117-
&mut applicability,
118-
);
119-
(macro_call.span, format!("({snippet})"))
120-
},
121-
_ => unreachable!(),
122-
};
119+
.to_string();
120+
(macro_call.span, suggestion)
121+
},
122+
// dbg!(2, 3) => (2, 3)
123+
[first, .., last] => {
124+
let snippet = snippet_with_applicability(
125+
cx,
126+
first.span.source_callsite().to(last.span.source_callsite()),
127+
"..",
128+
&mut applicability,
129+
);
130+
(macro_call.span, format!("({snippet})"))
131+
},
132+
};
123133

124134
diag.span_suggestion(
125135
sugg_span,

src/tools/clippy/clippy_utils/src/sym.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,6 @@ generate! {
202202
cx,
203203
cycle,
204204
cyclomatic_complexity,
205-
dbg_macro,
206205
de,
207206
debug_struct,
208207
deprecated_in_future,

src/tools/miri/tests/fail/dangling_pointers/dangling_primitive.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ error: Undefined Behavior: memory access failed: ALLOC has been freed, so this p
22
--> tests/fail/dangling_pointers/dangling_primitive.rs:LL:CC
33
|
44
LL | dbg!(*ptr);
5-
| ^^^^^^^^^^ Undefined Behavior occurred here
5+
| ^^^^ Undefined Behavior occurred here
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

src/tools/miri/tests/fail/function_calls/return_pointer_on_unwind.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ error: Undefined Behavior: reading memory at ALLOC[0x0..0x4], but memory is unin
77
--> tests/fail/function_calls/return_pointer_on_unwind.rs:LL:CC
88
|
99
LL | dbg!(x.0);
10-
| ^^^^^^^^^ Undefined Behavior occurred here
10+
| ^^^ Undefined Behavior occurred here
1111
|
1212
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
1313
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

tests/ui/borrowck/dbg-issue-120327.rs

Lines changed: 15 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,44 @@
1+
//! Diagnostic test for <https://github.com/rust-lang/rust/issues/120327>: suggest borrowing
2+
//! variables passed to `dbg!` that are later used.
3+
//@ dont-require-annotations: HELP
4+
15
fn s() -> String {
26
let a = String::new();
3-
dbg!(a);
7+
dbg!(a); //~ HELP consider borrowing instead of transferring ownership
48
return a; //~ ERROR use of moved value:
59
}
610

711
fn m() -> String {
812
let a = String::new();
9-
dbg!(1, 2, a, 1, 2);
13+
dbg!(1, 2, a, 1, 2); //~ HELP consider borrowing instead of transferring ownership
1014
return a; //~ ERROR use of moved value:
1115
}
1216

1317
fn t(a: String) -> String {
1418
let b: String = "".to_string();
15-
dbg!(a, b);
19+
dbg!(a, b); //~ HELP consider borrowing instead of transferring ownership
1620
return b; //~ ERROR use of moved value:
1721
}
1822

1923
fn x(a: String) -> String {
2024
let b: String = "".to_string();
21-
dbg!(a, b);
25+
dbg!(a, b); //~ HELP consider borrowing instead of transferring ownership
2226
return a; //~ ERROR use of moved value:
2327
}
2428

25-
macro_rules! my_dbg {
26-
() => {
27-
eprintln!("[{}:{}:{}]", file!(), line!(), column!())
28-
};
29-
($val:expr $(,)?) => {
30-
match $val {
31-
tmp => {
32-
eprintln!("[{}:{}:{}] {} = {:#?}",
33-
file!(), line!(), column!(), stringify!($val), &tmp);
34-
tmp
35-
}
36-
}
37-
};
38-
($($val:expr),+ $(,)?) => {
39-
($(my_dbg!($val)),+,)
40-
};
41-
}
42-
43-
fn test_my_dbg() -> String {
44-
let b: String = "".to_string();
45-
my_dbg!(b, 1);
46-
return b; //~ ERROR use of moved value:
47-
}
48-
49-
fn test_not_macro() -> String {
50-
let a = String::new();
51-
let _b = match a {
52-
tmp => {
53-
eprintln!("dbg: {}", tmp);
54-
tmp
55-
}
56-
};
57-
return a; //~ ERROR use of moved value:
29+
fn two_of_them(a: String) -> String {
30+
dbg!(a, a); //~ ERROR use of moved value
31+
//~| HELP consider borrowing instead of transferring ownership
32+
//~| HELP consider borrowing instead of transferring ownership
33+
return a; //~ ERROR use of moved value
5834
}
5935

6036
fn get_expr(_s: String) {}
6137

38+
// The suggestion is purely syntactic; applying it here will result in a type error.
6239
fn test() {
6340
let a: String = "".to_string();
64-
let _res = get_expr(dbg!(a));
41+
let _res = get_expr(dbg!(a)); //~ HELP consider borrowing instead of transferring ownership
6542
let _l = a.len(); //~ ERROR borrow of moved value
6643
}
6744

0 commit comments

Comments
 (0)