diff --git a/Cargo.lock b/Cargo.lock index 8d17f45cc4c..05fbf8a3445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1602,8 +1602,12 @@ version = "1.4.0" name = "icu_pattern" version = "0.1.5" dependencies = [ + "databake", "displaydoc", "writeable", + "yoke", + "zerofrom", + "zerovec", ] [[package]] diff --git a/utils/pattern/Cargo.toml b/utils/pattern/Cargo.toml index 424a4d37e13..463da670918 100644 --- a/utils/pattern/Cargo.toml +++ b/utils/pattern/Cargo.toml @@ -23,7 +23,17 @@ all-features = true [dependencies] displaydoc = { version = "0.2.3", default-features = false } writeable = { workspace = true } +databake = { workspace = true, features = ["derive"], optional = true } +yoke = { workspace = true, features = ["derive"], optional = true } +zerofrom = { workspace = true, features = ["derive"], optional = true } + +[dev-dependencies] +zerofrom = { workspace = true, features = ["alloc"] } +zerovec = { workspace = true, features = ["databake", "serde"] } [features] alloc = [] std = ["alloc"] +databake = ["dep:databake"] +yoke = ["dep:yoke"] +zerofrom = ["dep:zerofrom"] diff --git a/utils/pattern/src/common.rs b/utils/pattern/src/common.rs index 86e43d59221..939717e2805 100644 --- a/utils/pattern/src/common.rs +++ b/utils/pattern/src/common.rs @@ -32,6 +32,7 @@ pub enum PatternItem<'a, T> { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[allow(clippy::exhaustive_enums)] // Part of core data model #[cfg(feature = "alloc")] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub enum PatternItemCow<'a, T> { /// A placeholder of the type specified on this [`PatternItemCow`]. Placeholder(T), @@ -40,6 +41,7 @@ pub enum PatternItemCow<'a, T> { /// 1. Between the start of the string and the first placeholder (prefix) /// 2. Between two placeholders (infix) /// 3. Between the final placeholder and the end of the string (suffix) + #[cfg_attr(feature = "serde", serde(borrow))] Literal(Cow<'a, str>), } diff --git a/utils/pattern/src/frontend/databake.rs b/utils/pattern/src/frontend/databake.rs new file mode 100644 index 00000000000..13591fcee15 --- /dev/null +++ b/utils/pattern/src/frontend/databake.rs @@ -0,0 +1,47 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use core::any::TypeId; + +use crate::SinglePlaceholder; + +use super::*; +use ::databake::{quote, Bake, CrateEnv, TokenStream}; + +impl Bake for Pattern +where + B: 'static, + Store: Bake, +{ + fn bake(&self, ctx: &CrateEnv) -> TokenStream { + ctx.insert("icu_pattern"); + let store = self.store.bake(ctx); + let b = if TypeId::of::() == TypeId::of::() { + quote!(icu_pattern::SinglePlaceholder) + } else { + unreachable!("all impls of sealed trait PatternBackend should be covered") + }; + quote! { + icu_pattern::Pattern::<#b, _>::from_store_unchecked(#store) + } + } +} + +#[test] +/* +Test currently ignored because test_bake is broken: + + left: "icu_pattern :: Pattern :: < icu_pattern :: SinglePlaceholder , _ > :: from_store_unchecked (alloc :: borrow :: Cow :: Borrowed (\"\"))" + right: "icu_pattern :: Pattern ::< icu_pattern :: SinglePlaceholder , _ >:: from_store_unchecked (alloc :: borrow :: Cow :: Borrowed (\"\"))" +*/ +#[ignore] +fn test_baked() { + use ::databake::test_bake; + use alloc::borrow::Cow; + test_bake!( + Pattern>, + const: crate::Pattern::::from_store_unchecked(alloc::borrow::Cow::Borrowed("")), + icu_pattern + ); +} diff --git a/utils/pattern/src/frontend.rs b/utils/pattern/src/frontend/mod.rs similarity index 86% rename from utils/pattern/src/frontend.rs rename to utils/pattern/src/frontend/mod.rs index 8bd2b27b9e4..acca9b3866a 100644 --- a/utils/pattern/src/frontend.rs +++ b/utils/pattern/src/frontend/mod.rs @@ -2,6 +2,9 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). +#[cfg(feature = "databake")] +mod databake; + use core::{ fmt::{self, Write}, marker::PhantomData, @@ -41,7 +44,13 @@ use alloc::{borrow::ToOwned, str::FromStr, string::String}; /// - `Cow` for an owned-or-borrowed pattern /// /// [`SinglePlaceholder`]: crate::SinglePlaceholder -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))] +#[cfg_attr( + feature = "zerofrom", + derive(zerofrom::ZeroFrom), + zerofrom(may_borrow(Store)) +)] pub struct Pattern { _backend: PhantomData, store: Store, @@ -51,6 +60,42 @@ impl Pattern { pub fn take_store(self) -> Store { self.store } + + /// Creates a pattern from a serialized backing store without checking invariants. + /// Most users should prefer [`Pattern::try_from_store()`]. + /// + /// The store is expected to come from a valid `Pattern` with this `Backend`, + /// such as by calling [`Pattern::take_store()`]. If the store is not valid, + /// unexpected behavior may occur. + /// + /// To parse a pattern string, use [`Self::try_from_str()`]. + /// + /// # Examples + /// + /// ``` + /// use icu_pattern::Pattern; + /// use icu_pattern::SinglePlaceholder; + /// use writeable::assert_writeable_eq; + /// + /// // Create a pattern from a valid string: + /// let allocated_pattern: Pattern = + /// Pattern::try_from_str("{0} days").expect("valid pattern"); + /// + /// // Transform the store and create a new Pattern. This is valid because + /// // we call `.take_store()` and `.from_store_unchecked()` on patterns + /// // with the same backend (`SinglePlaceholder`). + /// let store = allocated_pattern.take_store(); + /// let borrowed_pattern: Pattern = + /// Pattern::from_store_unchecked(&store); + /// + /// assert_writeable_eq!(borrowed_pattern.interpolate([5]), "5 days"); + /// ``` + pub const fn from_store_unchecked(store: Store) -> Self { + Self { + _backend: PhantomData, + store, + } + } } impl Pattern diff --git a/utils/pattern/src/lib.rs b/utils/pattern/src/lib.rs index 53617838484..0d218f31125 100644 --- a/utils/pattern/src/lib.rs +++ b/utils/pattern/src/lib.rs @@ -96,3 +96,13 @@ mod private { /// assert_writeable_eq!(pattern.interpolate(["Alice"]), "Hello, Alice!"); /// ``` pub type SinglePlaceholderPattern = Pattern; + +#[test] +#[cfg(feature = "alloc")] +fn test_single_placeholder_pattern_impls() { + let a = SinglePlaceholderPattern::try_from_str("{0}").unwrap(); + let b = SinglePlaceholderPattern::try_from_str("{0}").unwrap(); + assert_eq!(a, b); + let c = b.clone(); + assert_eq!(a, c); +} diff --git a/utils/pattern/src/single.rs b/utils/pattern/src/single.rs index 43406ac6f9f..0dd57565101 100644 --- a/utils/pattern/src/single.rs +++ b/utils/pattern/src/single.rs @@ -145,7 +145,7 @@ where /// ``` /// /// [`Pattern::interpolate()`]: crate::Pattern::interpolate -#[derive(Debug)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[allow(clippy::exhaustive_enums)] // Empty Enum pub enum SinglePlaceholder {} diff --git a/utils/pattern/tests/derive_test.rs b/utils/pattern/tests/derive_test.rs new file mode 100644 index 00000000000..f5bc6391fcb --- /dev/null +++ b/utils/pattern/tests/derive_test.rs @@ -0,0 +1,41 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +#![allow(non_camel_case_types, non_snake_case)] + +extern crate alloc; + +use alloc::borrow::Cow; +use icu_pattern::{Pattern, SinglePlaceholder}; + +#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))] +#[cfg_attr(feature = "zerofrom", derive(zerofrom::ZeroFrom))] +// #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = crate))] +struct DeriveTest_SinglePlaceholderPattern_ZeroVec<'data> { + // #[cfg_attr(feature = "serde", serde(borrow))] + _data: Pattern>, +} + +#[test] +/* +Test currently ignored because test_bake is broken: + + left: "crate :: DeriveTest_SinglePlaceholderPattern_ZeroVec { _data : icu_pattern :: Pattern :: < icu_pattern :: SinglePlaceholder , _ > :: from_store_unchecked (alloc :: borrow :: Cow :: Borrowed (\"\")) , }" + right: "crate :: DeriveTest_SinglePlaceholderPattern_ZeroVec { _data : icu_pattern :: Pattern ::< icu_pattern :: SinglePlaceholder , _ >:: from_store_unchecked (alloc :: borrow :: Cow :: Borrowed (\"\")) , }" +*/ +#[ignore] +#[cfg(all(feature = "databake", feature = "alloc"))] +fn bake_SinglePlaceholderPattern_ZeroVec() { + use databake::*; + extern crate std; + test_bake!( + DeriveTest_SinglePlaceholderPattern_ZeroVec<'static>, + crate::DeriveTest_SinglePlaceholderPattern_ZeroVec { + _data: icu_pattern::Pattern::::from_store_unchecked( + alloc::borrow::Cow::Borrowed(""), + ) + }, + ); +}