Skip to content

Commit cb0132d

Browse files
committed
Auto merge of rust-lang#7865 - Herschel:fix-match-str-case-mismatch, r=xFrednet
Fix `match_str_case_mismatch` on uncased chars False positives would result because `char::is_lowercase` and friends will return `false` for non-alphabetic chars and alphabetic chars lacking case (such as CJK scripts). Care also has to be taken for handling titlecase characters (`Dz`) and lowercased chars with no uppercase equivalent (`ʁ`). For example, when verifying lowercase: * Check `!any(char::is_ascii_uppercase)` instead of `all(char::is_ascii_lowercase)` for ASCII. * Check that `all(|c| c.to_lowercase() == c)` instead of `all(char::is_lowercase)` for non-ASCII Fixes rust-lang#7863. changelog: Fix false positives in [`match_str_case_mismatch`] on uncased characters
2 parents 3ef1f19 + e953dff commit cb0132d

File tree

3 files changed

+139
-9
lines changed

3 files changed

+139
-9
lines changed

clippy_lints/src/match_str_case_mismatch.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@ fn get_case_method(segment_ident_str: &str) -> Option<CaseMethod> {
127127

128128
fn verify_case<'a>(case_method: &'a CaseMethod, arms: &'a [Arm<'_>]) -> Option<(Span, SymbolStr)> {
129129
let case_check = match case_method {
130-
CaseMethod::LowerCase => |input: &str| -> bool { input.chars().all(char::is_lowercase) },
131-
CaseMethod::AsciiLowerCase => |input: &str| -> bool { input.chars().all(|c| matches!(c, 'a'..='z')) },
132-
CaseMethod::UpperCase => |input: &str| -> bool { input.chars().all(char::is_uppercase) },
133-
CaseMethod::AsciiUppercase => |input: &str| -> bool { input.chars().all(|c| matches!(c, 'A'..='Z')) },
130+
CaseMethod::LowerCase => |input: &str| -> bool { input.chars().all(|c| c.to_lowercase().next() == Some(c)) },
131+
CaseMethod::AsciiLowerCase => |input: &str| -> bool { !input.chars().any(|c| c.is_ascii_uppercase()) },
132+
CaseMethod::UpperCase => |input: &str| -> bool { input.chars().all(|c| c.to_uppercase().next() == Some(c)) },
133+
CaseMethod::AsciiUppercase => |input: &str| -> bool { !input.chars().any(|c| c.is_ascii_lowercase()) },
134134
};
135135

136136
for arm in arms {
@@ -153,7 +153,7 @@ fn verify_case<'a>(case_method: &'a CaseMethod, arms: &'a [Arm<'_>]) -> Option<(
153153

154154
fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad_case_str: &str) {
155155
let (method_str, suggestion) = match case_method {
156-
CaseMethod::LowerCase => ("to_lower_case", bad_case_str.to_lowercase()),
156+
CaseMethod::LowerCase => ("to_lowercase", bad_case_str.to_lowercase()),
157157
CaseMethod::AsciiLowerCase => ("to_ascii_lowercase", bad_case_str.to_ascii_lowercase()),
158158
CaseMethod::UpperCase => ("to_uppercase", bad_case_str.to_uppercase()),
159159
CaseMethod::AsciiUppercase => ("to_ascii_uppercase", bad_case_str.to_ascii_uppercase()),

tests/ui/match_str_case_mismatch.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,49 @@ fn as_str_match() {
1212
}
1313
}
1414

15+
fn non_alphabetic() {
16+
let var = "~!@#$%^&*()-_=+FOO";
17+
18+
match var.to_ascii_lowercase().as_str() {
19+
"1234567890" => {},
20+
"~!@#$%^&*()-_=+foo" => {},
21+
"\n\r\t\x7F" => {},
22+
_ => {},
23+
}
24+
}
25+
26+
fn unicode_cased() {
27+
let var = "ВОДЫ";
28+
29+
match var.to_lowercase().as_str() {
30+
"水" => {},
31+
"νερό" => {},
32+
"воды" => {},
33+
"물" => {},
34+
_ => {},
35+
}
36+
}
37+
38+
fn titlecase() {
39+
let var = "BarDz";
40+
41+
match var.to_lowercase().as_str() {
42+
"foolj" => {},
43+
"bardz" => {},
44+
_ => {},
45+
}
46+
}
47+
48+
fn no_case_equivalent() {
49+
let var = "barʁ";
50+
51+
match var.to_uppercase().as_str() {
52+
"FOOɕ" => {},
53+
"BARʁ" => {},
54+
_ => {},
55+
}
56+
}
57+
1558
fn addrof_unary_match() {
1659
let var = "BAR";
1760

@@ -70,6 +113,49 @@ fn as_str_match_mismatch() {
70113
}
71114
}
72115

116+
fn non_alphabetic_mismatch() {
117+
let var = "~!@#$%^&*()-_=+FOO";
118+
119+
match var.to_ascii_lowercase().as_str() {
120+
"1234567890" => {},
121+
"~!@#$%^&*()-_=+Foo" => {},
122+
"\n\r\t\x7F" => {},
123+
_ => {},
124+
}
125+
}
126+
127+
fn unicode_cased_mismatch() {
128+
let var = "ВОДЫ";
129+
130+
match var.to_lowercase().as_str() {
131+
"水" => {},
132+
"νερό" => {},
133+
"Воды" => {},
134+
"물" => {},
135+
_ => {},
136+
}
137+
}
138+
139+
fn titlecase_mismatch() {
140+
let var = "BarDz";
141+
142+
match var.to_lowercase().as_str() {
143+
"foolj" => {},
144+
"barDz" => {},
145+
_ => {},
146+
}
147+
}
148+
149+
fn no_case_equivalent_mismatch() {
150+
let var = "barʁ";
151+
152+
match var.to_uppercase().as_str() {
153+
"FOOɕ" => {},
154+
"bARʁ" => {},
155+
_ => {},
156+
}
157+
}
158+
73159
fn addrof_unary_match_mismatch() {
74160
let var = "BAR";
75161

tests/ui/match_str_case_mismatch.stderr

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: this `match` arm has a differing case than its expression
2-
--> $DIR/match_str_case_mismatch.rs:68:9
2+
--> $DIR/match_str_case_mismatch.rs:111:9
33
|
44
LL | "Bar" => {},
55
| ^^^^^
@@ -11,7 +11,51 @@ LL | "bar" => {},
1111
| ~~~~~
1212

1313
error: this `match` arm has a differing case than its expression
14-
--> $DIR/match_str_case_mismatch.rs:78:9
14+
--> $DIR/match_str_case_mismatch.rs:121:9
15+
|
16+
LL | "~!@#$%^&*()-_=+Foo" => {},
17+
| ^^^^^^^^^^^^^^^^^^^^
18+
|
19+
help: consider changing the case of this arm to respect `to_ascii_lowercase`
20+
|
21+
LL | "~!@#$%^&*()-_=+foo" => {},
22+
| ~~~~~~~~~~~~~~~~~~~~
23+
24+
error: this `match` arm has a differing case than its expression
25+
--> $DIR/match_str_case_mismatch.rs:133:9
26+
|
27+
LL | "Воды" => {},
28+
| ^^^^^^
29+
|
30+
help: consider changing the case of this arm to respect `to_lowercase`
31+
|
32+
LL | "воды" => {},
33+
| ~~~~~~
34+
35+
error: this `match` arm has a differing case than its expression
36+
--> $DIR/match_str_case_mismatch.rs:144:9
37+
|
38+
LL | "barDz" => {},
39+
| ^^^^^^
40+
|
41+
help: consider changing the case of this arm to respect `to_lowercase`
42+
|
43+
LL | "bardz" => {},
44+
| ~~~~~~
45+
46+
error: this `match` arm has a differing case than its expression
47+
--> $DIR/match_str_case_mismatch.rs:154:9
48+
|
49+
LL | "bARʁ" => {},
50+
| ^^^^^^
51+
|
52+
help: consider changing the case of this arm to respect `to_uppercase`
53+
|
54+
LL | "BARʁ" => {},
55+
| ~~~~~~
56+
57+
error: this `match` arm has a differing case than its expression
58+
--> $DIR/match_str_case_mismatch.rs:164:9
1559
|
1660
LL | "Bar" => {},
1761
| ^^^^^
@@ -22,7 +66,7 @@ LL | "bar" => {},
2266
| ~~~~~
2367

2468
error: this `match` arm has a differing case than its expression
25-
--> $DIR/match_str_case_mismatch.rs:93:9
69+
--> $DIR/match_str_case_mismatch.rs:179:9
2670
|
2771
LL | "bAR" => {},
2872
| ^^^^^
@@ -32,5 +76,5 @@ help: consider changing the case of this arm to respect `to_ascii_uppercase`
3276
LL | "BAR" => {},
3377
| ~~~~~
3478

35-
error: aborting due to 3 previous errors
79+
error: aborting due to 7 previous errors
3680

0 commit comments

Comments
 (0)