diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/generics_enum.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/generics_enum.rs new file mode 100644 index 000000000..1e0604a9a --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/generics_enum.rs @@ -0,0 +1,9 @@ +#[versioned(version(name = "v1alpha1"), version(name = "v1"))] +// --- +pub enum Foo +where + T: Default, +{ + Bar(T), + Baz, +} diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/generics_module.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/generics_module.rs new file mode 100644 index 000000000..70c5809d0 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/generics_module.rs @@ -0,0 +1,23 @@ +#[versioned( + version(name = "v1alpha1"), + version(name = "v1"), + options(preserve_module) +)] +// --- +pub mod versioned { + struct Foo + where + T: Default, + { + bar: T, + baz: u8, + } + + enum Boom + where + T: Default, + { + Big(T), + Shaq, + } +} diff --git a/crates/stackable-versioned-macros/fixtures/inputs/default/generics_struct.rs b/crates/stackable-versioned-macros/fixtures/inputs/default/generics_struct.rs new file mode 100644 index 000000000..531db5bb9 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/inputs/default/generics_struct.rs @@ -0,0 +1,9 @@ +#[versioned(version(name = "v1alpha1"), version(name = "v1"))] +// --- +pub struct Foo +where + T: Default, +{ + bar: T, + baz: u8, +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@generics_enum.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@generics_enum.rs.snap new file mode 100644 index 000000000..8adccda04 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@generics_enum.rs.snap @@ -0,0 +1,39 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/generics_enum.rs +--- +#[automatically_derived] +pub mod v1alpha1 { + use super::*; + pub enum Foo + where + T: Default, + { + Bar(T), + Baz, + } +} +#[automatically_derived] +impl ::std::convert::From> for v1::Foo +where + T: Default, +{ + fn from(__sv_foo: v1alpha1::Foo) -> Self { + match __sv_foo { + v1alpha1::Foo::Bar(__sv_0) => v1::Foo::Bar(__sv_0), + v1alpha1::Foo::Baz => v1::Foo::Baz, + } + } +} +#[automatically_derived] +pub mod v1 { + use super::*; + pub enum Foo + where + T: Default, + { + Bar(T), + Baz, + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@generics_module.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@generics_module.rs.snap new file mode 100644 index 000000000..72c56daa2 --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@generics_module.rs.snap @@ -0,0 +1,64 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/generics_module.rs +--- +#[automatically_derived] +pub mod versioned { + pub mod v1alpha1 { + use super::*; + pub struct Foo + where + T: Default, + { + pub bar: T, + pub baz: u8, + } + pub enum Boom + where + T: Default, + { + Big(T), + Shaq, + } + } + impl ::std::convert::From> for v1::Foo + where + T: Default, + { + fn from(__sv_foo: v1alpha1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + } + } + } + impl ::std::convert::From> for v1::Boom + where + T: Default, + { + fn from(__sv_boom: v1alpha1::Boom) -> Self { + match __sv_boom { + v1alpha1::Boom::Big(__sv_0) => v1::Boom::Big(__sv_0), + v1alpha1::Boom::Shaq => v1::Boom::Shaq, + } + } + } + pub mod v1 { + use super::*; + pub struct Foo + where + T: Default, + { + pub bar: T, + pub baz: u8, + } + pub enum Boom + where + T: Default, + { + Big(T), + Shaq, + } + } +} diff --git a/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@generics_struct.rs.snap b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@generics_struct.rs.snap new file mode 100644 index 000000000..76781812f --- /dev/null +++ b/crates/stackable-versioned-macros/fixtures/snapshots/stackable_versioned_macros__test__default_snapshots@generics_struct.rs.snap @@ -0,0 +1,39 @@ +--- +source: crates/stackable-versioned-macros/src/lib.rs +expression: formatted +input_file: crates/stackable-versioned-macros/fixtures/inputs/default/generics_struct.rs +--- +#[automatically_derived] +pub mod v1alpha1 { + use super::*; + pub struct Foo + where + T: Default, + { + pub bar: T, + pub baz: u8, + } +} +#[automatically_derived] +impl ::std::convert::From> for v1::Foo +where + T: Default, +{ + fn from(__sv_foo: v1alpha1::Foo) -> Self { + Self { + bar: __sv_foo.bar.into(), + baz: __sv_foo.baz.into(), + } + } +} +#[automatically_derived] +pub mod v1 { + use super::*; + pub struct Foo + where + T: Default, + { + pub bar: T, + pub baz: u8, + } +} diff --git a/crates/stackable-versioned-macros/src/codegen/container/enum.rs b/crates/stackable-versioned-macros/src/codegen/container/enum.rs index f1c6d1e77..d5d95e898 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/enum.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/enum.rs @@ -3,7 +3,7 @@ use std::ops::Not; use darling::{util::IdentString, FromAttributes, Result}; use proc_macro2::TokenStream; use quote::quote; -use syn::ItemEnum; +use syn::{Generics, ItemEnum}; use crate::{ attrs::container::NestedContainerAttributes, @@ -46,6 +46,7 @@ impl Container { }; Ok(Self::Enum(Enum { + generics: item_enum.generics, variants: versioned_variants, common, })) @@ -82,6 +83,7 @@ impl Container { }; Ok(Self::Enum(Enum { + generics: item_enum.generics, variants: versioned_variants, common, })) @@ -96,12 +98,16 @@ pub(crate) struct Enum { /// Common container data which is shared between enums and structs. pub(crate) common: CommonContainerData, + + /// Generic types of the enum + pub generics: Generics, } // Common token generation impl Enum { /// Generates code for the enum definition. pub(crate) fn generate_definition(&self, version: &VersionDefinition) -> TokenStream { + let (_, type_generics, where_clause) = self.generics.split_for_impl(); let original_attributes = &self.common.original_attributes; let ident = &self.common.idents.original; let version_docs = &version.docs; @@ -114,7 +120,7 @@ impl Enum { quote! { #(#[doc = #version_docs])* #(#original_attributes)* - pub enum #ident { + pub enum #ident #type_generics #where_clause { #variants } } @@ -133,6 +139,12 @@ impl Enum { 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 enum_ident = &self.common.idents.original; let from_ident = &self.common.idents.from; @@ -158,8 +170,10 @@ impl Enum { Some(quote! { #automatically_derived #allow_attribute - impl ::std::convert::From<#version_ident::#enum_ident> for #next_version_ident::#enum_ident { - fn from(#from_ident: #version_ident::#enum_ident) -> Self { + impl #impl_generics ::std::convert::From<#version_ident::#enum_ident #type_generics> for #next_version_ident::#enum_ident #type_generics + #where_clause + { + fn from(#from_ident: #version_ident::#enum_ident #type_generics) -> Self { match #from_ident { #variants } diff --git a/crates/stackable-versioned-macros/src/codegen/container/struct.rs b/crates/stackable-versioned-macros/src/codegen/container/struct.rs index e98d4d763..a9505c3fb 100644 --- a/crates/stackable-versioned-macros/src/codegen/container/struct.rs +++ b/crates/stackable-versioned-macros/src/codegen/container/struct.rs @@ -3,7 +3,7 @@ use std::ops::Not; use darling::{util::IdentString, Error, FromAttributes, Result}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use syn::{parse_quote, ItemStruct, Path, Visibility}; +use syn::{parse_quote, Generics, ItemStruct, Path, Visibility}; use crate::{ attrs::container::NestedContainerAttributes, @@ -58,6 +58,7 @@ impl Container { }; Ok(Self::Struct(Struct { + generics: item_struct.generics, fields: versioned_fields, common, })) @@ -113,6 +114,7 @@ impl Container { }; Ok(Self::Struct(Struct { + generics: item_struct.generics, fields: versioned_fields, common, })) @@ -123,16 +125,20 @@ impl Container { pub(crate) struct Struct { /// List of fields defined in the original struct. How, and if, an item /// should generate code, is decided by the currently generated version. - pub(crate) fields: Vec, + pub fields: Vec, /// Common container data which is shared between structs and enums. - pub(crate) common: CommonContainerData, + pub common: CommonContainerData, + + /// Generic types of the struct + pub generics: Generics, } // Common token generation impl Struct { /// Generates code for the struct definition. pub(crate) fn generate_definition(&self, version: &VersionDefinition) -> TokenStream { + let (_, type_generics, where_clause) = self.generics.split_for_impl(); let original_attributes = &self.common.original_attributes; let ident = &self.common.idents.original; let version_docs = &version.docs; @@ -149,7 +155,7 @@ impl Struct { #(#[doc = #version_docs])* #(#original_attributes)* #kube_attribute - pub struct #ident { + pub struct #ident #type_generics #where_clause { #fields } } @@ -168,6 +174,12 @@ impl Struct { 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 struct_ident = &self.common.idents.original; let from_struct_ident = &self.common.idents.from; @@ -194,8 +206,10 @@ impl Struct { Some(quote! { #automatically_derived #allow_attribute - impl ::std::convert::From<#from_module_ident::#struct_ident> for #for_module_ident::#struct_ident { - fn from(#from_struct_ident: #from_module_ident::#struct_ident) -> Self { + 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 } diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index 44b767b84..0060a3a6b 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -4,11 +4,16 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Add basic support for generic types in struct and enum definitions ([#969]). + ### Changed - BREAKING: Move `preserve_module` option into `options` to unify option interface ([#961]). [#961]: https://github.com/stackabletech/operator-rs/pull/961 +[#969]: https://github.com/stackabletech/operator-rs/pull/969 ## [0.5.1] - 2025-02-14