11use crate :: consts:: constant_simple;
22use crate :: macros:: macro_backtrace;
3- use crate :: source:: snippet_opt;
3+ use crate :: source:: { get_source_text, snippet_opt, walk_span_to_context, SpanRange } ;
4+ use crate :: tokenize_with_text;
45use rustc_ast:: ast:: InlineAsmTemplatePiece ;
56use rustc_data_structures:: fx:: FxHasher ;
67use rustc_hir:: def:: Res ;
@@ -13,8 +14,9 @@ use rustc_hir::{
1314use rustc_lexer:: { tokenize, TokenKind } ;
1415use rustc_lint:: LateContext ;
1516use rustc_middle:: ty:: TypeckResults ;
16- use rustc_span:: { sym, Symbol } ;
17+ use rustc_span:: { sym, BytePos , Symbol , SyntaxContext } ;
1718use std:: hash:: { Hash , Hasher } ;
19+ use std:: ops:: Range ;
1820
1921/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but
2022/// other conditions would make them equal.
@@ -127,51 +129,83 @@ impl HirEqInterExpr<'_, '_, '_> {
127129
128130 /// Checks whether two blocks are the same.
129131 fn eq_block ( & mut self , left : & Block < ' _ > , right : & Block < ' _ > ) -> bool {
130- match ( left. stmts , left. expr , right. stmts , right. expr ) {
131- ( [ ] , None , [ ] , None ) => {
132- // For empty blocks, check to see if the tokens are equal. This will catch the case where a macro
133- // expanded to nothing, or the cfg attribute was used.
134- let ( Some ( left) , Some ( right) ) = (
135- snippet_opt ( self . inner . cx , left. span ) ,
136- snippet_opt ( self . inner . cx , right. span ) ,
137- ) else { return true } ;
138- let mut left_pos = 0 ;
139- let left = tokenize ( & left)
140- . map ( |t| {
141- let end = left_pos + t. len as usize ;
142- let s = & left[ left_pos..end] ;
143- left_pos = end;
144- ( t, s)
145- } )
146- . filter ( |( t, _) | {
147- !matches ! (
148- t. kind,
149- TokenKind :: LineComment { .. } | TokenKind :: BlockComment { .. } | TokenKind :: Whitespace
150- )
151- } )
152- . map ( |( _, s) | s) ;
153- let mut right_pos = 0 ;
154- let right = tokenize ( & right)
155- . map ( |t| {
156- let end = right_pos + t. len as usize ;
157- let s = & right[ right_pos..end] ;
158- right_pos = end;
159- ( t, s)
160- } )
161- . filter ( |( t, _) | {
162- !matches ! (
163- t. kind,
164- TokenKind :: LineComment { .. } | TokenKind :: BlockComment { .. } | TokenKind :: Whitespace
165- )
166- } )
167- . map ( |( _, s) | s) ;
168- left. eq ( right)
169- } ,
170- _ => {
171- over ( left. stmts , right. stmts , |l, r| self . eq_stmt ( l, r) )
172- && both ( & left. expr , & right. expr , |l, r| self . eq_expr ( l, r) )
173- } ,
132+ use TokenKind :: { BlockComment , LineComment , Semi , Whitespace } ;
133+ if left. stmts . len ( ) != right. stmts . len ( ) {
134+ return false ;
174135 }
136+ let lspan = left. span . data ( ) ;
137+ let rspan = right. span . data ( ) ;
138+ if lspan. ctxt != SyntaxContext :: root ( ) && rspan. ctxt != SyntaxContext :: root ( ) {
139+ // Don't try to check in between statements inside macros.
140+ return over ( left. stmts , right. stmts , |left, right| self . eq_stmt ( left, right) )
141+ && both ( & left. expr , & right. expr , |left, right| self . eq_expr ( left, right) ) ;
142+ }
143+ if lspan. ctxt != rspan. ctxt {
144+ return false ;
145+ }
146+
147+ let mut lstart = lspan. lo ;
148+ let mut rstart = rspan. lo ;
149+
150+ for ( left, right) in left. stmts . iter ( ) . zip ( right. stmts ) {
151+ if !self . eq_stmt ( left, right) {
152+ return false ;
153+ }
154+ let Some ( lstmt_span) = walk_span_to_context ( left. span , lspan. ctxt ) else {
155+ return false ;
156+ } ;
157+ let Some ( rstmt_span) = walk_span_to_context ( right. span , rspan. ctxt ) else {
158+ return false ;
159+ } ;
160+ let lstmt_span = lstmt_span. data ( ) ;
161+ let rstmt_span = rstmt_span. data ( ) ;
162+
163+ if lstmt_span. lo < lstart && rstmt_span. lo < rstart {
164+ // Can happen when macros expand to multiple statements, or rearrange statements.
165+ // Nothing in between the statements to check in this case.
166+ continue ;
167+ } else if lstmt_span. lo < lstart || rstmt_span. lo < rstart {
168+ // Only one of the blocks had a weird macro.
169+ return false ;
170+ }
171+ if !eq_span_tokens ( self . inner . cx , lstart..lstmt_span. lo , rstart..rstmt_span. lo , |t| {
172+ !matches ! ( t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi )
173+ } ) {
174+ return false ;
175+ }
176+
177+ lstart = lstmt_span. hi ;
178+ rstart = rstmt_span. hi ;
179+ }
180+
181+ let ( lend, rend) = match ( left. expr , right. expr ) {
182+ ( Some ( left) , Some ( right) ) => {
183+ if !self . eq_expr ( left, right) {
184+ return false ;
185+ }
186+ let Some ( lexpr_span) = walk_span_to_context ( left. span , lspan. ctxt ) else {
187+ return false ;
188+ } ;
189+ let Some ( rexpr_span) = walk_span_to_context ( right. span , rspan. ctxt ) else {
190+ return false ;
191+ } ;
192+ ( lexpr_span. lo ( ) , rexpr_span. lo ( ) )
193+ } ,
194+ ( None , None ) => ( lspan. hi , rspan. hi ) ,
195+ ( Some ( _) , None ) | ( None , Some ( _) ) => return false ,
196+ } ;
197+
198+ if lend < lstart && rend < rstart {
199+ // Can happen when macros rearrange the input.
200+ // Nothing in between the statements to check in this case.
201+ return true ;
202+ } else if lend < lstart || rend < rstart {
203+ // Only one of the blocks had a weird macro
204+ return false ;
205+ }
206+ eq_span_tokens ( self . inner . cx , lstart..lend, rstart..rend, |t| {
207+ !matches ! ( t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi )
208+ } )
175209 }
176210
177211 fn should_ignore ( & mut self , expr : & Expr < ' _ > ) -> bool {
@@ -1038,3 +1072,33 @@ pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
10381072 h. hash_expr ( e) ;
10391073 h. finish ( )
10401074}
1075+
1076+ fn eq_span_tokens (
1077+ cx : & LateContext < ' _ > ,
1078+ left : impl SpanRange ,
1079+ right : impl SpanRange ,
1080+ pred : impl Fn ( TokenKind ) -> bool ,
1081+ ) -> bool {
1082+ fn f ( cx : & LateContext < ' _ > , left : Range < BytePos > , right : Range < BytePos > , pred : impl Fn ( TokenKind ) -> bool ) -> bool {
1083+ if let Some ( lsrc) = get_source_text ( cx, left)
1084+ && let Some ( lsrc) = lsrc. as_str ( )
1085+ && let Some ( rsrc) = get_source_text ( cx, right)
1086+ && let Some ( rsrc) = rsrc. as_str ( )
1087+ {
1088+ let pred = |t : & ( _ , _ ) | pred ( t. 0 ) ;
1089+ let map = |( _, x) | x;
1090+
1091+ let ltok = tokenize_with_text ( lsrc)
1092+ . filter ( pred)
1093+ . map ( map) ;
1094+ let rtok = tokenize_with_text ( rsrc)
1095+ . filter ( pred)
1096+ . map ( map) ;
1097+ ltok. eq ( rtok)
1098+ } else {
1099+ // Unable to access the source. Conservatively assume the blocks aren't equal.
1100+ false
1101+ }
1102+ }
1103+ f ( cx, left. into_range ( ) , right. into_range ( ) , pred)
1104+ }
0 commit comments