Skip to content
Open
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
97 changes: 97 additions & 0 deletions provider/adapters/src/delegate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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 ).

//! Macros for delegating impls to inner data providers.

/// Delegate an impl of `DataProvider<M>` to a field of a struct.
///
/// # Examples
///
/// ```
/// use icu_provider::hello_world::*;
/// use icu_locale::locale;
///
/// struct Wrap(HelloWorldProvider);
///
/// // Delegate `DataProvider<HelloWorldV1>` to the field `self.0`
/// icu_provider_adapters::delegate::data_provider_to_field!(Wrap, HelloWorldV1, &self.0);
///
/// // Test that it works
/// let wrap = Wrap(HelloWorldProvider::default());
/// HelloWorldFormatter::try_new_unstable(&wrap, locale!("de").into()).unwrap();
/// ```
///
/// Also works if the field is a [`BufferProvider`]:
///
/// ```
/// use icu_provider::hello_world::*;
/// use icu_locale::locale;
///
/// struct Wrap(HelloWorldJsonProvider);
///
/// // Delegate `DataProvider<HelloWorldV1>` to the field `self.0`, calling `as_deserializing()` on the field
/// icu_provider_adapters::delegate::data_provider_to_field!(Wrap, HelloWorldV1, &self.0.as_deserializing());
///
/// // Test that it works
/// let wrap = Wrap(HelloWorldProvider::default().into_json_provider());
/// HelloWorldFormatter::try_new_unstable(&wrap, locale!("de").into()).unwrap();
/// ```
///
/// [`BufferProvider`]: icu_provider::prelude::BufferProvider
#[doc(hidden)] // macro
#[macro_export]
macro_rules! __data_provider_to_field {
($provider:path, $marker:path, &self.$field:tt.as_deserializing()) => {
impl $crate::icu_provider::DataProvider<$marker> for $provider {
fn load(&self, req: $crate::icu_provider::DataRequest) -> Result<$crate::icu_provider::DataResponse<$marker>, $crate::icu_provider::DataError> {
let provider = $crate::icu_provider::prelude::AsDeserializingBufferProvider::as_deserializing(&self.$field);
$crate::icu_provider::DataProvider::<$marker>::load(&provider, req)
}
}
};
($provider:path, $marker:path, &self.$field:tt) => {
impl $crate::icu_provider::DataProvider<$marker> for $provider {
fn load(&self, req: $crate::icu_provider::DataRequest) -> Result<$crate::icu_provider::DataResponse<$marker>, $crate::icu_provider::DataError> {
$crate::icu_provider::DataProvider::<$marker>::load(&self.$field, req)
}
}
Comment on lines +54 to +58
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a very small implementation which I'm not sure is worth a macro that has to be special cased for different providers

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ambivalent on this. I think it's fine, but I'm not strongly in favor of adding such a macro.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it less about the macro and more about blessing this pattern as one we want to encourage, with a docs page, etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so just write a docs page? the macro seems brittle and learning how to use the macro is more effort than writing the redirecting impl

};
}
Comment on lines +44 to +60

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current macro implementation is very restrictive as it only allows delegating to a direct field of self. The use of $field:tt matches only a single token tree, so it won't work for nested fields like self.foo.bar.

To make the macro more flexible and support nested field access, you can change $field:tt to $($field:tt)+. This will match one or more token trees, allowing for expressions like self.a.b.c.

Suggested change
macro_rules! __data_provider_to_field {
($provider:path, $marker:path, &self.$field:tt.as_deserializing()) => {
impl $crate::icu_provider::DataProvider<$marker> for $provider {
fn load(&self, req: $crate::icu_provider::DataRequest) -> Result<$crate::icu_provider::DataResponse<$marker>, $crate::icu_provider::DataError> {
let provider = $crate::icu_provider::prelude::AsDeserializingBufferProvider::as_deserializing(&self.$field);
$crate::icu_provider::DataProvider::<$marker>::load(&provider, req)
}
}
};
($provider:path, $marker:path, &self.$field:tt) => {
impl $crate::icu_provider::DataProvider<$marker> for $provider {
fn load(&self, req: $crate::icu_provider::DataRequest) -> Result<$crate::icu_provider::DataResponse<$marker>, $crate::icu_provider::DataError> {
$crate::icu_provider::DataProvider::<$marker>::load(&self.$field, req)
}
}
};
}
macro_rules! __data_provider_to_field {
($provider:path, $marker:path, &self.$($field:tt)+.as_deserializing()) => {
impl $crate::icu_provider::DataProvider<$marker> for $provider {
fn load(&self, req: $crate::icu_provider::DataRequest) -> Result<$crate::icu_provider::DataResponse<$marker>, $crate::icu_provider::DataError> {
let provider = $crate::icu_provider::prelude::AsDeserializingBufferProvider::as_deserializing(&self.$($field)+);
$crate::icu_provider::DataProvider::<$marker>::load(&provider, req)
}
}
};
($provider:path, $marker:path, &self.$($field:tt)+) => {
impl $crate::icu_provider::DataProvider<$marker> for $provider {
fn load(&self, req: $crate::icu_provider::DataRequest) -> Result<$crate::icu_provider::DataResponse<$marker>, $crate::icu_provider::DataError> {
$crate::icu_provider::DataProvider::<$marker>::load(&self.$($field)+, req)
}
}
};
}


#[doc(inline)]
pub use __data_provider_to_field as data_provider_to_field;

#[cfg(test)]
mod tests {
use icu_locale::locale;
use icu_provider::hello_world::{
HelloWorldFormatter, HelloWorldJsonProvider, HelloWorldProvider, HelloWorldV1,
};

use super::*;

#[test]
fn test_delegate() {
struct Wrap(HelloWorldProvider);
data_provider_to_field!(Wrap, HelloWorldV1, &self.0);

let hello1 = HelloWorldProvider::default();
let wrap = Wrap(hello1);

let formatter = HelloWorldFormatter::try_new_unstable(&wrap, locale!("de").into()).unwrap();
assert_eq!(formatter.format_to_string(), "Hallo Welt");
}

#[test]
fn test_delegate_to_buffer() {
struct Wrap(HelloWorldJsonProvider);
data_provider_to_field!(Wrap, HelloWorldV1, &self.0.as_deserializing());

let hello1 = HelloWorldProvider::default().into_json_provider();
let wrap = Wrap(hello1);

let formatter = HelloWorldFormatter::try_new_unstable(&wrap, locale!("de").into()).unwrap();
assert_eq!(formatter.format_to_string(), "Hallo Welt");
}
}
4 changes: 4 additions & 0 deletions provider/adapters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@

extern crate alloc;

pub mod delegate;
pub mod either;
pub mod empty;
pub mod fallback;
pub mod filter;
pub mod fixed;
pub mod fork;

#[doc(hidden)] // internal for macros
pub use icu_provider;
Loading