Add `Localize`` trait bound for each field in the derived item

finchie
Apr 2, 2025, 8:13 AM
JWZT34UC7OTMMUZKGYFF6NDGIFNOA6TYXAZ6K66ELM3ZW7ZM7I5AC

Dependencies

  • [2] 4BMW4JJO Add support for deriving items with generics
  • [3] CESJ4CTO Move macro-specific code into `macro_impl` module
  • [4] ATWBK622 Update style of the `_TRACKED_PATHS` variable
  • [5] KF65O6OD Add tests for placeables
  • [6] 7M4UI3TW Update dependencies to latest versions
  • [7] 3NMKD6I5 Refactor `Localize` trait to use `std::io::Write`
  • [8] K3G4HK2J Track Fluent files using `include!`

Change contents

  • replacement in fluent_embed_derive/tests/placeables.rs at line 3
    [3.136][3.136:176]()
    use fluent_embed::{localize, Localize};
    [3.136]
    [3.47]
    mod common;
    use common::compare_message;
    use fluent_embed::localize;
  • edit in fluent_embed_derive/tests/placeables.rs at line 8
    [3.71][3.199:233](),[3.199][3.199:233]()
    use pretty_assertions::assert_eq;
  • edit in fluent_embed_derive/tests/placeables.rs at line 9
    [3.253][3.253:290]()
    use rstest_reuse::{apply, template};
  • replacement in fluent_embed_derive/tests/placeables.rs at line 12
    [3.356][3.356:602]()
    OneNumber {
    number: u64,
    },
    OneString {
    first: String,
    },
    TwoStrings {
    first: String,
    second: String,
    },
    Many {
    first: String,
    second: String,
    third: u64,
    },
    [3.356]
    [3.602]
    OpenTabs { quantity: u64 },
    Person { name: String },
    TabStatus { name: String, quantity: u64 },
  • edit in fluent_embed_derive/tests/placeables.rs at line 17
    [3.605][3.605:617]()
    #[template]
  • replacement in fluent_embed_derive/tests/placeables.rs at line 18
    [3.627][3.627:779]()
    fn first_second_strings(
    #[values("a regular string", "", r#"""#)] first: String,
    #[values("a regular string", "", r#"""#)] second: String,
    ) {
    [3.627]
    [3.779]
    #[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
    [3.792][3.792:827]()
    #[case::basic("a regular string")]
  • replacement in fluent_embed_derive/tests/placeables.rs at line 31
    [3.846][3.846:1210]()
    #[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));
    [3.846]
    [3.1210]
    #[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
    [3.1213][3.1213:2015]()
    #[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!"#
    [3.1213]
    [3.2015]
    #[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
    [3.2022][3.2022:2285]()
    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
    [3.2287][3.2287:2768]()
    #[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
    [3.2808][3.2809:2873]()
    # $first (String)
    one-string = Here is a string: "{ $first }".
  • replacement in fluent_embed_derive/tests/locale/en-US/placeables.ftl at line 2
    [3.2892][3.2892:2936]()
    one-number = Here is a number: { $number }.
    [3.2892]
    [3.2936]
    open-tabs = { $quantity } tabs open.
  • replacement in fluent_embed_derive/tests/locale/en-US/placeables.ftl at line 4
    [3.2937][3.2937:3048]()
    # $first (String)
    # $second (String)
    two-strings = Here is a string: "{ $first }". And another: "{ $second }".
    [3.2937]
    [3.3048]
    # $name (String)
    person = How many tabs does { $name } have open?
  • replacement in fluent_embed_derive/tests/locale/en-US/placeables.ftl at line 7
    [3.3049][3.3049:3215]()
    # $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
    [3.162][2.0:22]()
    use syn::parse_quote;
    [3.162]
    [3.162]
    use syn::{parse_quote, parse_quote_spanned, spanned::Spanned};
  • edit in fluent_embed_derive/src/macro_impl/mod.rs at line 63
    [3.1490]
    [3.1490]
    // Get the original generics for the derived item
    let (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
    [3.1491][2.23:266]()
    // 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`
    [3.1491]
    [2.266]
    // 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`
  • replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 105
    [2.337][2.337:605]()
    let localize_impl_generics =
    derive_input
    .generics
    .params
    .clone()
    .into_iter()
    .chain(std::iter::once(syn::GenericParam::Type(
    parse_quote!(W: std::io::Write),
    )));
    [2.337]
    [2.605]
    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
    [3.1507][2.607:673]()
    impl #impl_generics #ident #type_generics #where_clause {
    [3.1507]
    [3.256]
    impl #initial_impl_generics #ident #initial_type_generics #initial_where_clause {
  • replacement in fluent_embed_derive/src/macro_impl/mod.rs at line 118
    [3.652][2.674:788]()
    impl <#(#localize_impl_generics),*> ::fluent_embed::Localize<W> for #ident #type_generics #where_clause {
    [3.652]
    [3.97]
    impl #impl_generics ::fluent_embed::Localize<W> for #ident #initial_type_generics #where_clause {