Repository: rust-phf/rust-phf
Pull request #289 (2023-06-27 09:02:43)

Add support for `unicase::Ascii`
This PR adds full support for `unicase::Ascii`. `unicase::Ascii` is similar to `unicase::UniCase`, except it has no space overhead over `&str`, so is useful when Unicode-aware folding is not needed.

Edit: It formerly implemented the missing macro support for `uncased::Uncased`, but that was merged in later PRs, https://github.com/rust-phf/rust-phf/pull/291 and https://github.com/rust-phf/rust-phf/pull/309. I have since rebased it onto master.

thaliaarchi: This was unintentionally closed when I renamed master in my fork to unicase-and-uncased
thaliaarchi: The latest commit fixes an issue, where a `macro_rules` macro that calls `phf_map!` (etc.) would insert implicit parentheses around the call argument. This unwraps that group. An example of this pattern has been added as a [test](https://github.com/thaliaarchi/rust-phf/blob/1001b4ed98571b6d6427e61e150abfa5ca69eb87/phf_macros_tests/tests/test.rs#L27-L39).
thaliaarchi: I've pulled `master` to resolve conflicts, because #291 does the same change I did in the second commit here.

Is there something you're waiting on for this PR?

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 1338770..c5f53fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,7 @@ resolver = "1"
 members = [
     "phf",
     "phf/examples/unicase-example",
+    "phf/examples/uncased-example",
     "phf_codegen",
     "phf_codegen/test",
     "phf_generator",
diff --git a/phf/examples/unicase-example/src/main.rs b/phf/examples/unicase-example/src/main.rs
index 34ba613..273fbb2 100644
--- a/phf/examples/unicase-example/src/main.rs
+++ b/phf/examples/unicase-example/src/main.rs
@@ -1,9 +1,14 @@
 use phf::phf_map;
-use unicase::UniCase;
+use unicase::{Ascii, UniCase};
 
 pub static MAP: phf::Map<UniCase<&'static str>, isize> = phf_map!(
     UniCase::ascii("Foo") => 0,
     UniCase::unicode("Bar") => 1,
 );
 
+pub static ASCII_MAP: phf::Map<Ascii<&'static str>, isize> = phf_map!(
+    Ascii::new("Foo") => 0,
+    Ascii::new("Bar") => 1,
+);
+
 fn main() {}
diff --git a/phf/src/lib.rs b/phf/src/lib.rs
index d00ba16..50d06f4 100644
--- a/phf/src/lib.rs
+++ b/phf/src/lib.rs
@@ -85,7 +85,8 @@ extern crate std as core;
 /// - literals: bools, (byte) strings, bytes, chars, and integers (these must have a type suffix)
 /// - arrays of `u8` integers
 /// - dereferenced byte string literals
-/// - `UniCase::unicode(string)` or `UniCase::ascii(string)` if the `unicase` feature is enabled
+/// - `UniCase::unicode(string)`, `UniCase::ascii(string)`, or `Ascii::new(string)` if the `unicase` feature is enabled
+/// - `UncasedStr::new(string)` if the `uncased` feature is enabled
 ///
 /// # Example
 ///
diff --git a/phf_macros/src/lib.rs b/phf_macros/src/lib.rs
index bc00f42..7002f66 100644
--- a/phf_macros/src/lib.rs
+++ b/phf_macros/src/lib.rs
@@ -15,7 +15,7 @@ use syn::{parse_macro_input, Error, Expr, ExprLit, Lit, Token, UnOp};
 #[cfg(feature = "uncased")]
 use uncased_::Uncased;
 #[cfg(feature = "unicase")]
