From 5f24d42c007cbc99df6c5eec73878e0eacc09dec Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 24 Feb 2025 17:27:19 +0100 Subject: [PATCH 1/2] feat(stackable-versioned): Add convert_with arg in changed action --- .../fixtures/inputs/default/convert_with.rs | 12 ++++ ...st__default_snapshots@convert_with.rs.snap | 42 +++++++++++++ .../src/attrs/item/mod.rs | 31 +++++++--- .../src/codegen/item/field.rs | 19 ++++-- .../src/codegen/mod.rs | 3 +- crates/stackable-versioned-macros/src/lib.rs | 59 +++++++++++++++++++ 6 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 crates/stackable-versioned-macros/fixtures/inputs/default/convert_with.rs create mode 100644 crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@convert_with.rs.snap diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/convert_with.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/convert_with.rs new file mode 100644 index 000000000..8055f961a --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/convert_with.rs @@ -0,0 +1,12 @@ +#[versioned(version(name = "v1alpha1"), version(name = "v1"), version(name = "v2"))] +// --- +struct Foo { + #[versioned( + // This tests two additional things: + // - that both unquoted and quoted usage works + // - that the renamed name does get picked up correctly by the conversion function + changed(since = "v1", from_type = "u16", from_name = "bar", convert_with = u16_to_u32), + changed(since = "v2", from_type = "u32", convert_with = "u32_to_u64") + )] + baz: u64, +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@convert_with.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@convert_with.rs.snap new file mode 100644 index 000000000..ae5a50e8e --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@convert_with.rs.snap @@ -0,0 +1,42 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/convert_with.rs +--- +#[automatically_derived] +mod v1alpha1 { + use super::*; + pub struct Foo { + pub bar: u16, + } +} +#[automatically_derived] +impl ::std::convert::From for v1::Foo { + fn from(__sv_foo: v1alpha1::Foo) -> Self { + Self { + baz: u16_to_u32(__sv_foo.bar), + } + } +} +#[automatically_derived] +mod v1 { + use super::*; + pub struct Foo { + pub baz: u32, + } +} +#[automatically_derived] +impl ::std::convert::From for v2::Foo { + fn from(__sv_foo: v1::Foo) -> Self { + Self { + baz: u32_to_u64(__sv_foo.baz), + } + } +} +#[automatically_derived] +mod v2 { + use super::*; + pub struct Foo { + pub baz: u64, + } +} diff --git a/crates/stackable-versioned-macros/src/attrs/item/mod.rs b/crates/stackable-versioned-macros/src/attrs/item/mod.rs index 712be5043..e9b19f4e6 100644 --- a/crates/stackable-versioned-macros/src/attrs/item/mod.rs +++ b/crates/stackable-versioned-macros/src/attrs/item/mod.rs @@ -237,6 +237,19 @@ impl CommonItemAttributes { ); } } + + // The convert_with argument only makes sense to use when the + // type changed + if let Some(convert_func) = change.convert_with.as_ref() { + if change.from_type.is_none() { + errors.push( + Error::custom( + "the `convert_with` argument must be used in combination with `from_type`", + ) + .with_span(&convert_func.span()), + ); + } + } } errors.finish() @@ -307,9 +320,10 @@ impl CommonItemAttributes { actions.insert( *change.since, ItemStatus::Change { + convert_with: change.convert_with.as_deref().cloned(), from_ident: from_ident.clone(), - to_ident: ident, from_type: from_ty.clone(), + to_ident: ident, to_type: ty, }, ); @@ -356,9 +370,10 @@ impl CommonItemAttributes { actions.insert( *change.since, ItemStatus::Change { + convert_with: change.convert_with.as_deref().cloned(), from_ident: from_ident.clone(), - to_ident: ident, from_type: from_ty.clone(), + to_ident: ident, to_type: ty, }, ); @@ -431,12 +446,14 @@ fn default_default_fn() -> SpannedValue { /// /// Example usage: /// - `changed(since = "...", from_name = "...")` -/// - `changed(since = "...", from_name = "..." from_type="...")` +/// - `changed(since = "...", from_name = "...", from_type="...")` +/// - `changed(since = "...", from_name = "...", from_type="...", convert_with = "...")` #[derive(Clone, Debug, FromMeta)] -pub(crate) struct ChangedAttributes { - pub(crate) since: SpannedValue, - pub(crate) from_name: Option>, - pub(crate) from_type: Option>, +pub struct ChangedAttributes { + pub since: SpannedValue, + pub from_name: Option>, + pub from_type: Option>, + pub convert_with: Option>, } /// For the deprecated() action diff --git a/crates/stackable-versioned-macros/src/codegen/item/field.rs b/crates/stackable-versioned-macros/src/codegen/item/field.rs index 97190ba28..413a50b21 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/field.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/field.rs @@ -167,18 +167,29 @@ impl VersionedField { _, ItemStatus::Change { from_ident: old_field_ident, + convert_with, to_ident, .. }, - ) => { - quote! { + ) => match convert_with { + // The user specified a custom conversion function which + // will be used here instead of the default .into() call + // which utilizes From impls. + Some(convert_fn) => quote! { + #to_ident: #convert_fn(#from_struct_ident.#old_field_ident), + }, + // Default .into() call using From impls. + None => quote! { #to_ident: #from_struct_ident.#old_field_ident.into(), - } - } + }, + }, (old, next) => { let next_field_ident = next.get_ident(); let old_field_ident = old.get_ident(); + // NOTE (@Techassi): Do we really need .into() here. I'm + // currently not sure why it is there and if it is needed + // in some edge cases. quote! { #next_field_ident: #from_struct_ident.#old_field_ident.into(), } diff --git a/crates/stackable-versioned-macros/src/codegen/mod.rs b/crates/stackable-versioned-macros/src/codegen/mod.rs index 50fa798fe..1a4e150b9 100644 --- a/crates/stackable-versioned-macros/src/codegen/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/mod.rs @@ -72,7 +72,7 @@ impl From<&ModuleAttributes> for Vec { } #[derive(Debug, PartialEq)] -pub(crate) enum ItemStatus { +pub enum ItemStatus { Addition { ident: IdentString, default_fn: Path, @@ -81,6 +81,7 @@ pub(crate) enum ItemStatus { ty: Type, }, Change { + convert_with: Option, from_ident: IdentString, to_ident: IdentString, from_type: Type, diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index a3f04118b..3ef686c11 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -400,6 +400,10 @@ mod utils; /// - `since` to indicate since which version the item is changed. /// - `from_name` to indicate from which previous name the field is renamed. /// - `from_type` to indicate from which previous type the field is changed. +/// - `convert_with` to provide a custom conversion function instead of using +/// a [`From`] implementation by default. This argument can only be used in +/// combination with the `from_type` argument. The expected function signature +/// is: `fn (OLD_TYPE) -> NEW_TYPE`. This function must not fail. /// /// ``` /// # use stackable_versioned_macros::versioned; @@ -521,6 +525,61 @@ mod utils; /// This automatic generation can be skipped to enable a custom implementation /// for more complex conversions. /// +/// ### Custom conversion function at field level +/// +/// As stated in the [`changed()`](#changed-action) section, a custom conversion +/// function can be provided using the `convert_with` argument. A simple example +/// looks like this: +/// +/// ``` +/// # use stackable_versioned_macros::versioned; +/// #[versioned( +/// version(name = "v1alpha1"), +/// version(name = "v1beta1") +/// )] +/// pub struct Foo { +/// #[versioned(changed( +/// since = "v1beta1", +/// from_type = "u8", +/// convert_with = "u8_to_u16" +/// ))] +/// bar: u16, +/// } +/// +/// fn u8_to_u16(old: u8) -> u16 { +/// old as u16 +/// } +/// ``` +/// +///
+/// Expand Generated Code +/// +/// ``` +/// pub mod v1alpha1 { +/// use super::*; +/// pub struct Foo { +/// pub bar: u8, +/// } +/// } +/// +/// impl ::std::convert::From for v1beta1::Foo { +/// fn from(__sv_foo: v1alpha1::Foo) -> Self { +/// Self { +/// bar: u8_to_u16(__sv_foo.bar), +/// } +/// } +/// } +/// +/// pub mod v1beta1 { +/// use super::*; +/// pub struct Foo { +/// pub bar: u16, +/// } +/// } +/// ``` +/// +///
+/// /// ### Skipping at the Container Level /// /// Disabling this behavior at the container level results in no `From` From f36a7675b08c07a5a5a2468d8b503479f31b61ba Mon Sep 17 00:00:00 2001 From: Techassi Date: Mon, 24 Feb 2025 17:39:45 +0100 Subject: [PATCH 2/2] test(docs): Add ignore keyword to expanded example code --- crates/stackable-versioned-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 3ef686c11..b27f12ce8 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -554,7 +554,7 @@ mod utils; ///
/// Expand Generated Code /// -/// ``` +/// ```ignore /// pub mod v1alpha1 { /// use super::*; /// pub struct Foo {