1
1
use crate :: consts:: constant_simple;
2
2
use 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;
4
5
use rustc_ast:: ast:: InlineAsmTemplatePiece ;
5
6
use rustc_data_structures:: fx:: FxHasher ;
6
7
use rustc_hir:: def:: Res ;
@@ -13,8 +14,9 @@ use rustc_hir::{
13
14
use rustc_lexer:: { tokenize, TokenKind } ;
14
15
use rustc_lint:: LateContext ;
15
16
use rustc_middle:: ty:: TypeckResults ;
16
- use rustc_span:: { sym, Symbol } ;
17
+ use rustc_span:: { sym, BytePos , Symbol , SyntaxContext } ;
17
18
use std:: hash:: { Hash , Hasher } ;
19
+ use std:: ops:: Range ;
18
20
19
21
/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but
20
22
/// other conditions would make them equal.
@@ -127,51 +129,83 @@ impl HirEqInterExpr<'_, '_, '_> {
127
129
128
130
/// Checks whether two blocks are the same.
129
131
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 ;
174
135
}
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
+ } )
175
209
}
176
210
177
211
fn should_ignore ( & mut self , expr : & Expr < ' _ > ) -> bool {
@@ -1038,3 +1072,33 @@ pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
1038
1072
h. hash_expr ( e) ;
1039
1073
h. finish ( )
1040
1074
}
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