diff --git a/crates/stackable-versioned-macros/src/attrs/common/item.rs b/crates/stackable-versioned-macros/src/attrs/common/item.rs index 744549a0e..c6ee146eb 100644 --- a/crates/stackable-versioned-macros/src/attrs/common/item.rs +++ b/crates/stackable-versioned-macros/src/attrs/common/item.rs @@ -1,7 +1,7 @@ use darling::{util::SpannedValue, Error, FromMeta}; use k8s_version::Version; use proc_macro2::Span; -use syn::{spanned::Spanned, Attribute, Ident, Path}; +use syn::{spanned::Spanned, Attribute, Ident, Path, Type}; use crate::{ attrs::common::ContainerAttributes, @@ -55,14 +55,14 @@ where } } - for rename in &*self.common_attributes().renames { + for change in &*self.common_attributes().changes { if !container_attrs .versions .iter() - .any(|v| v.name == *rename.since) + .any(|v| v.name == *change.since) { errors.push( - Error::custom("variant action `renamed` uses version which was not declared via #[versioned(version)]") + Error::custom("variant action `changed` uses version which was not declared via #[versioned(version)]") .with_span(item) ); } @@ -97,6 +97,11 @@ pub(crate) enum ItemType { Variant, } +// TODO (@Techassi): Shower thought: Track actions as a Vec of an ActionAttribute +// enum and implement Ord on it. This would allow us to order the items by type +// of action (added < changed < deprecated) as well as sort changed action to +// each other by specified version (which already implements Ord) + /// These attributes are meant to be used in super structs, which add /// [`Field`](syn::Field) or [`Variant`](syn::Variant) specific attributes via /// darling's flatten feature. This struct only provides shared attributes. @@ -104,8 +109,8 @@ pub(crate) enum ItemType { /// ### Shared Item Rules /// /// - An item can only ever be added once at most. An item not marked as 'added' -/// is part of the container in every version until renamed or deprecated. -/// - An item can be renamed many times. That's why renames are stored in a +/// is part of the container in every version until changed or deprecated. +/// - An item can be changed many times. That's why changes are stored in a /// [`Vec`]. /// - An item can only be deprecated once. A field or variant not marked as /// 'deprecated' will be included up until the latest version. @@ -115,10 +120,10 @@ pub(crate) struct ItemAttributes { /// only be present at most once. pub(crate) added: Option, - /// This parses the `renamed` attribute on items (fields or variants). It + /// This parses the `changed` attribute on items (fields or variants). It /// can be present 0..n times. - #[darling(multiple, rename = "renamed")] - pub(crate) renames: Vec, + #[darling(multiple, rename = "changed")] + pub(crate) changes: Vec, /// This parses the `deprecated` attribute on items (fields or variants). It /// can only be present at most once. @@ -138,20 +143,6 @@ impl ItemAttributes { let mut errors = Error::accumulator(); - // TODO (@Techassi): Make the field or variant 'note' optional, because - // in the future, the macro will generate parts of the deprecation note - // automatically. The user-provided note will then be appended to the - // auto-generated one. - - if let Some(deprecated) = &self.deprecated { - if deprecated.note.is_empty() { - errors.push( - Error::custom("deprecation note must not be empty") - .with_span(&deprecated.note.span()), - ); - } - } - // Semantic validation errors.handle(self.validate_action_combinations(item_ident, item_type)); errors.handle(self.validate_action_order(item_ident, item_type)); @@ -175,34 +166,34 @@ impl ItemAttributes { /// cannot be marked as added in a particular version and then marked as /// deprecated immediately after. Fields and variants must be included for /// at least one version before being marked deprecated. - /// - `added` and `renamed` using the same version: The same reasoning from + /// - `added` and `changed` using the same version: The same reasoning from /// above applies here as well. Fields and variants must be included for - /// at least one version before being renamed. - /// - `renamed` and `deprecated` using the same version: Again, the same + /// at least one version before being changed. + /// - `changed` and `deprecated` using the same version: Again, the same /// rules from above apply here as well. fn validate_action_combinations( &self, item_ident: &Ident, item_type: &ItemType, ) -> Result<(), Error> { - match (&self.added, &self.renames, &self.deprecated) { + match (&self.added, &self.changes, &self.deprecated) { (Some(added), _, Some(deprecated)) if *added.since == *deprecated.since => { Err(Error::custom(format!( "{item_type} cannot be marked as `added` and `deprecated` in the same version" )) .with_span(item_ident)) } - (Some(added), renamed, _) if renamed.iter().any(|r| *r.since == *added.since) => { + (Some(added), changed, _) if changed.iter().any(|r| *r.since == *added.since) => { Err(Error::custom(format!( - "{item_type} cannot be marked as `added` and `renamed` in the same version" + "{item_type} cannot be marked as `added` and `changed` in the same version" )) .with_span(item_ident)) } - (_, renamed, Some(deprecated)) - if renamed.iter().any(|r| *r.since == *deprecated.since) => + (_, changed, Some(deprecated)) + if changed.iter().any(|r| *r.since == *deprecated.since) => { Err(Error::custom( - format!("{item_type} cannot be marked as `deprecated` and `renamed` in the same version"), + format!("{item_type} cannot be marked as `deprecated` and `changed` in the same version"), ) .with_span(item_ident)) } @@ -220,7 +211,7 @@ impl ItemAttributes { /// ensures that these versions are chronologically sound, that means, /// that the version of the deprecated action must be greater than the /// version of the added action. - /// - All `renamed` actions must use a greater version than `added` but a + /// - All `changed` actions must use a greater version than `added` but a /// lesser version than `deprecated`. fn validate_action_order(&self, item_ident: &Ident, item_type: &ItemType) -> Result<(), Error> { let added_version = self.added.as_ref().map(|a| *a.since); @@ -238,14 +229,14 @@ impl ItemAttributes { } } - // Now, iterate over all renames and ensure that their versions are + // Now, iterate over all changes and ensure that their versions are // between the added and deprecated version. - if !self.renames.iter().all(|r| { + if !self.changes.iter().all(|r| { added_version.map_or(true, |a| a < *r.since) && deprecated_version.map_or(true, |d| d > *r.since) }) { return Err(Error::custom( - "all renames must use versions higher than `added` and lower than `deprecated`", + "all changes must use versions higher than `added` and lower than `deprecated`", ) .with_span(item_ident)); } @@ -320,27 +311,32 @@ pub(crate) struct AddedAttributes { fn default_default_fn() -> SpannedValue { SpannedValue::new( - syn::parse_str("std::default::Default::default").expect("internal error: path must parse"), + syn::parse_str("::std::default::Default::default") + .expect("internal error: path must parse"), Span::call_site(), ) } -/// For the renamed() action +/// For the changed() action /// /// Example usage: -/// - `renamed(since = "...", from = "...")` +/// - `changed(since = "...", from_name = "...")` +/// - `changed(since = "...", from_name = "..." from_type="...")` #[derive(Clone, Debug, FromMeta)] -pub(crate) struct RenamedAttributes { +pub(crate) struct ChangedAttributes { pub(crate) since: SpannedValue, - pub(crate) from: SpannedValue, + pub(crate) from_name: Option>, + + pub(crate) from_type: Option>, } /// For the deprecated() action /// /// Example usage: +/// - `deprecated(since = "...")` /// - `deprecated(since = "...", note = "...")` #[derive(Clone, Debug, FromMeta)] pub(crate) struct DeprecatedAttributes { pub(crate) since: SpannedValue, - pub(crate) note: SpannedValue, + pub(crate) note: Option>, } diff --git a/crates/stackable-versioned-macros/src/attrs/field.rs b/crates/stackable-versioned-macros/src/attrs/field.rs index e2816d927..d5c9aa9ff 100644 --- a/crates/stackable-versioned-macros/src/attrs/field.rs +++ b/crates/stackable-versioned-macros/src/attrs/field.rs @@ -50,8 +50,8 @@ impl FieldAttributes { .ident .as_ref() .expect("internal error: field must have an ident"); - self.common.validate(ident, &ItemType::Field, &self.attrs)?; + self.common.validate(ident, &ItemType::Field, &self.attrs)?; Ok(self) } } diff --git a/crates/stackable-versioned-macros/src/attrs/variant.rs b/crates/stackable-versioned-macros/src/attrs/variant.rs index 7f1e69657..bcdde256a 100644 --- a/crates/stackable-versioned-macros/src/attrs/variant.rs +++ b/crates/stackable-versioned-macros/src/attrs/variant.rs @@ -55,12 +55,14 @@ impl VariantAttributes { ); // Validate names of renames - for rename in &self.common.renames { - if !rename.from.is_case(Case::Pascal) { - errors.push( - Error::custom("renamed variant must use PascalCase") - .with_span(&rename.from.span()), - ) + for change in &self.common.changes { + if let Some(from_name) = &change.from_name { + if !from_name.is_case(Case::Pascal) { + errors.push( + Error::custom("renamed variant must use PascalCase") + .with_span(&from_name.span()), + ) + } } } diff --git a/crates/stackable-versioned-macros/src/codegen/chain.rs b/crates/stackable-versioned-macros/src/codegen/chain.rs index 097214b69..7c6913dc5 100644 --- a/crates/stackable-versioned-macros/src/codegen/chain.rs +++ b/crates/stackable-versioned-macros/src/codegen/chain.rs @@ -91,7 +91,7 @@ mod test { #[case(2, (Some(&"test1"), Some(&"test3")))] #[case(3, (Some(&"test1"), None))] #[case(4, (Some(&"test3"), None))] - fn test(#[case] key: i32, #[case] expected: (Option<&&str>, Option<&&str>)) { + fn neighbors(#[case] key: i32, #[case] expected: (Option<&&str>, Option<&&str>)) { let map = BTreeMap::from([(1, "test1"), (3, "test3")]); let neigbors = map.get_neighbors(&key); diff --git a/crates/stackable-versioned-macros/src/codegen/common/item.rs b/crates/stackable-versioned-macros/src/codegen/common/item.rs index 6baf1ff34..aa40d5abb 100644 --- a/crates/stackable-versioned-macros/src/codegen/common/item.rs +++ b/crates/stackable-versioned-macros/src/codegen/common/item.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, marker::PhantomData, ops::Deref}; use quote::format_ident; -use syn::{spanned::Spanned, Attribute, Ident, Path}; +use syn::{spanned::Spanned, Attribute, Ident, Path, Type}; use crate::{ attrs::common::{ContainerAttributes, ItemAttributes, ValidateVersions}, @@ -21,7 +21,7 @@ use crate::{ pub(crate) trait Item: Sized where A: for<'i> TryFrom<&'i I> + Attributes, - I: Named + Spanned, + I: InnerItem, { /// Creates a new versioned item (struct field or enum variant) by consuming /// the parsed [Field](syn::Field) or [Variant](syn::Variant) and validating @@ -43,6 +43,10 @@ where fn get_ident(&self, version: &ContainerVersion) -> Option<&Ident>; } +pub(crate) trait InnerItem: Named + Spanned { + fn ty(&self) -> Type; +} + /// This trait enables access to the ident of named items, like fields and /// variants. /// @@ -87,11 +91,11 @@ pub(crate) trait Attributes { pub(crate) struct VersionedItem where A: for<'i> TryFrom<&'i I> + Attributes, - I: Named + Spanned, + I: InnerItem, { + pub(crate) original_attributes: Vec, pub(crate) chain: Option, pub(crate) inner: I, - pub(crate) original_attributes: Vec, _marker: PhantomData, } @@ -99,7 +103,7 @@ impl Item for VersionedItem where syn::Error: for<'i> From<>::Error>, A: for<'i> TryFrom<&'i I> + Attributes + ValidateVersions, - I: Named + Spanned, + I: InnerItem, { fn new(item: I, container_attrs: &ContainerAttributes) -> syn::Result { // We use the TryFrom trait here, because the type parameter `A` can use @@ -125,37 +129,56 @@ where // Deprecating an item is always the last state an item can end up in. // For items which are not deprecated, the last change is either the - // latest rename or addition, which is handled below. The ident of the + // latest change or addition, which is handled below. The ident of the // deprecated item is guaranteed to include the 'deprecated_' or // 'DEPRECATED_' prefix. The ident can thus be used as is. if let Some(deprecated) = common_attributes.deprecated { let deprecated_ident = item.ident(); - // When the item is deprecated, any rename which occurred beforehand + // When the item is deprecated, any change which occurred beforehand // requires access to the item ident to infer the item ident for - // the latest rename. + // the latest change. let mut ident = item.cleaned_ident(); + let mut ty = item.ty(); + let mut actions = BTreeMap::new(); actions.insert( *deprecated.since, - ItemStatus::Deprecated { + ItemStatus::Deprecation { previous_ident: ident.clone(), ident: deprecated_ident.clone(), - note: deprecated.note.to_string(), + note: deprecated.note.as_deref().cloned(), }, ); - for rename in common_attributes.renames.iter().rev() { - let from = format_ident!("{from}", from = *rename.from); + for change in common_attributes.changes.iter().rev() { + let from_ident = if let Some(from) = change.from_name.as_deref() { + format_ident!("{from}") + } else { + ident.clone() + }; + + // TODO (@Techassi): This is an awful lot of cloning, can we get + // rid of it? + let from_ty = change + .from_type + .as_ref() + .map(|sv| sv.deref().clone()) + .unwrap_or(ty.clone()); + actions.insert( - *rename.since, - ItemStatus::Renamed { - from: from.clone(), - to: ident, + *change.since, + ItemStatus::Change { + from_ident: from_ident.clone(), + to_ident: ident, + from_type: from_ty.clone(), + to_type: ty, }, ); - ident = from; + + ident = from_ident; + ty = from_ty; } // After the last iteration above (if any) we use the ident for the @@ -163,9 +186,10 @@ where if let Some(added) = common_attributes.added { actions.insert( *added.since, - ItemStatus::Added { + ItemStatus::Addition { default_fn: added.default_fn.deref().clone(), ident, + ty, }, ); } @@ -173,23 +197,43 @@ where Ok(Self { _marker: PhantomData, chain: Some(actions), - inner: item, original_attributes, + inner: item, }) - } else if !common_attributes.renames.is_empty() { - let mut actions = BTreeMap::new(); + } else if !common_attributes.changes.is_empty() { let mut ident = item.ident().clone(); + let mut ty = item.ty(); + + let mut actions = BTreeMap::new(); + + for change in common_attributes.changes.iter().rev() { + dbg!(&ty, &change.since); + let from_ident = if let Some(from) = change.from_name.as_deref() { + format_ident!("{from}") + } else { + ident.clone() + }; + + // TODO (@Techassi): This is an awful lot of cloning, can we get + // rid of it? + let from_ty = change + .from_type + .as_ref() + .map(|sv| sv.deref().clone()) + .unwrap_or(ty.clone()); - for rename in common_attributes.renames.iter().rev() { - let from = format_ident!("{from}", from = *rename.from); actions.insert( - *rename.since, - ItemStatus::Renamed { - from: from.clone(), - to: ident, + *change.since, + ItemStatus::Change { + from_ident: from_ident.clone(), + to_ident: ident, + from_type: from_ty.clone(), + to_type: ty, }, ); - ident = from; + + ident = from_ident; + ty = from_ty; } // After the last iteration above (if any) we use the ident for the @@ -197,9 +241,10 @@ where if let Some(added) = common_attributes.added { actions.insert( *added.since, - ItemStatus::Added { + ItemStatus::Addition { default_fn: added.default_fn.deref().clone(), ident, + ty, }, ); } @@ -207,8 +252,8 @@ where Ok(Self { _marker: PhantomData, chain: Some(actions), - inner: item, original_attributes, + inner: item, }) } else { if let Some(added) = common_attributes.added { @@ -216,25 +261,26 @@ where actions.insert( *added.since, - ItemStatus::Added { + ItemStatus::Addition { default_fn: added.default_fn.deref().clone(), ident: item.ident().clone(), + ty: item.ty(), }, ); return Ok(Self { _marker: PhantomData, chain: Some(actions), - inner: item, original_attributes, + inner: item, }); } Ok(Self { _marker: PhantomData, + original_attributes, chain: None, inner: item, - original_attributes, }) } } @@ -248,13 +294,13 @@ where match chain.get_neighbors(&version.inner) { (None, Some(status)) => match status { - ItemStatus::Added { .. } => { + ItemStatus::Addition { .. } => { chain.insert(version.inner, ItemStatus::NotPresent) } - ItemStatus::Renamed { from, .. } => { - chain.insert(version.inner, ItemStatus::NoChange(from.clone())) + ItemStatus::Change { from_ident, .. } => { + chain.insert(version.inner, ItemStatus::NoChange(from_ident.clone())) } - ItemStatus::Deprecated { previous_ident, .. } => chain + ItemStatus::Deprecation { previous_ident, .. } => chain .insert(version.inner, ItemStatus::NoChange(previous_ident.clone())), ItemStatus::NoChange(ident) => { chain.insert(version.inner, ItemStatus::NoChange(ident.clone())) @@ -263,9 +309,9 @@ where }, (Some(status), None) => { let ident = match status { - ItemStatus::Added { ident, .. } => ident, - ItemStatus::Renamed { to, .. } => to, - ItemStatus::Deprecated { ident, .. } => ident, + ItemStatus::Addition { ident, .. } => ident, + ItemStatus::Change { to_ident, .. } => to_ident, + ItemStatus::Deprecation { ident, .. } => ident, ItemStatus::NoChange(ident) => ident, ItemStatus::NotPresent => unreachable!(), }; @@ -274,8 +320,8 @@ where } (Some(status), Some(_)) => { let ident = match status { - ItemStatus::Added { ident, .. } => ident, - ItemStatus::Renamed { to, .. } => to, + ItemStatus::Addition { ident, .. } => ident, + ItemStatus::Change { to_ident, .. } => to_ident, ItemStatus::NoChange(ident) => ident, _ => unreachable!(), }; @@ -299,20 +345,25 @@ where } } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub(crate) enum ItemStatus { - Added { + Addition { ident: Ident, default_fn: Path, + // NOTE (@Techassi): We need to carry idents and type information in + // nearly every status. Ideally, we would store this in separate maps. + ty: Type, }, - Renamed { - from: Ident, - to: Ident, + Change { + from_ident: Ident, + to_ident: Ident, + from_type: Type, + to_type: Type, }, - Deprecated { + Deprecation { previous_ident: Ident, + note: Option, ident: Ident, - note: String, }, NoChange(Ident), NotPresent, @@ -321,9 +372,9 @@ pub(crate) enum ItemStatus { impl ItemStatus { pub(crate) fn get_ident(&self) -> Option<&Ident> { match &self { - ItemStatus::Added { ident, .. } => Some(ident), - ItemStatus::Renamed { to, .. } => Some(to), - ItemStatus::Deprecated { ident, .. } => Some(ident), + ItemStatus::Addition { ident, .. } => Some(ident), + ItemStatus::Change { to_ident, .. } => Some(to_ident), + ItemStatus::Deprecation { ident, .. } => Some(ident), ItemStatus::NoChange(ident) => Some(ident), ItemStatus::NotPresent => None, } diff --git a/crates/stackable-versioned-macros/src/codegen/venum/variant.rs b/crates/stackable-versioned-macros/src/codegen/venum/variant.rs index e2b7eb6cf..3224b08f6 100644 --- a/crates/stackable-versioned-macros/src/codegen/venum/variant.rs +++ b/crates/stackable-versioned-macros/src/codegen/venum/variant.rs @@ -1,9 +1,9 @@ use std::ops::{Deref, DerefMut}; use darling::FromVariant; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{Ident, Variant}; +use syn::{token::Not, Ident, Type, TypeNever, Variant}; use crate::{ attrs::{ @@ -13,8 +13,8 @@ use crate::{ codegen::{ chain::BTreeMapExt, common::{ - remove_deprecated_variant_prefix, Attributes, ContainerVersion, Item, ItemStatus, - Named, VersionedItem, + remove_deprecated_variant_prefix, Attributes, ContainerVersion, InnerItem, Item, + ItemStatus, Named, VersionedItem, }, }, }; @@ -64,6 +64,17 @@ impl Attributes for VariantAttributes { } } +impl InnerItem for Variant { + fn ty(&self) -> syn::Type { + // FIXME (@Techassi): As we currently don't support enum variants with + // data, we just return the Never type as the code generation code for + // enum variants won't use this type information. + Type::Never(TypeNever { + bang_token: Not([Span::call_site()]), + }) + } +} + impl Named for Variant { fn cleaned_ident(&self) -> Ident { remove_deprecated_variant_prefix(self.ident()) @@ -102,19 +113,35 @@ impl VersionedVariant { container_version.inner ) }) { - ItemStatus::Added { ident, .. } => Some(quote! { + ItemStatus::Addition { ident, .. } => Some(quote! { #(#original_attributes)* #ident, }), - ItemStatus::Renamed { to, .. } => Some(quote! { + ItemStatus::Change { to_ident, .. } => Some(quote! { #(#original_attributes)* - #to, - }), - ItemStatus::Deprecated { ident, .. } => Some(quote! { - #(#original_attributes)* - #[deprecated] - #ident, + #to_ident, }), + ItemStatus::Deprecation { ident, note, .. } => { + // FIXME (@Techassi): Emitting the deprecated attribute + // should cary over even when the item status is + // 'NoChange'. + // TODO (@Techassi): Make the generation of deprecated + // items customizable. When a container is used as a K8s + // CRD, the item must continue to exist, even when + // deprecated. For other versioning use-cases, that + // might not be the case. + let deprecated_attr = if let Some(note) = note { + quote! {#[deprecated = #note]} + } else { + quote! {#[deprecated]} + }; + + Some(quote! { + #(#original_attributes)* + #deprecated_attr + #ident, + }) + } ItemStatus::NoChange(ident) => Some(quote! { #(#original_attributes)* #ident, @@ -149,7 +176,7 @@ impl VersionedVariant { chain.get_expect(&version.inner), chain.get_expect(&next_version.inner), ) { - (_, ItemStatus::Added { .. }) => quote! {}, + (_, ItemStatus::Addition { .. }) => quote! {}, (old, next) => { let old_variant_ident = old .get_ident() diff --git a/crates/stackable-versioned-macros/src/codegen/vstruct/field.rs b/crates/stackable-versioned-macros/src/codegen/vstruct/field.rs index 579cb13e4..fa6c48530 100644 --- a/crates/stackable-versioned-macros/src/codegen/vstruct/field.rs +++ b/crates/stackable-versioned-macros/src/codegen/vstruct/field.rs @@ -11,8 +11,8 @@ use crate::{ field::FieldAttributes, }, codegen::common::{ - remove_deprecated_field_prefix, Attributes, ContainerVersion, Item, ItemStatus, Named, - VersionedItem, + remove_deprecated_field_prefix, Attributes, ContainerVersion, InnerItem, Item, ItemStatus, + Named, VersionedItem, }, }; @@ -63,6 +63,12 @@ impl Attributes for FieldAttributes { } } +impl InnerItem for Field { + fn ty(&self) -> syn::Type { + self.ty.clone() + } +} + impl Named for Field { fn cleaned_ident(&self) -> Ident { let ident = self.ident(); @@ -115,23 +121,41 @@ impl VersionedField { container_version.inner ) }) { - ItemStatus::Added { ident, .. } => Some(quote! { + ItemStatus::Addition { ident, ty, .. } => Some(quote! { #(#original_attributes)* - pub #ident: #field_type, + pub #ident: #ty, }), - ItemStatus::Renamed { to, .. } => Some(quote! { + ItemStatus::Change { + to_ident, to_type, .. + } => Some(quote! { #(#original_attributes)* - pub #to: #field_type, + pub #to_ident: #to_type, }), - ItemStatus::Deprecated { + ItemStatus::Deprecation { ident: field_ident, note, .. - } => Some(quote! { - #(#original_attributes)* - #[deprecated = #note] - pub #field_ident: #field_type, - }), + } => { + // FIXME (@Techassi): Emitting the deprecated attribute + // should cary over even when the item status is + // 'NoChange'. + // TODO (@Techassi): Make the generation of deprecated + // items customizable. When a container is used as a K8s + // CRD, the item must continue to exist, even when + // deprecated. For other versioning use-cases, that + // might not be the case. + let deprecated_attr = if let Some(note) = note { + quote! {#[deprecated = #note]} + } else { + quote! {#[deprecated]} + }; + + Some(quote! { + #(#original_attributes)* + #deprecated_attr + pub #field_ident: #field_type, + }) + } ItemStatus::NotPresent => None, ItemStatus::NoChange(field_ident) => Some(quote! { #(#original_attributes)* @@ -170,9 +194,33 @@ impl VersionedField { .get(&next_version.inner) .expect("internal error: chain must contain container version"), ) { - (_, ItemStatus::Added { ident, default_fn }) => quote! { + ( + _, + ItemStatus::Addition { + ident, default_fn, .. + }, + ) => quote! { #ident: #default_fn(), }, + ( + _, + ItemStatus::Change { + from_ident: old_field_ident, + to_ident, + from_type, + to_type, + }, + ) => { + if from_type == to_type { + quote! { + #to_ident: #from_ident.#old_field_ident, + } + } else { + quote! { + #to_ident: #from_ident.#old_field_ident.into(), + } + } + } (old, next) => { let old_field_ident = old .get_ident() diff --git a/crates/stackable-versioned-macros/src/lib.rs b/crates/stackable-versioned-macros/src/lib.rs index 479eab5e2..d01ffb2ef 100644 --- a/crates/stackable-versioned-macros/src/lib.rs +++ b/crates/stackable-versioned-macros/src/lib.rs @@ -27,7 +27,7 @@ mod consts; /// /// My docs /// #[versioned( /// added(since = "v1beta1"), -/// renamed(since = "v1", from = "gau"), +/// changed(since = "v1", from_name = "gau"), /// deprecated(since = "v2", note = "not empty") /// )] /// deprecated_bar: usize, diff --git a/crates/stackable-versioned-macros/tests/good/attributes_enum.rs b/crates/stackable-versioned-macros/tests/good/attributes_enum.rs index 578f1cc5e..ae73085dd 100644 --- a/crates/stackable-versioned-macros/tests/good/attributes_enum.rs +++ b/crates/stackable-versioned-macros/tests/good/attributes_enum.rs @@ -36,8 +36,8 @@ fn main() { Baz, /// This is will keep changing over time. - #[versioned(renamed(since = "v1beta1", from = "Qoox"))] - #[versioned(renamed(since = "v1", from = "Qaax"))] + #[versioned(changed(since = "v1beta1", from_name = "Qoox"))] + #[versioned(changed(since = "v1", from_name = "Qaax"))] Quux, } } diff --git a/crates/stackable-versioned-macros/tests/good/attributes_struct.rs b/crates/stackable-versioned-macros/tests/good/attributes_struct.rs index 7747a960f..7790d424e 100644 --- a/crates/stackable-versioned-macros/tests/good/attributes_struct.rs +++ b/crates/stackable-versioned-macros/tests/good/attributes_struct.rs @@ -31,8 +31,8 @@ fn main() { baz: String, /// This is will keep changing over time. - #[versioned(renamed(since = "v1beta1", from = "qoox"))] - #[versioned(renamed(since = "v1", from = "qaax"))] + #[versioned(changed(since = "v1beta1", from_name = "qoox"))] + #[versioned(changed(since = "v1", from_name = "qaax"))] quux: String, } diff --git a/crates/stackable-versioned-macros/tests/good/basic.rs b/crates/stackable-versioned-macros/tests/good/basic.rs index 7ba40df21..9bf3d38ce 100644 --- a/crates/stackable-versioned-macros/tests/good/basic.rs +++ b/crates/stackable-versioned-macros/tests/good/basic.rs @@ -11,10 +11,11 @@ use stackable_versioned_macros::versioned; version(name = "v2"), version(name = "v3") )] -struct Foo { +pub(crate) struct Foo { #[versioned( added(since = "v1alpha1"), - renamed(since = "v1beta1", from = "jjj"), + changed(since = "v1beta1", from_name = "jjj", from_type = "u8"), + changed(since = "v1", from_type = "u16"), deprecated(since = "v2", note = "not empty") )] /// Test diff --git a/crates/stackable-versioned-macros/tests/good/rename.rs b/crates/stackable-versioned-macros/tests/good/rename.rs index 0728f4804..dac9b7689 100644 --- a/crates/stackable-versioned-macros/tests/good/rename.rs +++ b/crates/stackable-versioned-macros/tests/good/rename.rs @@ -7,7 +7,7 @@ fn main() { version(name = "v1") )] struct Foo { - #[versioned(renamed(since = "v1beta1", from = "bat"))] + #[versioned(changed(since = "v1beta1", from_name = "bat"))] bar: usize, baz: bool, } diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index 352960457..85d7b2360 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -6,16 +6,23 @@ All notable changes to this project will be documented in this file. ### Added +- Add new `from_type` parameter to `changed()` action ([#844]). - Pass through container and item attributes (including doc-comments). Add attribute for version specific docs ([#847]). - Forward container visibility to generated modules ([#850]). +### Changed + +- BREAKING: Rename `renamed()` action to `changed()` and renamed `from` + parameter to `from_name` ([#844]). + ### Fixed - Report variant rename validation error at the correct span and trim underscores from variants not using PascalCase (#[842]). [#842]: https://github.com/stackabletech/operator-rs/pull/842 +[#844]: https://github.com/stackabletech/operator-rs/pull/844 [#847]: https://github.com/stackabletech/operator-rs/pull/847 [#850]: https://github.com/stackabletech/operator-rs/pull/850 diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index 22925d547..4f3de2f34 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -19,7 +19,7 @@ //! /// My docs //! #[versioned( //! added(since = "v1beta1"), -//! renamed(since = "v1", from = "gau"), +//! changed(since = "v1", from_name = "gau"), //! deprecated(since = "v2", note = "not empty") //! )] //! deprecated_bar: usize,