Skip to content

Commit f98e3ec

Browse files
committed
Merge pull request #813 from oli-obk/fix/non_expressive_names
Fix/non expressive names
2 parents e878ab4 + f03d93e commit f98e3ec

File tree

2 files changed

+86
-66
lines changed

2 files changed

+86
-66
lines changed

src/non_expressive_names.rs

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,25 @@ impl LintPass for NonExpressiveNames {
4141
}
4242
}
4343

44+
struct ExistingName {
45+
interned: InternedString,
46+
span: Span,
47+
len: usize,
48+
whitelist: &'static[&'static str],
49+
}
50+
4451
struct SimilarNamesLocalVisitor<'a, 'b: 'a> {
45-
names: Vec<(InternedString, Span, usize)>,
52+
names: Vec<ExistingName>,
4653
cx: &'a EarlyContext<'b>,
4754
lint: &'a NonExpressiveNames,
4855
single_char_names: Vec<char>,
4956
}
5057

51-
const WHITELIST: &'static [&'static str] = &[
52-
"lhs", "rhs",
58+
// this list contains lists of names that are allowed to be similar
59+
// the assumption is that no name is ever contained in multiple lists.
60+
const WHITELIST: &'static [&'static [&'static str]] = &[
61+
&["parsed", "parser"],
62+
&["lhs", "rhs"],
5363
];
5464

5565
struct SimilarNamesNameVisitor<'a, 'b: 'a, 'c: 'b>(&'a mut SimilarNamesLocalVisitor<'b, 'c>);
@@ -63,21 +73,27 @@ impl<'v, 'a, 'b, 'c> visit::Visitor<'v> for SimilarNamesNameVisitor<'a, 'b, 'c>
6373
}
6474
}
6575

