Skip to content
Open
22 changes: 15 additions & 7 deletions crates/stackable-versioned-macros/src/codegen/container/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,12 @@ pub struct Enum {
/// and where clause.
///
/// We use a [`OnceCell`] here to avoid outer mutability and instead rely on interior mutability
/// to lazily initialize this field. This is done to avoid the macro the re-clone and
/// to lazily initialize this field. This is done to avoid the macro re-cloning and
/// re-construct these generics over and over again.
///
/// NOTE: Do **NOT** make public. Access to this field and its value is handled in a controlled
/// fashion via [`Enum::get_tracking_generics`].
///
/// SAFETY: Using [`OnceCell`] instead of `OnceLock` is fine here, because proc-macros run in a
/// single thread.
tracking_generics: OnceCell<Generics>,
Expand Down Expand Up @@ -205,10 +208,10 @@ impl Enum {
quote! {
#automatically_derived
#allow_attribute
impl #impl_generics #versioned_path::TrackingFrom<#from_module_ident::#enum_ident #type_generics, S> for #for_module_ident::#enum_ident #type_generics
impl #impl_generics #versioned_path::TrackingFrom<#from_module_ident::#enum_ident #type_generics, __SvStatus> for #for_module_ident::#enum_ident #type_generics
#where_clause
{
fn tracking_from(#from_enum_ident: #from_module_ident::#enum_ident #type_generics, status: &mut S, parent: &str) -> Self {
fn tracking_from(#from_enum_ident: #from_module_ident::#enum_ident #type_generics, status: &mut __SvStatus, parent: &str) -> Self {
// TODO (@Techassi): Only emit this if any of the variants below need it
use #versioned_path::TrackingInto as _;

Expand Down Expand Up @@ -247,11 +250,16 @@ impl Enum {
self.tracking_generics.get_or_init(|| {
let versioned_path = &*mod_gen_ctx.crates.versioned;

// The generic type is prefixed to avoid collisions with generic types declared on the
// enum, like `enum Foo<S>`.
let mut tracking_generics = self.generics.clone();
tracking_generics.params.push(parse_quote! { S });
tracking_generics.make_where_clause().predicates.push(
parse_quote! { S: #versioned_path::TrackingStatus + ::core::default::Default },
);
tracking_generics.params.push(parse_quote! { __SvStatus });
tracking_generics
.make_where_clause()
.predicates
.push(parse_quote! {
__SvStatus: #versioned_path::TrackingStatus + ::core::default::Default
});

tracking_generics
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#[cfg(doc)]
use std::cell::OnceCell;
use std::{borrow::Cow, cmp::Ordering};

use indoc::formatdoc;
use itertools::Itertools as _;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::parse_quote;
use syn::{Generics, parse_quote};

use crate::{
codegen::{
Expand Down Expand Up @@ -122,6 +124,10 @@ impl Struct {
// 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 (_, type_generics, _) = self.generics.split_for_impl();
let (impl_generics, _, where_clause) =
self.get_tracking_generics(mod_gen_ctx).split_for_impl();

let from_struct_ident = &self.common.idents.parameter;
let struct_ident = &self.common.idents.original;

Expand Down Expand Up @@ -173,17 +179,15 @@ impl Struct {
let removals = self.generate_tracking_removals(direction, next_version, mod_gen_ctx);
let json_paths = self.generate_json_paths(next_version, mod_gen_ctx);

// TODO (@Techassi): Re-add support for generics
// TODO (@Techassi): We know the status, so we can hard-code it, but hard to track across structs

quote! {
#automatically_derived
#allow_attribute
impl<S> #versioned_path::TrackingFrom<#from_module_ident::#struct_ident, S> for #for_module_ident::#struct_ident
where
S: #versioned_path::TrackingStatus + ::core::default::Default
impl #impl_generics #versioned_path::TrackingFrom<#from_module_ident::#struct_ident #type_generics, __SvStatus> for #for_module_ident::#struct_ident #type_generics
#where_clause
{
fn tracking_from(#from_struct_ident: #from_module_ident::#struct_ident, status: &mut S, parent: &str) -> Self {
fn tracking_from(#from_struct_ident: #from_module_ident::#struct_ident #type_generics, status: &mut __SvStatus, parent: &str) -> Self {
// TODO (@Techassi): Only emit this if any of the fields below need it
use #versioned_path::TrackingInto as _;

Expand Down Expand Up @@ -809,6 +813,32 @@ impl Struct {
TracingTokens::default()
}
}

/// Construct (if needed) and return the [`Struct`]'s generics extended with the tracking status
/// generic type and where clause.
///
/// This function lazily initializes the underlying field in [`Struct`]. Afterwards, the value
/// is re-used without constructing the value from scratch over and over again. This is achieved
/// through interior mutability via [`OnceCell`]. See [`Struct`]'s `tracking_generics` field for
/// more details.
fn get_tracking_generics(&self, mod_gen_ctx: ModuleGenerationContext<'_>) -> &Generics {
self.tracking_generics.get_or_init(|| {
let versioned_path = &*mod_gen_ctx.crates.versioned;

// The generic type is prefixed to avoid collisions with generic types declared on the
// struct, like `struct Foo<S>`.
let mut tracking_generics = self.generics.clone();
tracking_generics.params.push(parse_quote! { __SvStatus });
tracking_generics
.make_where_clause()
.predicates
.push(parse_quote! {
__SvStatus: #versioned_path::TrackingStatus + ::core::default::Default
});

tracking_generics
})
}
}

fn conversion_paths<T>(elements: &[T]) -> Vec<(&T, Cow<'_, [T]>)>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::cell::OnceCell;

use darling::{Error, FromAttributes, Result, util::IdentString};
use itertools::Itertools;
use proc_macro2::TokenStream;
Expand Down Expand Up @@ -78,6 +80,7 @@ impl Container {
};

Ok(Self::Struct(Struct {
tracking_generics: OnceCell::new(),
generics: item_struct.generics,
kubernetes_data,
fields: versioned_fields,
Expand All @@ -100,6 +103,20 @@ pub struct Struct {

/// Generic types of the struct
pub generics: Generics,

/// Generic types of the struct which additionally also include the tracking state generic type
/// and where clause.
///
/// We use a [`OnceCell`] here to avoid outer mutability and instead rely on interior mutability
/// to lazily initialize this field. This is done to avoid the macro re-cloning and
/// re-construct these generics over and over again.
///
/// NOTE: Do **NOT** make public. Access to this field and its value is handled in a controlled
/// fashion via [`Struct::get_tracking_generics`].
///
/// SAFETY: Using [`OnceCell`] instead of `OnceLock` is fine here, because proc-macros run in a
/// single thread.
tracking_generics: OnceCell<Generics>,
}

#[derive(Debug)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,3 @@ fn main() {}
pub struct FooStatus {
foo: String,
}

fn usize_to_u16(input: usize) -> u16 {
input.try_into().unwrap()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use kube::CustomResource;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use stackable_versioned::versioned;
// ---
#[versioned(
version(name = "v1alpha1"),
version(name = "v1beta1"),
version(name = "v1"),
options(k8s(experimental_conversion_tracking))
)]
// ---
pub(crate) mod versioned {
#[versioned(crd(
group = "stackable.tech",
status = FooStatus,
doc = "Test",
))]
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)]
pub(crate) struct FooSpec {
#[versioned(added(since = "v1beta1"), changed(since = "v1", from_name = "bah"))]
bar: usize,

#[versioned(nested)]
baz: Baz<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[allow(dead_code)]
// The generic type is named S on purpose, to ensure it doesn't collide with the generic type
// used for the tracking status in the generated TrackingFrom impls.
pub(crate) struct Baz<S>
where
S: Clone + Default,
{
inner_1: bool,

// FIXME (@Techassi): This currently leads to errors, because the generic type S is emitted
// for v1alpha1 as well. There it is unused. We need to somehow figure out if a generic type
// is used and if so, emit it. Otherwise, don't emit it.
// #[versioned(added(since = "v1beta1"))]
inner_2: S,

// Concrete type on purpose, see the FIXME above. Added in v1 (not v1beta1) so it doesn't
// share the v1beta1 upgrade bucket with FooSpec::bar.
// FIXME: This is a known BUG and will be fixed in the future.
#[versioned(added(since = "v1"))]
inner_3: String,
}
}
// ---
fn main() {}

#[derive(Clone, Debug, Default, JsonSchema, Deserialize, Serialize)]
pub struct FooStatus {
foo: String,
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading