@@ -119,7 +119,6 @@ impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> {
119119
120120// this list contains lists of names that are allowed to be similar
121121// the assumption is that no name is ever contained in multiple lists.
122- #[ rustfmt:: skip]
123122const ALLOWED_TO_BE_SIMILAR : & [ & [ & str ] ] = & [
124123 & [ "parsed" , "parser" ] ,
125124 & [ "lhs" , "rhs" ] ,
@@ -132,6 +131,14 @@ const ALLOWED_TO_BE_SIMILAR: &[&[&str]] = &[
132131 & [ "iter" , "item" ] ,
133132] ;
134133
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+
135142struct SimilarNamesNameVisitor < ' a , ' tcx , ' b > ( & ' b mut SimilarNamesLocalVisitor < ' a , ' tcx > ) ;
136143
137144impl < ' a , ' tcx , ' b > Visitor < ' tcx > for SimilarNamesNameVisitor < ' a , ' tcx , ' b > {
@@ -189,7 +196,6 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
189196 }
190197 }
191198
192- #[ expect( clippy:: too_many_lines) ]
193199 fn check_ident ( & mut self , ident : Ident ) {
194200 let interned_name = ident. name . as_str ( ) ;
195201 if interned_name. chars ( ) . any ( char:: is_uppercase) {
@@ -219,71 +225,28 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
219225 if allowed_to_be_similar ( interned_name, existing_name. exemptions ) {
220226 continue ;
221227 }
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 ;
286238 }
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+
287250 span_lint_and_then (
288251 self . 0 . cx ,
289252 SIMILAR_NAMES ,
@@ -302,6 +265,57 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
302265 len : count,
303266 } ) ;
304267 }
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+ }
305319}
306320
307321impl < ' a , ' b > SimilarNamesLocalVisitor < ' a , ' b > {
0 commit comments