@@ -14,7 +14,7 @@ use rustc_hir::{
14
14
use rustc_lexer:: { tokenize, TokenKind } ;
15
15
use rustc_lint:: LateContext ;
16
16
use rustc_middle:: ty:: TypeckResults ;
17
- use rustc_span:: { sym, BytePos , Symbol , SyntaxContext } ;
17
+ use rustc_span:: { sym, BytePos , ExpnKind , MacroKind , Symbol , SyntaxContext } ;
18
18
use std:: hash:: { Hash , Hasher } ;
19
19
use std:: ops:: Range ;
20
20
@@ -67,6 +67,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
67
67
pub fn inter_expr ( & mut self ) -> HirEqInterExpr < ' _ , ' a , ' tcx > {
68
68
HirEqInterExpr {
69
69
inner : self ,
70
+ left_ctxt : SyntaxContext :: root ( ) ,
71
+ right_ctxt : SyntaxContext :: root ( ) ,
70
72
locals : HirIdMap :: default ( ) ,
71
73
}
72
74
}
@@ -94,6 +96,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
94
96
95
97
pub struct HirEqInterExpr < ' a , ' b , ' tcx > {
96
98
inner : & ' a mut SpanlessEq < ' b , ' tcx > ,
99
+ left_ctxt : SyntaxContext ,
100
+ right_ctxt : SyntaxContext ,
97
101
98
102
// When binding are declared, the binding ID in the left expression is mapped to the one on the
99
103
// right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`,
@@ -128,6 +132,7 @@ impl HirEqInterExpr<'_, '_, '_> {
128
132
}
129
133
130
134
/// Checks whether two blocks are the same.
135
+ #[ expect( clippy:: similar_names) ]
131
136
fn eq_block ( & mut self , left : & Block < ' _ > , right : & Block < ' _ > ) -> bool {
132
137
use TokenKind :: { BlockComment , LineComment , Semi , Whitespace } ;
133
138
if left. stmts . len ( ) != right. stmts . len ( ) {
@@ -166,7 +171,8 @@ impl HirEqInterExpr<'_, '_, '_> {
166
171
// Can happen when macros expand to multiple statements, or rearrange statements.
167
172
// Nothing in between the statements to check in this case.
168
173
continue ;
169
- } else if lstmt_span. lo < lstart || rstmt_span. lo < rstart {
174
+ }
175
+ if lstmt_span. lo < lstart || rstmt_span. lo < rstart {
170
176
// Only one of the blocks had a weird macro.
171
177
return false ;
172
178
}
@@ -243,7 +249,7 @@ impl HirEqInterExpr<'_, '_, '_> {
243
249
244
250
#[ expect( clippy:: similar_names) ]
245
251
pub fn eq_expr ( & mut self , left : & Expr < ' _ > , right : & Expr < ' _ > ) -> bool {
246
- if !self . inner . allow_side_effects && left. span . ctxt ( ) != right. span . ctxt ( ) {
252
+ if !self . check_ctxt ( left. span . ctxt ( ) , right. span . ctxt ( ) ) {
247
253
return false ;
248
254
}
249
255
@@ -476,6 +482,45 @@ impl HirEqInterExpr<'_, '_, '_> {
476
482
fn eq_type_binding ( & mut self , left : & TypeBinding < ' _ > , right : & TypeBinding < ' _ > ) -> bool {
477
483
left. ident . name == right. ident . name && self . eq_ty ( left. ty ( ) , right. ty ( ) )
478
484
}
485
+
486
+ fn check_ctxt ( & mut self , left : SyntaxContext , right : SyntaxContext ) -> bool {
487
+ if self . left_ctxt == left && self . right_ctxt == right {
488
+ return true ;
489
+ } else if self . left_ctxt == left || self . right_ctxt == right {
490
+ // Only one context has changed. This can only happen if the two nodes are written differently.
491
+ return false ;
492
+ } else if left != SyntaxContext :: root ( ) {
493
+ let mut left_data = left. outer_expn_data ( ) ;
494
+ let mut right_data = right. outer_expn_data ( ) ;
495
+ loop {
496
+ use TokenKind :: { BlockComment , LineComment , Whitespace } ;
497
+ if left_data. macro_def_id != right_data. macro_def_id
498
+ || ( matches ! ( left_data. kind, ExpnKind :: Macro ( MacroKind :: Bang , name) if name == sym:: cfg)
499
+ && !eq_span_tokens ( self . inner . cx , left_data. call_site , right_data. call_site , |t| {
500
+ !matches ! ( t, Whitespace | LineComment { .. } | BlockComment { .. } )
501
+ } ) )
502
+ {
503
+ // Either a different chain of macro calls, or different arguments to the `cfg` macro.
504
+ return false ;
505
+ }
506
+ let left_ctxt = left_data. call_site . ctxt ( ) ;
507
+ let right_ctxt = right_data. call_site . ctxt ( ) ;
508
+ if left_ctxt == SyntaxContext :: root ( ) && right_ctxt == SyntaxContext :: root ( ) {
509
+ break ;
510
+ }
511
+ if left_ctxt == SyntaxContext :: root ( ) || right_ctxt == SyntaxContext :: root ( ) {
512
+ // Different lengths for the expansion stack. This can only happen if nodes are written differently,
513
+ // or shouldn't be compared to start with.
514
+ return false ;
515
+ }
516
+ left_data = left_ctxt. outer_expn_data ( ) ;
517
+ right_data = right_ctxt. outer_expn_data ( ) ;
518
+ }
519
+ }
520
+ self . left_ctxt = left;
521
+ self . right_ctxt = right;
522
+ true
523
+ }
479
524
}
480
525
481
526
/// Some simple reductions like `{ return }` => `return`
@@ -1075,6 +1120,7 @@ pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
1075
1120
h. finish ( )
1076
1121
}
1077
1122
1123
+ #[ expect( clippy:: similar_names) ]
1078
1124
fn eq_span_tokens (
1079
1125
cx : & LateContext < ' _ > ,
1080
1126
left : impl SpanRange ,
0 commit comments