Add `Localize`` trait bound for each field in the derived item
Dependencies
- [2]
4BMW4JJOAdd support for deriving items with generics - [3]
CESJ4CTOMove macro-specific code into `macro_impl` module - [4]
ATWBK622Update style of the `_TRACKED_PATHS` variable - [5]
KF65O6ODAdd tests for placeables - [6]
7M4UI3TWUpdate dependencies to latest versions - [7]
3NMKD6I5Refactor `Localize` trait to use `std::io::Write` - [8]
K3G4HK2JTrack Fluent files using `include!`
Change contents
- replacement in fluent_embed_derive/tests/placeables.rs at line 3
use fluent_embed::{localize, Localize};mod common;use common::compare_message;use fluent_embed::localize; - edit in fluent_embed_derive/tests/placeables.rs at line 8
use pretty_assertions::assert_eq; - edit in fluent_embed_derive/tests/placeables.rs at line 9
use rstest_reuse::{apply, template}; - replacement in fluent_embed_derive/tests/placeables.rs at line 12
OneNumber {number: u64,},OneString {first: String,},TwoStrings {first: String,second: String,},Many {first: String,second: String,third: u64,},OpenTabs { quantity: u64 },Person { name: String },TabStatus { name: String, quantity: u64 }, - edit in fluent_embed_derive/tests/placeables.rs at line 17
#[template] - replacement in fluent_embed_derive/tests/placeables.rs at line 18
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"),) - edit in fluent_embed_derive/tests/placeables.rs at line 30
#[case::basic("a regular string")] - replacement in fluent_embed_derive/tests/placeables.rs at line 31
#[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"),) - replacement in fluent_embed_derive/tests/placeables.rs at line 43
#[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"), - edit in fluent_embed_derive/tests/placeables.rs at line 56
let data = Message::Many {first,second,third,};let mut buffer = Vec::new();data.message_for_locale(&mut buffer, &langid!("en-US")).unwrap();assert_eq!(String::from_utf8(buffer), Ok(expected_message)); - edit in fluent_embed_derive/tests/placeables.rs at line 57
#[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));} - edit in fluent_embed_derive/tests/locale/en-US/placeables.ftl at line 1
# $first (String)one-string = Here is a string: "{ $first }". - replacement in fluent_embed_derive/tests/locale/en-US/placeables.ftl at line 2
one-number = Here is a number: { $number }.open-tabs = { $quantity } tabs open. - replacement in fluent_embed_derive/tests/locale/en-US/placeables.ftl at line 4
# $first (String)# $second (String)two-strings = Here is a string: "{ $first }". And another: "{ $second }".# $name (String)person = How many tabs does { $name } have open? - replacement in fluent_embed_derive/tests/locale/en-US/placeables.ftl at line 7
# $first (String)# $second (String)# $third (Number)many = Here is a string: "{ $first }". And another: "{ $second }". I once counted { $third } strings in total![3.3049]# $name (String), $quantity (Number)tab-status = { $name } has { $quantity } tabs open! - replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 5
use syn::parse_quote;use syn::{parse_quote, parse_quote_spanned, spanned::Spanned}; - edit in fluent_embed_derive/src/macro_impl/mod.rs at line 63
// Get the original generics for the derived itemlet (initial_impl_generics, initial_type_generics, initial_where_clause) =derive_input.generics.split_for_impl(); - replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 68
// Get the generics for the derived itemlet (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 fieldlet 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 typelet mut generics = derive_input.generics.clone();let additional_bounds = named_fields.into_iter().map(|field| -> syn::WherePredicate {// Attribute this bound to the original source codelet 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` - replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 105
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(); - replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 111
impl #impl_generics #ident #type_generics #where_clause {impl #initial_impl_generics #ident #initial_type_generics #initial_where_clause { - replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 118
impl <#(#localize_impl_generics),*> ::fluent_embed::Localize<W> for #ident #type_generics #where_clause {impl #impl_generics ::fluent_embed::Localize<W> for #ident #initial_type_generics #where_clause {