From aa7d1bb8d72316f1e4aebe5fbf29836c22603ee9 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 30 Nov 2023 11:54:57 +1100 Subject: [PATCH 01/11] Features WIP --- examples/features.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 42 ++++++++++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 examples/features.rs diff --git a/examples/features.rs b/examples/features.rs new file mode 100644 index 00000000..6c45ba9c --- /dev/null +++ b/examples/features.rs @@ -0,0 +1,55 @@ +use superstruct::superstruct; + +enum ForkName { + Bellatrix, + Capella, + Deneb, + Electra, +} + +enum FeatureName { + Merge, + Withdrawals, + Blobs, + EIP6110, + Verge, +} + +#[superstruct(FORK_ORDER)] +const FORK_ORDER: Vec<(ForkName, Vec)> = vec![ + (ForkName::Bellatrix, vec![FeatureName::Merge]), + (ForkName::Capella, vec![FeatureName::Withdrawals]), + ( + ForkName::Electra, + vec![FeatureName::EIP6110, FeatureName::Verge], + ), +]; + +const FEATURE_DEPENDENCIES: Vec<(FeatureName, Vec)> = vec![ + (FeatureName::Withdrawals, vec![FeatureName::Merge]), + (FeatureName::Blobs, vec![FeatureName::Withdrawals]), + (FeatureName::EIP6110, vec![FeatureName::Merge]), + (FeatureName::Verge, vec![FeatureName::Merge]), +]; + +#[superstruct( + variants_and_features_from(FORK_NAME), + feature_dependencies(FEATURE_DEPENDENCIES), + variant_type(ForkName), + feature_type(FeatureName) +)] +struct Block { + #[until(Withdrawals)] // until the fork that has Withdrawals feature + historical_updates: String, + #[from(Withdrawals)] + historical_summaries: String, + #[from(Withdrawals)] // in the Withdrawals fork, and all subsequent + withdrawals: Vec, + #[from(Blobs)] // if Blobs is not enabled, this is completely disabled + blobs: Vec, + #[from(EIP6110)] + deposits: Vec, +} +// TODO: try some variants as well + +fn main() {} diff --git a/src/lib.rs b/src/lib.rs index d6b675a7..68e867fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,8 @@ mod utils; #[derive(Debug, FromMeta)] struct StructOpts { /// List of variant names of the superstruct being derived. - variants: IdentList, + #[darling(default)] + variants: Option, /// List of attributes to apply to the variant structs. #[darling(default)] variant_attributes: Option, @@ -60,6 +61,18 @@ struct StructOpts { /// List of other superstruct types to generate mappings into from RefMut. #[darling(default)] map_ref_mut_into: Option, + + /* + * FEATURE EXPERIMENT + */ + #[darling(default)] + variants_and_features_from: Option, + #[darling(default)] + feature_dependencies: Option, + #[darling(default)] + variant_type: Option, + #[darling(default)] + feature_type: Option, } /// Field-level configuration. @@ -71,6 +84,14 @@ struct FieldOpts { getter: Option, #[darling(default)] partial_getter: Option, + + /* + * FEATURE EXPERIMENT + */ + #[darling(default)] + from: Option, + #[darling(default)] + until: Option, } /// Getter configuration for a specific field @@ -132,6 +153,21 @@ impl FieldData { } } +fn get_variant_and_feature_names(opts: &StructOpts) -> (Vec, Option>) { + // Fixed list of variants. + if let Some(variants) = &opts.variants { + assert!( + opts.variants_and_features_from.is_none(), + "cannot have variants and variants_and_features_from" + ); + return (variants.idents.clone(), None); + } + + // Dynamic list of variants and features. + + todo!() +} + #[proc_macro_attribute] pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { let attr_args = parse_macro_input!(args as AttributeArgs); @@ -150,7 +186,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { let mk_struct_name = |variant_name: &Ident| format_ident!("{}{}", type_name, variant_name); - let variant_names = &opts.variants.idents; + let (variant_names, feature_names) = get_variant_and_feature_names(&opts); let struct_names = variant_names.iter().map(mk_struct_name).collect_vec(); // Vec of field data. @@ -515,7 +551,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { &ref_mut_ty_name, num_generics, &struct_names, - variant_names, + &variant_names, &opts, &mut output_items, ); From 1af3c1e9374665f1f1287aecc4a0acc234419be8 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 30 Nov 2023 13:02:14 +1100 Subject: [PATCH 02/11] PROGRESS --- Cargo.toml | 3 +- examples/features.rs | 26 +++++++++------- src/lib.rs | 72 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4d8a3286..e5128eca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ proc-macro2 = "1.0.32" quote = "1.0.10" syn = "1.0.82" smallvec = "1.8.0" - -[dev-dependencies] serde = { version = "1.0.130", features = ["derive"] } serde_json = "1.0.72" +macro_state = "0.2.0" diff --git a/examples/features.rs b/examples/features.rs index 6c45ba9c..5a3849e1 100644 --- a/examples/features.rs +++ b/examples/features.rs @@ -1,5 +1,7 @@ +use serde::{Deserialize, Serialize}; use superstruct::superstruct; +#[derive(Serialize, Deserialize)] enum ForkName { Bellatrix, Capella, @@ -7,6 +9,7 @@ enum ForkName { Electra, } +#[derive(Serialize, Deserialize)] enum FeatureName { Merge, Withdrawals, @@ -15,25 +18,26 @@ enum FeatureName { Verge, } -#[superstruct(FORK_ORDER)] -const FORK_ORDER: Vec<(ForkName, Vec)> = vec![ - (ForkName::Bellatrix, vec![FeatureName::Merge]), - (ForkName::Capella, vec![FeatureName::Withdrawals]), +#[superstruct(variants_and_features_decl = "fork_order")] +const FORK_ORDER: &[(ForkName, &[FeatureName])] = &[ + (ForkName::Bellatrix, &[FeatureName::Merge]), + (ForkName::Capella, &[FeatureName::Withdrawals]), ( ForkName::Electra, - vec![FeatureName::EIP6110, FeatureName::Verge], + &[FeatureName::EIP6110, FeatureName::Verge], ), ]; -const FEATURE_DEPENDENCIES: Vec<(FeatureName, Vec)> = vec![ - (FeatureName::Withdrawals, vec![FeatureName::Merge]), - (FeatureName::Blobs, vec![FeatureName::Withdrawals]), - (FeatureName::EIP6110, vec![FeatureName::Merge]), - (FeatureName::Verge, vec![FeatureName::Merge]), +#[superstruct(feature_dependencies_decl = "feature_dependencies")] +const FEATURE_DEPENDENCIES: &[(FeatureName, &[FeatureName])] = &[ + (FeatureName::Withdrawals, &[FeatureName::Merge]), + (FeatureName::Blobs, &[FeatureName::Withdrawals]), + (FeatureName::EIP6110, &[FeatureName::Merge]), + (FeatureName::Verge, &[FeatureName::Merge]), ]; #[superstruct( - variants_and_features_from(FORK_NAME), + variants_and_features_from(FORK_ORDER), feature_dependencies(FEATURE_DEPENDENCIES), variant_type(ForkName), feature_type(FeatureName) diff --git a/src/lib.rs b/src/lib.rs index 68e867fe..58b16dff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,8 +12,8 @@ use quote::{format_ident, quote, ToTokens}; use std::collections::HashMap; use std::iter::{self, FromIterator}; use syn::{ - parse_macro_input, Attribute, AttributeArgs, Expr, Field, GenericParam, Ident, ItemStruct, - Lifetime, LifetimeDef, Type, TypeGenerics, TypeParamBound, + parse_macro_input, Attribute, AttributeArgs, Expr, Field, GenericParam, Ident, ItemConst, + ItemStruct, Lifetime, LifetimeDef, Type, TypeGenerics, TypeParamBound, }; mod attributes; @@ -73,6 +73,12 @@ struct StructOpts { variant_type: Option, #[darling(default)] feature_type: Option, + + // Separate invocations + #[darling(default)] + variants_and_features_decl: Option, + #[darling(default)] + feature_dependencies_decl: Option, } /// Field-level configuration. @@ -164,13 +170,71 @@ fn get_variant_and_feature_names(opts: &StructOpts) -> (Vec, Option TokenStream { let attr_args = parse_macro_input!(args as AttributeArgs); + let opts = StructOpts::from_list(&attr_args).unwrap(); + + // Early return for "helper" invocations. + if opts.variants_and_features_decl.is_some() || opts.feature_dependencies_decl.is_some() { + let input2 = input.clone(); + let item = parse_macro_input!(input2 as ItemConst); + + let Expr::Reference(ref_expr) = *item.expr else { + panic!("ref bad"); + }; + let Expr::Array(array_expr) = *ref_expr.expr else { + panic!("bad"); + }; + + fn path_to_string(e: &Expr) -> String { + let Expr::Path(path) = e else { + panic!("path bad"); + }; + let last_segment_str = path.path.segments.iter().last().unwrap().ident.to_string(); + last_segment_str + } + + let data: Vec<(String, Vec)> = array_expr + .elems + .iter() + .map(|expr| { + let Expr::Tuple(tuple_expr) = expr else { + panic!("bad2"); + }; + let tuple_parts = tuple_expr.elems.iter().cloned().collect::>(); + assert_eq!(tuple_parts.len(), 2); + + let variant_name = path_to_string(&tuple_parts[0]); + + let Expr::Reference(feature_ref_expr) = tuple_parts[1].clone() else { + panic!("ref bad"); + }; + let Expr::Array(feature_array_expr) = *feature_ref_expr.expr else { + panic!("bad"); + }; + let feature_names = feature_array_expr + .elems + .iter() + .map(|expr| path_to_string(expr)) + .collect::>(); + + (variant_name, feature_names) + }) + .collect::>(); + + // TODO: write data as JSON using macro_state + + return input; + } + let item = parse_macro_input!(input as ItemStruct); let type_name = &item.ident; @@ -180,8 +244,6 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { // Generics used for the impl block. let (impl_generics, ty_generics, where_clause) = &item.generics.split_for_impl(); - let opts = StructOpts::from_list(&attr_args).unwrap(); - let mut output_items: Vec = vec![]; let mk_struct_name = |variant_name: &Ident| format_ident!("{}{}", type_name, variant_name); From f187cc487955a4ba4e8f5da60eff0d91cf44bcfa Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 19 Apr 2024 15:57:43 +1000 Subject: [PATCH 03/11] It kinda works --- Cargo.toml | 2 +- build.rs | 2 + examples/features.rs | 35 +++++++--- src/feature_expr.rs | 75 ++++++++++++++++++++++ src/lib.rs | 148 ++++++++++++++++++++++++++++++++++++------- 5 files changed, 227 insertions(+), 35 deletions(-) create mode 100644 build.rs create mode 100644 src/feature_expr.rs diff --git a/Cargo.toml b/Cargo.toml index e5128eca..7bf21c1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,4 @@ +workspace = { members = ["examples/features_example"] } [package] name = "superstruct" version = "0.7.0" @@ -22,4 +23,3 @@ syn = "1.0.82" smallvec = "1.8.0" serde = { version = "1.0.130", features = ["derive"] } serde_json = "1.0.72" -macro_state = "0.2.0" diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..05b2fa29 --- /dev/null +++ b/build.rs @@ -0,0 +1,2 @@ +// Dummy build.rs to ensure OUT_DIR environment variable is set for tests and examples. +fn main() {} diff --git a/examples/features.rs b/examples/features.rs index 5a3849e1..ae0b1908 100644 --- a/examples/features.rs +++ b/examples/features.rs @@ -16,9 +16,10 @@ enum FeatureName { Blobs, EIP6110, Verge, + EIP7549, } -#[superstruct(variants_and_features_decl = "fork_order")] +#[superstruct(variants_and_features_decl = "FORK_ORDER")] const FORK_ORDER: &[(ForkName, &[FeatureName])] = &[ (ForkName::Bellatrix, &[FeatureName::Merge]), (ForkName::Capella, &[FeatureName::Withdrawals]), @@ -28,7 +29,7 @@ const FORK_ORDER: &[(ForkName, &[FeatureName])] = &[ ), ]; -#[superstruct(feature_dependencies_decl = "feature_dependencies")] +#[superstruct(feature_dependencies_decl = "FEATURE_DEPENDENCIES")] const FEATURE_DEPENDENCIES: &[(FeatureName, &[FeatureName])] = &[ (FeatureName::Withdrawals, &[FeatureName::Merge]), (FeatureName::Blobs, &[FeatureName::Withdrawals]), @@ -37,23 +38,37 @@ const FEATURE_DEPENDENCIES: &[(FeatureName, &[FeatureName])] = &[ ]; #[superstruct( - variants_and_features_from(FORK_ORDER), - feature_dependencies(FEATURE_DEPENDENCIES), + variants_and_features_from = "FORK_ORDER", + feature_dependencies = "FEATURE_DEPENDENCIES", variant_type(ForkName), feature_type(FeatureName) )] struct Block { - #[until(Withdrawals)] // until the fork that has Withdrawals feature historical_updates: String, - #[from(Withdrawals)] + #[superstruct(feature(Withdrawals))] historical_summaries: String, - #[from(Withdrawals)] // in the Withdrawals fork, and all subsequent + #[superstruct(feature(Withdrawals))] // in the Withdrawals fork, and all subsequent withdrawals: Vec, - #[from(Blobs)] // if Blobs is not enabled, this is completely disabled + #[superstruct(feature(Blobs))] // if Blobs is not enabled, this is completely disabled blobs: Vec, - #[from(EIP6110)] + #[superstruct(feature(EIP6110))] deposits: Vec, } -// TODO: try some variants as well + +// Should generate this: +/* +impl Block { + fn fork_name(&self) -> ForkName; + + fn feature_names(&self) -> &'static [FeatureName]; + + fn is_feature_enabled(&self, feature: FeatureName) -> bool { + match self { + Self::Capella => false, + Self::Electra => true, + } + } +} +*/ fn main() {} diff --git a/src/feature_expr.rs b/src/feature_expr.rs new file mode 100644 index 00000000..5f8bb919 --- /dev/null +++ b/src/feature_expr.rs @@ -0,0 +1,75 @@ +use darling::{Error, FromMeta}; +use syn::{Ident, Meta, NestedMeta}; + +/// A cfg-like expression in terms of features, which can be evaluated for each fork at each field +/// to determine whether that field is turned on. +#[derive(Debug)] +pub enum FeatureExpr { + And(Box, Box), + Or(Box, Box), + Not(Box), + Literal(Ident), +} + +fn parse(meta: NestedMeta) -> Result { + match meta { + // TODO: assert 1 segment + NestedMeta::Meta(Meta::Path(path)) => Ok(FeatureExpr::Literal( + path.segments.last().unwrap().ident.clone(), + )), + NestedMeta::Meta(Meta::List(meta_list)) => { + let segments = &meta_list.path.segments; + assert_eq!(segments.len(), 1); + let operator = &segments.last().unwrap().ident; + match operator.to_string().as_str() { + "and" => { + let mut nested = meta_list.nested; + assert_eq!(nested.len(), 2, "`and` should have exactly 2 operands"); + let right_meta = nested.pop().unwrap().into_value(); + let left_meta = nested.pop().unwrap().into_value(); + Ok(FeatureExpr::And( + Box::new(parse(left_meta)?), + Box::new(parse(right_meta)?), + )) + } + "or" => { + let mut nested = meta_list.nested; + assert_eq!(nested.len(), 2, "`or` should have exactly 2 operands"); + let right_meta = nested.pop().unwrap().into_value(); + let left_meta = nested.pop().unwrap().into_value(); + Ok(FeatureExpr::Or( + Box::new(parse(left_meta)?), + Box::new(parse(right_meta)?), + )) + } + "not" => { + let mut nested = meta_list.nested; + assert_eq!(nested.len(), 1, "`not` should have exactly 1 operand"); + let inner_meta = nested.pop().unwrap().into_value(); + Ok(FeatureExpr::Not(Box::new(parse(inner_meta)?))) + } + op => panic!("unsupported operator: {op}"), + } + } + _ => panic!("unexpected feature expr: {meta:?}"), + } +} + +impl FromMeta for FeatureExpr { + fn from_list(items: &[NestedMeta]) -> Result { + assert_eq!(items.len(), 1, "feature expr should have 1 part"); + let expr_meta = items.first().cloned().unwrap(); + parse(expr_meta) + } +} + +impl FeatureExpr { + pub fn eval(&self, features: &[Ident]) -> bool { + match self { + Self::Literal(feature_name) => features.contains(&feature_name), + Self::And(left, right) => left.eval(features) && right.eval(features), + Self::Or(left, right) => left.eval(features) || right.eval(features), + Self::Not(inner) => !inner.eval(features), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 58b16dff..a352de81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use attributes::{IdentList, NestedMetaList}; use darling::FromMeta; +use feature_expr::FeatureExpr; use from::{ generate_from_enum_trait_impl_for_ref, generate_from_variant_trait_impl, generate_from_variant_trait_impl_for_ref, @@ -10,13 +11,16 @@ use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{format_ident, quote, ToTokens}; use std::collections::HashMap; +use std::fs::File; use std::iter::{self, FromIterator}; +use std::path::PathBuf; use syn::{ parse_macro_input, Attribute, AttributeArgs, Expr, Field, GenericParam, Ident, ItemConst, ItemStruct, Lifetime, LifetimeDef, Type, TypeGenerics, TypeParamBound, }; mod attributes; +mod feature_expr; mod from; mod macros; mod naming; @@ -66,9 +70,9 @@ struct StructOpts { * FEATURE EXPERIMENT */ #[darling(default)] - variants_and_features_from: Option, + variants_and_features_from: Option, #[darling(default)] - feature_dependencies: Option, + feature_dependencies: Option, #[darling(default)] variant_type: Option, #[darling(default)] @@ -95,9 +99,7 @@ struct FieldOpts { * FEATURE EXPERIMENT */ #[darling(default)] - from: Option, - #[darling(default)] - until: Option, + feature: Option, } /// Getter configuration for a specific field @@ -149,17 +151,23 @@ struct FieldData { name: Ident, field: Field, only: Option>, + feature: Option, + /// Variants for which this field is enabled. + variants: Vec, getter_opts: GetterOpts, partial_getter_opts: GetterOpts, } impl FieldData { fn is_common(&self) -> bool { - self.only.is_none() + self.only.is_none() && self.feature.is_none() } } -fn get_variant_and_feature_names(opts: &StructOpts) -> (Vec, Option>) { +/// Return list of variants and mapping from variants to their full list of enabled features. +fn get_variant_and_feature_names( + opts: &StructOpts, +) -> (Vec, Option>>) { // Fixed list of variants. if let Some(variants) = &opts.variants { assert!( @@ -170,11 +178,74 @@ fn get_variant_and_feature_names(opts: &StructOpts) -> (Vec, Option)> = + serde_json::from_reader(variants_file).unwrap(); + let feature_dependencies: Vec<(String, Vec)> = + serde_json::from_reader(features_file).unwrap(); + + // Sanity check dependency graph. + // Create list of features enabled at each variant (cumulative). + let mut variant_features_cumulative: HashMap> = HashMap::new(); + for (i, (variant, features)) in variants_and_features.iter().enumerate() { + let variant_features = variant_features_cumulative + .entry(variant.clone()) + .or_default(); + + for (_, prior_features) in variants_and_features.iter().take(i) { + variant_features.extend_from_slice(prior_features); + } + variant_features.extend_from_slice(features); + } + + // Check dependency graph. + for (feature, dependencies) in feature_dependencies { + for (variant, _) in &variants_and_features { + let cumulative_features = variant_features_cumulative.get(variant).unwrap(); + if cumulative_features.contains(&feature) { + // All feature dependencies are enabled for this variant. + for dependency in &dependencies { + if !cumulative_features.contains(&dependency) { + panic!("feature {feature} depends on {dependency} but it is not enabled for variant {variant}") + } + } + } + } + } + + let variants = variants_and_features + .iter() + .map(|(variant, _)| Ident::new(variant, Span::call_site())) + .collect(); + + let variant_features_cumulative_idents = variant_features_cumulative + .into_iter() + .map(|(variant, features)| { + ( + Ident::new(&variant, Span::call_site()), + features + .into_iter() + .map(|feature| Ident::new(&feature, Span::call_site())) + .collect(), + ) + }) + .collect(); + + (variants, Some(variant_features_cumulative_idents)) } #[proc_macro_attribute] @@ -184,6 +255,10 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { // Early return for "helper" invocations. if opts.variants_and_features_decl.is_some() || opts.feature_dependencies_decl.is_some() { + let decl_name = opts + .variants_and_features_decl + .or(opts.feature_dependencies_decl) + .unwrap(); let input2 = input.clone(); let item = parse_macro_input!(input2 as ItemConst); @@ -230,7 +305,11 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { }) .collect::>(); - // TODO: write data as JSON using macro_state + let out_dir = + PathBuf::from(&std::env::var("OUT_DIR").expect("your crate needs a build.rs")); + let output_path = out_dir.join(format!("{decl_name}.json")); + let output_file = File::create(output_path).expect("create output file"); + serde_json::to_writer(output_file, &data).expect("write output file"); return input; } @@ -248,7 +327,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { let mk_struct_name = |variant_name: &Ident| format_ident!("{}{}", type_name, variant_name); - let (variant_names, feature_names) = get_variant_and_feature_names(&opts); + let (variant_names, all_variant_features) = get_variant_and_feature_names(&opts); let struct_names = variant_names.iter().map(mk_struct_name).collect_vec(); // Vec of field data. @@ -274,15 +353,32 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { output_field.attrs = discard_superstruct_attrs(&output_field.attrs); // Add the field to the `variant_fields` map for all applicable variants. - let field_variants = field_opts.only.as_ref().map_or_else( - || variant_names.clone(), - |only| only.keys().cloned().collect_vec(), - ); + let field_variants = if let Some(only_variants) = field_opts.only.as_ref() { + only_variants.keys().cloned().collect_vec() + } else if let Some(feature_expr) = field_opts.feature.as_ref() { + let all_variant_features = all_variant_features + .as_ref() + .expect("all_variant_features is set"); + // Check whether field is enabled for each variant. + variant_names + .iter() + .filter(|variant| { + let variant_features = all_variant_features + .get(&variant) + .expect("variant should be in all_variant_features"); + feature_expr.eval(&variant_features) + }) + .cloned() + .collect() + } else { + // Enable for all variants. + variant_names.clone() + }; - for variant_name in field_variants { + for variant_name in &field_variants { variant_fields - .get_mut(&variant_name) - .expect("invalid variant name in `only`") + .get_mut(variant_name) + .expect("invalid variant name in `only` or `feature` expression") .push(output_field.clone()); } @@ -292,8 +388,10 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { } else if field_opts.only.is_none() && field_opts.partial_getter.is_some() { panic!("can't set `partial_getter` options on common field"); } + // TODO: check feature & only mutually exclusive let only = field_opts.only.map(|only| only.keys().cloned().collect()); + let feature = field_opts.feature; let getter_opts = field_opts.getter.unwrap_or_default(); let partial_getter_opts = field_opts.partial_getter.unwrap_or_default(); @@ -302,6 +400,8 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { name, field: output_field, only, + feature, + variants: field_variants, getter_opts, partial_getter_opts, }); @@ -453,7 +553,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { .filter(|f| !f.is_common()) .cartesian_product(&[false, true]) .flat_map(|(field_data, mutability)| { - let field_variants = field_data.only.as_ref()?; + let field_variants = &field_data.variants; Some(make_partial_getter( type_name, &field_data, @@ -533,7 +633,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { .iter() .filter(|f| !f.is_common()) .flat_map(|field_data| { - let field_variants = field_data.only.as_ref()?; + let field_variants = &field_data.variants; Some(make_partial_getter( &ref_ty_name, &field_data, @@ -580,7 +680,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { .iter() .filter(|f| !f.is_common() && !f.partial_getter_opts.no_mut) .flat_map(|field_data| { - let field_variants = field_data.only.as_ref()?; + let field_variants = &field_data.variants; Some(make_partial_getter( &ref_mut_ty_name, &field_data, From 22657614f9f8e4d578631f15a41321dee75929bc Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 19 Apr 2024 15:58:42 +1000 Subject: [PATCH 04/11] Fix cargo toml --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7bf21c1b..6e5c57fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,3 @@ -workspace = { members = ["examples/features_example"] } [package] name = "superstruct" version = "0.7.0" From 06132f875603d2985a414e6e3683c42bc9505f49 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 19 Apr 2024 18:52:39 +1000 Subject: [PATCH 05/11] Fix partial_getter check --- src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a352de81..682386c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -383,9 +383,11 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { } // Check field opts - if field_opts.only.is_some() && field_opts.getter.is_some() { - panic!("can't configure `only` and `getter` on the same field"); - } else if field_opts.only.is_none() && field_opts.partial_getter.is_some() { + let common = field_opts.only.is_none() && field_opts.feature.is_none(); + + if !common && field_opts.getter.is_some() { + panic!("can't configure `getter` on non-common field"); + } else if common && field_opts.partial_getter.is_some() { panic!("can't set `partial_getter` options on common field"); } // TODO: check feature & only mutually exclusive From 050d90d4687d13eac9f6cac59f31a4e10d79fc48 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 22 Apr 2024 16:47:43 +1000 Subject: [PATCH 06/11] Implement feature type getters etc --- examples/features.rs | 28 ++++++++++++++++++----- src/feature_getters.rs | 51 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 38 +++++++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 src/feature_getters.rs diff --git a/examples/features.rs b/examples/features.rs index ae0b1908..d69a53c2 100644 --- a/examples/features.rs +++ b/examples/features.rs @@ -1,7 +1,8 @@ +// TODO: make this into a test use serde::{Deserialize, Serialize}; use superstruct::superstruct; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, PartialEq, Debug)] enum ForkName { Bellatrix, Capella, @@ -40,8 +41,8 @@ const FEATURE_DEPENDENCIES: &[(FeatureName, &[FeatureName])] = &[ #[superstruct( variants_and_features_from = "FORK_ORDER", feature_dependencies = "FEATURE_DEPENDENCIES", - variant_type(ForkName), - feature_type(FeatureName) + variant_type(name = "ForkName", getter = "fork_name"), + feature_type(name = "FeatureName") )] struct Block { historical_updates: String, @@ -58,8 +59,6 @@ struct Block { // Should generate this: /* impl Block { - fn fork_name(&self) -> ForkName; - fn feature_names(&self) -> &'static [FeatureName]; fn is_feature_enabled(&self, feature: FeatureName) -> bool { @@ -69,6 +68,23 @@ impl Block { } } } + +chain_spec + .fork_name_at_slot(slot) + .is_feature_enabled(feature) + +impl ForkName { + fn is_feature_enabled(&self, feature: FeatureName) -> bool +} */ -fn main() {} +fn main() { + let block = Block::Electra(BlockElectra { + historical_updates: "hey".into(), + historical_summaries: "thing".into(), + withdrawals: vec![1, 2, 3], + deposits: vec![0, 0, 0, 0, 0, 0], + }); + + assert_eq!(block.fork_name(), ForkName::Electra); +} diff --git a/src/feature_getters.rs b/src/feature_getters.rs new file mode 100644 index 00000000..77203265 --- /dev/null +++ b/src/feature_getters.rs @@ -0,0 +1,51 @@ +use crate::{FeatureTypeOpts, VariantTypeOpts}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::Ident; + +const DEFAULT_VARIANT_TYPE_GETTER: &str = "variant_type"; + +pub fn get_feature_getters( + type_name: &Ident, + variant_names: &[Ident], + variant_type_opts: &Option, + feature_type_opts: &Option, +) -> Vec { + let Some(variant_type) = variant_type_opts else { + return vec![]; + }; + let Some(feature_type) = feature_type_opts else { + return vec![]; + }; + let mut output = vec![]; + + output.extend(get_variant_type_getters( + type_name, + variant_names, + variant_type, + )); + // output.extend(get_feature_type_getters(variant_type)); + output +} + +pub fn get_variant_type_getters( + type_name: &Ident, + variant_names: &[Ident], + variant_type: &VariantTypeOpts, +) -> Vec { + let variant_type_name = &variant_type.name; + let getter_name = variant_type + .getter + .clone() + .unwrap_or_else(|| Ident::new(DEFAULT_VARIANT_TYPE_GETTER, Span::call_site())); + let getter = quote! { + fn #getter_name(&self) -> #variant_type_name { + match self { + #( + #type_name::#variant_names(..) => #variant_type_name::#variant_names, + )* + } + } + }; + vec![getter.into()] +} diff --git a/src/lib.rs b/src/lib.rs index 682386c9..3def0758 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ use syn::{ mod attributes; mod feature_expr; +mod feature_getters; mod from; mod macros; mod naming; @@ -74,9 +75,12 @@ struct StructOpts { #[darling(default)] feature_dependencies: Option, #[darling(default)] - variant_type: Option, + variant_type: Option, #[darling(default)] - feature_type: Option, + feature_type: Option, + + // variant_type(name = "ForkName", getter = "fork_name") + // feature_type(name = "FeatureName", list = "list_all_features", check = "is_feature_enabled") // Separate invocations #[darling(default)] @@ -121,6 +125,22 @@ struct ErrorOpts { expr: Option, } +#[derive(Debug, FromMeta)] +struct VariantTypeOpts { + name: Ident, + #[darling(default)] + getter: Option, +} + +#[derive(Debug, FromMeta)] +struct FeatureTypeOpts { + name: Ident, + #[darling(default)] + list: Option, + #[darling(default)] + check: Option, +} + impl ErrorOpts { fn parse(&self) -> Option<(Type, Expr)> { let err_ty_str = self.ty.as_ref()?; @@ -185,6 +205,10 @@ fn get_variant_and_feature_names( panic!("variants_and_features_from requires feature_dependencies"); }; + if opts.variant_type.is_none() || opts.feature_type.is_none() { + panic!("variant_type and feature_type must be defined"); + } + let out_dir = PathBuf::from(&std::env::var("OUT_DIR").expect("your crate needs a build.rs")); let variants_path = out_dir.join(format!("{variants_and_features_from}.json")); @@ -587,6 +611,13 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { }) .collect_vec(); + let feature_getters = feature_getters::get_feature_getters( + type_name, + &variant_names, + &opts.variant_type, + &opts.feature_type, + ); + let impl_block = quote! { impl #impl_generics #type_name #ty_generics #where_clause { pub fn to_ref<#ref_ty_lifetime>(&#ref_ty_lifetime self) -> #ref_ty_name #ref_ty_generics { @@ -617,6 +648,9 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { #( #partial_getters )* + #( + #feature_getters + )* } }; output_items.push(impl_block.into()); From 8086e6c00afea1c5f41ac34da025c0908390c231 Mon Sep 17 00:00:00 2001 From: Mac L Date: Mon, 22 Apr 2024 23:38:39 +1000 Subject: [PATCH 07/11] Add list_features and check_feature_enabled functions --- examples/features.rs | 18 ++++++++++++-- src/feature_getters.rs | 55 +++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 1 + 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/examples/features.rs b/examples/features.rs index d69a53c2..cd89a6e7 100644 --- a/examples/features.rs +++ b/examples/features.rs @@ -10,7 +10,7 @@ enum ForkName { Electra, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, PartialEq, Debug)] enum FeatureName { Merge, Withdrawals, @@ -42,7 +42,11 @@ const FEATURE_DEPENDENCIES: &[(FeatureName, &[FeatureName])] = &[ variants_and_features_from = "FORK_ORDER", feature_dependencies = "FEATURE_DEPENDENCIES", variant_type(name = "ForkName", getter = "fork_name"), - feature_type(name = "FeatureName") + feature_type( + name = "FeatureName", + list = "feature_names", + check = "check_feature_enabled" + ) )] struct Block { historical_updates: String, @@ -87,4 +91,14 @@ fn main() { }); assert_eq!(block.fork_name(), ForkName::Electra); + assert_eq!( + block.feature_names(), + vec![ + FeatureName::Merge, + FeatureName::Withdrawals, + FeatureName::EIP6110, + FeatureName::Verge + ] + ); + assert!(block.check_feature_enabled(FeatureName::EIP6110)); } diff --git a/src/feature_getters.rs b/src/feature_getters.rs index 77203265..7b319214 100644 --- a/src/feature_getters.rs +++ b/src/feature_getters.rs @@ -1,13 +1,17 @@ use crate::{FeatureTypeOpts, VariantTypeOpts}; use proc_macro2::{Span, TokenStream}; use quote::quote; +use std::collections::HashMap; use syn::Ident; const DEFAULT_VARIANT_TYPE_GETTER: &str = "variant_type"; +const DEFAULT_FEATURE_TYPE_LIST: &str = "list_all_features"; +const DEFAULT_FEATURE_TYPE_CHECK: &str = "is_feature_enabled"; pub fn get_feature_getters( type_name: &Ident, variant_names: &[Ident], + all_variant_features: HashMap>, variant_type_opts: &Option, feature_type_opts: &Option, ) -> Vec { @@ -24,7 +28,11 @@ pub fn get_feature_getters( variant_names, variant_type, )); - // output.extend(get_feature_type_getters(variant_type)); + output.extend(get_feature_type_getters( + type_name, + all_variant_features, + feature_type, + )); output } @@ -49,3 +57,48 @@ pub fn get_variant_type_getters( }; vec![getter.into()] } + +pub fn get_feature_type_getters( + type_name: &Ident, + all_variant_features: HashMap>, + feature_type: &FeatureTypeOpts, +) -> Vec { + let feature_type_name = &feature_type.name; + let list_features = feature_type + .list + .clone() + .unwrap_or_else(|| Ident::new(DEFAULT_FEATURE_TYPE_LIST, Span::call_site())); + let all_variant_names: Vec<_> = all_variant_features.keys().collect(); + + let mut feature_sets: Vec> = vec![]; + + for variant in all_variant_names.clone() { + let feature_set: &Vec = all_variant_features.get(variant).unwrap(); // TODO: Remove unwrap + feature_sets.push(feature_set.clone()); + } + + let feature_list = quote! { + fn #list_features(&self) -> &'static [#feature_type_name] { + match self { + #( + #type_name::#all_variant_names(..) => &[#(#feature_type_name::#feature_sets),*], + )* + } + } + }; + + let check_feature = feature_type + .check + .clone() + .unwrap_or_else(|| Ident::new(DEFAULT_FEATURE_TYPE_CHECK, Span::call_site())); + let feature_check = quote! { + fn #check_feature(&self, feature: #feature_type_name) -> bool { + match self { + #( + #type_name::#all_variant_names(..) => self.#list_features().contains(&feature), + )* + } + } + }; + vec![feature_list.into(), feature_check.into()] +} diff --git a/src/lib.rs b/src/lib.rs index 3def0758..1dded089 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -614,6 +614,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { let feature_getters = feature_getters::get_feature_getters( type_name, &variant_names, + all_variant_features.unwrap(), // TODO: Remove unwrap &opts.variant_type, &opts.feature_type, ); From 3cfe2c7116be4f3bfd64300d69e1b5a5e649e30c Mon Sep 17 00:00:00 2001 From: Mac L Date: Wed, 24 Apr 2024 11:40:04 +1000 Subject: [PATCH 08/11] Generate files in the target/profile directory --- src/feature_getters.rs | 6 +++++- src/lib.rs | 24 +++++++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/feature_getters.rs b/src/feature_getters.rs index 7b319214..a9c41d88 100644 --- a/src/feature_getters.rs +++ b/src/feature_getters.rs @@ -11,7 +11,7 @@ const DEFAULT_FEATURE_TYPE_CHECK: &str = "is_feature_enabled"; pub fn get_feature_getters( type_name: &Ident, variant_names: &[Ident], - all_variant_features: HashMap>, + all_variant_features_opts: Option>>, variant_type_opts: &Option, feature_type_opts: &Option, ) -> Vec { @@ -21,6 +21,10 @@ pub fn get_feature_getters( let Some(feature_type) = feature_type_opts else { return vec![]; }; + let Some(all_variant_features) = all_variant_features_opts else { + return vec![]; + }; + let mut output = vec![]; output.extend(get_variant_type_getters( diff --git a/src/lib.rs b/src/lib.rs index 1dded089..aecb3749 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,10 +209,10 @@ fn get_variant_and_feature_names( panic!("variant_type and feature_type must be defined"); } - let out_dir = PathBuf::from(&std::env::var("OUT_DIR").expect("your crate needs a build.rs")); + let target_dir = get_cargo_target_dir().expect("your crate needs a build.rs"); - let variants_path = out_dir.join(format!("{variants_and_features_from}.json")); - let features_path = out_dir.join(format!("{feature_dependencies}.json")); + let variants_path = target_dir.join(format!("{variants_and_features_from}.json")); + let features_path = target_dir.join(format!("{feature_dependencies}.json")); let variants_file = File::open(&variants_path).expect("variants_and_features file exists"); let features_file = File::open(&features_path).expect("feature_dependencies file exists"); @@ -329,9 +329,8 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { }) .collect::>(); - let out_dir = - PathBuf::from(&std::env::var("OUT_DIR").expect("your crate needs a build.rs")); - let output_path = out_dir.join(format!("{decl_name}.json")); + let target_dir = get_cargo_target_dir().expect("your crate needs a build.rs"); + let output_path = target_dir.join(format!("{decl_name}.json")); let output_file = File::create(output_path).expect("create output file"); serde_json::to_writer(output_file, &data).expect("write output file"); @@ -614,7 +613,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { let feature_getters = feature_getters::get_feature_getters( type_name, &variant_names, - all_variant_features.unwrap(), // TODO: Remove unwrap + all_variant_features, &opts.variant_type, &opts.feature_type, ); @@ -1027,3 +1026,14 @@ fn is_attr_with_ident(attr: &Attribute, ident: &str) -> bool { .get_ident() .map_or(false, |attr_ident| attr_ident.to_string() == ident) } + +fn get_cargo_target_dir() -> Result> { + let mut target_dir = PathBuf::from(&std::env::var("OUT_DIR")?); + // Pop 3 times to ensure that the files are generated in $CARGO_TARGET_DIR/$PROFILE. + // This workaround is required since the above env vars are not available at the time of the + // macro execution. + target_dir.pop(); + target_dir.pop(); + target_dir.pop(); + Ok(target_dir) +} From 323829f56b9bd6960da3314658470b594db6411a Mon Sep 17 00:00:00 2001 From: Mac L Date: Mon, 29 Apr 2024 20:29:20 +1000 Subject: [PATCH 09/11] Make generated functions public --- src/feature_getters.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/feature_getters.rs b/src/feature_getters.rs index a9c41d88..4b0978a8 100644 --- a/src/feature_getters.rs +++ b/src/feature_getters.rs @@ -51,7 +51,7 @@ pub fn get_variant_type_getters( .clone() .unwrap_or_else(|| Ident::new(DEFAULT_VARIANT_TYPE_GETTER, Span::call_site())); let getter = quote! { - fn #getter_name(&self) -> #variant_type_name { + pub fn #getter_name(&self) -> #variant_type_name { match self { #( #type_name::#variant_names(..) => #variant_type_name::#variant_names, @@ -82,7 +82,7 @@ pub fn get_feature_type_getters( } let feature_list = quote! { - fn #list_features(&self) -> &'static [#feature_type_name] { + pub fn #list_features(&self) -> &'static [#feature_type_name] { match self { #( #type_name::#all_variant_names(..) => &[#(#feature_type_name::#feature_sets),*], @@ -96,7 +96,7 @@ pub fn get_feature_type_getters( .clone() .unwrap_or_else(|| Ident::new(DEFAULT_FEATURE_TYPE_CHECK, Span::call_site())); let feature_check = quote! { - fn #check_feature(&self, feature: #feature_type_name) -> bool { + pub fn #check_feature(&self, feature: #feature_type_name) -> bool { match self { #( #type_name::#all_variant_names(..) => self.#list_features().contains(&feature), From 1abc91b37599412f0c63d1042a707b7a52500889 Mon Sep 17 00:00:00 2001 From: Mac L Date: Tue, 30 Apr 2024 14:25:30 +1000 Subject: [PATCH 10/11] Add optional feature expression for variants --- examples/features.rs | 33 +++++++++++++-------------------- src/lib.rs | 35 +++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/examples/features.rs b/examples/features.rs index cd89a6e7..33e7ec9a 100644 --- a/examples/features.rs +++ b/examples/features.rs @@ -60,27 +60,20 @@ struct Block { deposits: Vec, } -// Should generate this: -/* -impl Block { - fn feature_names(&self) -> &'static [FeatureName]; - - fn is_feature_enabled(&self, feature: FeatureName) -> bool { - match self { - Self::Capella => false, - Self::Electra => true, - } - } -} - -chain_spec - .fork_name_at_slot(slot) - .is_feature_enabled(feature) - -impl ForkName { - fn is_feature_enabled(&self, feature: FeatureName) -> bool +#[superstruct( + feature(Merge), + variants_and_features_from = "FORK_ORDER", + feature_dependencies = "FEATURE_DEPENDENCIES", + variant_type(name = "ForkName", getter = "fork_name"), + feature_type( + name = "FeatureName", + list = "feature_names", + check = "check_feature_enabled" + ) +)] +struct Payload { + transactions: Vec, } -*/ fn main() { let block = Block::Electra(BlockElectra { diff --git a/src/lib.rs b/src/lib.rs index aecb3749..23f858eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,8 @@ struct StructOpts { variant_type: Option, #[darling(default)] feature_type: Option, + #[darling(default)] + feature: Option, // variant_type(name = "ForkName", getter = "fork_name") // feature_type(name = "FeatureName", list = "list_all_features", check = "is_feature_enabled") @@ -209,6 +211,16 @@ fn get_variant_and_feature_names( panic!("variant_type and feature_type must be defined"); } + let starting_feature: Option = opts + .feature + .as_ref() + .map(|f| { + assert!(f.idents.len() == 1, "feature must be singular"); + f.idents.first() + }) + .flatten() + .cloned(); + let target_dir = get_cargo_target_dir().expect("your crate needs a build.rs"); let variants_path = target_dir.join(format!("{variants_and_features_from}.json")); @@ -222,18 +234,29 @@ fn get_variant_and_feature_names( let feature_dependencies: Vec<(String, Vec)> = serde_json::from_reader(features_file).unwrap(); + let starting_index = if let Some(feature) = starting_feature { + variants_and_features + .iter() + .position(|(_, deps)| deps.iter().any(|f| *f == feature.to_string())) + .expect("variants_and_features does not contain the required feature") + } else { + 0 + }; + // Sanity check dependency graph. // Create list of features enabled at each variant (cumulative). let mut variant_features_cumulative: HashMap> = HashMap::new(); for (i, (variant, features)) in variants_and_features.iter().enumerate() { - let variant_features = variant_features_cumulative - .entry(variant.clone()) - .or_default(); + if i >= starting_index { + let variant_features = variant_features_cumulative + .entry(variant.clone()) + .or_default(); - for (_, prior_features) in variants_and_features.iter().take(i) { - variant_features.extend_from_slice(prior_features); + for (_, prior_features) in variants_and_features.iter().take(i) { + variant_features.extend_from_slice(prior_features); + } + variant_features.extend_from_slice(features); } - variant_features.extend_from_slice(features); } // Check dependency graph. From 822403d4d415c181aa7f4765e118606c6772dc58 Mon Sep 17 00:00:00 2001 From: Mac L Date: Wed, 1 May 2024 12:15:38 +1000 Subject: [PATCH 11/11] Fix definitions --- examples/features.rs | 2 +- src/feature_getters.rs | 17 +++++++++++------ src/lib.rs | 38 ++++++++++++++++++++------------------ 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/examples/features.rs b/examples/features.rs index 33e7ec9a..d93703de 100644 --- a/examples/features.rs +++ b/examples/features.rs @@ -61,7 +61,7 @@ struct Block { } #[superstruct( - feature(Merge), + feature(Withdrawals), variants_and_features_from = "FORK_ORDER", feature_dependencies = "FEATURE_DEPENDENCIES", variant_type(name = "ForkName", getter = "fork_name"), diff --git a/src/feature_getters.rs b/src/feature_getters.rs index 4b0978a8..e51e0744 100644 --- a/src/feature_getters.rs +++ b/src/feature_getters.rs @@ -34,6 +34,7 @@ pub fn get_feature_getters( )); output.extend(get_feature_type_getters( type_name, + variant_names, all_variant_features, feature_type, )); @@ -64,6 +65,7 @@ pub fn get_variant_type_getters( pub fn get_feature_type_getters( type_name: &Ident, + variant_names: &[Ident], all_variant_features: HashMap>, feature_type: &FeatureTypeOpts, ) -> Vec { @@ -72,20 +74,23 @@ pub fn get_feature_type_getters( .list .clone() .unwrap_or_else(|| Ident::new(DEFAULT_FEATURE_TYPE_LIST, Span::call_site())); - let all_variant_names: Vec<_> = all_variant_features.keys().collect(); let mut feature_sets: Vec> = vec![]; - for variant in all_variant_names.clone() { - let feature_set: &Vec = all_variant_features.get(variant).unwrap(); // TODO: Remove unwrap - feature_sets.push(feature_set.clone()); + for variant in variant_names { + // If not all variants are defined for this type, then skip. + if let Some(feature_set) = all_variant_features.get(variant) { + feature_sets.push(feature_set.clone()); + } else { + continue; + } } let feature_list = quote! { pub fn #list_features(&self) -> &'static [#feature_type_name] { match self { #( - #type_name::#all_variant_names(..) => &[#(#feature_type_name::#feature_sets),*], + #type_name::#variant_names(..) => &[#(#feature_type_name::#feature_sets),*], )* } } @@ -99,7 +104,7 @@ pub fn get_feature_type_getters( pub fn #check_feature(&self, feature: #feature_type_name) -> bool { match self { #( - #type_name::#all_variant_names(..) => self.#list_features().contains(&feature), + #type_name::#variant_names(..) => self.#list_features().contains(&feature), )* } } diff --git a/src/lib.rs b/src/lib.rs index 23f858eb..eaf4ebed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -229,34 +229,23 @@ fn get_variant_and_feature_names( let variants_file = File::open(&variants_path).expect("variants_and_features file exists"); let features_file = File::open(&features_path).expect("feature_dependencies file exists"); - let variants_and_features: Vec<(String, Vec)> = + let mut variants_and_features: Vec<(String, Vec)> = serde_json::from_reader(variants_file).unwrap(); let feature_dependencies: Vec<(String, Vec)> = serde_json::from_reader(features_file).unwrap(); - let starting_index = if let Some(feature) = starting_feature { - variants_and_features - .iter() - .position(|(_, deps)| deps.iter().any(|f| *f == feature.to_string())) - .expect("variants_and_features does not contain the required feature") - } else { - 0 - }; - // Sanity check dependency graph. // Create list of features enabled at each variant (cumulative). let mut variant_features_cumulative: HashMap> = HashMap::new(); for (i, (variant, features)) in variants_and_features.iter().enumerate() { - if i >= starting_index { - let variant_features = variant_features_cumulative - .entry(variant.clone()) - .or_default(); + let variant_features = variant_features_cumulative + .entry(variant.clone()) + .or_default(); - for (_, prior_features) in variants_and_features.iter().take(i) { - variant_features.extend_from_slice(prior_features); - } - variant_features.extend_from_slice(features); + for (_, prior_features) in variants_and_features.iter().take(i) { + variant_features.extend_from_slice(prior_features); } + variant_features.extend_from_slice(features); } // Check dependency graph. @@ -274,6 +263,19 @@ fn get_variant_and_feature_names( } } + // In some instances, we might want to restrict what variants are generated for a type. + // In this case, a `starting_feature` is defined and we only include variants starting from + // the first variant to include that feature as a dependency. + let starting_index = if let Some(feature) = starting_feature { + variants_and_features + .iter() + .position(|(_, deps)| deps.iter().any(|f| *f == feature.to_string())) + .expect("variants_and_features does not contain the required feature") + } else { + 0 + }; + variants_and_features = variants_and_features[starting_index..].to_vec(); + let variants = variants_and_features .iter() .map(|(variant, _)| Ident::new(variant, Span::call_site()))