11use clippy_utils:: diagnostics:: span_lint_and_sugg;
2- use clippy_utils:: macros:: root_macro_call_first_node ;
2+ use clippy_utils:: macros:: { macro_backtrace , MacroCall } ;
33use clippy_utils:: source:: snippet_with_applicability;
44use clippy_utils:: { is_in_cfg_test, is_in_test_function} ;
5+ use rustc_data_structures:: fx:: FxHashSet ;
56use rustc_errors:: Applicability ;
6- use rustc_hir:: { Expr , ExprKind , Node } ;
7+ use rustc_hir:: { Expr , ExprKind , HirId , Node } ;
78use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
9+ use rustc_middle:: lint:: in_external_macro;
810use rustc_session:: impl_lint_pass;
9- use rustc_span:: sym;
11+ use rustc_span:: { sym, Span , SyntaxContext } ;
1012
1113declare_clippy_lint ! {
1214 /// ### What it does
@@ -31,31 +33,38 @@ declare_clippy_lint! {
3133 "`dbg!` macro is intended as a debugging tool"
3234}
3335
34- #[ derive( Copy , Clone ) ]
36+ #[ derive( Clone ) ]
3537pub struct DbgMacro {
3638 allow_dbg_in_tests : bool ,
39+ /// Tracks the `dbg!` macro callsites that are already checked.
40+ checked_dbg_call_site : FxHashSet < Span > ,
41+ /// Tracks the previous `SyntaxContext`, to avoid walking the same context chain.
42+ prev_ctxt : SyntaxContext ,
3743}
3844
3945impl_lint_pass ! ( DbgMacro => [ DBG_MACRO ] ) ;
4046
4147impl DbgMacro {
4248 pub fn new ( allow_dbg_in_tests : bool ) -> Self {
43- DbgMacro { allow_dbg_in_tests }
49+ DbgMacro {
50+ allow_dbg_in_tests,
51+ checked_dbg_call_site : FxHashSet :: default ( ) ,
52+ prev_ctxt : SyntaxContext :: root ( ) ,
53+ }
4454 }
4555}
4656
4757impl LateLintPass < ' _ > for DbgMacro {
4858 fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
49- let Some ( macro_call) = root_macro_call_first_node ( cx, expr) else {
50- return ;
51- } ;
52- if cx. tcx . is_diagnostic_item ( sym:: dbg_macro, macro_call. def_id ) {
59+ let cur_syntax_ctxt = expr. span . ctxt ( ) ;
60+
61+ if cur_syntax_ctxt != self . prev_ctxt &&
62+ let Some ( macro_call) = first_dbg_macro_in_expansion ( cx, expr. span ) &&
63+ !in_external_macro ( cx. sess ( ) , macro_call. span ) &&
64+ self . checked_dbg_call_site . insert ( macro_call. span ) &&
5365 // allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml
54- if self . allow_dbg_in_tests
55- && ( is_in_test_function ( cx. tcx , expr. hir_id ) || is_in_cfg_test ( cx. tcx , expr. hir_id ) )
56- {
57- return ;
58- }
66+ !( self . allow_dbg_in_tests && is_in_test ( cx, expr. hir_id ) )
67+ {
5968 let mut applicability = Applicability :: MachineApplicable ;
6069
6170 let ( sugg_span, suggestion) = match expr. peel_drop_temps ( ) . kind {
@@ -101,6 +110,8 @@ impl LateLintPass<'_> for DbgMacro {
101110 _ => return ,
102111 } ;
103112
113+ self . prev_ctxt = cur_syntax_ctxt;
114+
104115 span_lint_and_sugg (
105116 cx,
106117 DBG_MACRO ,
@@ -112,4 +123,16 @@ impl LateLintPass<'_> for DbgMacro {
112123 ) ;
113124 }
114125 }
126+
127+ fn check_crate_post ( & mut self , _: & LateContext < ' _ > ) {
128+ self . checked_dbg_call_site = FxHashSet :: default ( ) ;
129+ }
130+ }
131+
132+ fn is_in_test ( cx : & LateContext < ' _ > , hir_id : HirId ) -> bool {
133+ is_in_test_function ( cx. tcx , hir_id) || is_in_cfg_test ( cx. tcx , hir_id)
134+ }
135+
136+ fn first_dbg_macro_in_expansion ( cx : & LateContext < ' _ > , span : Span ) -> Option < MacroCall > {
137+ macro_backtrace ( span) . find ( |mc| cx. tcx . is_diagnostic_item ( sym:: dbg_macro, mc. def_id ) )
115138}
0 commit comments