-
Notifications
You must be signed in to change notification settings - Fork 266
Add FixedLengthVarULE and user in icu_plurals #7394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0a337f7
2df46a9
fd0f17a
415999f
8dd4e32
7a25487
031ad7f
3af9c1b
789e7ce
576d7c3
2ce55ac
56f5e42
1143c4c
87c2ad7
2120095
76d4930
8723e53
44a1ecb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,11 +27,11 @@ use yoke::Yokeable; | |
| use zerofrom::ZeroFrom; | ||
| use zerovec::ule::vartuple::VarTuple; | ||
| use zerovec::ule::vartuple::VarTupleULE; | ||
| use zerovec::ule::AsULE; | ||
| use zerovec::ule::EncodeAsVarULE; | ||
| use zerovec::ule::UleError; | ||
| use zerovec::ule::VarULE; | ||
| use zerovec::ule::ULE; | ||
| use zerovec::ule::{AsULE, SizedVarULEBytes}; | ||
| use zerovec::VarZeroSlice; | ||
|
|
||
| pub mod rules; | ||
|
|
@@ -482,6 +482,94 @@ where | |
| core::mem::transmute(bytes) | ||
| } | ||
|
|
||
| /// Creates a [`PluralElementsPackedULE`] with an "other" variant in a const context. | ||
| /// | ||
| /// Const parameters: | ||
| /// | ||
| /// - `M`: the length of `input` | ||
| /// - `N`: the length of the return value which is `M + 1` | ||
| /// | ||
| /// When [generic_const_exprs] is stabilized, we will be able to add a new | ||
| /// function signature without both const parameters. | ||
| /// | ||
| /// # Panics | ||
| /// | ||
| /// Panics if N != M + 1. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// use icu::plurals::provider::PluralElementsPackedULE; | ||
| /// use icu::plurals::provider::FourBitMetadata; | ||
| /// use icu::plurals::PluralRules; | ||
| /// use icu::locale::locale; | ||
| /// use zerovec::ule::SizedVarULEBytes; | ||
| /// | ||
| /// let value = "hello, world!"; // 13 bytes long | ||
| /// let metadata = FourBitMetadata::try_from_byte(11).unwrap(); | ||
| /// let inner_ule = SizedVarULEBytes::<13, str>::try_from_encodeable(value).unwrap(); | ||
| /// let plural_ule = PluralElementsPackedULE::new_mn::<_, 14>(metadata, inner_ule); | ||
| /// let rules = PluralRules::try_new(locale!("en").into(), Default::default()).unwrap(); | ||
| /// | ||
| /// assert_eq!(plural_ule.as_varule().get(0.into(), &rules), (metadata, "hello, world!")); | ||
| /// assert_eq!(plural_ule.as_varule().get(1.into(), &rules), (metadata, "hello, world!")); | ||
| /// assert_eq!(plural_ule.as_varule().get(2.into(), &rules), (metadata, "hello, world!")); | ||
| /// ``` | ||
| /// | ||
| /// In a const context: | ||
| /// | ||
| /// ``` | ||
| /// use icu::plurals::provider::PluralElementsPackedULE; | ||
| /// use icu::plurals::provider::FourBitMetadata; | ||
| /// use icu::plurals::PluralRules; | ||
| /// use icu::locale::locale; | ||
| /// use zerovec::ule::SizedVarULEBytes; | ||
| /// | ||
| /// const metadata: FourBitMetadata = FourBitMetadata::zero(); | ||
| /// let plural_ule = const { | ||
| /// PluralElementsPackedULE::new_mn::<_, 1>(metadata, SizedVarULEBytes::EMPTY_STR) | ||
| /// }; | ||
| /// | ||
| /// let rules = PluralRules::try_new(locale!("en").into(), Default::default()).unwrap(); | ||
| /// | ||
| /// assert_eq!(plural_ule.as_varule().get(0.into(), &rules), (metadata, "")); | ||
| /// assert_eq!(plural_ule.as_varule().get(1.into(), &rules), (metadata, "")); | ||
| /// assert_eq!(plural_ule.as_varule().get(2.into(), &rules), (metadata, "")); | ||
| /// ``` | ||
| /// | ||
| /// [generic_const_exprs]: https://doc.rust-lang.org/beta/unstable-book/language-features/generic-const-exprs.html#generic_const_exprs | ||
| pub const fn new_mn<const M: usize, const N: usize>( | ||
| metadata: FourBitMetadata, | ||
| input: SizedVarULEBytes<M, V>, | ||
| ) -> SizedVarULEBytes<N, PluralElementsPackedULE<V>> { | ||
| #[allow(clippy::panic)] // for safety, and documented | ||
| if N != M + 1 { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: this should be a const assertion, not a runtime panic
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please suggest code that compiles with a const assertion.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you can't do it then we shouldn't have this yet
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm? We have lots of const code with runtime assertions because there isn't a good way to write const assertions.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even the Rust standard library does runtime panics on const parameters. For example: https://doc.rust-lang.org/std/primitive.slice.html#method.as_chunks
|
||
| panic!(concat!( | ||
| "new_mn: N (", | ||
| stringify!(N), | ||
| ") != 1 + M (", | ||
| stringify!(M), | ||
| ")" | ||
| )); | ||
| } | ||
| let mut bytes = [0u8; N]; | ||
| #[allow(clippy::unwrap_used)] // the bytes are nonempty because N > 0 | ||
| let (start, remainder) = bytes.split_first_mut().unwrap(); | ||
| // TODO(1.87): use copy_from_slice | ||
| let mut i = 0; | ||
| #[allow(clippy::indexing_slicing)] // both remainder and input are length M | ||
| while i < M { | ||
| remainder[i] = input.as_bytes()[i]; | ||
| i += 1; | ||
| } | ||
| // First byte = 0...mmmm for a singleton | ||
| *start = metadata.get(); | ||
| // Safety: bytes are a valid representation of this type: | ||
| // 1. The first bit is 0 which indicates a singleton | ||
| // 2. The remainder is a valid V by invariant of the input parameter | ||
| unsafe { SizedVarULEBytes::new_unchecked(bytes) } | ||
| } | ||
|
|
||
| /// Returns a tuple with: | ||
| /// 1. The lead byte | ||
| /// 2. Bytes corresponding to the default V | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,181 @@ | ||||||
| // 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 crate::ule::{EncodeAsVarULE, UleError, VarULE, ULE}; | ||||||
| use core::fmt; | ||||||
| use core::marker::PhantomData; | ||||||
| use core::ops::Deref; | ||||||
|
|
||||||
| /// A container for a [`VarULE`] with a fixed byte length. | ||||||
| /// | ||||||
| /// This container may be useful if the length of your VarULE is known at compile-time. | ||||||
| /// | ||||||
| /// To construct one of these in a const context, consider [`to_sized_varule_bytes!`]. | ||||||
| /// | ||||||
| /// # Examples | ||||||
| /// | ||||||
| /// ``` | ||||||
| /// use zerovec::ule::SizedVarULEBytes; | ||||||
| /// use zerovec::ule::to_sized_varule_bytes; | ||||||
| /// | ||||||
| /// let from_constructor = SizedVarULEBytes::<13, str>::from_varule("hello, world!").unwrap(); | ||||||
| /// let from_macro = to_sized_varule_bytes!("hello, world!"); | ||||||
| /// | ||||||
| /// assert_eq!(&*from_constructor, "hello, world!"); | ||||||
| /// assert_eq!(&*from_macro, "hello, world!"); | ||||||
| /// ``` | ||||||
| #[derive(Copy, Clone, PartialEq, Eq)] | ||||||
| pub struct SizedVarULEBytes<const N: usize, V: VarULE + ?Sized> { | ||||||
| /// Invariant: The bytes MUST be a valid VarULE representation of `V`. | ||||||
| bytes: [u8; N], | ||||||
| _marker: PhantomData<V>, | ||||||
| } | ||||||
|
|
||||||
| impl<const N: usize, V: VarULE + ?Sized> SizedVarULEBytes<N, V> { | ||||||
| /// Creates one of these from an [`EncodeAsVarULE`]. | ||||||
| /// | ||||||
| /// Returns an error if the byte length in the container is not the correct length | ||||||
| /// for the encodeable object. | ||||||
| /// | ||||||
| /// # Examples | ||||||
| /// | ||||||
| /// ``` | ||||||
| /// use zerovec::ule::SizedVarULEBytes; | ||||||
| /// | ||||||
| /// let container = SizedVarULEBytes::<13, str>::try_from_encodeable("hello, world!").unwrap(); | ||||||
| /// | ||||||
| /// assert_eq!(&*container, "hello, world!"); | ||||||
| /// | ||||||
| /// // Returns an error if the container is not the correct size: | ||||||
| /// SizedVarULEBytes::<20, str>::try_from_encodeable("hello, world!").unwrap_err(); | ||||||
| /// ``` | ||||||
| pub fn try_from_encodeable(input: impl EncodeAsVarULE<V>) -> Result<Self, UleError> { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure the recommend style here. It doesn't matter if the trait is implemented on a reference. @Manishearth ?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is style choice. you only test this with a |
||||||
| let len = input.encode_var_ule_len(); | ||||||
| if len != N { | ||||||
| return Err(UleError::length::<V>(len)); | ||||||
| } | ||||||
| let mut bytes = [0u8; N]; | ||||||
| input.encode_var_ule_write(&mut bytes); | ||||||
| // Safety: the bytes were just written from an EncodeAsVarULE impl | ||||||
| unsafe { Ok(Self::new_unchecked(bytes)) } | ||||||
| } | ||||||
|
|
||||||
| /// Creates one of these from a [`VarULE`]. | ||||||
| /// | ||||||
| /// Returns an error if the byte length in the container is not the correct length | ||||||
| /// for the encodeable object. | ||||||
| pub fn from_varule(input: &V) -> Result<Self, UleError> { | ||||||
| let src = input.as_bytes(); | ||||||
| let len = src.len(); | ||||||
| if len != N { | ||||||
| return Err(UleError::length::<V>(len)); | ||||||
| } | ||||||
| let mut bytes = [0u8; N]; | ||||||
| bytes.copy_from_slice(src); | ||||||
| // Safety: the bytes were just copied from V | ||||||
| unsafe { Ok(Self::new_unchecked(bytes)) } | ||||||
| } | ||||||
|
|
||||||
| /// Creates one of these directly from bytes. | ||||||
| /// | ||||||
| /// # Safety | ||||||
| /// | ||||||
| /// The bytes MUST be a valid VarULE representation of `V`. | ||||||
| pub const unsafe fn new_unchecked(bytes: [u8; N]) -> Self { | ||||||
| Self { | ||||||
| bytes, | ||||||
| _marker: PhantomData, | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| #[doc(hidden)] // macro constructor | ||||||
| pub const unsafe fn new_unchecked_with_type_hint(bytes: [u8; N], _hint: &V) -> Self { | ||||||
| Self::new_unchecked(bytes) | ||||||
| } | ||||||
|
|
||||||
| /// Returns the bytes backing this [`SizedVarULEBytes`], which are | ||||||
| /// guaranteed to be a valid VarULE representation of `V`. | ||||||
| pub const fn as_bytes(&self) -> &[u8; N] { | ||||||
| &self.bytes | ||||||
| } | ||||||
|
|
||||||
| /// Returns the container as an instance of `V`. | ||||||
| pub fn as_varule(&self) -> &V { | ||||||
| debug_assert!(V::validate_bytes(&self.bytes).is_ok()); | ||||||
| // Safety: self.bytes are a valid VarULE representation of `V`. | ||||||
| unsafe { V::from_bytes_unchecked(&self.bytes) } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| impl<const N: usize, V: VarULE + ?Sized> fmt::Debug for SizedVarULEBytes<N, V> | ||||||
| where | ||||||
| V: fmt::Debug, | ||||||
| { | ||||||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
| self.as_varule().fmt(f) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| impl<const N: usize, V: VarULE + ?Sized> AsRef<V> for SizedVarULEBytes<N, V> { | ||||||
| fn as_ref(&self) -> &V { | ||||||
| self.as_varule() | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| impl<const N: usize, V: VarULE + ?Sized> Deref for SizedVarULEBytes<N, V> { | ||||||
| type Target = V; | ||||||
| fn deref(&self) -> &Self::Target { | ||||||
| self.as_varule() | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| impl SizedVarULEBytes<0, str> { | ||||||
| /// The empty string as a [`SizedVarULEBytes`]. | ||||||
| // Safety: the empty slice is a valid str | ||||||
| pub const EMPTY_STR: Self = unsafe { Self::new_unchecked([]) }; | ||||||
| } | ||||||
|
|
||||||
| impl<T: ULE> SizedVarULEBytes<0, [T]> { | ||||||
| /// The empty slice as a [`SizedVarULEBytes`]. | ||||||
| // Safety: the empty slice is a valid str | ||||||
| pub const EMPTY_SLICE: Self = unsafe { Self::new_unchecked([]) }; | ||||||
| } | ||||||
|
|
||||||
| /// Takes a const expression resolving to a [`VarULE`] and returns one | ||||||
| /// resolving to an appropriately sized [`SizedVarULEBytes`]. | ||||||
| /// | ||||||
| /// The expression is inserted twice into code, once for evaluation and once | ||||||
| /// for the type hint only. If this is a problem, save the expression into a | ||||||
| /// const variable first. | ||||||
| /// | ||||||
| /// # Examples | ||||||
| /// | ||||||
| /// ``` | ||||||
| /// use zerovec::ule::SizedVarULEBytes; | ||||||
| /// use zerovec::ule::to_sized_varule_bytes; | ||||||
| /// | ||||||
| /// let stack_str = const { to_sized_varule_bytes!("hello, world!") }; | ||||||
| /// assert_eq!(&*stack_str, "hello, world!"); | ||||||
| /// ``` | ||||||
| #[macro_export] | ||||||
| #[doc(hidden)] // macro | ||||||
| macro_rules! __to_sized_varule_bytes { | ||||||
| ($expr:expr) => {{ | ||||||
| const SRC: &[u8] = { $expr }.as_bytes(); | ||||||
| const N: usize = SRC.len(); | ||||||
| let mut bytes: [u8; N] = [0; N]; | ||||||
| // TODO(1.87): use copy_from_slice | ||||||
| let mut i = 0; | ||||||
| #[allow(clippy::indexing_slicing)] // both bytes and SRC are length N | ||||||
| while i < N { | ||||||
| bytes[i] = SRC[i]; | ||||||
| i += 1; | ||||||
| } | ||||||
| // Safety: `bytes` is a valid representation of input by the VarULE | ||||||
| // trait bound on SizedVarULEBytes below | ||||||
| unsafe { SizedVarULEBytes::new_unchecked_with_type_hint(bytes, { $expr }) } | ||||||
| }}; | ||||||
| } | ||||||
| #[doc(inline)] | ||||||
| pub use __to_sized_varule_bytes as to_sized_varule_bytes; | ||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: this isn't a motivating example: this doesn't work in
constanyway (since there'stry_from_encodeable)(It's an example, but not a motivating one)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would work in const if I replaced
try_from_encodeablewithnew_unchecked, I just wanted to not have unsafe code in the docs testThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's fine, I was just saying that the example itself wasn't motivating the new zerovec API.
But I have a clearer idea of what's going on now.