Skip to content

Commit

Permalink
Add RcBuf variant to DataPayload (#816)
Browse files Browse the repository at this point in the history
- Adds yoke::trait_hack and other Yoke improvements
  • Loading branch information
sffc authored Jun 23, 2021
1 parent 80c7066 commit 2b7ca3b
Show file tree
Hide file tree
Showing 13 changed files with 664 additions and 139 deletions.
6 changes: 3 additions & 3 deletions experimental/provider_static/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ where
}
}

impl<'de> SerdeDeDataProvider<'de> for StaticDataProvider {
impl SerdeDeDataProvider for StaticDataProvider {
fn load_to_receiver(
&self,
req: &DataRequest,
receiver: &mut dyn SerdeDeDataReceiver<'de>,
receiver: &mut dyn SerdeDeDataReceiver,
) -> Result<DataResponseMetadata, DataError> {
let file = self.get_file(req)?;
receiver.receive_deserializer(&mut erased_serde::Deserializer::erase(
receiver.receive_static(&mut erased_serde::Deserializer::erase(
&mut serde_json::Deserializer::from_reader(file.as_bytes()),
))?;

Expand Down
6 changes: 3 additions & 3 deletions ffi/capi/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl ICU4XDataProvider {
}

/// Construct a [`ICU4XDataProvider`] this from a boxed [`SerdeDeDataProvider`]
pub fn from_boxed(x: Box<dyn SerdeDeDataProvider<'static>>) -> Self {
pub fn from_boxed(x: Box<dyn SerdeDeDataProvider>) -> Self {
unsafe {
// If the layout changes this will error
// Once Rust gets pointer metadata APIs we should switch to using those
Expand All @@ -51,15 +51,15 @@ impl ICU4XDataProvider {
}

/// Obtain the original boxed Rust [`SerdeDeDataProvider`] for this
pub fn into_boxed(self) -> Box<dyn SerdeDeDataProvider<'static>> {
pub fn into_boxed(self) -> Box<dyn SerdeDeDataProvider> {
debug_assert!(self._field1 != 0);
// If the layout changes this will error
// Once Rust gets pointer metadata APIs we should switch to using those
unsafe { mem::transmute(self) }
}

/// Convert a borrowed reference to a borrowed [`SerdeDeDataProvider`]
pub fn as_dyn_ref(&self) -> &dyn SerdeDeDataProvider<'static> {
pub fn as_dyn_ref(&self) -> &dyn SerdeDeDataProvider {
debug_assert!(self._field1 != 0);
unsafe {
// &dyn Trait and Box<dyn Trait> have the same layout
Expand Down
66 changes: 66 additions & 0 deletions provider/core/src/data_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ where
Borrowed(Yoke<M::Yokeable, &'d M::Cart>),
RcStruct(Yoke<M::Yokeable, Rc<M::Cart>>),
Owned(Yoke<M::Yokeable, ()>),
RcBuf(Yoke<M::Yokeable, Rc<[u8]>>),
}

/// A wrapper around the payload returned in a [`DataResponse`].
Expand Down Expand Up @@ -149,6 +150,7 @@ where
Borrowed(yoke) => Borrowed(yoke.clone()),
RcStruct(yoke) => RcStruct(yoke.clone()),
Owned(yoke) => Owned(yoke.clone()),
RcBuf(yoke) => RcBuf(yoke.clone()),
};
Self { inner: new_inner }
}
Expand Down Expand Up @@ -219,6 +221,68 @@ where
}
}

impl<'d, 's, M> DataPayload<'d, 's, M>
where
M: DataMarker<'s>,
{
/// Convert a byte buffer into a [`DataPayload`]. A function must be provided to perform the
/// conversion. This can often be a Serde deserialization operation.
///
/// Due to [compiler bug #84937](https://github.com/rust-lang/rust/issues/84937), call sites
/// for this function may not compile; if this happens, use
/// [`try_from_rc_buffer_badly()`](Self::try_from_rc_buffer_badly) instead.
#[inline]
pub fn try_from_rc_buffer<E>(
rc_buffer: Rc<[u8]>,
f: impl for<'de> FnOnce(&'de [u8]) -> Result<<M::Yokeable as Yokeable<'de>>::Output, E>,
) -> Result<Self, E> {
let yoke = Yoke::try_attach_to_cart(rc_buffer, f)?;
Ok(Self {
inner: DataPayloadInner::RcBuf(yoke),
})
}

/// Convert a byte buffer into a [`DataPayload`]. A function must be provided to perform the
/// conversion. This can often be a Serde deserialization operation.
///
/// For a version of this function that takes a `FnOnce` instead of a raw function pointer,
/// see [`try_from_rc_buffer()`](Self::try_from_rc_buffer).
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "provider_serde")] {
/// use icu_provider::prelude::*;
/// use icu_provider::hello_world::*;
/// use std::rc::Rc;
/// use icu_provider::yoke::Yokeable;
///
/// let json_text = "{\"message\":\"Hello World\"}";
/// let json_rc_buffer: Rc<[u8]> = json_text.as_bytes().into();
///
/// let payload = DataPayload::<HelloWorldV1Marker>::try_from_rc_buffer_badly(
/// json_rc_buffer.clone(),
/// |bytes| {
/// serde_json::from_slice(bytes)
/// }
/// )
/// .expect("JSON is valid");
///
/// assert_eq!("Hello World", payload.get().message);
/// # } // feature = "provider_serde"
/// ```
#[allow(clippy::type_complexity)]
pub fn try_from_rc_buffer_badly<E>(
rc_buffer: Rc<[u8]>,
f: for<'de> fn(&'de [u8]) -> Result<<M::Yokeable as Yokeable<'de>>::Output, E>,
) -> Result<Self, E> {
let yoke = Yoke::try_attach_to_cart_badly(rc_buffer, f)?;
Ok(Self {
inner: DataPayloadInner::RcBuf(yoke),
})
}
}

impl<'d, 's, M> DataPayload<'d, 's, M>
where
M: DataMarker<'s>,
Expand Down Expand Up @@ -289,6 +353,7 @@ where
Borrowed(yoke) => yoke.with_mut(f),
RcStruct(yoke) => yoke.with_mut(f),
Owned(yoke) => yoke.with_mut(f),
RcBuf(yoke) => yoke.with_mut(f),
}
}

Expand All @@ -314,6 +379,7 @@ where
Borrowed(yoke) => yoke.get(),
RcStruct(yoke) => yoke.get(),
Owned(yoke) => yoke.get(),
RcBuf(yoke) => yoke.get(),
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions provider/core/src/dynutil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,27 @@ where
Self: Sized + crate::prelude::DataMarker<'s>,
{
/// Upcast a `DataPayload<T>` to a `DataPayload<S>` where `T` implements trait `S`.
///
/// # Examples
///
/// Upcast and then downcast a data struct of type `Cow<str>` (cart type `String`) via
/// [`ErasedDataStruct`](crate::erased::ErasedDataStruct):
///
/// ```
/// use icu_provider::prelude::*;
/// use icu_provider::erased::*;
/// use icu_provider::dynutil::UpcastDataPayload;
/// use icu_provider::marker::CowStringMarker;
/// use std::borrow::Cow;
///
/// let data = "foo".to_string();
/// let original = DataPayload::<CowStringMarker>::from_owned(Cow::Owned(data));
/// let upcasted = ErasedDataStructMarker::upcast(original);
/// let downcasted = upcasted
/// .downcast::<CowStringMarker>()
/// .expect("Type conversion");
/// assert_eq!(downcasted.get(), "foo");
/// ```
fn upcast(
other: crate::prelude::DataPayload<'d, 's, M>,
) -> crate::prelude::DataPayload<'d, 's, Self>;
Expand Down
74 changes: 73 additions & 1 deletion provider/core/src/erased.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ where
let cart: Rc<dyn ErasedDataStruct> = Rc::from(yoke);
DataPayload::from_partial_owned(cart)
}
RcBuf(yoke) => {
// Case 4: Cast the whole RcBuf Yoke to the trait object.
let cart: Rc<dyn ErasedDataStruct> = Rc::from(yoke);
DataPayload::from_partial_owned(cart)
}
}
}
}
Expand Down Expand Up @@ -203,15 +208,27 @@ impl<'d> DataPayload<'d, 'static, ErasedDataStructMarker> {
},
Err(any_rc) => any_rc,
};
// Check for Case 4: an RcBuf Yoke.
let y2 = any_rc.downcast::<Yoke<M::Yokeable, Rc<[u8]>>>();
let any_rc = match y2 {
Ok(rc_yoke) => match Rc::try_unwrap(rc_yoke) {
Ok(yoke) => return Ok(DataPayload { inner: RcBuf(yoke) }),
// Note: We could consider cloning the Yoke instead of erroring out.
Err(_) => return Err(Error::MultipleReferences),
},
Err(any_rc) => any_rc,
};
// None of the downcasts succeeded; return an error.
Err(Error::MismatchedType {
actual: Some(any_rc.type_id()),
generic: Some(TypeId::of::<M::Cart>()),
})
}
// This is unreachable because ErasedDataStructMarker cannot be fully owned, since it
// This is unreachable because ErasedDataStruct cannot be fully owned, since it
// contains a reference.
Owned(_) => unreachable!(),
// This is unreachable because ErasedDataStruct needs to reference an object.
RcBuf(_) => unreachable!(),
}
}
}
Expand Down Expand Up @@ -284,3 +301,58 @@ where
})
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::dynutil::UpcastDataPayload;
use crate::marker::CowStringMarker;
use std::borrow::Cow;

