From 9d65b7dcd19557bca9aa4b175efa14a35db6d713 Mon Sep 17 00:00:00 2001
From: Arjen Laarhoven <arjen@laarhoven.info>
Date: Thu, 15 Jul 2021 19:55:52 +0200
Subject: [PATCH] feat: upper- or lowercase hexadecimal literals

---
 Configurations.md                    |  7 ++++++
 src/config/mod.rs                    |  3 +++
 src/config/options.rs                | 11 ++++++++++
 src/expr.rs                          | 33 +++++++++++++++++++++++++++-
 tests/source/hex_literal_lower.rs    |  5 +++++
 tests/source/hex_literal_upper.rs    |  5 +++++
 tests/target/hex_literal_lower.rs    |  5 +++++
 tests/target/hex_literal_preserve.rs |  5 +++++
 tests/target/hex_literal_upper.rs    |  5 +++++
 9 files changed, 78 insertions(+), 1 deletion(-)
 create mode 100644 tests/source/hex_literal_lower.rs
 create mode 100644 tests/source/hex_literal_upper.rs
 create mode 100644 tests/target/hex_literal_lower.rs
 create mode 100644 tests/target/hex_literal_preserve.rs
 create mode 100644 tests/target/hex_literal_upper.rs

diff --git a/Configurations.md b/Configurations.md
index 84e8c3f7db6..b8f8f305396 100644
--- a/Configurations.md
+++ b/Configurations.md
@@ -1056,6 +1056,13 @@ fn lorem() -> usize {
 
 See also: [`tab_spaces`](#tab_spaces).
 
+## `hex_literal_case`
+
+Control the case of the letters in hexadecimal literal values
+
+- **Default value**: `Preserve`
+- **Possible values**: `Upper`, `Lower`
+- **Stable**: No
 
 ## `hide_parse_errors`
 
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 3d6e32fdb60..c6cee8ed227 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -69,6 +69,8 @@ create_config! {
     format_macro_matchers: bool, false, false,
         "Format the metavariable matching patterns in macros";
     format_macro_bodies: bool, true, false, "Format the bodies of macros";
+    hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false,
+        "Format hexadecimal integer literals";
 
     // Single line expressions and items
     empty_item_single_line: bool, true, false,
@@ -570,6 +572,7 @@ license_template_path = ""
 format_strings = false
 format_macro_matchers = false
 format_macro_bodies = true
+hex_literal_case = "Preserve"
 empty_item_single_line = true
 struct_lit_single_line = true
 fn_single_line = false
diff --git a/src/config/options.rs b/src/config/options.rs
index db15ee97a40..e92f8e8a531 100644
--- a/src/config/options.rs
+++ b/src/config/options.rs
@@ -129,6 +129,17 @@ pub enum ImportGranularity {
     One,
 }
 
+/// Controls how rustfmt should handle case in hexadecimal literals.
+#[config_type]
+pub enum HexLiteralCase {
+    /// Leave the literal as-is
+    Preserve,
+    /// Ensure all literals use uppercase lettering
+    Upper,
+    /// Ensure all literals use lowercase lettering
+    Lower,
+}
+
 #[config_type]
 pub enum ReportTactic {
     Always,
diff --git a/src/expr.rs b/src/expr.rs
index 6cfeb9977a9..01cc388c186 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -13,7 +13,7 @@ use crate::comment::{
     rewrite_missing_comment, CharClasses, FindUncommented,
 };
 use crate::config::lists::*;
-use crate::config::{Config, ControlBraceStyle, IndentStyle, Version};
+use crate::config::{Config, ControlBraceStyle, HexLiteralCase, IndentStyle, Version};
 use crate::lists::{
     definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape,
     struct_lit_tactic, write_list, ListFormatting, Separator,
@@ -1168,6 +1168,7 @@ pub(crate) fn rewrite_literal(
 ) -> Option<String> {
     match l.kind {
         ast::LitKind::Str(_, ast::StrStyle::Cooked) => rewrite_string_lit(context, l.span, shape),
+        ast::LitKind::Int(..) => rewrite_int_lit(context, l, shape),
         _ => wrap_str(
             context.snippet(l.span).to_owned(),
             context.config.max_width(),
@@ -1202,6 +1203,36 @@ fn rewrite_string_lit(context: &RewriteContext<'_>, span: Span, shape: Shape) ->
     )
 }
 
+fn rewrite_int_lit(context: &RewriteContext<'_>, lit: &ast::Lit, shape: Shape) -> Option<String> {
+    let span = lit.span;
+    let symbol = lit.token.symbol.as_str();
+
+    if symbol.starts_with("0x") {
+        let hex_lit = match context.config.hex_literal_case() {
+            HexLiteralCase::Preserve => None,
+            HexLiteralCase::Upper => Some(symbol[2..].to_ascii_uppercase()),
+            HexLiteralCase::Lower => Some(symbol[2..].to_ascii_lowercase()),
+        };
+        if let Some(hex_lit) = hex_lit {
+            return wrap_str(
+                format!(
+                    "0x{}{}",
+                    hex_lit,
+                    lit.token.suffix.map_or(String::new(), |s| s.to_string())
+                ),
+                context.config.max_width(),
+                shape,
+            );
+        }
+    }
+
+    wrap_str(
+        context.snippet(span).to_owned(),
+        context.config.max_width(),
+        shape,
+    )
+}
+
 fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option<SeparatorTactic> {
     if context.inside_macro() {
         if span_ends_with_comma(context, span) {
diff --git a/tests/source/hex_literal_lower.rs b/tests/source/hex_literal_lower.rs
new file mode 100644
index 00000000000..ce307b3aa52
--- /dev/null
+++ b/tests/source/hex_literal_lower.rs
@@ -0,0 +1,5 @@
+// rustfmt-hex_literal_case: Lower
+fn main() {
+    let h1 = 0xCAFE_5EA7;
+    let h2 = 0xCAFE_F00Du32;
+}
diff --git a/tests/source/hex_literal_upper.rs b/tests/source/hex_literal_upper.rs
new file mode 100644
index 00000000000..b1092ad71ba
--- /dev/null
+++ b/tests/source/hex_literal_upper.rs
@@ -0,0 +1,5 @@
+// rustfmt-hex_literal_case: Upper
+fn main() {
+    let h1 = 0xCaFE_5ea7;
+    let h2 = 0xCAFE_F00Du32;
+}
diff --git a/tests/target/hex_literal_lower.rs b/tests/target/hex_literal_lower.rs
new file mode 100644
index 00000000000..5c27fded167
--- /dev/null
+++ b/tests/target/hex_literal_lower.rs
@@ -0,0 +1,5 @@
+// rustfmt-hex_literal_case: Lower
+fn main() {
+    let h1 = 0xcafe_5ea7;
+    let h2 = 0xcafe_f00du32;
+}
diff --git a/tests/target/hex_literal_preserve.rs b/tests/target/hex_literal_preserve.rs
new file mode 100644
index 00000000000..e8774d0bb24
--- /dev/null
+++ b/tests/target/hex_literal_preserve.rs
@@ -0,0 +1,5 @@
+// rustfmt-hex_literal_case: Preserve
+fn main() {
+    let h1 = 0xcAfE_5Ea7;
+    let h2 = 0xCaFe_F00du32;
+}
diff --git a/tests/target/hex_literal_upper.rs b/tests/target/hex_literal_upper.rs
new file mode 100644
index 00000000000..48bb93d2c1c
--- /dev/null
+++ b/tests/target/hex_literal_upper.rs
@@ -0,0 +1,5 @@
+// rustfmt-hex_literal_case: Upper
+fn main() {
+    let h1 = 0xCAFE_5EA7;
+    let h2 = 0xCAFE_F00Du32;
+}