Skip to content

Commit

Permalink
Start to move code, add enum impls
Browse files Browse the repository at this point in the history
  • Loading branch information
Techassi committed Jun 19, 2024
1 parent 52b4536 commit 6721b0c
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 58 deletions.
31 changes: 31 additions & 0 deletions crates/stackable-versioned-macros/src/attrs/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use darling::{util::SpannedValue, FromMeta};
use k8s_version::Version;
use proc_macro2::Span;
use syn::Path;

#[derive(Clone, Debug, FromMeta)]
pub(crate) struct AddedAttributes {
pub(crate) since: SpannedValue<Version>,

#[darling(rename = "default", default = "default_default_fn")]
pub(crate) default_fn: SpannedValue<Path>,
}

fn default_default_fn() -> SpannedValue<Path> {
SpannedValue::new(
syn::parse_str("std::default::Default::default").expect("internal error: path must parse"),
Span::call_site(),
)
}

#[derive(Clone, Debug, FromMeta)]
pub(crate) struct RenamedAttributes {
pub(crate) since: SpannedValue<Version>,
pub(crate) from: SpannedValue<String>,
}

#[derive(Clone, Debug, FromMeta)]
pub(crate) struct DeprecatedAttributes {
pub(crate) since: SpannedValue<Version>,
pub(crate) note: SpannedValue<String>,
}
41 changes: 9 additions & 32 deletions crates/stackable-versioned-macros/src/attrs/field.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use darling::{util::SpannedValue, Error, FromField, FromMeta};
use k8s_version::Version;
use proc_macro2::Span;
use syn::{Field, Ident, Path};
use darling::{Error, FromField};
use syn::{Field, Ident};

use crate::{attrs::container::ContainerAttributes, consts::DEPRECATED_PREFIX};
use crate::{
attrs::{
common::{AddedAttributes, DeprecatedAttributes, RenamedAttributes},
container::ContainerAttributes,
},
consts::DEPRECATED_PREFIX,
};

/// This struct describes all available field attributes, as well as the field
/// name to display better diagnostics.
Expand Down Expand Up @@ -38,33 +42,6 @@ pub(crate) struct FieldAttributes {
pub(crate) deprecated: Option<DeprecatedAttributes>,
}

#[derive(Clone, Debug, FromMeta)]
pub(crate) struct AddedAttributes {
pub(crate) since: SpannedValue<Version>,

#[darling(rename = "default", default = "default_default_fn")]
pub(crate) default_fn: SpannedValue<Path>,
}

fn default_default_fn() -> SpannedValue<Path> {
SpannedValue::new(
syn::parse_str("std::default::Default::default").expect("internal error: path must parse"),
Span::call_site(),
)
}

#[derive(Clone, Debug, FromMeta)]
pub(crate) struct RenamedAttributes {
pub(crate) since: SpannedValue<Version>,
pub(crate) from: SpannedValue<String>,
}

#[derive(Clone, Debug, FromMeta)]
pub(crate) struct DeprecatedAttributes {
pub(crate) since: SpannedValue<Version>,
pub(crate) note: SpannedValue<String>,
}

impl FieldAttributes {
/// This associated function is called by darling (see and_then attribute)
/// after it successfully parsed the attribute. This allows custom
Expand Down
2 changes: 2 additions & 0 deletions crates/stackable-versioned-macros/src/attrs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
pub(crate) mod common;
pub(crate) mod container;
pub(crate) mod field;
pub(crate) mod variant;
40 changes: 40 additions & 0 deletions crates/stackable-versioned-macros/src/attrs/variant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// TODO (@Techassi): Think about what can be moved into a common impl for both
// fields and variants.

use darling::{Error, FromVariant};
use syn::{Ident, Variant};

use crate::attrs::{
common::{AddedAttributes, DeprecatedAttributes, RenamedAttributes},
container::ContainerAttributes,
};

#[derive(Debug, FromVariant)]
#[darling(
attributes(versioned),
forward_attrs(allow, doc, cfg, serde),
and_then = VariantAttributes::validate
)]
pub(crate) struct VariantAttributes {
pub(crate) ident: Ident,
pub(crate) added: Option<AddedAttributes>,

#[darling(multiple, rename = "renamed")]
pub(crate) renames: Vec<RenamedAttributes>,

pub(crate) deprecated: Option<DeprecatedAttributes>,
}

