about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Macleod <alex@macleod.io>2025-03-04 14:10:44 +0000
committerGitHub <noreply@github.com>2025-03-04 14:10:44 +0000
commitdd8cf052c247c02cdd8add6579df86342cf580b7 (patch)
tree44c5a958040454a5dd8a4e4408f05261a4d56055
parentcdc1d9d87ab8c50a2ab2771906a1573c061ce02b (diff)
parente587a5d0f08ca138e42bff6f3308bc7950dc6b82 (diff)
better help for `mixed_case_hex_literals` (#14235)
It can be error-prone for developers to manually change literals with
mixed uppercase and lowercase letters into consistently all-lowercase or
all-uppercase literals. Therefore, this lint rule should suggest
alternative literals.

changelog: [`mixed_case_hex_literals`]: add alternative suggestions
-rw-r--r--clippy_lints/src/misc_early/mixed_case_hex_literals.rs33
-rw-r--r--tests/ui/literals.rs4
-rw-r--r--tests/ui/literals.stderr45
3 files changed, 62 insertions, 20 deletions
diff --git a/clippy_lints/src/misc_early/mixed_case_hex_literals.rs b/clippy_lints/src/misc_early/mixed_case_hex_literals.rs
index 9151cc63320..4fbd3c9874d 100644
--- a/clippy_lints/src/misc_early/mixed_case_hex_literals.rs
+++ b/clippy_lints/src/misc_early/mixed_case_hex_literals.rs
@@ -1,32 +1,51 @@
-use clippy_utils::diagnostics::span_lint;
+use clippy_utils::diagnostics::span_lint_and_help;
 use rustc_lint::EarlyContext;
 use rustc_span::Span;
 
 use super::MIXED_CASE_HEX_LITERALS;
 
 pub(super) fn check(cx: &EarlyContext<'_>, lit_span: Span, suffix: &str, lit_snip: &str) {
-    let Some(maybe_last_sep_idx) = lit_snip.len().checked_sub(suffix.len() + 1) else {
-        return; // It's useless so shouldn't lint.
+    let num_end_idx = match lit_snip.strip_suffix(suffix) {
+        Some(p) if p.ends_with('_') => lit_snip.len() - (suffix.len() + 1),
+        Some(_) => lit_snip.len() - suffix.len(),
+        None => lit_snip.len(),
     };
-    if maybe_last_sep_idx <= 2 {
+
+    if num_end_idx <= 2 {
         // It's meaningless or causes range error.
         return;
     }
+
     let mut seen = (false, false);
-    for ch in &lit_snip.as_bytes()[2..=maybe_last_sep_idx] {
+    for ch in &lit_snip.as_bytes()[2..num_end_idx] {
         match ch {
             b'a'..=b'f' => seen.0 = true,
             b'A'..=b'F' => seen.1 = true,
             _ => {},
         }
         if seen.0 && seen.1 {
-            span_lint(
+            let raw_digits = &lit_snip[2..num_end_idx];
+            let (sugg_lower, sugg_upper) = if suffix.is_empty() {
+                (
+                    format!("0x{}", raw_digits.to_lowercase()),
+                    format!("0x{}", raw_digits.to_uppercase()),
+                )
+            } else {
+                (
+                    format!("0x{}_{}", raw_digits.to_lowercase(), suffix),
+                    format!("0x{}_{}", raw_digits.to_uppercase(), suffix),
+                )
+            };
+
+            span_lint_and_help(
                 cx,
                 MIXED_CASE_HEX_LITERALS,
                 lit_span,
                 "inconsistent casing in hexadecimal literal",
+                None,
+                format!("consider using `{sugg_lower}` or `{sugg_upper}`"),
             );
-            break;
+            return;
         }
     }
 }
diff --git a/tests/ui/literals.rs b/tests/ui/literals.rs
index d21d49310a0..ba343a8f62f 100644
--- a/tests/ui/literals.rs
+++ b/tests/ui/literals.rs
@@ -30,6 +30,10 @@ fn main() {
     //~^ separated_literal_suffix
     //~| mixed_case_hex_literals
 
+    let fail2 = 0xab_CD_isize;
+    //~^ separated_literal_suffix
+    //~| mixed_case_hex_literals
+
     let fail_multi_zero = 000_123usize;
     //~^ unseparated_literal_suffix
     //~| zero_prefixed_literal
diff --git a/tests/ui/literals.stderr b/tests/ui/literals.stderr
index d65cd3c89eb..6bfeb91625b 100644
--- a/tests/ui/literals.stderr
+++ b/tests/ui/literals.stderr
@@ -25,6 +25,7 @@ error: inconsistent casing in hexadecimal literal
 LL |     let fail1 = 0xabCD;
    |                 ^^^^^^
    |
+   = help: consider using `0xabcd` or `0xABCD`
    = note: `-D clippy::mixed-case-hex-literals` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::mixed_case_hex_literals)]`
 
@@ -39,6 +40,8 @@ error: inconsistent casing in hexadecimal literal
    |
 LL |     let fail2 = 0xabCD_u32;
    |                 ^^^^^^^^^^
+   |
+   = help: consider using `0xabcd_u32` or `0xABCD_u32`
 
 error: integer type suffix should not be separated by an underscore
   --> tests/ui/literals.rs:29:17
@@ -51,9 +54,25 @@ error: inconsistent casing in hexadecimal literal
    |
 LL |     let fail2 = 0xabCD_isize;
    |                 ^^^^^^^^^^^^
+   |
+   = help: consider using `0xabcd_isize` or `0xABCD_isize`
+
+error: integer type suffix should not be separated by an underscore
+  --> tests/ui/literals.rs:33:17
+   |
+LL |     let fail2 = 0xab_CD_isize;
+   |                 ^^^^^^^^^^^^^ help: remove the underscore: `0xab_CDisize`
+
+error: inconsistent casing in hexadecimal literal
+  --> tests/ui/literals.rs:33:17
+   |
+LL |     let fail2 = 0xab_CD_isize;
+   |                 ^^^^^^^^^^^^^
+   |
+   = help: consider using `0xab_cd_isize` or `0xAB_CD_isize`
 
 error: integer type suffix should be separated by an underscore
-  --> tests/ui/literals.rs:33:27
+  --> tests/ui/literals.rs:37:27
    |
 LL |     let fail_multi_zero = 000_123usize;
    |                           ^^^^^^^^^^^^ help: add an underscore: `000_123_usize`
@@ -62,7 +81,7 @@ LL |     let fail_multi_zero = 000_123usize;
    = help: to override `-D warnings` add `#[allow(clippy::unseparated_literal_suffix)]`
 
 error: this is a decimal constant
-  --> tests/ui/literals.rs:33:27
+  --> tests/ui/literals.rs:37:27
    |
 LL |     let fail_multi_zero = 000_123usize;
    |                           ^^^^^^^^^^^^
@@ -81,13 +100,13 @@ LL +     let fail_multi_zero = 0o123usize;
    |
 
 error: integer type suffix should not be separated by an underscore
-  --> tests/ui/literals.rs:38:16
+  --> tests/ui/literals.rs:42:16
    |
 LL |     let ok10 = 0_i64;
    |                ^^^^^ help: remove the underscore: `0i64`
 
 error: this is a decimal constant
-  --> tests/ui/literals.rs:41:17
+  --> tests/ui/literals.rs:45:17
    |
 LL |     let fail8 = 0123;
    |                 ^^^^
@@ -103,13 +122,13 @@ LL |     let fail8 = 0o123;
    |                  +
 
 error: integer type suffix should not be separated by an underscore
-  --> tests/ui/literals.rs:51:16
+  --> tests/ui/literals.rs:55:16
    |
 LL |     let ok17 = 0x123_4567_8901_usize;
    |                ^^^^^^^^^^^^^^^^^^^^^ help: remove the underscore: `0x123_4567_8901usize`
 
 error: digits grouped inconsistently by underscores
-  --> tests/ui/literals.rs:56:18
+  --> tests/ui/literals.rs:60:18
    |
 LL |     let fail19 = 12_3456_21;
    |                  ^^^^^^^^^^ help: consider: `12_345_621`
@@ -118,19 +137,19 @@ LL |     let fail19 = 12_3456_21;
    = help: to override `-D warnings` add `#[allow(clippy::inconsistent_digit_grouping)]`
 
 error: digits grouped inconsistently by underscores
-  --> tests/ui/literals.rs:59:18
+  --> tests/ui/literals.rs:63:18
    |
 LL |     let fail22 = 3__4___23;
    |                  ^^^^^^^^^ help: consider: `3_423`
 
 error: digits grouped inconsistently by underscores
-  --> tests/ui/literals.rs:62:18
+  --> tests/ui/literals.rs:66:18
    |
 LL |     let fail23 = 3__16___23;
    |                  ^^^^^^^^^^ help: consider: `31_623`
 
 error: digits of hex, binary or octal literal not in groups of equal size
-  --> tests/ui/literals.rs:65:18
+  --> tests/ui/literals.rs:69:18
    |
 LL |     let fail24 = 0xAB_ABC_AB;
    |                  ^^^^^^^^^^^ help: consider: `0x0ABA_BCAB`
@@ -139,7 +158,7 @@ LL |     let fail24 = 0xAB_ABC_AB;
    = help: to override `-D warnings` add `#[allow(clippy::unusual_byte_groupings)]`
 
 error: this is a decimal constant
-  --> tests/ui/literals.rs:75:13
+  --> tests/ui/literals.rs:79:13
    |
 LL |     let _ = 08;
    |             ^^
@@ -151,7 +170,7 @@ LL +     let _ = 8;
    |
 
 error: this is a decimal constant
-  --> tests/ui/literals.rs:78:13
+  --> tests/ui/literals.rs:82:13
    |
 LL |     let _ = 09;
    |             ^^
@@ -163,7 +182,7 @@ LL +     let _ = 9;
    |
 
 error: this is a decimal constant
-  --> tests/ui/literals.rs:81:13
+  --> tests/ui/literals.rs:85:13
    |
 LL |     let _ = 089;
    |             ^^^
@@ -174,5 +193,5 @@ LL -     let _ = 089;
 LL +     let _ = 89;
    |
 
-error: aborting due to 20 previous errors
+error: aborting due to 22 previous errors