Repository: rust-phf/rust-phf
Pull request #309 (2024-02-17 12:39:26)

Macro support for uncased
As the title says, macro support for uncased, since `UncasedStr::new` is const now.
Tests and examples matching the existing ones for Unicase are included.
I wrote this before noticing that #289 includes the same feature, and either will do, but I'm hoping a more focused PR has a better chance of getting merged.


Patch:
diff --git a/phf/Cargo.toml b/phf/Cargo.toml
index c2d40a9..aeca358 100644
--- a/phf/Cargo.toml
+++ b/phf/Cargo.toml
@@ -18,7 +18,7 @@ test = false
 [features]
 default = ["std"]
 std = ["phf_shared/std"]
-uncased = ["phf_shared/uncased"]
+uncased = ["phf_macros?/uncased", "phf_shared/uncased"]
 unicase = ["phf_macros?/unicase", "phf_shared/unicase"]
 macros = ["phf_macros"]
 
diff --git a/phf/examples/uncased-example/Cargo.toml b/phf/examples/uncased-example/Cargo.toml
new file mode 100644
index 0000000..606fabc
--- /dev/null
+++ b/phf/examples/uncased-example/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "uncased-example"
+version = "0.1.0"
+edition = "2021"
+rust-version = "1.61"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+phf = { version = "^0.11.2", features = ["macros", "uncased"] }
+uncased = "0.9.7"
diff --git a/phf/examples/uncased-example/src/main.rs b/phf/examples/uncased-example/src/main.rs
new file mode 100644
index 0000000..ee28ce4
--- /dev/null
+++ b/phf/examples/uncased-example/src/main.rs
@@ -0,0 +1,9 @@
+use phf::phf_map;
+use uncased::UncasedStr;
+
+pub static MAP: phf::Map<&'static UncasedStr, isize> = phf_map!(
+    UncasedStr::new("Foo") => 0,
+    UncasedStr::new("Bar") => 1,
+);
+
+fn main() {}
diff --git a/phf_macros/Cargo.toml b/phf_macros/Cargo.toml
index 921d3e6..eb1f805 100644
--- a/phf_macros/Cargo.toml
+++ b/phf_macros/Cargo.toml
@@ -15,12 +15,14 @@ proc-macro = true
 
 [features]
 unicase = ["unicase_", "phf_shared/unicase"]
+uncased = ["uncased_", "phf_shared/uncased"]
 
 [dependencies]
 syn = { version = "2", features = ["full"] }
 quote = "1"
 proc-macro2 = "1"
 unicase_ = { package = "unicase", version = "2.4.0", optional = true }
+uncased_ = { package = "uncased", version = "0.9.7", optional = true }
 
 phf_generator = "0.11.1"
 phf_shared = { version = "^0.11.2", default-features = false }
diff --git a/phf_macros/src/lib.rs b/phf_macros/src/lib.rs
index a033036..8db0f8a 100644
--- a/phf_macros/src/lib.rs
+++ b/phf_macros/src/lib.rs
@@ -12,6 +12,8 @@ use std::hash::Hasher;
 use syn::parse::{self, Parse, ParseStream};
 use syn::punctuated::Punctuated;
 use syn::{parse_macro_input, Error, Expr, ExprLit, Lit, Token, UnOp};
+#[cfg(feature = "uncased")]
+use uncased_::Uncased;
 #[cfg(feature = "unicase")]
 use unicase_::UniCase;
 
@@ -33,6 +35,8 @@ enum ParsedKey {
     Bool(bool),
     #[cfg(feature = "unicase")]
     UniCase(UniCase<String>),
+    #[cfg(feature = "uncased")]
+    Uncased(Uncased<'static>),
 }
 
 impl PhfHash for ParsedKey {
@@ -57,6 +61,8 @@ impl PhfHash for ParsedKey {
             ParsedKey::Bool(s) => s.phf_hash(state),
             #[cfg(feature = "unicase")]
             ParsedKey::UniCase(s) => s.phf_hash(state),
+            #[cfg(feature = "uncased")]
+            ParsedKey::Uncased(s) => s.phf_hash(state),
         }
     }
 }
@@ -138,34 +144,42 @@ impl ParsedKey {
                 }
             }
             Expr::Group(group) => ParsedKey::from_expr(&group.expr),
