Skip to content

Commit

Permalink
Auto merge of rust-lang#7865 - Herschel:fix-match-str-case-mismatch, …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
bors committed Oct 25, 2021
2 parents 3ef1f19 + e953dff commit cb0132d
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 9 deletions.
10 changes: 5 additions & 5 deletions clippy_lints/src/match_str_case_mismatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()),
Expand Down
86 changes: 86 additions & 0 deletions tests/ui/match_str_case_mismatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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";

Expand Down
52 changes: 48 additions & 4 deletions tests/ui/match_str_case_mismatch.stderr
Original file line number Diff line number Diff line change
@@ -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" => {},
| ^^^^^
Expand All @@ -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" => {},
| ^^^^^
Expand All @@ -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" => {},
| ^^^^^
Expand All @@ -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

0 comments on commit cb0132d

Please sign in to comment.