Skip to content
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

Add RcBuf variant to DataPayload #816

Merged
merged 20 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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