@@ -119,7 +119,6 @@ impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> {
119
119
120
120
// this list contains lists of names that are allowed to be similar
121
121
// the assumption is that no name is ever contained in multiple lists.
122
- #[ rustfmt:: skip]
123
122
const ALLOWED_TO_BE_SIMILAR : & [ & [ & str ] ] = & [
124
123
& [ "parsed" , "parser" ] ,
125
124
& [ "lhs" , "rhs" ] ,
@@ -132,6 +131,14 @@ const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
132
131
& [ "iter" , "item" ] ,
133
132
] ;
134
133
134
+ /// Characters that look visually similar
135
+ const SIMILAR_CHARS : & [ ( char , char ) ] = & [ ( 'l' , 'i' ) , ( 'l' , '1' ) , ( 'i' , '1' ) , ( 'u' , 'v' ) ] ;
136
+
137
+ /// Return true if two characters are visually similar
138
+ fn chars_are_similar ( a : char , b : char ) -> bool {
139
+ a == b || SIMILAR_CHARS . contains ( & ( a, b) ) || SIMILAR_CHARS . contains ( & ( b, a) )
140
+ }
141
+
135
142
struct SimilarNamesNameVisitor < ' a , ' tcx , ' b > ( & ' b mut SimilarNamesLocalVisitor < ' a , ' tcx > ) ;
136
143
137
144
impl < ' a , ' tcx , ' b > Visitor < ' tcx > for SimilarNamesNameVisitor < ' a , ' tcx , ' b > {
@@ -189,7 +196,6 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
189
196
}
190
197
}
191
198
192
- #[ expect( clippy:: too_many_lines) ]
193
199
fn check_ident ( & mut self , ident : Ident ) {
194
200
let interned_name = ident. name . as_str ( ) ;
195
201
if interned_name. chars ( ) . any ( char:: is_uppercase) {
@@ -219,71 +225,28 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
219
225
if allowed_to_be_similar ( interned_name, existing_name. exemptions ) {
220
226
continue ;
221
227
}
222
- match existing_name. len . cmp ( & count) {
223
- Ordering :: Greater => {
224
- if existing_name. len - count != 1
225
- || levenstein_not_1 ( interned_name, existing_name. interned . as_str ( ) )
226
- {
227
- continue ;
228
- }
229
- } ,
230
- Ordering :: Less => {
231
- if count - existing_name. len != 1
232
- || levenstein_not_1 ( existing_name. interned . as_str ( ) , interned_name)
233
- {
234
- continue ;
235
- }
236
- } ,
237
- Ordering :: Equal => {
238
- let mut interned_chars = interned_name. chars ( ) ;
239
- let interned_str = existing_name. interned . as_str ( ) ;
240
- let mut existing_chars = interned_str. chars ( ) ;
241
- let first_i = interned_chars. next ( ) . expect ( "we know we have at least one char" ) ;
242
- let first_e = existing_chars. next ( ) . expect ( "we know we have at least one char" ) ;
243
- let eq_or_numeric = |( a, b) : ( char , char ) | a == b || a. is_numeric ( ) && b. is_numeric ( ) ;
244
-
245
- if eq_or_numeric ( ( first_i, first_e) ) {
246
- let last_i = interned_chars. next_back ( ) . expect ( "we know we have at least two chars" ) ;
247
- let last_e = existing_chars. next_back ( ) . expect ( "we know we have at least two chars" ) ;
248
- if eq_or_numeric ( ( last_i, last_e) ) {
249
- if interned_chars
250
- . zip ( existing_chars)
251
- . filter ( |& ie| !eq_or_numeric ( ie) )
252
- . count ( )
253
- != 1
254
- {
255
- continue ;
256
- }
257
- } else {
258
- let second_last_i = interned_chars
259
- . next_back ( )
260
- . expect ( "we know we have at least three chars" ) ;
261
- let second_last_e = existing_chars
262
- . next_back ( )
263
- . expect ( "we know we have at least three chars" ) ;
264
- if !eq_or_numeric ( ( second_last_i, second_last_e) )
265
- || second_last_i == '_'
266
- || !interned_chars. zip ( existing_chars) . all ( eq_or_numeric)
267
- {
268
- // allowed similarity foo_x, foo_y
269
- // or too many chars differ (foo_x, boo_y) or (foox, booy)
270
- continue ;
271
- }
272
- }
273
- } else {
274
- let second_i = interned_chars. next ( ) . expect ( "we know we have at least two chars" ) ;
275
- let second_e = existing_chars. next ( ) . expect ( "we know we have at least two chars" ) ;
276
- if !eq_or_numeric ( ( second_i, second_e) )
277
- || second_i == '_'
278
- || !interned_chars. zip ( existing_chars) . all ( eq_or_numeric)
279
- {
280
- // allowed similarity x_foo, y_foo
281
- // or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
282
- continue ;
283
- }
284
- }
285
- } ,
228
+
229
+ let existing_str = existing_name. interned . as_str ( ) ;
230
+
231
+ // The first char being different is usually enough to set identifiers apart, as long
232
+ // as the characters aren't too similar.
233
+ if !chars_are_similar (
234
+ interned_name. chars ( ) . next ( ) . expect ( "len >= 1" ) ,
235
+ existing_str. chars ( ) . next ( ) . expect ( "len >= 1" ) ,
236
+ ) {
237
+ continue ;
286
238
}
239
+
240
+ let dissimilar = match existing_name. len . cmp ( & count) {
241
+ Ordering :: Greater => existing_name. len - count != 1 || levenstein_not_1 ( interned_name, existing_str) ,
242
+ Ordering :: Less => count - existing_name. len != 1 || levenstein_not_1 ( existing_str, interned_name) ,
243
+ Ordering :: Equal => Self :: equal_length_strs_not_similar ( interned_name, existing_str) ,
244
+ } ;
245
+
246
+ if dissimilar {
247
+ continue ;
248
+ }
249
+
287
250
span_lint_and_then (
288
251
self . 0 . cx ,
289
252
SIMILAR_NAMES ,
@@ -302,6 +265,57 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
302
265
len : count,
303
266
} ) ;
304
267
}
268
+
269
+ fn equal_length_strs_not_similar ( interned_name : & str , existing_name : & str ) -> bool {
270
+ let mut interned_chars = interned_name. chars ( ) ;
271
+ let mut existing_chars = existing_name. chars ( ) ;
272
+ let first_i = interned_chars. next ( ) . expect ( "we know we have at least one char" ) ;
273
+ let first_e = existing_chars. next ( ) . expect ( "we know we have at least one char" ) ;
274
+ let eq_or_numeric = |( a, b) : ( char , char ) | a == b || a. is_numeric ( ) && b. is_numeric ( ) ;
275
+
276
+ if eq_or_numeric ( ( first_i, first_e) ) {
277
+ let last_i = interned_chars. next_back ( ) . expect ( "we know we have at least two chars" ) ;
278
+ let last_e = existing_chars. next_back ( ) . expect ( "we know we have at least two chars" ) ;
279
+ if eq_or_numeric ( ( last_i, last_e) ) {
280
+ if interned_chars
281
+ . zip ( existing_chars)
282
+ . filter ( |& ie| !eq_or_numeric ( ie) )
283
+ . count ( )
284
+ != 1
285
+ {
286
+ return true ;
287
+ }
288
+ } else {
289
+ let second_last_i = interned_chars
290
+ . next_back ( )
291
+ . expect ( "we know we have at least three chars" ) ;
292
+ let second_last_e = existing_chars
293
+ . next_back ( )
294
+ . expect ( "we know we have at least three chars" ) ;
295
+ if !eq_or_numeric ( ( second_last_i, second_last_e) )
296
+ || second_last_i == '_'
297
+ || !interned_chars. zip ( existing_chars) . all ( eq_or_numeric)
298
+ {
299
+ // allowed similarity foo_x, foo_y
300
+ // or too many chars differ (foo_x, boo_y) or (foox, booy)
301
+ return true ;
302
+ }
303
+ }
304
+ } else {
305
+ let second_i = interned_chars. next ( ) . expect ( "we know we have at least two chars" ) ;
306
+ let second_e = existing_chars. next ( ) . expect ( "we know we have at least two chars" ) ;
307
+ if !eq_or_numeric ( ( second_i, second_e) )
308
+ || second_i == '_'
309
+ || !interned_chars. zip ( existing_chars) . all ( eq_or_numeric)
310
+ {
311
+ // allowed similarity x_foo, y_foo
312
+ // or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
313
+ return true ;
314
+ }
315
+ }
316
+
317
+ false
318
+ }
305
319
}
306
320
307
321
impl < ' a , ' b > SimilarNamesLocalVisitor < ' a , ' b > {
0 commit comments