diff --git a/.vscode/settings.json b/.vscode/settings.json index 8c9fdee9..2571fe27 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,8 @@ "rust-analyzer.rustfmt.overrideCommand": [ "rustfmt", "+nightly-2025-05-26", + "--edition", + "2024", "--" ] } diff --git a/crates/stackable-versioned-macros/src/codegen/container/enum.rs b/crates/stackable-versioned-macros/src/codegen/container/enum.rs index 27814543..7ca6efb9 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/enum.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/enum.rs @@ -10,7 +10,7 @@ use crate::{ codegen::{ ItemStatus, StandaloneContainerAttributes, VersionDefinition, changes::Neighbors, - container::{CommonContainerData, Container, ContainerIdents, ContainerOptions}, + container::{CommonContainerData, Container, ContainerIdents, ContainerOptions, Direction}, item::VersionedVariant, }, }; @@ -122,9 +122,10 @@ impl Enum { } } - /// Generates code for the `From for NextVersion` implementation. - pub fn generate_upgrade_from_impl( + // TODO (@Techassi): Add doc comments + pub fn generate_from_impl( &self, + direction: Direction, version: &VersionDefinition, next_version: Option<&VersionDefinition>, add_attributes: bool, @@ -133,119 +134,69 @@ impl Enum { return None; } - match next_version { - Some(next_version) => { - // TODO (@Techassi): Support generic types which have been removed in newer versions, - // but need to exist for older versions How do we represent that? Because the - // defined struct always represents the latest version. I guess we could generally - // advise against using generic types, but if you have to, avoid removing it in - // later versions. - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let from_enum_ident = &self.common.idents.parameter; - let enum_ident = &self.common.idents.original; - - let for_module_ident = &next_version.idents.module; - let from_module_ident = &version.idents.module; - - let variants: TokenStream = self - .variants - .iter() - .filter_map(|v| { - v.generate_for_upgrade_from_impl(version, next_version, enum_ident) - }) - .collect(); - - // Include allow(deprecated) only when this or the next version is - // deprecated. Also include it, when a variant in this or the next - // version is deprecated. - let allow_attribute = (version.deprecated.is_some() - || next_version.deprecated.is_some() - || self.is_any_variant_deprecated(version) - || self.is_any_variant_deprecated(next_version)) - .then_some(quote! { #[allow(deprecated)] }); - - // Only add the #[automatically_derived] attribute only if this impl is used - // outside of a module (in standalone mode). - let automatically_derived = add_attributes - .not() - .then(|| quote! {#[automatically_derived]}); - - Some(quote! { - #automatically_derived - #allow_attribute - impl #impl_generics ::std::convert::From<#from_module_ident::#enum_ident #type_generics> for #for_module_ident::#enum_ident #type_generics - #where_clause - { - fn from(#from_enum_ident: #from_module_ident::#enum_ident #type_generics) -> Self { - match #from_enum_ident { - #variants - } - } - } - }) - } - None => None, - } - } - - pub fn generate_downgrade_from_impl( - &self, - version: &VersionDefinition, - next_version: Option<&VersionDefinition>, - add_attributes: bool, - ) -> Option { - if version.skip_from || self.common.options.skip_from { - return None; - } - - match next_version { - Some(next_version) => { - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let from_enum_ident = &self.common.idents.parameter; - let enum_ident = &self.common.idents.original; - - let from_module_ident = &next_version.idents.module; - let for_module_ident = &version.idents.module; - - let variants: TokenStream = self - .variants + next_version.map(|next_version| { + // TODO (@Techassi): Support generic types which have been removed in newer versions, + // but need to exist for older versions How do we represent that? Because the + // defined struct always represents the latest version. I guess we could generally + // advise against using generic types, but if you have to, avoid removing it in + // later versions. + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let from_enum_ident = &self.common.idents.parameter; + let enum_ident = &self.common.idents.original; + + // Include allow(deprecated) only when this or the next version is + // deprecated. Also include it, when a variant in this or the next + // version is deprecated. + let allow_attribute = (version.deprecated.is_some() + || next_version.deprecated.is_some() + || self.is_any_variant_deprecated(version) + || self.is_any_variant_deprecated(next_version)) + .then_some(quote! { #[allow(deprecated)] }); + + // Only add the #[automatically_derived] attribute only if this impl is used + // outside of a module (in standalone mode). + let automatically_derived = add_attributes + .not() + .then(|| quote! {#[automatically_derived]}); + + let variants = |direction: Direction| -> TokenStream { + self.variants .iter() .filter_map(|v| { - v.generate_for_downgrade_from_impl(version, next_version, enum_ident) + v.generate_for_from_impl(direction, version, next_version, enum_ident) }) - .collect(); - - // Include allow(deprecated) only when this or the next version is - // deprecated. Also include it, when a variant in this or the next - // version is deprecated. - let allow_attribute = (version.deprecated.is_some() - || next_version.deprecated.is_some() - || self.is_any_variant_deprecated(version) - || self.is_any_variant_deprecated(next_version)) - .then_some(quote! { #[allow(deprecated)] }); - - // Only add the #[automatically_derived] attribute only if this impl is used - // outside of a module (in standalone mode). - let automatically_derived = add_attributes - .not() - .then(|| quote! {#[automatically_derived]}); - - Some(quote! { - #automatically_derived - #allow_attribute - impl #impl_generics ::std::convert::From<#from_module_ident::#enum_ident #type_generics> for #for_module_ident::#enum_ident #type_generics - #where_clause - { - fn from(#from_enum_ident: #from_module_ident::#enum_ident #type_generics) -> Self { - match #from_enum_ident { - #variants - } + .collect() + }; + + let (variants, for_module_ident, from_module_ident) = match direction { + Direction::Upgrade => { + let for_module_ident = &next_version.idents.module; + let from_module_ident = &version.idents.module; + + (variants(Direction::Upgrade), for_module_ident, from_module_ident) + }, + Direction::Downgrade => { + let for_module_ident = &version.idents.module; + let from_module_ident = &next_version.idents.module; + + (variants(Direction::Downgrade), for_module_ident, from_module_ident) + }, + }; + + quote! { + #automatically_derived + #allow_attribute + impl #impl_generics ::std::convert::From<#from_module_ident::#enum_ident #type_generics> for #for_module_ident::#enum_ident #type_generics + #where_clause + { + fn from(#from_enum_ident: #from_module_ident::#enum_ident #type_generics) -> Self { + match #from_enum_ident { + #variants } } - }) + } } - None => None, - } + }) } /// Returns whether any variant is deprecated in the provided `version`. diff --git a/crates/stackable-versioned-macros/src/codegen/container/mod.rs b/crates/stackable-versioned-macros/src/codegen/container/mod.rs index ed72fbe4..8699e673 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/mod.rs @@ -46,36 +46,20 @@ impl Container { } } - /// Generates the `From for NextVersion` implementation for the container. - pub fn generate_upgrade_from_impl( + pub fn generate_from_impl( &self, + direction: Direction, version: &VersionDefinition, next_version: Option<&VersionDefinition>, add_attributes: bool, ) -> Option { match self { Container::Struct(s) => { - s.generate_upgrade_from_impl(version, next_version, add_attributes) + // TODO (@Techassi): Decide here (based on K8s args) what we want to generate + s.generate_from_impl(direction, version, next_version, add_attributes) } Container::Enum(e) => { - e.generate_upgrade_from_impl(version, next_version, add_attributes) - } - } - } - - /// Generates the `From for Version` implementation for the container. - pub fn generate_downgrade_from_impl( - &self, - version: &VersionDefinition, - next_version: Option<&VersionDefinition>, - add_attributes: bool, - ) -> Option { - match self { - Container::Struct(s) => { - s.generate_downgrade_from_impl(version, next_version, add_attributes) - } - Container::Enum(e) => { - e.generate_downgrade_from_impl(version, next_version, add_attributes) + e.generate_from_impl(direction, version, next_version, add_attributes) } } } @@ -183,12 +167,15 @@ impl StandaloneContainer { // Generate the From impl for upgrading the CRD. let upgrade_from_impl = self.container - .generate_upgrade_from_impl(version, next_version, false); + .generate_from_impl(Direction::Upgrade, version, next_version, false); // Generate the From impl for downgrading the CRD. - let downgrade_from_impl = - self.container - .generate_downgrade_from_impl(version, next_version, false); + let downgrade_from_impl = self.container.generate_from_impl( + Direction::Downgrade, + version, + next_version, + false, + ); // Add the #[deprecated] attribute when the version is marked as deprecated. let deprecated_attribute = version @@ -287,3 +274,9 @@ pub struct ContainerOptions { pub kubernetes_arguments: Option, pub skip_from: bool, } + +#[derive(Copy, Clone, Debug)] +pub enum Direction { + Upgrade, + Downgrade, +} diff --git a/crates/stackable-versioned-macros/src/codegen/container/struct/mod.rs b/crates/stackable-versioned-macros/src/codegen/container/struct/mod.rs index ae13fe21..1bdbb25a 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/struct/mod.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/struct/mod.rs @@ -10,7 +10,7 @@ use crate::{ codegen::{ ItemStatus, StandaloneContainerAttributes, VersionDefinition, changes::Neighbors, - container::{CommonContainerData, Container, ContainerIdents, ContainerOptions}, + container::{CommonContainerData, Container, ContainerIdents, ContainerOptions, Direction}, item::VersionedField, }, }; @@ -161,14 +161,14 @@ impl Struct { } } - // TODO (@Techassi): It looks like some of the stuff from the upgrade and downgrade functions - // can be combined into a single piece of code. Figure out a nice way to do that. + // TODO (@Techassi): Adjust doc comment /// Generates code for the `From for NextVersion` implementation. /// /// The `add_attributes` parameter declares if attributes (macros) should be added to the /// generated `From` impl block. - pub fn generate_upgrade_from_impl( + pub fn generate_from_impl( &self, + direction: Direction, version: &VersionDefinition, next_version: Option<&VersionDefinition>, add_attributes: bool, @@ -177,119 +177,75 @@ impl Struct { return None; } - match next_version { - Some(next_version) => { - // TODO (@Techassi): Support generic types which have been removed in newer versions, - // but need to exist for older versions How do we represent that? Because the - // defined struct always represents the latest version. I guess we could generally - // advise against using generic types, but if you have to, avoid removing it in - // later versions. - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let from_struct_ident = &self.common.idents.parameter; - let struct_ident = &self.common.idents.original; - - let for_module_ident = &next_version.idents.module; - let from_module_ident = &version.idents.module; - - let fields: TokenStream = self + next_version.map(|next_version| { + // TODO (@Techassi): Support generic types which have been removed in newer versions, + // but need to exist for older versions How do we represent that? Because the + // defined struct always represents the latest version. I guess we could generally + // advise against using generic types, but if you have to, avoid removing it in + // later versions. + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let from_struct_ident = &self.common.idents.parameter; + let struct_ident = &self.common.idents.original; + + // Include allow(deprecated) only when this or the next version is + // deprecated. Also include it, when a field in this or the next + // version is deprecated. + let allow_attribute = (version.deprecated.is_some() + || next_version.deprecated.is_some() + || self.is_any_field_deprecated(version) + || self.is_any_field_deprecated(next_version)) + .then(|| quote! { #[allow(deprecated)] }); + + // Only add the #[automatically_derived] attribute only if this impl is used + // outside of a module (in standalone mode). + let automatically_derived = add_attributes + .not() + .then(|| quote! {#[automatically_derived]}); + + let fields = |direction: Direction| -> TokenStream { + self .fields .iter() - .map(|f| { - f.generate_for_upgrade_from_impl(version, next_version, from_struct_ident) + .filter_map(|f| { + f.generate_for_from_impl( + direction, + version, + next_version, + from_struct_ident, + ) }) - .collect(); - - // Include allow(deprecated) only when this or the next version is - // deprecated. Also include it, when a field in this or the next - // version is deprecated. - let allow_attribute = (version.deprecated.is_some() - || next_version.deprecated.is_some() - || self.is_any_field_deprecated(version) - || self.is_any_field_deprecated(next_version)) - .then(|| quote! { #[allow(deprecated)] }); - - // Only add the #[automatically_derived] attribute only if this impl is used - // outside of a module (in standalone mode). - let automatically_derived = add_attributes - .not() - .then(|| quote! {#[automatically_derived]}); - - Some(quote! { - #automatically_derived - #allow_attribute - impl #impl_generics ::std::convert::From<#from_module_ident::#struct_ident #type_generics> for #for_module_ident::#struct_ident #type_generics - #where_clause - { - fn from(#from_struct_ident: #from_module_ident::#struct_ident #type_generics) -> Self { - Self { - #fields - } - } - } - }) - } - None => None, - } - } - - pub fn generate_downgrade_from_impl( - &self, - version: &VersionDefinition, - next_version: Option<&VersionDefinition>, - add_attributes: bool, - ) -> Option { - if version.skip_from || self.common.options.skip_from { - return None; - } - - match next_version { - Some(next_version) => { - let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); - let from_struct_ident = &self.common.idents.parameter; - let struct_ident = &self.common.idents.original; - - let from_module_ident = &next_version.idents.module; - let for_module_ident = &version.idents.module; - - let fields: TokenStream = self - .fields - .iter() - .map(|f| { - f.generate_for_downgrade_from_impl(version, next_version, from_struct_ident) - }) - .collect(); - - // Include allow(deprecated) only when this or the next version is - // deprecated. Also include it, when a field in this or the next - // version is deprecated. - let allow_attribute = (version.deprecated.is_some() - || next_version.deprecated.is_some() - || self.is_any_field_deprecated(version) - || self.is_any_field_deprecated(next_version)) - .then(|| quote! { #[allow(deprecated)] }); - - // Only add the #[automatically_derived] attribute only if this impl is used - // outside of a module (in standalone mode). - let automatically_derived = add_attributes - .not() - .then(|| quote! {#[automatically_derived]}); - - Some(quote! { - #automatically_derived - #allow_attribute - impl #impl_generics ::std::convert::From<#from_module_ident::#struct_ident #type_generics> for #for_module_ident::#struct_ident #type_generics - #where_clause - { - fn from(#from_struct_ident: #from_module_ident::#struct_ident #type_generics) -> Self { - Self { - #fields - } + .collect() + }; + + let (fields, for_module_ident, from_module_ident) = match direction { + Direction::Upgrade => { + let from_module_ident = &version.idents.module; + let for_module_ident = &next_version.idents.module; + + (fields(Direction::Upgrade), for_module_ident, from_module_ident) + } + Direction::Downgrade => { + let from_module_ident = &next_version.idents.module; + let for_module_ident = &version.idents.module; + + (fields(Direction::Downgrade), for_module_ident, from_module_ident) + } + }; + + quote! { + #automatically_derived + #allow_attribute + impl #impl_generics ::std::convert::From<#from_module_ident::#struct_ident #type_generics> for #for_module_ident::#struct_ident #type_generics + #where_clause + { + fn from(#from_struct_ident: #from_module_ident::#struct_ident #type_generics) -> Self { + Self { + #fields } } - }) + } } - None => None, - } + }) } /// Returns whether any field is deprecated in the provided `version`. diff --git a/crates/stackable-versioned-macros/src/codegen/item/field.rs b/crates/stackable-versioned-macros/src/codegen/item/field.rs index 358a9400..9cfd403b 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/field.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/field.rs @@ -11,6 +11,7 @@ use crate::{ codegen::{ ItemStatus, VersionDefinition, changes::{BTreeMapExt, ChangesetExt}, + container::Direction, }, utils::FieldIdent, }; @@ -140,13 +141,13 @@ impl VersionedField { } } - // TODO (@Techassi): This should probably return an optional token stream. - pub fn generate_for_upgrade_from_impl( + pub fn generate_for_from_impl( &self, + direction: Direction, version: &VersionDefinition, next_version: &VersionDefinition, from_struct_ident: &IdentString, - ) -> TokenStream { + ) -> Option { match &self.changes { Some(changes) => { let next_change = changes.get_expect(&next_version.inner); @@ -156,93 +157,45 @@ impl VersionedField { // If both this status and the next one is NotPresent, which means // a field was introduced after a bunch of versions, we don't // need to generate any code for the From impl. - (ItemStatus::NotPresent, ItemStatus::NotPresent) => { - quote! {} - } + (ItemStatus::NotPresent, ItemStatus::NotPresent) => None, ( _, ItemStatus::Addition { ident, default_fn, .. }, - ) => quote! { - #ident: #default_fn(), + ) => match direction { + Direction::Upgrade => Some(quote! { #ident: #default_fn(), }), + Direction::Downgrade => None, }, - ( - _, - ItemStatus::Change { - from_ident: old_field_ident, - upgrade_with, - to_ident, - .. - }, - ) => match upgrade_with { - // The user specified a custom conversion function which - // will be used here instead of the default .into() call - // which utilizes From impls. - Some(upgrade_fn) => quote! { - #to_ident: #upgrade_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(), - } - } - } - } - None => { - let field_ident = &*self.ident; - - quote! { - #field_ident: #from_struct_ident.#field_ident.into(), - } - } - } - } - - pub fn generate_for_downgrade_from_impl( - &self, - version: &VersionDefinition, - next_version: &VersionDefinition, - from_struct_ident: &IdentString, - ) -> TokenStream { - match &self.changes { - Some(changes) => { - let next_change = changes.get_expect(&next_version.inner); - let change = changes.get_expect(&version.inner); - - match (change, next_change) { - // If both this status and the next one is NotPresent, which means - // a field was introduced after a bunch of versions, we don't - // need to generate any code for the From impl. - (ItemStatus::NotPresent, ItemStatus::NotPresent) => { - quote! {} - } - (_, ItemStatus::Addition { .. }) => quote! {}, ( _, ItemStatus::Change { downgrade_with, - from_ident: old_field_ident, + upgrade_with, + from_ident, to_ident, .. }, - ) => match downgrade_with { - Some(downgrade_fn) => quote! { - #old_field_ident: #downgrade_fn(#from_struct_ident.#to_ident), + ) => match direction { + Direction::Upgrade => match upgrade_with { + // The user specified a custom conversion function which + // will be used here instead of the default .into() call + // which utilizes From impls. + Some(upgrade_fn) => Some(quote! { + #to_ident: #upgrade_fn(#from_struct_ident.#from_ident), + }), + // Default .into() call using From impls. + None => Some(quote! { + #to_ident: #from_struct_ident.#from_ident.into(), + }), }, - None => quote! { - #old_field_ident: #from_struct_ident.#to_ident.into(), + Direction::Downgrade => match downgrade_with { + Some(downgrade_fn) => Some(quote! { + #from_ident: #downgrade_fn(#from_struct_ident.#to_ident), + }), + None => Some(quote! { + #from_ident: #from_struct_ident.#to_ident.into(), + }), }, }, (old, next) => { @@ -252,8 +205,13 @@ impl VersionedField { // 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! { - #old_field_ident: #from_struct_ident.#next_field_ident.into(), + match direction { + Direction::Upgrade => Some(quote! { + #next_field_ident: #from_struct_ident.#old_field_ident.into(), + }), + Direction::Downgrade => Some(quote! { + #old_field_ident: #from_struct_ident.#next_field_ident.into(), + }), } } } @@ -261,9 +219,9 @@ impl VersionedField { None => { let field_ident = &*self.ident; - quote! { + Some(quote! { #field_ident: #from_struct_ident.#field_ident.into(), - } + }) } } } diff --git a/crates/stackable-versioned-macros/src/codegen/item/variant.rs b/crates/stackable-versioned-macros/src/codegen/item/variant.rs index b9c30f75..30dd188d 100644 --- a/crates/stackable-versioned-macros/src/codegen/item/variant.rs +++ b/crates/stackable-versioned-macros/src/codegen/item/variant.rs @@ -11,6 +11,7 @@ use crate::{ codegen::{ ItemStatus, VersionDefinition, changes::{BTreeMapExt, ChangesetExt}, + container::Direction, }, utils::VariantIdent, }; @@ -130,8 +131,9 @@ impl VersionedVariant { } } - pub fn generate_for_upgrade_from_impl( + pub fn generate_for_from_impl( &self, + direction: Direction, version: &VersionDefinition, next_version: &VersionDefinition, enum_ident: &IdentString, @@ -159,9 +161,10 @@ impl VersionedVariant { #next_version_ident::#enum_ident::#next_variant_ident #variant_fields }; - Some(quote! { - #old => #next, - }) + match direction { + Direction::Upgrade => Some(quote! { #old => #next, }), + Direction::Downgrade => Some(quote! { #next => #old, }), + } } } } @@ -177,64 +180,11 @@ impl VersionedVariant { #next_version_ident::#enum_ident::#variant_ident #variant_fields }; - Some(quote! { - #old => #next, - }) - } - } - } - - pub fn generate_for_downgrade_from_impl( - &self, - version: &VersionDefinition, - next_version: &VersionDefinition, - enum_ident: &IdentString, - ) -> Option { - let variant_fields = self.fields_as_token_stream(); - - match &self.changes { - Some(changes) => { - let next_change = changes.get_expect(&next_version.inner); - let change = changes.get_expect(&version.inner); - - match (change, next_change) { - (_, ItemStatus::Addition { .. }) => None, - (old, next) => { - let next_version_ident = &next_version.idents.module; - let old_version_ident = &version.idents.module; - - let next_variant_ident = next.get_ident(); - let old_variant_ident = old.get_ident(); - - let old = quote! { - #old_version_ident::#enum_ident::#old_variant_ident #variant_fields - }; - let next = quote! { - #next_version_ident::#enum_ident::#next_variant_ident #variant_fields - }; - - Some(quote! { - #next => #old, - }) - } + match direction { + Direction::Upgrade => Some(quote! { #old => #next, }), + Direction::Downgrade => Some(quote! { #next => #old, }), } } - None => { - let next_version_ident = &next_version.idents.module; - let old_version_ident = &version.idents.module; - let variant_ident = &*self.ident; - - let old = quote! { - #old_version_ident::#enum_ident::#variant_ident #variant_fields - }; - let next = quote! { - #next_version_ident::#enum_ident::#variant_ident #variant_fields - }; - - Some(quote! { - #next => #old, - }) - } } } diff --git a/crates/stackable-versioned-macros/src/codegen/module.rs b/crates/stackable-versioned-macros/src/codegen/module.rs index a66ea689..3525602d 100644 --- a/crates/stackable-versioned-macros/src/codegen/module.rs +++ b/crates/stackable-versioned-macros/src/codegen/module.rs @@ -7,7 +7,10 @@ use syn::{Ident, Item, ItemMod, ItemUse, Visibility, token::Pub}; use crate::{ ModuleAttributes, - codegen::{KubernetesTokens, VersionDefinition, container::Container}, + codegen::{ + KubernetesTokens, VersionDefinition, + container::{Container, Direction}, + }, }; /// A versioned module. @@ -60,12 +63,18 @@ impl Module { for item in items { match item { Item::Enum(item_enum) => { - let container = Container::new_enum_nested(item_enum, &versions)?; - containers.push(container); + if let Some(container) = + errors.handle(Container::new_enum_nested(item_enum, &versions)) + { + containers.push(container); + }; } Item::Struct(item_struct) => { - let container = Container::new_struct_nested(item_struct, &versions)?; - containers.push(container); + if let Some(container) = + errors.handle(Container::new_struct_nested(item_struct, &versions)) + { + containers.push(container); + } } Item::Mod(submodule) => { if !versions @@ -159,13 +168,15 @@ impl Module { container_definitions.extend(container.generate_definition(version)); if !self.skip_from { - from_impls.extend(container.generate_upgrade_from_impl( + from_impls.extend(container.generate_from_impl( + Direction::Upgrade, version, next_version, self.preserve_module, )); - from_impls.extend(container.generate_downgrade_from_impl( + from_impls.extend(container.generate_from_impl( + Direction::Downgrade, version, next_version, self.preserve_module,