Skip to content

feat(stackable-versioned): Use attribute instead of derive macro #793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/stackable-versioned/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 3 additions & 8 deletions crates/stackable-versioned/src/attrs/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<Vec<VersionAttributes>>,
Expand Down
19 changes: 5 additions & 14 deletions crates/stackable-versioned/src/gen/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
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;
pub(crate) mod neighbors;
pub(crate) mod venum;
pub(crate) mod version;
pub(crate) mod vstruct;

Expand All @@ -26,20 +24,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<TokenStream> {
// 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<TokenStream> {
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",
))
}
};
Expand Down
23 changes: 0 additions & 23 deletions crates/stackable-versioned/src/gen/venum.rs

This file was deleted.

42 changes: 23 additions & 19 deletions crates/stackable-versioned/src/gen/vstruct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ pub(crate) struct VersionedStruct {
}

impl ToTokens for VersionedStruct {
fn to_tokens(&self, _tokens: &mut TokenStream) {
let mut versions = self.versions.iter().peekable();
fn to_tokens(&self, tokens: &mut TokenStream) {
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 {
Expand All @@ -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;
})
}
}

Expand Down
23 changes: 20 additions & 3 deletions crates/stackable-versioned/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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()
}
13 changes: 11 additions & 2 deletions crates/stackable-versioned/tests/basic.rs
Original file line number Diff line number Diff line change
@@ -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"),
Expand All @@ -27,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,
Expand Down