Especially when paired with the diagnostic attribute, this should make it much easier to understand why certain types don't fit together when using fluent_embed
.
JWZT34UC7OTMMUZKGYFF6NDGIFNOA6TYXAZ6K66ELM3ZW7ZM7I5AC
fn first_second_strings(
#[values("a regular string", "", r#"""#)] first: String,
#[values("a regular string", "", r#"""#)] second: String,
) {
#[case::zero(0_u64, "0")]
#[case::two(2_u64, "2")]
#[case::max(u64::MAX, "18,446,744,073,709,551,615")]
fn number(#[case] quantity: u64, #[case] expected: String) {
compare_message(
Message::OpenTabs { quantity },
format!("{expected} tabs open."),
langid!("en-US"),
)
#[case::double_quote(r#"""#)]
fn one_string(#[case] first: String) {
let expected_message = format!(r#"Here is a string: "{first}"."#);
let data = Message::OneString { first };
let mut buffer = Vec::new();
data.message_for_locale(&mut buffer, &langid!("en-US"))
.unwrap();
assert_eq!(String::from_utf8(buffer), Ok(expected_message));
#[case::single_hyphen(r#"""#)]
#[case::english("Ferris")]
#[case::accent("Férris")]
#[case::unicode("蟹")]
fn string(#[case] name: String) {
compare_message(
Message::Person { name: name.clone() },
format!("How many tabs does {name} have open?"),
langid!("en-US"),
)
#[apply(first_second_strings)]
fn two_strings(first: String, second: String) {
let expected_message = format!(r#"Here is a string: "{first}". And another: "{second}"."#);
let data = Message::TwoStrings { first, second };
let mut buffer = Vec::new();
data.message_for_locale(&mut buffer, &langid!("en-US"))
.unwrap();
assert_eq!(String::from_utf8(buffer), Ok(expected_message));
}
#[apply(first_second_strings)]
#[case::zero(0, "0")]
#[case::one(1, "1")]
#[case::two(2, "2")]
#[case::max(u64::MAX, "18,446,744,073,709,551,615")]
fn many_interpolations(first: String, second: String, #[case] third: u64, #[case] plural: &str) {
let expected_message = format!(
r#"Here is a string: "{first}". And another: "{second}". I once counted {plural} strings in total!"#
#[rstest]
#[case::empty(0_u64, "0", "")]
#[case::simple(2_u64, "2", "Ferris")]
#[case::complex(u64::MAX, "18,446,744,073,709,551,615", "蟹")]
fn numbers_and_strings(#[case] quantity: u64, #[case] expected: String, #[case] name: String) {
compare_message(
Message::TabStatus {
name: name.clone(),
quantity,
},
format!("{name} has {expected} tabs open!"),
langid!("en-US"),
#[rstest]
#[case::zero(0, "0")]
#[case::one(1, "1")]
#[case::two(2, "2")]
#[case::max(u64::MAX, "18,446,744,073,709,551,615")]
fn one_number(#[case] number: u64, #[case] plural: &str) {
let expected_message = format!("Here is a number: {plural}.");
let data = Message::OneNumber { number };
let mut buffer = Vec::new();
data.message_for_locale(&mut buffer, &langid!("en-US"))
.unwrap();
assert_eq!(String::from_utf8(buffer), Ok(expected_message));
}
// Get the generics for the derived item
let (impl_generics, type_generics, where_clause) = derive_input.generics.split_for_impl();
// Combine all of the derived item's generic parameters along with `std::io::Write` for `Localize`
// Get the types of each named field
let named_fields: Vec<&syn::Type> = match &derive_input.data {
syn::Data::Struct(struct_data) => match &struct_data.fields {
syn::Fields::Named(named_fields) => {
named_fields.named.iter().map(|field| &field.ty).collect()
}
_ => todo!(),
},
syn::Data::Enum(enum_data) => enum_data
.variants
.iter()
.flat_map(|variant| match &variant.fields {
syn::Fields::Named(named_fields) => {
named_fields.named.iter().map(|field| &field.ty)
}
_ => todo!(),
})
.collect(),
syn::Data::Union(_union_data) => todo!(),
};
// Add a bound on `Localize` for each field's type
let mut generics = derive_input.generics.clone();
let additional_bounds = named_fields
.into_iter()
.map(|field| -> syn::WherePredicate {
// Attribute this bound to the original source code
let span = field.span();
parse_quote_spanned!(span=> #field: ::fluent_embed::Localize<W>)
});
generics
.make_where_clause()
.predicates
.extend(additional_bounds);
// Define a parameter of `std::io::Write` for `Localize`
let localize_impl_generics =
derive_input
.generics
.params
.clone()
.into_iter()
.chain(std::iter::once(syn::GenericParam::Type(
parse_quote!(W: std::io::Write),
)));
generics
.params
.push(syn::GenericParam::Type(parse_quote!(W: std::io::Write)));
let (impl_generics, _type_generics, where_clause) = generics.split_for_impl();