-use unicase_::UniCase;
+use unicase_::{Ascii, UniCase};
 
 #[derive(Hash, PartialEq, Eq, Clone)]
 enum ParsedKey {
@@ -37,6 +37,8 @@ enum ParsedKey {
     Bool(bool),
     #[cfg(feature = "unicase")]
     UniCase(UniCase<String>),
+    #[cfg(feature = "unicase")]
+    UniCaseAscii(Ascii<String>),
     #[cfg(feature = "uncased")]
     Uncased(Uncased<'static>),
 }
@@ -65,6 +67,8 @@ impl PhfHash for ParsedKey {
             ParsedKey::Bool(s) => s.phf_hash(state),
             #[cfg(feature = "unicase")]
             ParsedKey::UniCase(s) => s.phf_hash(state),
+            #[cfg(feature = "unicase")]
+            ParsedKey::UniCaseAscii(s) => s.phf_hash(state),
             #[cfg(feature = "uncased")]
             ParsedKey::Uncased(s) => s.phf_hash(state),
         }
@@ -184,6 +188,8 @@ impl ParsedKey {
                     ("UniCase", "unicode") => Some(ParsedKey::UniCase(UniCase::unicode(_value))),
                     #[cfg(feature = "unicase")]
                     ("UniCase", "ascii") => Some(ParsedKey::UniCase(UniCase::ascii(_value))),
+                    #[cfg(feature = "unicase")]
+                    ("Ascii", "new") => Some(ParsedKey::UniCaseAscii(Ascii::new(_value))),
                     #[cfg(feature = "uncased")]
                     ("UncasedStr", "new") => Some(ParsedKey::Uncased(Uncased::new(_value))),
                     _ => None,
diff --git a/phf_shared/src/lib.rs b/phf_shared/src/lib.rs
index 990c8f1..b4725c3 100644
--- a/phf_shared/src/lib.rs
+++ b/phf_shared/src/lib.rs
@@ -308,6 +308,36 @@ impl<'b, 'a: 'b, S: ?Sized + 'a> PhfBorrow<unicase::UniCase<&'b S>> for unicase:
     }
 }
 
+#[cfg(feature = "unicase")]
+impl<S> PhfHash for unicase::Ascii<S>
+where
+    unicase::Ascii<S>: Hash,
+{
+    #[inline]
+    fn phf_hash<H: Hasher>(&self, state: &mut H) {
+        self.hash(state)
+    }
+}
+
+#[cfg(feature = "unicase")]
+impl<S> FmtConst for unicase::Ascii<S>
+where
+    S: AsRef<str>,
+{
+    fn fmt_const(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str("Ascii::new(")?;
+        self.as_ref().fmt_const(f)?;
+        f.write_str(")")
+    }
+}
+
+#[cfg(feature = "unicase")]
+impl<'b, 'a: 'b, S: ?Sized + 'a> PhfBorrow<unicase::Ascii<&'b S>> for unicase::Ascii<&'a S> {
+    fn borrow(&self) -> &unicase::Ascii<&'b S> {
+        self
+    }
+}
+
 #[cfg(feature = "uncased")]
 impl PhfHash for uncased::UncasedStr {
     #[inline]


Test patch:
diff --git a/phf_codegen/test/build.rs b/phf_codegen/test/build.rs
index 94235d5..031c7d1 100644
--- a/phf_codegen/test/build.rs
+++ b/phf_codegen/test/build.rs
@@ -4,7 +4,7 @@ use std::io::{self, BufWriter, Write};
 use std::path::Path;
 
 use uncased::UncasedStr;
-use unicase::UniCase;
+use unicase::{Ascii, UniCase};
 
 fn main() -> io::Result<()> {
     let file = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs");
@@ -69,6 +69,15 @@ fn main() -> io::Result<()> {
             .build()
     )?;
 
+    write!(
+        &mut file,
+        "static UNICASE_ASCII_MAP: ::phf::Map<::unicase::Ascii<&'static str>, &'static str> = \n{};",
+        phf_codegen::Map::new()
+            .entry(Ascii::new("abc"), "\"a\"")
+            .entry(Ascii::new("DEF"), "\"b\"")
+            .build()
+    )?;
+
     write!(
         &mut file,
         "static UNCASED_MAP: ::phf::Map<&'static ::uncased::UncasedStr, &'static str> = \n{};",
diff --git a/phf_codegen/test/src/lib.rs b/phf_codegen/test/src/lib.rs
index 7d95a06..a82f535 100644
--- a/phf_codegen/test/src/lib.rs
+++ b/phf_codegen/test/src/lib.rs
@@ -1,7 +1,7 @@
 #[cfg(test)]
 mod test {
     use uncased::UncasedStr;
-    use unicase::UniCase;
+    use unicase::{Ascii, UniCase};
 
     include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
 
@@ -68,6 +68,22 @@ mod test {
         assert_eq!("b", UNICASE_MAP[&UniCase::new(&*local_str_3)]);
     }
 
+    #[test]
+    fn unicase_ascii_map() {
+        assert_eq!("a", UNICASE_ASCII_MAP[&Ascii::new("AbC")]);
+        assert_eq!("a", UNICASE_ASCII_MAP[&Ascii::new("abc")]);
+        assert_eq!("b", UNICASE_ASCII_MAP[&Ascii::new("DEf")]);
+        assert!(!UNICASE_ASCII_MAP.contains_key(&Ascii::new("XyZ")));
+
+        // allow lookup with non-static slices
+        let local_str_1 = "AbC".to_string();
+        let local_str_2 = "abc".to_string();
+        let local_str_3 = "DEf".to_string();
+        assert_eq!("a", UNICASE_ASCII_MAP[&Ascii::new(&*local_str_1)]);
+        assert_eq!("a", UNICASE_ASCII_MAP[&Ascii::new(&*local_str_2)]);
+        assert_eq!("b", UNICASE_ASCII_MAP[&Ascii::new(&*local_str_3)]);
+    }
+
     #[test]
     fn uncased_map() {
         assert_eq!("a", UNCASED_MAP[UncasedStr::new("AbC")]);
diff --git a/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.rs b/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.rs
index 3ebcd01..ada76de 100644
--- a/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.rs
+++ b/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.rs
@@ -1,9 +1,14 @@
-use unicase::UniCase;
 use phf::phf_map;
+use unicase::{Ascii, UniCase};
 
 static MAP: phf::Map<UniCase<&'static str>, isize> = phf_map!(
     UniCase::ascii("FOO") => 42,
-    UniCase::ascii("foo") => 42, //~ ERROR duplicate key UniCase("FOO")
+    UniCase::ascii("foo") => 42, //~ ERROR duplicate key
+);
+
+static ASCII_MAP: phf::Map<Ascii<&'static str>, isize> = phf_map!(
+    Ascii::new("FOO") => 42,
+    Ascii::new("foo") => 42, //~ ERROR duplicate key
 );
 
 fn main() {}
diff --git a/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.stderr b/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.stderr
index 29ea8e0..5723530 100644
--- a/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.stderr
+++ b/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.stderr
@@ -1,5 +1,11 @@
 error: duplicate key
  --> tests/compile-fail-unicase/equivalent-keys.rs:6:5
   |
-6 |     UniCase::ascii("foo") => 42, //~ ERROR duplicate key UniCase("FOO")
+6 |     UniCase::ascii("foo") => 42, //~ ERROR duplicate key
   |     ^^^^^^^^^^^^^^^^^^^^^
+
+error: duplicate key
+  --> tests/compile-fail-unicase/equivalent-keys.rs:11:5
+   |
+11 |     Ascii::new("foo") => 42, //~ ERROR duplicate key
+   |     ^^^^^^^^^^^^^^^^^
diff --git a/phf_macros_tests/tests/test.rs b/phf_macros_tests/tests/test.rs
index adb52f7..9669f6a 100644
--- a/phf_macros_tests/tests/test.rs
+++ b/phf_macros_tests/tests/test.rs
@@ -286,6 +286,18 @@ mod map {
         assert_eq!(None, MAP.get(&UniCase::new("asdf")));
     }
 
+    #[test]
+    fn test_unicase_ascii() {
+        use unicase::Ascii;
+        static MAP: phf::Map<Ascii<&'static str>, isize> = phf_map!(
+            Ascii::new("FOO") => 10,
+            Ascii::new("Bar") => 11,
+        );
+        assert!(Some(&10) == MAP.get(&Ascii::new("FOo")));
+        assert!(Some(&11) == MAP.get(&Ascii::new("bar")));
+        assert_eq!(None, MAP.get(&Ascii::new("asdf")));
+    }
+
     #[test]
     fn test_uncased() {
         use uncased::UncasedStr;