impl VariantAttributes {
pub(crate) fn validate(self) -> Result<Self, Error> {
Ok(self)
}

pub(crate) fn validate_versions(
&self,
attributes: &ContainerAttributes,
variant: &Variant,
) -> Result<(), Error> {
todo!()
}
}
51 changes: 51 additions & 0 deletions crates/stackable-versioned-macros/src/gen/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use k8s_version::Version;
use proc_macro2::{Span, TokenStream};
use syn::Ident;

use crate::attrs::container::ContainerAttributes;

#[derive(Debug, Clone)]
pub(crate) struct ContainerVersion {
pub(crate) deprecated: bool,
pub(crate) skip_from: bool,
pub(crate) inner: Version,
pub(crate) ident: Ident,
}

impl From<&ContainerAttributes> for Vec<ContainerVersion> {
fn from(attributes: &ContainerAttributes) -> Self {
attributes
.versions
.iter()
.map(|v| ContainerVersion {
skip_from: v.skip.as_ref().map_or(false, |s| s.from.is_present()),
ident: Ident::new(&v.name.to_string(), Span::call_site()),
deprecated: v.deprecated.is_present(),
inner: v.name,
})
.collect()
}
}

pub(crate) trait VersionedContainer {
type Container;
type Data;

fn new(ident: Ident, data: Self::Data, attributes: ContainerAttributes) -> Self::Container;
fn generate_tokens(&self) -> TokenStream;
}

pub(crate) struct Container<T: Sized> {
/// The ident, or name, of the versioned enum.
pub(crate) ident: Ident,

/// List of declared versions for this enum. Each version, except the
/// latest, generates a definition with appropriate fields.
pub(crate) versions: Vec<ContainerVersion>,

pub(crate) items: Vec<T>,

/// The name of the enum used in `From` implementations.
pub(crate) from_ident: Ident,
pub(crate) skip_from: bool,
}
18 changes: 12 additions & 6 deletions crates/stackable-versioned-macros/src/gen/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use proc_macro2::TokenStream;
use syn::{spanned::Spanned, Data, DeriveInput, Error, Result};

use crate::{attrs::container::ContainerAttributes, gen::vstruct::VersionedStruct};
use crate::{
attrs::container::ContainerAttributes,
gen::{venum::VersionedEnum, vstruct::VersionedStruct},
};

pub(crate) mod field;
pub(crate) mod common;
pub(crate) mod neighbors;
pub(crate) mod version;
pub(crate) mod venum;
pub(crate) mod vstruct;

// NOTE (@Techassi): This derive macro cannot handle multiple structs / enums
Expand All @@ -20,13 +23,16 @@ pub(crate) mod vstruct;
// TODO (@Techassi): Think about how we can handle nested structs / enums which
// are also versioned.

