Skip to content

Commit e67b7e0

Browse files
committed
Auto merge of rust-lang#12258 - tgross35:similiar-names-update, r=Alexendoo
[`similar_names`]: don't raise if the first character is different A lot of cases of the "noise" cases of `similar_names` come from two idents with a different first letter, which is easy enough to differentiate visually but causes this lint to be raised. Do not raise the lint in these cases, as long as the first character does not have a lookalike. Helps with rust-lang/rust-clippy#10926 (does not fix) This is per-commit reviewable, the first commit is just refactoring. changelog: [`similar_names`]: don't raise if the first character is different
2 parents 28443e6 + 5250afb commit e67b7e0

11 files changed

+121
-141
lines changed

clippy_lints/src/item_name_repetitions.rs

-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,6 @@ impl LateLintPass<'_> for ItemNameRepetitions {
385385
assert!(last.is_some());
386386
}
387387

388-
#[expect(clippy::similar_names)]
389388
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
390389
let item_name = item.ident.name.as_str();
391390
let item_camel = to_camel_case(item_name);

clippy_lints/src/non_expressive_names.rs

+80-66
Original file line numberDiff line numberDiff line change
@@ -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]
123122
const 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+
135142
struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>);
136143

137144
impl<'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

307321
impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> {

clippy_lints/src/operators/double_comparison.rs

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use rustc_span::Span;
88

99
use super::DOUBLE_COMPARISONS;
1010

11-
#[expect(clippy::similar_names)]
1211
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>, span: Span) {
1312
let (lkind, llhs, lrhs, rkind, rlhs, rrhs) = match (&lhs.kind, &rhs.kind) {
1413
(ExprKind::Binary(lb, llhs, lrhs), ExprKind::Binary(rb, rlhs, rrhs)) => {

clippy_lints/src/operators/op_ref.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_middle::ty::{self, Ty};
1111

1212
use super::OP_REF;
1313

14-
#[expect(clippy::similar_names, clippy::too_many_lines)]
14+
#[expect(clippy::too_many_lines)]
1515
pub(crate) fn check<'tcx>(
1616
cx: &LateContext<'tcx>,
1717
e: &'tcx Expr<'_>,

clippy_utils/src/ast_utils.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
44
5-
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
5+
#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
66

77
use crate::{both, over};
88
use rustc_ast::ptr::P;
@@ -296,7 +296,7 @@ pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> b
296296
eq_id(l.ident, r.ident) && over(&l.attrs, &r.attrs, eq_attr) && eq_vis(&l.vis, &r.vis) && eq_kind(&l.kind, &r.kind)
297297
}
298298

299-
#[expect(clippy::too_many_lines)] // Just a big match statement
299+
#[expect(clippy::similar_names, clippy::too_many_lines)] // Just a big match statement
300300
pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
301301
use ItemKind::*;
302302
match (l, r) {

clippy_utils/src/hir_utils.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ impl HirEqInterExpr<'_, '_, '_> {
132132
}
133133

134134
/// Checks whether two blocks are the same.
135-
#[expect(clippy::similar_names)]
136135
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
137136
use TokenKind::{Semi, Whitespace};
138137
if left.stmts.len() != right.stmts.len() {
@@ -247,7 +246,7 @@ impl HirEqInterExpr<'_, '_, '_> {
247246
res
248247
}
249248

250-
#[expect(clippy::similar_names, clippy::too_many_lines)]
249+
#[expect(clippy::too_many_lines)]
251250
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
252251
if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) {
253252
return false;
@@ -463,7 +462,6 @@ impl HirEqInterExpr<'_, '_, '_> {
463462
}
464463
}
465464

466-
#[expect(clippy::similar_names)]
467465
fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
468466
match (left, right) {
469467
(&QPath::Resolved(ref lty, lpath), &QPath::Resolved(ref rty, rpath)) => {
@@ -1158,7 +1156,6 @@ pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
11581156
h.finish()
11591157
}
11601158

1161-
#[expect(clippy::similar_names)]
11621159
fn eq_span_tokens(
11631160
cx: &LateContext<'_>,
11641161
left: impl SpanRange,

clippy_utils/src/qualify_min_const_fn.rs

-1
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,6 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
389389
})
390390
}
391391

392-
#[expect(clippy::similar_names)] // bit too pedantic
393392
fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {
394393
// FIXME(effects, fee1-dead) revert to const destruct once it works again
395394
#[expect(unused)]

tests/ui/approx_const.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#[warn(clippy::approx_constant)]
2-
#[allow(clippy::similar_names)]
32
fn main() {
43
let my_e = 2.7182;
54
//~^ ERROR: approximate value of `f{32, 64}::consts::E` found

0 commit comments

Comments
 (0)