-            #[cfg(feature = "unicase")]
-            Expr::Call(call) => {
+            Expr::Call(call) if call.args.len() == 1 => {
+                let last;
+                let last_ahead;
+
                 if let Expr::Path(ep) = call.func.as_ref() {
-                    let segments = &mut ep.path.segments.iter().rev();
-                    let last = &segments.next()?.ident;
-                    let last_ahead = &segments.next()?.ident;
-                    let is_unicode = last_ahead == "UniCase" && last == "unicode";
-                    let is_ascii = last_ahead == "UniCase" && last == "ascii";
-                    if call.args.len() == 1 && (is_unicode || is_ascii) {
-                        if let Some(Expr::Lit(ExprLit {
-                            attrs: _,
-                            lit: Lit::Str(s),
-                        })) = call.args.first()
-                        {
-                            let v = if is_unicode {
-                                UniCase::unicode(s.value())
-                            } else {
-                                UniCase::ascii(s.value())
-                            };
-                            Some(ParsedKey::UniCase(v))
-                        } else {
-                            None
-                        }
-                    } else {
-                        None
-                    }
+                    let mut segments = ep.path.segments.iter();
+                    last = segments.next_back()?.ident.to_string();
+                    last_ahead = segments.next_back()?.ident.to_string();
                 } else {
-                    None
+                    return None;
+                }
+
+                let mut arg = call.args.first().unwrap();
+
+                while let Expr::Group(group) = arg {
+                    arg = &group.expr;
+                }
+
+                let _value = match arg {
+                    Expr::Lit(ExprLit {
+                        attrs: _,
+                        lit: Lit::Str(s),
+                    }) => s.value(),
+                    _ => {
+                        return None;
+                    }
+                };
+
+                match (&*last_ahead, &*last) {
+                    #[cfg(feature = "unicase")]
+                    ("UniCase", "unicode") => Some(ParsedKey::UniCase(UniCase::unicode(_value))),
+                    #[cfg(feature = "unicase")]
+                    ("UniCase", "ascii") => Some(ParsedKey::UniCase(UniCase::ascii(_value))),
+                    #[cfg(feature = "uncased")]
+                    ("UncasedStr", "new") => Some(ParsedKey::Uncased(Uncased::new(_value))),
+                    _ => None,
                 }
             }
             _ => None,


Test patch:
diff --git a/phf_codegen/test/Cargo.toml b/phf_codegen/test/Cargo.toml
index 1c98a25..8ab2fee 100644
--- a/phf_codegen/test/Cargo.toml
+++ b/phf_codegen/test/Cargo.toml
@@ -7,10 +7,10 @@ edition = "2021"
 
 [dependencies]
 phf = { version = "^0.11.2", features = ["uncased", "unicase"] }
-uncased = { version = "0.9.6", default-features = false }
+uncased = { version = "0.9.7", default-features = false }
 unicase = "2.4.0"
 
 [build-dependencies]
 phf_codegen = { version = "^0.11.2", path = ".." }
 unicase = "2.4.0"
-uncased = { version = "0.9.6", default-features = false }
+uncased = { version = "0.9.7", default-features = false }
diff --git a/phf_macros_tests/Cargo.toml b/phf_macros_tests/Cargo.toml
index dd38f82..e4ef387 100644
--- a/phf_macros_tests/Cargo.toml
+++ b/phf_macros_tests/Cargo.toml
@@ -14,5 +14,6 @@ categories = ["data-structures"]
 [dev-dependencies]
 trybuild = "1.0"
 phf = { version = "0.11", features = ["macros"] }
-phf_macros = { version = "0.11", features = ["unicase"] }
+phf_macros = { version = "0.11", features = ["unicase", "uncased"] }
 unicase = "2.4.0"
+uncased = "0.9.7"
diff --git a/phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.rs b/phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.rs
new file mode 100644
index 0000000..b7101b1
--- /dev/null
+++ b/phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.rs
@@ -0,0 +1,9 @@
+use phf::phf_map;
+use uncased::UncasedStr;
+
+static MAP: phf::Map<&'static UncasedStr, isize> = phf_map!(
+    UncasedStr::new("FOO") => 42,
+    UncasedStr::new("foo") => 42, //~ ERROR duplicate key UncasedStr("FOO")
+);
+
+fn main() {}
diff --git a/phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.stderr b/phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.stderr
new file mode 100644
index 0000000..077c569
--- /dev/null
+++ b/phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.stderr
@@ -0,0 +1,5 @@
+error: duplicate key
+ --> tests/compile-fail-uncased/equivalent-keys.rs:6:5
+  |
+6 |     UncasedStr::new("foo") => 42, //~ ERROR duplicate key UncasedStr("FOO")
+  |     ^^^^^^^^^^^^^^^^^^^^^^
diff --git a/phf_macros_tests/tests/test.rs b/phf_macros_tests/tests/test.rs
index 7123753..68ff486 100644
--- a/phf_macros_tests/tests/test.rs
+++ b/phf_macros_tests/tests/test.rs
@@ -256,6 +256,18 @@ mod map {
         assert!(Some(&11) == MAP.get(&UniCase::new("bar")));
         assert_eq!(None, MAP.get(&UniCase::new("asdf")));
     }
+
+    #[test]
+    fn test_uncased() {
+        use uncased::UncasedStr;
+        static MAP: phf::Map<&'static UncasedStr, isize> = phf_map!(
+            UncasedStr::new("FOO") => 10,
+            UncasedStr::new("Bar") => 11,
+        );
+        assert!(Some(&10) == MAP.get("FOo".into()));
+        assert!(Some(&11) == MAP.get("bar".into()));
+        assert_eq!(None, MAP.get("asdf".into()));
+    }
 }
 
 mod set {
diff --git a/phf_macros_tests/tests/trybuild.rs b/phf_macros_tests/tests/trybuild.rs
index 8e80715..a15c406 100644
--- a/phf_macros_tests/tests/trybuild.rs
+++ b/phf_macros_tests/tests/trybuild.rs
@@ -9,6 +9,13 @@ fn compile_test_unicase() {
     t.compile_fail("tests/compile-fail-unicase/*.rs");
 }
 
+#[test]
+#[ignore]
+fn compile_test_uncased() {
+    let t = trybuild::TestCases::new();
+    t.compile_fail("tests/compile-fail-uncased/*.rs");
+}
+
 #[test]
 #[ignore]
 fn compile_fail() {