Auto merge of #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 #7863.

changelog: Fix false positives in [`match_str_case_mismatch`] on uncased characters
This commit is contained in:
bors 2021-10-25 21:34:11 +00:00
commit cb0132d209
3 changed files with 139 additions and 9 deletions

View file

@ -127,10 +127,10 @@ fn get_case_method(segment_ident_str: &str) -> Option<CaseMethod> {
fn verify_case<'a>(case_method: &'a CaseMethod, arms: &'a [Arm<'_>]) -> Option<(Span, SymbolStr)> {
let case_check = match case_method {
CaseMethod::LowerCase => |input: &str| -> bool { input.chars().all(char::is_lowercase) },
CaseMethod::AsciiLowerCase => |input: &str| -> bool { input.chars().all(|c| matches!(c, 'a'..='z')) },
CaseMethod::UpperCase => |input: &str| -> bool { input.chars().all(char::is_uppercase) },
CaseMethod::AsciiUppercase => |input: &str| -> bool { input.chars().all(|c| matches!(c, 'A'..='Z')) },
CaseMethod::LowerCase => |input: &str| -> bool { input.chars().all(|c| c.to_lowercase().next() == Some(c)) },
CaseMethod::AsciiLowerCase => |input: &str| -> bool { !input.chars().any(|c| c.is_ascii_uppercase()) },
CaseMethod::UpperCase => |input: &str| -> bool { input.chars().all(|c| c.to_uppercase().next() == Some(c)) },
CaseMethod::AsciiUppercase => |input: &str| -> bool { !input.chars().any(|c| c.is_ascii_lowercase()) },
};
for arm in arms {
@ -153,7 +153,7 @@ fn verify_case<'a>(case_method: &'a CaseMethod, arms: &'a [Arm<'_>]) -> Option<(
fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad_case_str: &str) {
let (method_str, suggestion) = match case_method {
CaseMethod::LowerCase => ("to_lower_case", bad_case_str.to_lowercase()),
CaseMethod::LowerCase => ("to_lowercase", bad_case_str.to_lowercase()),
CaseMethod::AsciiLowerCase => ("to_ascii_lowercase", bad_case_str.to_ascii_lowercase()),
CaseMethod::UpperCase => ("to_uppercase", bad_case_str.to_uppercase()),
CaseMethod::AsciiUppercase => ("to_ascii_uppercase", bad_case_str.to_ascii_uppercase()),

View file

@ -12,6 +12,49 @@ fn as_str_match() {
}
}
fn non_alphabetic() {
let var = "~!@#$%^&*()-_=+FOO";
match var.to_ascii_lowercase().as_str() {
"1234567890" => {},
"~!@#$%^&*()-_=+foo" => {},
"\n\r\t\x7F" => {},
_ => {},
}
}
fn unicode_cased() {
let var = "ВОДЫ";
match var.to_lowercase().as_str() {
"" => {},
"νερό" => {},
"воды" => {},
"" => {},
_ => {},
}
}
fn titlecase() {
let var = "BarDz";
match var.to_lowercase().as_str() {
"foolj" => {},
"bardz" => {},
_ => {},
}
}
fn no_case_equivalent() {
let var = "barʁ";
match var.to_uppercase().as_str() {
"FOOɕ" => {},
"BARʁ" => {},
_ => {},
}
}
fn addrof_unary_match() {
let var = "BAR";
@ -70,6 +113,49 @@ fn as_str_match_mismatch() {
}
}
fn non_alphabetic_mismatch() {
let var = "~!@#$%^&*()-_=+FOO";
match var.to_ascii_lowercase().as_str() {
"1234567890" => {},
"~!@#$%^&*()-_=+Foo" => {},
"\n\r\t\x7F" => {},
_ => {},
}
}
fn unicode_cased_mismatch() {
let var = "ВОДЫ";
match var.to_lowercase().as_str() {
"" => {},
"νερό" => {},
"Воды" => {},
"" => {},
_ => {},
}
}
fn titlecase_mismatch() {
let var = "BarDz";
match var.to_lowercase().as_str() {
"foolj" => {},
"barDz" => {},
_ => {},
}
}
fn no_case_equivalent_mismatch() {
let var = "barʁ";
match var.to_uppercase().as_str() {
"FOOɕ" => {},
"bARʁ" => {},
_ => {},
}
}
fn addrof_unary_match_mismatch() {
let var = "BAR";

View file

@ -1,5 +1,5 @@
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:68:9
--> $DIR/match_str_case_mismatch.rs:111:9
|
LL | "Bar" => {},
| ^^^^^
@ -11,7 +11,51 @@ LL | "bar" => {},
| ~~~~~
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:78:9
--> $DIR/match_str_case_mismatch.rs:121:9
|
LL | "~!@#$%^&*()-_=+Foo" => {},
| ^^^^^^^^^^^^^^^^^^^^
|
help: consider changing the case of this arm to respect `to_ascii_lowercase`
|
LL | "~!@#$%^&*()-_=+foo" => {},
| ~~~~~~~~~~~~~~~~~~~~
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:133:9
|
LL | "Воды" => {},
| ^^^^^^
|
help: consider changing the case of this arm to respect `to_lowercase`
|
LL | "воды" => {},
| ~~~~~~
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:144:9
|
LL | "barDz" => {},
| ^^^^^^
|
help: consider changing the case of this arm to respect `to_lowercase`
|
LL | "bardz" => {},
| ~~~~~~
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:154:9
|
LL | "bARʁ" => {},
| ^^^^^^
|
help: consider changing the case of this arm to respect `to_uppercase`
|
LL | "BARʁ" => {},
| ~~~~~~
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:164:9
|
LL | "Bar" => {},
| ^^^^^
@ -22,7 +66,7 @@ LL | "bar" => {},
| ~~~~~
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:93:9
--> $DIR/match_str_case_mismatch.rs:179:9
|
LL | "bAR" => {},
| ^^^^^
@ -32,5 +76,5 @@ help: consider changing the case of this arm to respect `to_ascii_uppercase`
LL | "BAR" => {},
| ~~~~~
error: aborting due to 3 previous errors
error: aborting due to 7 previous errors