• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // This file is part of ICU4X. For terms of use, please see the file
2 // called LICENSE at the top level of the ICU4X source tree
3 // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4 
5 use core::any::Any;
6 
7 use crate::prelude::*;
8 use crate::ule::MaybeEncodeAsVarULE;
9 use crate::{dynutil::UpcastDataPayload, ule::MaybeAsVarULE};
10 use alloc::sync::Arc;
11 use databake::{Bake, BakeSize, CrateEnv, TokenStream};
12 use yoke::*;
13 use zerovec::VarZeroVec;
14 
15 #[cfg(doc)]
16 use zerovec::ule::VarULE;
17 
18 trait ExportableDataPayload {
bake_yoke(&self, ctx: &CrateEnv) -> TokenStream19     fn bake_yoke(&self, ctx: &CrateEnv) -> TokenStream;
bake_size(&self) -> usize20     fn bake_size(&self) -> usize;
serialize_yoke( &self, serializer: &mut dyn erased_serde::Serializer, ) -> Result<(), DataError>21     fn serialize_yoke(
22         &self,
23         serializer: &mut dyn erased_serde::Serializer,
24     ) -> Result<(), DataError>;
maybe_bake_varule_encoded( &self, rest: &[&DataPayload<ExportMarker>], ctx: &CrateEnv, ) -> Option<TokenStream>25     fn maybe_bake_varule_encoded(
26         &self,
27         rest: &[&DataPayload<ExportMarker>],
28         ctx: &CrateEnv,
29     ) -> Option<TokenStream>;
as_any(&self) -> &dyn Any30     fn as_any(&self) -> &dyn Any;
eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool31     fn eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool;
32 }
33 
34 impl<M: DynamicDataMarker> ExportableDataPayload for DataPayload<M>
35 where
36     for<'a> <M::DataStruct as Yokeable<'a>>::Output:
37         Bake + BakeSize + serde::Serialize + MaybeEncodeAsVarULE + PartialEq,
38 {
bake_yoke(&self, ctx: &CrateEnv) -> TokenStream39     fn bake_yoke(&self, ctx: &CrateEnv) -> TokenStream {
40         self.get().bake(ctx)
41     }
42 
bake_size(&self) -> usize43     fn bake_size(&self) -> usize {
44         core::mem::size_of::<<M::DataStruct as Yokeable>::Output>() + self.get().borrows_size()
45     }
46 
serialize_yoke( &self, serializer: &mut dyn erased_serde::Serializer, ) -> Result<(), DataError>47     fn serialize_yoke(
48         &self,
49         serializer: &mut dyn erased_serde::Serializer,
50     ) -> Result<(), DataError> {
51         use erased_serde::Serialize;
52         self.get()
53             .erased_serialize(serializer)
54             .map_err(|e| DataError::custom("Serde export").with_display_context(&e))?;
55         Ok(())
56     }
57 
maybe_bake_varule_encoded( &self, rest: &[&DataPayload<ExportMarker>], ctx: &CrateEnv, ) -> Option<TokenStream>58     fn maybe_bake_varule_encoded(
59         &self,
60         rest: &[&DataPayload<ExportMarker>],
61         ctx: &CrateEnv,
62     ) -> Option<TokenStream> {
63         let first_varule = self.get().maybe_encode_as_varule()?;
64         let recovered_vec: Vec<
65             &<<M::DataStruct as Yokeable<'_>>::Output as MaybeAsVarULE>::EncodedStruct,
66         > = core::iter::once(first_varule)
67             .chain(rest.iter().map(|v| {
68                 #[allow(clippy::expect_used)] // exporter code
69                 v.get()
70                     .payload
71                     .as_any()
72                     .downcast_ref::<Self>()
73                     .expect("payloads expected to be same type")
74                     .get()
75                     .maybe_encode_as_varule()
76                     .expect("MaybeEncodeAsVarULE impl should be symmetric")
77             }))
78             .collect();
79         let vzv: VarZeroVec<
80             <<M::DataStruct as Yokeable<'_>>::Output as MaybeAsVarULE>::EncodedStruct,
81         > = VarZeroVec::from(&recovered_vec);
82         let vzs = vzv.as_slice();
83         Some(vzs.bake(ctx))
84     }
85 
as_any(&self) -> &dyn Any86     fn as_any(&self) -> &dyn Any {
87         self
88     }
89 
eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool90     fn eq_dyn(&self, other: &dyn ExportableDataPayload) -> bool {
91         match other.as_any().downcast_ref::<Self>() {
92             Some(downcasted) => (*self).eq(downcasted),
93             None => {
94                 debug_assert!(
95                     false,
96                     "cannot compare ExportableDataPayloads of different types: self is {:?} but other is {:?}",
97                     self.type_id(),
98                     other.as_any().type_id(),
99                 );
100                 false
101             }
102         }
103     }
104 }
105 
106 #[doc(hidden)] // macro
107 #[derive(yoke::Yokeable, Clone)]
108 pub struct ExportBox {
109     payload: Arc<dyn ExportableDataPayload + Sync + Send>,
110 }
111 
112 impl PartialEq for ExportBox {
eq(&self, other: &Self) -> bool113     fn eq(&self, other: &Self) -> bool {
114         self.payload.eq_dyn(&*other.payload)
115     }
116 }
117 
118 impl Eq for ExportBox {}
119 
120 impl core::fmt::Debug for ExportBox {
fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result121     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122         f.debug_struct("ExportBox")
123             .field("payload", &"<payload>")
124             .finish()
125     }
126 }
127 
128 impl<M> UpcastDataPayload<M> for ExportMarker
129 where
130     M: DynamicDataMarker,
131     M::DataStruct: Sync + Send,
132     for<'a> <M::DataStruct as Yokeable<'a>>::Output:
133         Bake + BakeSize + serde::Serialize + MaybeEncodeAsVarULE + PartialEq,
134 {
upcast(other: DataPayload<M>) -> DataPayload<ExportMarker>135     fn upcast(other: DataPayload<M>) -> DataPayload<ExportMarker> {
136         DataPayload::from_owned(ExportBox {
137             payload: Arc::new(other),
138         })
139     }
140 }
141 
142 impl DataPayload<ExportMarker> {
143     /// Serializes this [`DataPayload`] into a serializer using Serde.
144     ///
145     /// # Examples
146     ///
147     /// ```
148     /// use icu_provider::dynutil::UpcastDataPayload;
149     /// use icu_provider::export::*;
150     /// use icu_provider::hello_world::HelloWorldV1;
151     /// use icu_provider::prelude::*;
152     ///
153     /// // Create an example DataPayload
154     /// let payload: DataPayload<HelloWorldV1> = Default::default();
155     /// let export: DataPayload<ExportMarker> = UpcastDataPayload::upcast(payload);
156     ///
157     /// // Serialize the payload to a JSON string
158     /// let mut buffer: Vec<u8> = vec![];
159     /// export
160     ///     .serialize(&mut serde_json::Serializer::new(&mut buffer))
161     ///     .expect("Serialization should succeed");
162     /// assert_eq!(r#"{"message":"(und) Hello World"}"#.as_bytes(), buffer);
163     /// ```
serialize<S>(&self, serializer: S) -> Result<(), DataError> where S: serde::Serializer, S::Ok: 'static,164     pub fn serialize<S>(&self, serializer: S) -> Result<(), DataError>
165     where
166         S: serde::Serializer,
167         S::Ok: 'static, // erased_serde requirement, cannot return values in `Ok`
168     {
169         self.get()
170             .payload
171             .serialize_yoke(&mut <dyn erased_serde::Serializer>::erase(serializer))
172     }
173 
174     /// Serializes this [`DataPayload`]'s value into a [`TokenStream`]
175     /// using its [`Bake`] implementations.
176     ///
177     /// # Examples
178     ///
179     /// ```
180     /// use icu_provider::dynutil::UpcastDataPayload;
181     /// use icu_provider::export::*;
182     /// use icu_provider::hello_world::HelloWorldV1;
183     /// use icu_provider::prelude::*;
184     /// # use databake::quote;
185     /// # use std::collections::BTreeSet;
186     ///
187     /// // Create an example DataPayload
188     /// let payload: DataPayload<HelloWorldV1> = Default::default();
189     /// let export: DataPayload<ExportMarker> = UpcastDataPayload::upcast(payload);
190     ///
191     /// let env = databake::CrateEnv::default();
192     /// let tokens = export.tokenize(&env);
193     /// assert_eq!(
194     ///     quote! {
195     ///         icu_provider::hello_world::HelloWorld {
196     ///             message: alloc::borrow::Cow::Borrowed("(und) Hello World"),
197     ///         }
198     ///     }
199     ///     .to_string(),
200     ///     tokens.to_string()
201     /// );
202     /// assert_eq!(
203     ///     env.into_iter().collect::<BTreeSet<_>>(),
204     ///     ["icu_provider", "alloc"]
205     ///         .into_iter()
206     ///         .collect::<BTreeSet<_>>()
207     /// );
208     /// ```
tokenize(&self, ctx: &CrateEnv) -> TokenStream209     pub fn tokenize(&self, ctx: &CrateEnv) -> TokenStream {
210         self.get().payload.bake_yoke(ctx)
211     }
212 
213     /// If this payload's struct can be dereferenced as a [`VarULE`],
214     /// returns a [`TokenStream`] of the slice encoded as a [`VarZeroVec`].
tokenize_encoded_seq(structs: &[&Self], ctx: &CrateEnv) -> Option<TokenStream>215     pub fn tokenize_encoded_seq(structs: &[&Self], ctx: &CrateEnv) -> Option<TokenStream> {
216         let (first, rest) = structs.split_first()?;
217         first.get().payload.maybe_bake_varule_encoded(rest, ctx)
218     }
219 
220     /// Returns the data size using postcard encoding
postcard_size(&self) -> usize221     pub fn postcard_size(&self) -> usize {
222         use postcard::ser_flavors::{Flavor, Size};
223         let mut serializer = postcard::Serializer {
224             output: Size::default(),
225         };
226         let _infallible = self
227             .get()
228             .payload
229             .serialize_yoke(&mut <dyn erased_serde::Serializer>::erase(&mut serializer));
230 
231         serializer.output.finalize().unwrap_or_default()
232     }
233 
234     /// Returns an estimate of the baked size, made up of the size of the struct itself,
235     /// as well as the sizes of all its static borrows.
236     ///
237     /// As static borrows are deduplicated by the linker, this is often overcounting.
baked_size(&self) -> usize238     pub fn baked_size(&self) -> usize {
239         self.get().payload.bake_size()
240     }
241 }
242 
243 impl core::hash::Hash for DataPayload<ExportMarker> {
hash<H: core::hash::Hasher>(&self, state: &mut H)244     fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
245         self.hash_and_postcard_size(state);
246     }
247 }
248 
249 impl DataPayload<ExportMarker> {
250     /// Calculates a payload hash and the postcard size
hash_and_postcard_size<H: core::hash::Hasher>(&self, state: &mut H) -> usize251     pub fn hash_and_postcard_size<H: core::hash::Hasher>(&self, state: &mut H) -> usize {
252         use postcard::ser_flavors::Flavor;
253 
254         struct HashFlavor<'a, H>(&'a mut H, usize);
255         impl<H: core::hash::Hasher> Flavor for HashFlavor<'_, H> {
256             type Output = usize;
257 
258             fn try_push(&mut self, data: u8) -> postcard::Result<()> {
259                 self.0.write_u8(data);
260                 self.1 += 1;
261                 Ok(())
262             }
263 
264             fn finalize(self) -> postcard::Result<Self::Output> {
265                 Ok(self.1)
266             }
267         }
268 
269         let mut serializer = postcard::Serializer {
270             output: HashFlavor(state, 0),
271         };
272 
273         let _infallible = self
274             .get()
275             .payload
276             .serialize_yoke(&mut <dyn erased_serde::Serializer>::erase(&mut serializer));
277 
278         serializer.output.1
279     }
280 }
281 
282 /// Marker type for [`ExportBox`].
283 #[allow(clippy::exhaustive_structs)] // marker type
284 #[derive(Debug)]
285 pub struct ExportMarker {}
286 
287 impl DynamicDataMarker for ExportMarker {
288     type DataStruct = ExportBox;
289 }
290 
291 #[cfg(test)]
292 mod tests {
293     use super::*;
294     use crate::hello_world::*;
295 
296     #[test]
test_compare_with_dyn()297     fn test_compare_with_dyn() {
298         let payload1: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
299             message: "abc".into(),
300         });
301         let payload2: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
302             message: "abc".into(),
303         });
304         let payload3: DataPayload<HelloWorldV1> = DataPayload::from_owned(HelloWorld {
305             message: "def".into(),
306         });
307 
308         assert!(payload1.eq_dyn(&payload2));
309         assert!(payload2.eq_dyn(&payload1));
310 
311         assert!(!payload1.eq_dyn(&payload3));
312         assert!(!payload3.eq_dyn(&payload1));
313     }
314 
315     #[test]
test_export_marker_partial_eq()316     fn test_export_marker_partial_eq() {
317         let payload1: DataPayload<ExportMarker> =
318             UpcastDataPayload::upcast(DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
319                 message: "abc".into(),
320             }));
321         let payload2: DataPayload<ExportMarker> =
322             UpcastDataPayload::upcast(DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
323                 message: "abc".into(),
324             }));
325         let payload3: DataPayload<ExportMarker> =
326             UpcastDataPayload::upcast(DataPayload::<HelloWorldV1>::from_owned(HelloWorld {
327                 message: "def".into(),
328             }));
329 
330         assert_eq!(payload1, payload2);
331         assert_eq!(payload2, payload1);
332         assert_ne!(payload1, payload3);
333         assert_ne!(payload3, payload1);
334     }
335 }
336