#[test]
fn test_erased_case_1() {
let data = "foo".to_string();
let original = DataPayload::<CowStringMarker>::from_borrowed(&data);
let upcasted = ErasedDataStructMarker::upcast(original);
let downcasted = upcasted
.downcast::<CowStringMarker>()
.expect("Type conversion");
assert_eq!(downcasted.get(), "foo");
}

#[test]
fn test_erased_case_2() {
let data = Rc::new("foo".to_string());
let original = DataPayload::<CowStringMarker>::from_partial_owned(data);
let upcasted = ErasedDataStructMarker::upcast(original);
let downcasted = upcasted
.downcast::<CowStringMarker>()
.expect("Type conversion");
assert_eq!(downcasted.get(), "foo");
}

#[test]
fn test_erased_case_3() {
let data = "foo".to_string();
let original = DataPayload::<CowStringMarker>::from_owned(Cow::Owned(data));
let upcasted = ErasedDataStructMarker::upcast(original);
let downcasted = upcasted
.downcast::<CowStringMarker>()
.expect("Type conversion");
assert_eq!(downcasted.get(), "foo");
}

#[test]
fn test_erased_case_4() {
let data: Rc<[u8]> = "foo".as_bytes().into();
let original = DataPayload::<CowStringMarker>::try_from_rc_buffer_badly(data, |bytes| {
std::str::from_utf8(bytes).map(|s| Cow::Borrowed(s))
})
.expect("String is valid UTF-8");
let upcasted = ErasedDataStructMarker::upcast(original);
let downcasted = upcasted
.downcast::<CowStringMarker>()
.expect("Type conversion");
assert_eq!(downcasted.get(), "foo");
}
}
Loading

0 comments on commit 2b7ca3b

Please sign in to comment.