From 44291d50fe457a4a667baeeb236608df8055b570 Mon Sep 17 00:00:00 2001 From: Techassi Date: Wed, 22 May 2024 17:05:21 +0200 Subject: [PATCH 1/4] Update changelog --- crates/stackable-versioned/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/stackable-versioned/CHANGELOG.md b/crates/stackable-versioned/CHANGELOG.md index fa261cf80..c026f6e0a 100644 --- a/crates/stackable-versioned/CHANGELOG.md +++ b/crates/stackable-versioned/CHANGELOG.md @@ -4,9 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- Change from derive macro to attribute macro to be able to generate code + _in place_ instead of _appending_ new code ([#CHANGEME]). - Improve action chain generation ([#784]). -[#784](ttps://github.com/stackabletech/operator-rs/pull/784) +[#784](https://github.com/stackabletech/operator-rs/pull/784) +[#CHANGEME](https://github.com/stackabletech/operator-rs/pull/CHANGEME) ## [0.1.0] - 2024-05-08 From f68977f10286281e6cb204bd48782c22bd2380d4 Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 23 May 2024 14:41:19 +0200 Subject: [PATCH 2/4] Add basic adjustements to support attribute macro --- .../src/attrs/container.rs | 11 +++------ crates/stackable-versioned/src/gen/mod.rs | 18 ++++----------- crates/stackable-versioned/src/lib.rs | 23 ++++++++++++++++--- crates/stackable-versioned/tests/basic.rs | 3 +-- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/crates/stackable-versioned/src/attrs/container.rs b/crates/stackable-versioned/src/attrs/container.rs index 56ef87045..b0314bd26 100644 --- a/crates/stackable-versioned/src/attrs/container.rs +++ b/crates/stackable-versioned/src/attrs/container.rs @@ -2,7 +2,7 @@ use std::{cmp::Ordering, collections::HashSet, ops::Deref}; use darling::{ util::{Flag, SpannedValue}, - Error, FromDeriveInput, FromMeta, Result, + Error, FromMeta, Result, }; use k8s_version::Version; @@ -12,13 +12,8 @@ use k8s_version::Version; /// /// - `version`, which can occur one or more times. See [`VersionAttributes`]. /// - `options`, which allow further customization of the generated code. See [`ContainerOptions`]. -#[derive(Clone, Debug, FromDeriveInput)] -#[darling( - attributes(versioned), - supports(struct_named), - forward_attrs(allow, doc, cfg, serde), - and_then = ContainerAttributes::validate -)] +#[derive(Debug, FromMeta)] +#[darling(and_then = ContainerAttributes::validate)] pub(crate) struct ContainerAttributes { #[darling(multiple, rename = "version")] pub(crate) versions: SpannedValue>, diff --git a/crates/stackable-versioned/src/gen/mod.rs b/crates/stackable-versioned/src/gen/mod.rs index 4c3729873..61e331eec 100644 --- a/crates/stackable-versioned/src/gen/mod.rs +++ b/crates/stackable-versioned/src/gen/mod.rs @@ -1,11 +1,10 @@ -use darling::FromDeriveInput; use proc_macro2::TokenStream; use quote::ToTokens; use syn::{spanned::Spanned, Data, DeriveInput, Error, Result}; use crate::{ attrs::container::ContainerAttributes, - gen::{venum::VersionedEnum, version::ContainerVersion, vstruct::VersionedStruct}, + gen::{version::ContainerVersion, vstruct::VersionedStruct}, }; pub(crate) mod field; @@ -26,20 +25,13 @@ pub(crate) mod vstruct; // TODO (@Techassi): Think about how we can handle nested structs / enums which // are also versioned. -pub(crate) fn expand(input: DeriveInput) -> Result { - // Extract container attributes - let attributes = ContainerAttributes::from_derive_input(&input)?; - - // Validate container shape and generate code +pub(crate) fn expand(attrs: ContainerAttributes, input: DeriveInput) -> Result { let expanded = match input.data { - Data::Struct(data) => { - VersionedStruct::new(input.ident, data, attributes)?.to_token_stream() - } - Data::Enum(data) => VersionedEnum::new(input.ident, data, attributes)?.to_token_stream(), - Data::Union(_) => { + Data::Struct(data) => VersionedStruct::new(input.ident, data, attrs)?.to_token_stream(), + _ => { return Err(Error::new( input.span(), - "derive macro `Versioned` only supports structs and enums", + "attribute macro `versioned` only supports structs", )) } }; diff --git a/crates/stackable-versioned/src/lib.rs b/crates/stackable-versioned/src/lib.rs index 33aeecf67..34d79cf2c 100644 --- a/crates/stackable-versioned/src/lib.rs +++ b/crates/stackable-versioned/src/lib.rs @@ -1,15 +1,32 @@ +use darling::{ast::NestedMeta, FromMeta}; use proc_macro::TokenStream; use syn::{DeriveInput, Error}; +use crate::attrs::container::ContainerAttributes; + mod attrs; mod consts; mod gen; -#[proc_macro_derive(Versioned, attributes(versioned))] -pub fn versioned_macro_derive(input: TokenStream) -> TokenStream { +#[proc_macro_attribute] +pub fn versioned(attrs: TokenStream, input: TokenStream) -> TokenStream { + let attrs = match NestedMeta::parse_meta_list(attrs.into()) { + Ok(attrs) => match ContainerAttributes::from_list(&attrs) { + Ok(attrs) => attrs, + Err(err) => return err.write_errors().into(), + }, + Err(err) => return darling::Error::from(err).write_errors().into(), + }; + + // NOTE (@Techassi): For now, we can just use the DeriveInput type here, + // because we only support structs (and eventually enums) to be versioned. + // In the future - if we decide to support modules - this requires + // adjustments to also support modules. One possible solution might be to + // use an enum with two variants: Container(DeriveInput) and + // Module(ItemMod). let input = syn::parse_macro_input!(input as DeriveInput); - gen::expand(input) + gen::expand(attrs, input) .unwrap_or_else(Error::into_compile_error) .into() } diff --git a/crates/stackable-versioned/tests/basic.rs b/crates/stackable-versioned/tests/basic.rs index 6f952c618..13f0eacb6 100644 --- a/crates/stackable-versioned/tests/basic.rs +++ b/crates/stackable-versioned/tests/basic.rs @@ -1,9 +1,8 @@ -use stackable_versioned::Versioned; +use stackable_versioned::versioned; // To expand the generated code (for debugging and testing), it is recommended // to first change directory via `cd crates/stackable-versioned` and to then // run `cargo expand --test basic --all-features`. -#[derive(Versioned)] #[allow(dead_code)] #[versioned( version(name = "v1alpha1"), From c8e9a5cbba5f253d5e63c13a9e611cb9670bbe28 Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 23 May 2024 15:09:08 +0200 Subject: [PATCH 3/4] Add type alias for latest version --- crates/stackable-versioned/src/gen/vstruct.rs | 38 ++++++++++--------- crates/stackable-versioned/tests/basic.rs | 10 +++++ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/crates/stackable-versioned/src/gen/vstruct.rs b/crates/stackable-versioned/src/gen/vstruct.rs index f92587419..ec4fbefc2 100644 --- a/crates/stackable-versioned/src/gen/vstruct.rs +++ b/crates/stackable-versioned/src/gen/vstruct.rs @@ -27,8 +27,9 @@ pub(crate) struct VersionedStruct { } impl ToTokens for VersionedStruct { - fn to_tokens(&self, _tokens: &mut TokenStream) { + fn to_tokens(&self, tokens: &mut TokenStream) { let mut versions = self.versions.iter().peekable(); + let struct_name = &self.ident; while let Some(version) = versions.next() { let mut fields = TokenStream::new(); @@ -43,24 +44,27 @@ impl ToTokens for VersionedStruct { let deprecated_attr = version.deprecated.then_some(quote! {#[deprecated]}); let module_name = format_ident!("{version}", version = version.inner.to_string()); - let struct_name = &self.ident; - - // Only generate a module when there is at least one more version. - // This skips generating a module for the latest version, because - // the base struct always represents the latest version. - if versions.peek().is_some() { - _tokens.extend(quote! { - #[automatically_derived] - #deprecated_attr - pub mod #module_name { - - pub struct #struct_name { - #fields - } + + tokens.extend(quote! { + #[automatically_derived] + #deprecated_attr + pub mod #module_name { + + pub struct #struct_name { + #fields } - }); - } + } + }); } + + // Special handling for the last (and thus latest) version + let module_name = format_ident!( + "{version}", + version = self.versions.last().unwrap().inner.to_string() + ); + tokens.extend(quote! { + pub type #struct_name = #module_name::#struct_name; + }) } } diff --git a/crates/stackable-versioned/tests/basic.rs b/crates/stackable-versioned/tests/basic.rs index 13f0eacb6..b3dd86db8 100644 --- a/crates/stackable-versioned/tests/basic.rs +++ b/crates/stackable-versioned/tests/basic.rs @@ -26,6 +26,16 @@ struct Foo { fn basic() { let _ = v1alpha1::Foo { jjj: 0, baz: false }; let _ = v1beta1::Foo { bar: 0, baz: false }; + let _ = v1::Foo { bar: 0, baz: false }; + + #[allow(deprecated)] + let _ = v2::Foo { + deprecated_bar: 0, + baz: false, + }; + + // The latest version (v3) + #[allow(deprecated)] let _ = Foo { deprecated_bar: 0, baz: false, From 99d4fb9dce4fe717f77034aed2615b08e77cc17e Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 23 May 2024 16:24:10 +0200 Subject: [PATCH 4/4] Remove unused code --- crates/stackable-versioned/src/gen/mod.rs | 1 - crates/stackable-versioned/src/gen/venum.rs | 23 ------------------- crates/stackable-versioned/src/gen/vstruct.rs | 4 ++-- 3 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 crates/stackable-versioned/src/gen/venum.rs diff --git a/crates/stackable-versioned/src/gen/mod.rs b/crates/stackable-versioned/src/gen/mod.rs index 61e331eec..0acb31699 100644 --- a/crates/stackable-versioned/src/gen/mod.rs +++ b/crates/stackable-versioned/src/gen/mod.rs @@ -9,7 +9,6 @@ use crate::{ pub(crate) mod field; pub(crate) mod neighbors; -pub(crate) mod venum; pub(crate) mod version; pub(crate) mod vstruct; diff --git a/crates/stackable-versioned/src/gen/venum.rs b/crates/stackable-versioned/src/gen/venum.rs deleted file mode 100644 index 9297cbcf6..000000000 --- a/crates/stackable-versioned/src/gen/venum.rs +++ /dev/null @@ -1,23 +0,0 @@ -use proc_macro2::TokenStream; -use quote::ToTokens; -use syn::{DataEnum, Ident, Result}; - -use crate::attrs::container::ContainerAttributes; - -pub(crate) struct VersionedEnum {} - -impl VersionedEnum { - pub(crate) fn new( - _ident: Ident, - _data: DataEnum, - _attributes: ContainerAttributes, - ) -> Result { - todo!() - } -} - -impl ToTokens for VersionedEnum { - fn to_tokens(&self, _tokens: &mut TokenStream) { - todo!() - } -} diff --git a/crates/stackable-versioned/src/gen/vstruct.rs b/crates/stackable-versioned/src/gen/vstruct.rs index ec4fbefc2..379d2ed3b 100644 --- a/crates/stackable-versioned/src/gen/vstruct.rs +++ b/crates/stackable-versioned/src/gen/vstruct.rs @@ -28,10 +28,10 @@ pub(crate) struct VersionedStruct { impl ToTokens for VersionedStruct { fn to_tokens(&self, tokens: &mut TokenStream) { - let mut versions = self.versions.iter().peekable(); + let versions = self.versions.iter().peekable(); let struct_name = &self.ident; - while let Some(version) = versions.next() { + for version in versions { let mut fields = TokenStream::new(); for field in &self.fields {