pub(crate) fn expand(attrs: ContainerAttributes, input: DeriveInput) -> Result<TokenStream> {
pub(crate) fn expand(attributes: ContainerAttributes, input: DeriveInput) -> Result<TokenStream> {
let expanded = match input.data {
Data::Struct(data) => VersionedStruct::new(input.ident, data, attrs)?.generate_tokens(),
Data::Struct(data) => {
VersionedStruct::new(input.ident, data, attributes)?.generate_tokens()
}
Data::Enum(data) => VersionedEnum::new(input.ident, data, attributes)?.generate_tokens(),
_ => {
return Err(Error::new(
input.span(),
"attribute macro `versioned` only supports structs",
"attribute macro `versioned` only supports structs and enums",
))
}
};
Expand Down
66 changes: 66 additions & 0 deletions crates/stackable-versioned-macros/src/gen/venum/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use std::borrow::Borrow;

use darling::FromVariant;
use itertools::Itertools;
use proc_macro2::TokenStream;
use syn::{DataEnum, Error, Ident, Result};

use crate::{
attrs::{container::ContainerAttributes, variant::VariantAttributes},
gen::{common::ContainerVersion, venum::variant::VersionedVariant},
};

mod variant;

#[derive(Debug)]
pub(crate) struct VersionedEnum {
/// The ident, or name, of the versioned enum.
pub(crate) ident: Ident,

/// List of declared versions for this enum. Each version, except the
/// latest, generates a definition with appropriate fields.
pub(crate) versions: Vec<ContainerVersion>,

/// List of variants defined in the base enum. How, and if, a variant should
/// generate code, is decided by the currently generated version.
pub(crate) variants: Vec<VersionedVariant>,

/// The name of the enum used in `From` implementations.
pub(crate) from_ident: Ident,
pub(crate) skip_from: bool,
}

impl VersionedEnum {
pub(crate) fn new(
ident: Ident,
data: DataEnum,
attributes: ContainerAttributes,
) -> Result<Self> {
let versions: Vec<ContainerVersion> = attributes.borrow().into();
let mut variants = Vec::new();

for variant in data.variants {
let attrs = VariantAttributes::from_variant(&variant)?;
attrs.validate_versions(&attributes, &variant)?;

let mut versioned_variant = VersionedVariant::new();
versioned_variant.insert_container_versions(&versions);
variants.push(versioned_variant);
}

for version in &versions {
if !variants.iter().map(|v| v.get_ident(version)).all_unique() {
return Err(Error::new(
ident.span(),
format!("enum contains renamed variant which collide with other variant in version {version}", version = version.inner),
));
}
}

todo!()
}

pub(crate) fn generate_tokens(&self) -> TokenStream {
todo!()
}
}
27 changes: 27 additions & 0 deletions crates/stackable-versioned-macros/src/gen/venum/variant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use syn::Ident;

use crate::gen::common::ContainerVersion;

#[derive(Debug)]
pub(crate) struct VersionedVariant {}

impl VersionedVariant {
pub(crate) fn new() -> Self {
todo!()
}

pub(crate) fn insert_container_versions(&mut self, versions: &[ContainerVersion]) {
todo!()
}

pub(crate) fn get_ident(&self, version: &ContainerVersion) -> Option<&Ident> {
// match &self.chain {
// Some(chain) => chain
// .get(&version.inner)
// .expect("internal error: chain must contain container version")
// .get_ident(),
// None => self.inner.ident.as_ref(),
// }
todo!()
}
}
10 changes: 0 additions & 10 deletions crates/stackable-versioned-macros/src/gen/version.rs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use syn::{Field, Ident, Path};
use crate::{
attrs::field::FieldAttributes,
consts::DEPRECATED_PREFIX,
gen::{neighbors::Neighbors, version::ContainerVersion},
gen::{common::ContainerVersion, neighbors::Neighbors},
};

/// A versioned field, which contains contains common [`Field`] data and a chain
Expand Down Expand Up @@ -158,7 +158,7 @@ impl VersionedField {
///
/// This continuous chain ensures that when generating code (tokens), each
/// field can lookup the status for a requested version.
pub(crate) fn insert_container_versions(&mut self, versions: &Vec<ContainerVersion>) {
pub(crate) fn insert_container_versions(&mut self, versions: &[ContainerVersion]) {
if let Some(chain) = &mut self.chain {
for version in versions {
if chain.contains_key(&version.inner) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ use syn::{DataStruct, Error, Ident, Result};

use crate::{
attrs::{container::ContainerAttributes, field::FieldAttributes},
gen::{field::VersionedField, version::ContainerVersion},
gen::{common::ContainerVersion, vstruct::field::VersionedField},
};

mod field;

/// Stores individual versions of a single struct. Each version tracks field
/// actions, which describe if the field was added, renamed or deprecated in
/// that version. Fields which are not versioned, are included in every
Expand Down Expand Up @@ -39,7 +41,7 @@ impl VersionedStruct {
attributes: ContainerAttributes,
) -> Result<Self> {
// Convert the raw version attributes into a container version.
let versions = attributes
let versions: Vec<_> = attributes
.versions
.iter()
.map(|v| ContainerVersion {
Expand Down Expand Up @@ -69,17 +71,12 @@ impl VersionedStruct {
// Collect the idents of all fields for a single version and then
// ensure that all idents are unique. If they are not, return an
// error.
let mut idents = Vec::new();

// TODO (@Techassi): Report which field(s) use a duplicate ident and
// also hint what can be done to fix it based on the field action /
// status.

for field in &fields {
idents.push(field.get_ident(version))
}

if !idents.iter().all_unique() {
if !fields.iter().map(|f| f.get_ident(version)).all_unique() {
return Err(Error::new(
ident.span(),
format!("struct contains renamed fields which collide with other fields in version {version}", version = version.inner),
Expand Down

0 comments on commit 6721b0c

Please sign in to comment.