66-
fn whitelisted(interned_name: &str) -> bool {
76+
fn get_whitelist(interned_name: &str) -> Option<&'static[&'static str]> {
6777
for &allow in WHITELIST {
68-
if interned_name == allow {
69-
return true;
78+
if whitelisted(interned_name, allow) {
79+
return Some(allow);
7080
}
71-
if interned_name.len() <= allow.len() {
72-
continue;
73-
}
74-
// allow_*
75-
let allow_start = allow.chars().chain(Some('_'));
81+
}
82+
None
83+
}
84+
85+
fn whitelisted(interned_name: &str, list: &[&str]) -> bool {
86+
if list.iter().any(|&name| interned_name == name) {
87+
return true;
88+
}
89+
for name in list {
90+
// name_*
91+
let allow_start = name.chars().chain(Some('_'));
7692
if interned_name.chars().zip(allow_start).all(|(l, r)| l == r) {
7793
return true;
7894
}
79-
// *_allow
80-
let allow_end = Some('_').into_iter().chain(allow.chars());
95+
// *_name
96+
let allow_end = Some('_').into_iter().chain(name.chars());
8197
if interned_name.chars().rev().zip(allow_end.rev()).all(|(l, r)| l == r) {
8298
return true;
8399
}
@@ -110,83 +126,66 @@ impl<'a, 'b, 'c> SimilarNamesNameVisitor<'a, 'b, 'c> {
110126
}
111127
let count = interned_name.chars().count();
112128
if count < 3 {
113-
if count != 1 {
114-
return;
129+
if count == 1 {
130+
let c = interned_name.chars().next().expect("already checked");
131+
self.check_short_name(c, span);
115132
}
116-
let c = interned_name.chars().next().expect("already checked");
117-
self.check_short_name(c, span);
118-
return;
119-
}
120-
if whitelisted(&interned_name) {
121133
return;
122134
}
123-
for &(ref existing_name, sp, existing_len) in &self.0.names {
135+
for existing_name in &self.0.names {
136+
if whitelisted(&interned_name, existing_name.whitelist) {
137+
continue;
138+
}
124139
let mut split_at = None;
125-
if existing_len > count {
126-
if existing_len - count != 1 {
127-
continue;
128-
}
129-
if levenstein_not_1(&interned_name, &existing_name) {
140+
if existing_name.len > count {
141+
if existing_name.len - count != 1 || levenstein_not_1(&interned_name, &existing_name.interned) {
130142
continue;
131143
}
132-
} else if existing_len < count {
133-
if count - existing_len != 1 {
134-
continue;
135-
}
136-
if levenstein_not_1(&existing_name, &interned_name) {
144+
} else if existing_name.len < count {
145+
if count - existing_name.len != 1 || levenstein_not_1(&existing_name.interned, &interned_name) {
137146
continue;
138147
}
139148
} else {
140149
let mut interned_chars = interned_name.chars();
141-
let mut existing_chars = existing_name.chars();
150+
let mut existing_chars = existing_name.interned.chars();
151+
let first_i = interned_chars.next().expect("we know we have at least one char");
152+
let first_e = existing_chars.next().expect("we know we have at least one char");
153+
let eq_or_numeric = |a: char, b: char| a == b || a.is_numeric() && b.is_numeric();
142154

143-
if interned_chars.next() != existing_chars.next() {
144-
let i = interned_chars.next().expect("we know we have more than 1 char");
145-
let e = existing_chars.next().expect("we know we have more than 1 char");
146-
if i == e {
147-
if i == '_' {
148-
// allowed similarity x_foo, y_foo
149-
// or too many chars differ (x_foo, y_boo)
155+
if eq_or_numeric(first_i, first_e) {
156+
let last_i = interned_chars.next_back().expect("we know we have at least two chars");
157+
let last_e = existing_chars.next_back().expect("we know we have at least two chars");
158+
if eq_or_numeric(last_i, last_e) {
159+
if interned_chars.zip(existing_chars).filter(|&(i, e)| !eq_or_numeric(i, e)).count() != 1 {
150160
continue;
151-
} else if interned_chars.ne(existing_chars) {
152-
// too many chars differ
153-
continue
154161
}
155162
} else {
156-
// too many chars differ
157-
continue;
158-
}
159-
split_at = interned_name.chars().next().map(|c| c.len_utf8());
160-
} else if interned_chars.next_back() == existing_chars.next_back() {
161-
if interned_chars.zip(existing_chars).filter(|&(i, e)| i != e).count() != 1 {
162-
// too many chars differ, or none differ (aka shadowing)
163-
continue;
164-
}
165-
} else {
166-
let i = interned_chars.next_back().expect("we know we have more than 2 chars");
167-
let e = existing_chars.next_back().expect("we know we have more than 2 chars");
168-
if i == e {
169-
if i == '_' {
170-
// allowed similarity foo_x, foo_x
171-
// or too many chars differ (foo_x, boo_x)
163+
let second_last_i = interned_chars.next_back().expect("we know we have at least three chars");
164+
let second_last_e = existing_chars.next_back().expect("we know we have at least three chars");
165+
if !eq_or_numeric(second_last_i, second_last_e) || second_last_i == '_' || !interned_chars.zip(existing_chars).all(|(i, e)| eq_or_numeric(i, e)) {
166+
// allowed similarity foo_x, foo_y
167+
// or too many chars differ (foo_x, boo_y) or (foox, booy)
172168
continue;
173-
} else if interned_chars.ne(existing_chars) {
174-
// too many chars differ
175-
continue
176169
}
177-
} else {
178-
// too many chars differ
170+
split_at = interned_name.char_indices().rev().next().map(|(i, _)| i);
171+
}
172+
} else {
173+
let second_i = interned_chars.next().expect("we know we have at least two chars");
174+
let second_e = existing_chars.next().expect("we know we have at least two chars");
175+
if !eq_or_numeric(second_i, second_e) || second_i == '_' || !interned_chars.zip(existing_chars).all(|(i, e)| eq_or_numeric(i, e)) {
176+
// allowed similarity x_foo, y_foo
177+
// or too many chars differ (x_foo, y_boo) or (xfoo, yboo)
179178
continue;
180179
}
181-
split_at = interned_name.char_indices().rev().next().map(|(i, _)| i);
180+
split_at = interned_name.chars().next().map(|c| c.len_utf8());
182181
}
183182
}
184183
span_lint_and_then(self.0.cx,
185184
SIMILAR_NAMES,
186185
span,
187186
"binding's name is too similar to existing binding",
188187
|diag| {
189-
diag.span_note(sp, "existing binding defined here");
188+
diag.span_note(existing_name.span, "existing binding defined here");
190189
if let Some(split) = split_at {
191190
diag.span_help(span, &format!("separate the discriminating character \
192191
by an underscore like: `{}_{}`",
@@ -196,7 +195,12 @@ impl<'a, 'b, 'c> SimilarNamesNameVisitor<'a, 'b, 'c> {
196195
});
197196
return;
198197
}
199-
self.0.names.push((interned_name, span, count));
198+
self.0.names.push(ExistingName {
199+
whitelist: get_whitelist(&interned_name).unwrap_or(&[]),
200+
interned: interned_name,
201+
span: span,
202+
len: count,
203+
});
200204
}
201205
}
202206

tests/compile-fail/non_expressive_names.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//~| NOTE: lint level defined here
1111
//~| NOTE: lint level defined here
1212
//~| NOTE: lint level defined here
13+
//~| NOTE: lint level defined here
14+
//~| NOTE: lint level defined here
1315
#![allow(unused)]
1416

1517
fn main() {
@@ -67,6 +69,20 @@ fn main() {
6769
(cheese2, 2) => panic!(),
6870
_ => println!(""),
6971
}
72+
let ipv4: i32;
73+
let ipv6: i32;
74+
let abcd1: i32;
75+
let abdc2: i32;
76+
let xyz1abc: i32; //~ NOTE: existing binding defined here
77+
let xyz2abc: i32;
78+
let xyzeabc: i32; //~ ERROR: name is too similar
79+
//~| HELP: for further information visit
80+
81+
let parser: i32; //~ NOTE: existing binding defined here
82+
let parsed: i32;
83+
let parsee: i32; //~ ERROR: name is too similar
84+
//~| HELP: for further information visit
85+
//~| HELP: separate the discriminating character by an underscore like: `parse_e`
7086
}
7187

7288
#[derive(Clone, Debug)]

0 commit comments

Comments
 (0)