From 180b9c32934619c4eb62365b4cb9a2634265e794 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sun, 22 Jun 2025 19:21:33 +0900 Subject: [PATCH] feat: Add tuple support for key --- .github/workflows/ci.yml | 2 +- Cargo.toml | 2 +- phf_codegen/test/build.rs | 44 ++++++++++++++++ phf_codegen/test/src/lib.rs | 36 +++++++++++++ phf_macros/src/lib.rs | 25 +++++++++ .../Cargo.toml | 2 +- .../benches/bench.rs | 0 .../src/lib.rs | 0 .../compile-fail-uncased/equivalent-keys.rs | 0 .../equivalent-keys.stderr | 0 .../compile-fail-unicase/equivalent-keys.rs | 0 .../equivalent-keys.stderr | 0 .../tests/compile-fail/bad-syntax.rs | 0 .../tests/compile-fail/bad-syntax.stderr | 0 .../tests/compile-fail/mixed.rs | 0 .../tests/compile-fail/mixed.stderr | 10 ++-- .../tests/test.rs | 52 +++++++++++++++++++ .../tests/trybuild.rs | 0 phf_shared/src/lib.rs | 49 +++++++++++++++++ 19 files changed, 214 insertions(+), 8 deletions(-) rename {phf_macros_tests => phf_macros_test}/Cargo.toml (95%) rename {phf_macros_tests => phf_macros_test}/benches/bench.rs (100%) rename {phf_macros_tests => phf_macros_test}/src/lib.rs (100%) rename {phf_macros_tests => phf_macros_test}/tests/compile-fail-uncased/equivalent-keys.rs (100%) rename {phf_macros_tests => phf_macros_test}/tests/compile-fail-uncased/equivalent-keys.stderr (100%) rename {phf_macros_tests => phf_macros_test}/tests/compile-fail-unicase/equivalent-keys.rs (100%) rename {phf_macros_tests => phf_macros_test}/tests/compile-fail-unicase/equivalent-keys.stderr (100%) rename {phf_macros_tests => phf_macros_test}/tests/compile-fail/bad-syntax.rs (100%) rename {phf_macros_tests => phf_macros_test}/tests/compile-fail/bad-syntax.stderr (100%) rename {phf_macros_tests => phf_macros_test}/tests/compile-fail/mixed.rs (100%) rename {phf_macros_tests => phf_macros_test}/tests/compile-fail/mixed.stderr (77%) rename {phf_macros_tests => phf_macros_test}/tests/test.rs (92%) rename {phf_macros_tests => phf_macros_test}/tests/trybuild.rs (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ab8e650..969118ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: - name: trybuild test if: matrix.version == 'stable' - run: cargo test -p phf_macros_tests -- --ignored + run: cargo test -p phf_macros_test -- --ignored - name: no_std build check working-directory: phf diff --git a/Cargo.toml b/Cargo.toml index c5f53fb9..b9368289 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ "phf_codegen/test", "phf_generator", "phf_macros", - "phf_macros_tests", + "phf_macros_test", "phf_shared" ] diff --git a/phf_codegen/test/build.rs b/phf_codegen/test/build.rs index 6cd35853..8dc11f3c 100644 --- a/phf_codegen/test/build.rs +++ b/phf_codegen/test/build.rs @@ -130,5 +130,49 @@ fn main() -> io::Result<()> { .into_iter() .collect::>() .build() + )?; + + // Test tuple keys for Map + writeln!( + &mut file, + "static TUPLE_MAP: ::phf::Map<(u32, &'static str), &'static str> = \n{};", + phf_codegen::Map::new() + .entry((1u32, "a"), "\"first\"") + .entry((2u32, "b"), "\"second\"") + .entry((3u32, "c"), "\"third\"") + .build() + )?; + + // Test tuple keys for Set + writeln!( + &mut file, + "static TUPLE_SET: ::phf::Set<(u32, &'static str)> = \n{};", + phf_codegen::Set::new() + .entry((1u32, "x")) + .entry((2u32, "y")) + .entry((3u32, "z")) + .build() + )?; + + // Test nested tuple keys for Map + writeln!( + &mut file, + "static NESTED_TUPLE_MAP: ::phf::Map<((u32, u32), &'static str), u32> = \n{};", + phf_codegen::Map::new() + .entry(((1u32, 2u32), "nested"), "10") + .entry(((3u32, 4u32), "tuple"), "20") + .entry(((5u32, 6u32), "keys"), "30") + .build() + )?; + + // Test mixed type tuple keys + writeln!( + &mut file, + "static MIXED_TUPLE_MAP: ::phf::Map<(bool, u8, &'static str), &'static str> = \n{};", + phf_codegen::Map::new() + .entry((true, 1u8, "test"), "\"value1\"") + .entry((false, 2u8, "demo"), "\"value2\"") + .entry((true, 3u8, "example"), "\"value3\"") + .build() ) } diff --git a/phf_codegen/test/src/lib.rs b/phf_codegen/test/src/lib.rs index 9778307c..6a7ed179 100644 --- a/phf_codegen/test/src/lib.rs +++ b/phf_codegen/test/src/lib.rs @@ -125,4 +125,40 @@ mod test { assert_eq!(3, FROM_ITER_MAP["three"]); assert!(!FROM_ITER_MAP.contains_key("four")); } + + #[test] + fn tuple_map() { + assert_eq!("first", TUPLE_MAP[&(1u32, "a")]); + assert_eq!("second", TUPLE_MAP[&(2u32, "b")]); + assert_eq!("third", TUPLE_MAP[&(3u32, "c")]); + assert!(!TUPLE_MAP.contains_key(&(4u32, "d"))); + assert!(!TUPLE_MAP.contains_key(&(1u32, "b"))); + } + + #[test] + fn tuple_set() { + assert!(TUPLE_SET.contains(&(1u32, "x"))); + assert!(TUPLE_SET.contains(&(2u32, "y"))); + assert!(TUPLE_SET.contains(&(3u32, "z"))); + assert!(!TUPLE_SET.contains(&(4u32, "w"))); + assert!(!TUPLE_SET.contains(&(1u32, "y"))); + } + + #[test] + fn nested_tuple_map() { + assert_eq!(10, NESTED_TUPLE_MAP[&((1u32, 2u32), "nested")]); + assert_eq!(20, NESTED_TUPLE_MAP[&((3u32, 4u32), "tuple")]); + assert_eq!(30, NESTED_TUPLE_MAP[&((5u32, 6u32), "keys")]); + assert!(!NESTED_TUPLE_MAP.contains_key(&((7u32, 8u32), "missing"))); + assert!(!NESTED_TUPLE_MAP.contains_key(&((1u32, 2u32), "wrong"))); + } + + #[test] + fn mixed_tuple_map() { + assert_eq!("value1", MIXED_TUPLE_MAP[&(true, 1u8, "test")]); + assert_eq!("value2", MIXED_TUPLE_MAP[&(false, 2u8, "demo")]); + assert_eq!("value3", MIXED_TUPLE_MAP[&(true, 3u8, "example")]); + assert!(!MIXED_TUPLE_MAP.contains_key(&(false, 1u8, "test"))); + assert!(!MIXED_TUPLE_MAP.contains_key(&(true, 4u8, "missing"))); + } } diff --git a/phf_macros/src/lib.rs b/phf_macros/src/lib.rs index 871bda5d..e502a704 100644 --- a/phf_macros/src/lib.rs +++ b/phf_macros/src/lib.rs @@ -35,6 +35,7 @@ enum ParsedKey { U128(u128), Usize(usize), Bool(bool), + Tuple(Vec), #[cfg(feature = "unicase")] UniCase(UniCase), #[cfg(feature = "unicase")] @@ -65,6 +66,11 @@ impl PhfHash for ParsedKey { ParsedKey::U128(s) => s.phf_hash(state), ParsedKey::Usize(s) => s.phf_hash(state), ParsedKey::Bool(s) => s.phf_hash(state), + ParsedKey::Tuple(elements) => { + for element in elements { + element.phf_hash(state); + } + } #[cfg(feature = "unicase")] ParsedKey::UniCase(s) => s.phf_hash(state), #[cfg(feature = "unicase")] @@ -99,6 +105,14 @@ impl ParsedKey { "u64" => Some(ParsedKey::U64(s.base10_parse::().unwrap())), "u128" => Some(ParsedKey::U128(s.base10_parse::().unwrap())), "usize" => Some(ParsedKey::Usize(s.base10_parse::().unwrap())), + // Handle unsuffixed integer literals, default to i32 + "" => { + if let Ok(val) = s.base10_parse::() { + Some(ParsedKey::I32(val)) + } else { + None + } + } _ => None, }, Lit::Bool(s) => Some(ParsedKey::Bool(s.value)), @@ -161,6 +175,17 @@ impl ParsedKey { _ => None, } } + Expr::Tuple(tuple) => { + let mut elements = Vec::new(); + for elem in &tuple.elems { + if let Some(parsed_elem) = ParsedKey::from_expr(elem) { + elements.push(parsed_elem); + } else { + return None; + } + } + Some(ParsedKey::Tuple(elements)) + } Expr::Group(group) => ParsedKey::from_expr(&group.expr), Expr::Call(call) if call.args.len() == 1 => { let last; diff --git a/phf_macros_tests/Cargo.toml b/phf_macros_test/Cargo.toml similarity index 95% rename from phf_macros_tests/Cargo.toml rename to phf_macros_test/Cargo.toml index ee363c6d..d978a8ee 100644 --- a/phf_macros_tests/Cargo.toml +++ b/phf_macros_test/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "phf_macros_tests" +name = "phf_macros_test" version = "0.1.0" authors = ["Yuki Okushi"] edition = "2021" diff --git a/phf_macros_tests/benches/bench.rs b/phf_macros_test/benches/bench.rs similarity index 100% rename from phf_macros_tests/benches/bench.rs rename to phf_macros_test/benches/bench.rs diff --git a/phf_macros_tests/src/lib.rs b/phf_macros_test/src/lib.rs similarity index 100% rename from phf_macros_tests/src/lib.rs rename to phf_macros_test/src/lib.rs diff --git a/phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.rs b/phf_macros_test/tests/compile-fail-uncased/equivalent-keys.rs similarity index 100% rename from phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.rs rename to phf_macros_test/tests/compile-fail-uncased/equivalent-keys.rs diff --git a/phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.stderr b/phf_macros_test/tests/compile-fail-uncased/equivalent-keys.stderr similarity index 100% rename from phf_macros_tests/tests/compile-fail-uncased/equivalent-keys.stderr rename to phf_macros_test/tests/compile-fail-uncased/equivalent-keys.stderr diff --git a/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.rs b/phf_macros_test/tests/compile-fail-unicase/equivalent-keys.rs similarity index 100% rename from phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.rs rename to phf_macros_test/tests/compile-fail-unicase/equivalent-keys.rs diff --git a/phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.stderr b/phf_macros_test/tests/compile-fail-unicase/equivalent-keys.stderr similarity index 100% rename from phf_macros_tests/tests/compile-fail-unicase/equivalent-keys.stderr rename to phf_macros_test/tests/compile-fail-unicase/equivalent-keys.stderr diff --git a/phf_macros_tests/tests/compile-fail/bad-syntax.rs b/phf_macros_test/tests/compile-fail/bad-syntax.rs similarity index 100% rename from phf_macros_tests/tests/compile-fail/bad-syntax.rs rename to phf_macros_test/tests/compile-fail/bad-syntax.rs diff --git a/phf_macros_tests/tests/compile-fail/bad-syntax.stderr b/phf_macros_test/tests/compile-fail/bad-syntax.stderr similarity index 100% rename from phf_macros_tests/tests/compile-fail/bad-syntax.stderr rename to phf_macros_test/tests/compile-fail/bad-syntax.stderr diff --git a/phf_macros_tests/tests/compile-fail/mixed.rs b/phf_macros_test/tests/compile-fail/mixed.rs similarity index 100% rename from phf_macros_tests/tests/compile-fail/mixed.rs rename to phf_macros_test/tests/compile-fail/mixed.rs diff --git a/phf_macros_tests/tests/compile-fail/mixed.stderr b/phf_macros_test/tests/compile-fail/mixed.stderr similarity index 77% rename from phf_macros_tests/tests/compile-fail/mixed.stderr rename to phf_macros_test/tests/compile-fail/mixed.stderr index 9943b2b0..dd1246c7 100644 --- a/phf_macros_tests/tests/compile-fail/mixed.stderr +++ b/phf_macros_test/tests/compile-fail/mixed.stderr @@ -23,11 +23,11 @@ error[E0277]: the trait bound `UniCase<&str>: phf_shared::PhfBorrow<_>` is not s `&[u8; N]` implements `phf_shared::PhfBorrow<[u8; N]>` `&[u8]` implements `phf_shared::PhfBorrow<[u8]>` `&str` implements `phf_shared::PhfBorrow` - `String` implements `phf_shared::PhfBorrow` - `Vec` implements `phf_shared::PhfBorrow<[u8]>` - `[bool; N]` implements `phf_shared::PhfBorrow<[bool]>` - `[char; N]` implements `phf_shared::PhfBorrow<[char]>` - `[i128; N]` implements `phf_shared::PhfBorrow<[i128]>` + `(A, B)` implements `phf_shared::PhfBorrow<(A, B)>` + `(A, B, C)` implements `phf_shared::PhfBorrow<(A, B, C)>` + `(A, B, C, D)` implements `phf_shared::PhfBorrow<(A, B, C, D)>` + `(A, B, C, D, E)` implements `phf_shared::PhfBorrow<(A, B, C, D, E)>` + `(A, B, C, D, E, F)` implements `phf_shared::PhfBorrow<(A, B, C, D, E, F)>` and $N others note: required by a bound in `phf::Map::::get` --> $WORKSPACE/phf/src/map.rs diff --git a/phf_macros_tests/tests/test.rs b/phf_macros_test/tests/test.rs similarity index 92% rename from phf_macros_tests/tests/test.rs rename to phf_macros_test/tests/test.rs index ee33cb54..f775cc5f 100644 --- a/phf_macros_tests/tests/test.rs +++ b/phf_macros_test/tests/test.rs @@ -329,6 +329,19 @@ mod map { #[cfg(not(feature = "enabled_feature"))] assert_eq!(None, MY_MAP.get("baz")); } + + #[test] + fn test_tuples() { + static MAP: phf::Map<(u32, &str), u32> = phf_map! { + (0, "a") => 1, + (1, "b") => 2, + (2, "c") => 3, + }; + assert_eq!(Some(&1), MAP.get(&(0, "a"))); + assert_eq!(Some(&2), MAP.get(&(1, "b"))); + assert_eq!(Some(&3), MAP.get(&(2, "c"))); + assert_eq!(None, MAP.get(&(3, "d"))); + } } mod set { @@ -408,6 +421,19 @@ mod set { #[cfg(not(feature = "enabled_feature"))] assert!(!SET.contains("baz")); } + + #[test] + fn test_tuples() { + static SET: phf::Set<(u32, &str)> = phf_set! { + (0, "a"), + (1, "b"), + (2, "c"), + }; + assert!(SET.contains(&(0, "a"))); + assert!(SET.contains(&(1, "b"))); + assert!(SET.contains(&(2, "c"))); + assert!(!SET.contains(&(3, "d"))); + } } mod ordered_map { @@ -551,6 +577,19 @@ mod ordered_map { #[cfg(not(feature = "enabled_feature"))] assert_eq!(None, MY_MAP.get("baz")); } + + #[test] + fn test_tuples() { + static MAP: phf::OrderedMap<(u32, &str), u32> = phf_ordered_map! { + (0, "a") => 1, + (1, "b") => 2, + (2, "c") => 3, + }; + assert_eq!(Some(&1), MAP.get(&(0, "a"))); + assert_eq!(Some(&2), MAP.get(&(1, "b"))); + assert_eq!(Some(&3), MAP.get(&(2, "c"))); + assert_eq!(None, MAP.get(&(3, "d"))); + } } mod ordered_set { @@ -652,4 +691,17 @@ mod ordered_set { #[cfg(not(feature = "enabled_feature"))] assert!(!SET.contains("baz")); } + + #[test] + fn test_tuples() { + static SET: phf::OrderedSet<(u32, &str)> = phf_ordered_set! { + (0, "a"), + (1, "b"), + (2, "c"), + }; + assert!(SET.contains(&(0, "a"))); + assert!(SET.contains(&(1, "b"))); + assert!(SET.contains(&(2, "c"))); + assert!(!SET.contains(&(3, "d"))); + } } diff --git a/phf_macros_tests/tests/trybuild.rs b/phf_macros_test/tests/trybuild.rs similarity index 100% rename from phf_macros_tests/tests/trybuild.rs rename to phf_macros_test/tests/trybuild.rs diff --git a/phf_shared/src/lib.rs b/phf_shared/src/lib.rs index 04953444..b7b9cba3 100644 --- a/phf_shared/src/lib.rs +++ b/phf_shared/src/lib.rs @@ -473,3 +473,52 @@ slice_impl!(u128); slice_impl!(i128); slice_impl!(bool); slice_impl!(char); + +macro_rules! tuple_impl { + ($($t:ident),+) => { + impl<$($t: PhfHash),+> PhfHash for ($($t,)+) { + fn phf_hash(&self, state: &mut HS) { + #[allow(non_snake_case)] + let ($($t,)+) = self; + $( + $t.phf_hash(state); + )+ + } + } + + impl<$($t: PhfHash),+> PhfBorrow<($($t,)+)> for ($($t,)+) { + fn borrow(&self) -> &($($t,)+) { + self + } + } + + impl<$($t: FmtConst),+> FmtConst for ($($t,)+) { + fn fmt_const(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[allow(non_snake_case)] + let ($($t,)+) = self; + write!(f, "(")?; + let mut first = true; + $( + if !core::mem::replace(&mut first, false) { + write!(f, ", ")?; + } + $t.fmt_const(f)?; + )+ + write!(f, ")") + } + } + }; +} + +tuple_impl!(A); +tuple_impl!(A, B); +tuple_impl!(A, B, C); +tuple_impl!(A, B, C, D); +tuple_impl!(A, B, C, D, E); +tuple_impl!(A, B, C, D, E, F); +tuple_impl!(A, B, C, D, E, F, G); +tuple_impl!(A, B, C, D, E, F, G, HT); +tuple_impl!(A, B, C, D, E, F, G, HT, I); +tuple_impl!(A, B, C, D, E, F, G, HT, I, J); +tuple_impl!(A, B, C, D, E, F, G, HT, I, J, K); +tuple_impl!(A, B, C, D, E, F, G, HT, I, J, K, L);