diff --git a/book/src/codegen/meta-variants.md b/book/src/codegen/meta-variants.md new file mode 100644 index 00000000..72707822 --- /dev/null +++ b/book/src/codegen/meta-variants.md @@ -0,0 +1,73 @@ +# Meta variant structs and enums + +Meta variants are an optional feature, useful for scenarios where you'd want nested +enums at the top-level. structs will be created for all combinations of `meta_variants` +and `variants`, names in the format `{BaseName}{MetaVariantName}{VariantName}`. +Additionally, enums will be created for each `meta_variant` named `{BaseName}{MetaVariantName}`. + +For example: + +```rust,no_run,no_playground +#[superstruct(meta_variants(Baz, Qux), variants(Foo, Bar))] +struct MyStruct { + name: String, + #[superstruct(only(Foo))] + location: u16, + #[superstruct(meta_only(Baz))] + score: u64, + #[superstruct(only(Bar), meta_only(Qux))] + id: usize, +} +``` + +Here the `BaseName` is `MyStruct` and there are two variants in the meta-enum called +`Baz` and `Qux`. + +The generated enums are: + +```rust,no_run,no_playground +enum MyStruct { + Baz(MyStructBaz), + Qux(MyStructQux), +} + +enum MyStructBaz { + Foo(MyStructBazFoo), + Bar(MyStructBazBar), +} + +enum MyStructQux { + Foo(MyStructQuxFoo), + Bar(MyStructQuxBar), +} +``` + +The generated variant structs are: + +```rust,no_run,no_playground +struct MyStructBazFoo { + name: String, + location: u16, + score: u64, +} + +struct MyStructBazBar { + name: String, + score: u64, +} + +struct MyStructQuxFoo { + name: String, + location: u16, +} + +struct MyStructQuxBar { + name: String, + id: usize, +} +``` + +Note how the `only` attribute still applies, and a new `meta_only` attribute can be used to +control the presence of fields in each meta variant. + +For more information see [Struct attributes](../config/struct.md). diff --git a/book/src/config/struct.md b/book/src/config/struct.md index aa01d4a6..8b701122 100644 --- a/book/src/config/struct.md +++ b/book/src/config/struct.md @@ -110,3 +110,16 @@ Please see the documentation on [Mapping into other types](./codegen/map-macros. for an explanation of how these macros operate. **Format**: one or more `superstruct` type names + +## Meta variants + +``` +#[superstruct(meta_variants(A, B, ...), variants(C, D, ...))] +``` + +Generate a two-dimensional superstruct. +See [meta variant structs](../codegen/meta-variants.md). + +The `meta_variants` attribute is optional. + +**Format**: 1+ comma-separated identifiers. diff --git a/src/attributes.rs b/src/attributes.rs index e9a04ba3..245492ab 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -21,7 +21,7 @@ impl FromMeta for NestedMetaList { /// List of identifiers implementing `FromMeta`. /// /// Useful for imposing ordering, unlike the `HashMap` options provided by `darling`. -#[derive(Debug)] +#[derive(Debug, Default, Clone)] pub struct IdentList { pub idents: Vec, } diff --git a/src/lib.rs b/src/lib.rs index fa836505..455b53af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,9 @@ mod utils; /// Top-level configuration via the `superstruct` attribute. #[derive(Debug, FromMeta)] struct StructOpts { + /// List of meta variant names of the superstruct being derived. + #[darling(default)] + meta_variants: Option, /// List of variant names of the superstruct being derived. variants: IdentList, /// List of attributes to apply to the variant structs. @@ -74,18 +77,13 @@ struct FieldOpts { #[darling(default)] only: Option>, #[darling(default)] + meta_only: Option>, + #[darling(default)] getter: Option, #[darling(default)] partial_getter: Option, } -fn should_skip(flatten: &Override>, key: &Ident) -> bool { - match flatten { - Override::Inherit => false, - Override::Explicit(map) => !map.is_empty() && !map.contains_key(key), - } -} - /// Getter configuration for a specific field #[derive(Debug, Default, FromMeta)] struct GetterOpts { @@ -134,15 +132,40 @@ impl ErrorOpts { struct FieldData { name: Ident, field: Field, - only: Option>, + only_combinations: Vec, getter_opts: GetterOpts, partial_getter_opts: GetterOpts, + is_common: bool, } impl FieldData { fn is_common(&self) -> bool { - self.only.is_none() + self.is_common } + + /// Checks whether this field should be included in creating + /// partial getters for the given type name. + fn exists_in_meta(&self, type_name: &Ident) -> bool { + let only_metas = self + .only_combinations + .iter() + .filter_map(|only| only.meta_variant.as_ref()) + .map(ToString::to_string) + .collect::>(); + + if only_metas.is_empty() { + return true; + } + only_metas + .iter() + .any(|only| type_name.to_string().ends_with(only)) + } +} + +#[derive(Hash, Eq, PartialEq, Debug, Clone)] +struct VariantKey { + variant: Ident, + meta_variant: Option, } #[proc_macro_attribute] @@ -151,26 +174,54 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as ItemStruct); let type_name = &item.ident; - let visibility = item.vis; + let visibility = item.vis.clone(); // Extract the generics to use for the top-level type and all variant structs. let decl_generics = &item.generics; // Generics used for the impl block. - let (impl_generics, ty_generics, where_clause) = &item.generics.split_for_impl(); + let (_, _, 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); + let mk_struct_name = |variant_key: &VariantKey| { + let VariantKey { + variant, + meta_variant, + } = variant_key; + + if let Some(meta_variant) = meta_variant { + format_ident!("{}{}{}", type_name, meta_variant, variant) + } else { + format_ident!("{}{}", type_name, variant) + } + }; let variant_names = &opts.variants.idents; - let struct_names = variant_names.iter().map(mk_struct_name).collect_vec(); + let meta_variant_names = &opts + .meta_variants + .clone() + .map(|mv| mv.idents.into_iter().map(Some).collect_vec()) + .unwrap_or(vec![None]); + let variant_combinations = variant_names + .iter() + .cloned() + .cartesian_product(meta_variant_names.iter().cloned()) + .map(|(v, mv)| VariantKey { + variant: v, + meta_variant: mv, + }); + + let struct_names = variant_combinations + .clone() + .map(|key| mk_struct_name(&key)) + .collect_vec(); // Vec of field data. let mut fields = vec![]; - // Map from variant to variant fields. + // Map from variant or meta variant to variant fields. let mut variant_fields = - HashMap::<_, _>::from_iter(variant_names.iter().zip(iter::repeat(vec![]))); + HashMap::<_, _>::from_iter(variant_combinations.clone().zip(iter::repeat(vec![]))); for field in &item.fields { let name = field.ident.clone().expect("named fields only"); @@ -193,18 +244,43 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { || variant_names.clone(), |only| only.keys().cloned().collect_vec(), ); + let field_meta_variants = field_opts.meta_only.as_ref().map_or_else( + || meta_variant_names.clone(), + |meta_only| meta_only.keys().cloned().map(Some).collect_vec(), + ); + + // Field is common if it is part of every meta variant AND every variant. + let is_common_meta = opts + .meta_variants + .as_ref() + .map_or(true, |struct_meta_variants| { + struct_meta_variants.idents.len() == field_meta_variants.len() + }); + let is_common = field_variants.len() == variant_names.len() && is_common_meta; - for variant_name in field_variants { + let only_combinations = field_variants + .iter() + .cartesian_product(field_meta_variants.iter()); + + for (variant, meta_variant) in only_combinations.clone() { variant_fields - .get_mut(&variant_name) - .expect("invalid variant name in `only`") + .get_mut(&VariantKey { + variant: variant.clone(), + meta_variant: meta_variant.clone(), + }) + .expect("invalid variant name in `only` or `meta_only`") .push(output_field.clone()); } // 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() { + } else if field_opts.meta_only.is_some() && field_opts.getter.is_some() { + panic!("can't configure `meta_only` and `getter` on the same field"); + } else if field_opts.only.is_none() + && field_opts.meta_only.is_none() + && field_opts.partial_getter.is_some() + { panic!("can't set `partial_getter` options on common field"); } else if field_opts.flatten.is_some() && field_opts.only.is_some() { panic!("can't set `flatten` and `only` on the same field"); @@ -214,23 +290,32 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { panic!("can't set `flatten` and `partial_getter` on the same field"); } - let only = field_opts.only.map(|only| only.keys().cloned().collect()); let getter_opts = field_opts.getter.unwrap_or_default(); let partial_getter_opts = field_opts.partial_getter.unwrap_or_default(); if let Some(flatten_opts) = field_opts.flatten { - for variant in variant_names { - let variant_field_index = variant_fields - .get(variant) + for variant_key in variant_combinations.clone() { + let variant = &variant_key.variant; + let meta_variant = variant_key.meta_variant.as_ref(); + + let Some(variant_field_index) = variant_fields + .get(&variant_key) .expect("invalid variant name") .iter() .position(|f| f.ident.as_ref() == Some(&name)) - .expect("flattened fields are present on all variants"); + else { + continue; + }; - if should_skip(&flatten_opts, variant) { + if should_skip( + variant_names, + meta_variant_names, + &flatten_opts, + &variant_key, + ) { // Remove the field from the field map let fields = variant_fields - .get_mut(variant) + .get_mut(&variant_key) .expect("invalid variant name"); fields.remove(variant_field_index); continue; @@ -238,23 +323,62 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { // Update the struct name for this variant. let mut next_variant_field = output_field.clone(); - match &mut next_variant_field.ty { + + let last_segment_mut_ref = match next_variant_field.ty { Type::Path(ref mut p) => { - let last_segment = &mut p + &mut p .path .segments .last_mut() - .expect("path should have at least one segment"); - let inner_ty_name = last_segment.ident.clone(); - let next_variant_ty_name = format_ident!("{}{}", inner_ty_name, variant); - last_segment.ident = next_variant_ty_name; + .expect("path should have at least one segment") + .ident } _ => panic!("field must be a path"), }; + let (next_variant_ty_name, partial_getter_rename) = + if let Some(meta_variant) = meta_variant { + if let Some(meta_only) = field_opts.meta_only.as_ref() { + assert_eq!( + meta_only.len(), + 1, + "when used in combination with flatten, only \ + one meta variant specification is allowed" + ); + assert_eq!( + meta_only.keys().next().unwrap(), + meta_variant, + "flattened meta variant does not match" + ); + ( + format_ident!("{}{}", last_segment_mut_ref.clone(), variant), + format_ident!("{}_{}", name, variant.to_string().to_lowercase()), + ) + } else { + ( + format_ident!( + "{}{}{}", + last_segment_mut_ref.clone(), + meta_variant, + variant + ), + format_ident!( + "{}_{}_{}", + name, + meta_variant.to_string().to_lowercase(), + variant.to_string().to_lowercase() + ), + ) + } + } else { + ( + format_ident!("{}{}", last_segment_mut_ref.clone(), variant), + format_ident!("{}_{}", name, variant.to_string().to_lowercase()), + ) + }; + *last_segment_mut_ref = next_variant_ty_name; + // Create a partial getter for the field. - let partial_getter_rename = - format_ident!("{}_{}", name, variant.to_string().to_lowercase()); let partial_getter_opts = GetterOpts { rename: Some(partial_getter_rename), ..<_>::default() @@ -264,14 +388,15 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { name: name.clone(), field: next_variant_field.clone(), // Make sure the field is only accessible from this variant. - only: Some(vec![variant.clone()]), + only_combinations: vec![variant_key.clone()], getter_opts: <_>::default(), partial_getter_opts, + is_common: false, }); // Update the variant field map let fields = variant_fields - .get_mut(variant) + .get_mut(&variant_key) .expect("invalid variant name"); *fields .get_mut(variant_field_index) @@ -281,9 +406,15 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { fields.push(FieldData { name, field: output_field, - only, + only_combinations: only_combinations + .map(|(variant, meta_variant)| VariantKey { + variant: variant.clone(), + meta_variant: meta_variant.clone(), + }) + .collect_vec(), getter_opts, partial_getter_opts, + is_common, }); } } @@ -294,21 +425,30 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { .as_ref() .map_or(&[][..], |attrs| &attrs.metas); - for (variant_name, struct_name) in variant_names.iter().zip(struct_names.iter()) { - let fields = &variant_fields[variant_name]; + for (variant_key, struct_name) in variant_combinations.zip(struct_names.iter()) { + let fields = &variant_fields[&variant_key]; let specific_struct_attributes = opts .specific_variant_attributes .as_ref() - .and_then(|sv| sv.get(&variant_name)) + .and_then(|sv| sv.get(&variant_key.variant)) + .map_or(&[][..], |attrs| &attrs.metas); + let specific_struct_attributes_meta = opts + .specific_variant_attributes + .as_ref() + .and_then(|sv| variant_key.meta_variant.and_then(|mv| sv.get(&mv))) .map_or(&[][..], |attrs| &attrs.metas); + let spatt = specific_struct_attributes + .iter() + .chain(specific_struct_attributes_meta.iter()) + .unique(); let variant_code = quote! { #( #[#universal_struct_attributes] )* #( - #[#specific_struct_attributes] + #[#spatt] )* #visibility struct #struct_name #decl_generics #where_clause { #( @@ -324,6 +464,71 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { return TokenStream::from_iter(output_items); } + let mut inner_enum_names = vec![]; + + // Generate inner enums if necessary. + for meta_variant in meta_variant_names { + if let Some(meta_variant) = meta_variant { + let inner_enum_name = format_ident!("{}{}", type_name, meta_variant); + inner_enum_names.push(inner_enum_name.clone()); + let inner_struct_names = variant_names + .iter() + .map(|variant_name| format_ident!("{}{}", inner_enum_name, variant_name)) + .collect_vec(); + generate_wrapper_enums( + &inner_enum_name, + &item, + &opts, + &mut output_items, + variant_names, + &inner_struct_names, + &fields, + false, + ); + } + } + + // Generate outer enum. + let variant_names = opts + .meta_variants + .as_ref() + .map(|mv| &mv.idents) + .unwrap_or(variant_names); + let struct_names = &opts + .meta_variants + .as_ref() + .map(|_| inner_enum_names) + .unwrap_or(struct_names); + generate_wrapper_enums( + type_name, + &item, + &opts, + &mut output_items, + variant_names, + struct_names, + &fields, + opts.meta_variants.is_some(), + ); + + TokenStream::from_iter(output_items) +} + +fn generate_wrapper_enums( + type_name: &Ident, + item: &ItemStruct, + opts: &StructOpts, + mut output_items: &mut Vec, + variant_names: &[Ident], + struct_names: &[Ident], + fields: &[FieldData], + is_meta: bool, +) { + let visibility = &item.vis; + // Extract the generics to use for the top-level type and all variant structs. + let decl_generics = &item.generics; + // Generics used for the impl block. + let (impl_generics, ty_generics, where_clause) = &item.generics.split_for_impl(); + // Construct the top-level enum. let top_level_attrs = discard_superstruct_attrs(&item.attrs); let enum_item = quote! { @@ -422,19 +627,22 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { let getters = fields .iter() .filter(|f| f.is_common()) - .map(|field_data| make_field_getter(type_name, &variant_names, &field_data, None)); + .map(|field_data| make_field_getter(type_name, &variant_names, &field_data, None, is_meta)); let mut_getters = fields .iter() .filter(|f| f.is_common() && !f.getter_opts.no_mut) - .map(|field_data| make_mut_field_getter(type_name, &variant_names, &field_data, None)); + .map(|field_data| { + make_mut_field_getter(type_name, &variant_names, &field_data, None, is_meta) + }); let partial_getters = fields .iter() .filter(|f| !f.is_common()) + .filter(|f| is_meta || f.exists_in_meta(type_name)) .cartesian_product(&[false, true]) .flat_map(|(field_data, mutability)| { - let field_variants = field_data.only.as_ref()?; + let field_variants = &field_data.only_combinations; Some(make_partial_getter( type_name, &field_data, @@ -442,6 +650,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { &opts.partial_getter_error, *mutability, None, + is_meta, )) }); @@ -507,21 +716,24 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { &variant_names, &field_data, Some(&ref_ty_lifetime), + is_meta, ) }); let ref_partial_getters = fields .iter() .filter(|f| !f.is_common()) + .filter(|f| is_meta || f.exists_in_meta(type_name)) .flat_map(|field_data| { - let field_variants = field_data.only.as_ref()?; + let field_variants = &field_data.only_combinations; Some(make_partial_getter( &ref_ty_name, - &field_data, - &field_variants, + field_data, + field_variants, &opts.partial_getter_error, false, Some(&ref_ty_lifetime), + is_meta, )) }); @@ -554,14 +766,16 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { &variant_names, &field_data, Some(&ref_mut_ty_lifetime), + is_meta, ) }); let ref_mut_partial_getters = fields .iter() .filter(|f| !f.is_common() && !f.partial_getter_opts.no_mut) + .filter(|f| is_meta || f.exists_in_meta(type_name)) .flat_map(|field_data| { - let field_variants = field_data.only.as_ref()?; + let field_variants = &field_data.only_combinations; Some(make_partial_getter( &ref_mut_ty_name, &field_data, @@ -569,6 +783,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { &opts.partial_getter_error, true, Some(&ref_mut_ty_lifetime), + is_meta, )) }); @@ -614,7 +829,7 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { } // Generate trait implementations. - for (variant_name, struct_name) in variant_names.iter().zip_eq(&struct_names) { + for (variant_name, struct_name) in variant_names.into_iter().zip_eq(struct_names) { let from_impl = generate_from_variant_trait_impl( type_name, impl_generics, @@ -649,8 +864,6 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream { where_clause, ); output_items.push(ref_from_top_level_impl.into()); - - TokenStream::from_iter(output_items) } /// Generate a getter method for a field. @@ -659,6 +872,7 @@ fn make_field_getter( variant_names: &[Ident], field_data: &FieldData, lifetime: Option<&Lifetime>, + is_meta: bool, ) -> proc_macro2::TokenStream { let field_name = &field_data.name; let field_type = &field_data.field.ty; @@ -670,7 +884,10 @@ fn make_field_getter( } else { quote! { &#lifetime #field_type} }; - let return_expr = if getter_opts.copy { + + let return_expr = if is_meta { + quote! { inner.#field_name() } + } else if getter_opts.copy { quote! { inner.#field_name } } else { quote! { &inner.#field_name } @@ -701,6 +918,7 @@ fn make_mut_field_getter( variant_names: &[Ident], field_data: &FieldData, lifetime: Option<&Lifetime>, + is_meta: bool, ) -> proc_macro2::TokenStream { let field_name = &field_data.name; let field_type = &field_data.field.ty; @@ -709,7 +927,11 @@ fn make_mut_field_getter( let fn_name = format_ident!("{}_mut", getter_opts.rename.as_ref().unwrap_or(field_name)); let return_type = quote! { &#lifetime mut #field_type }; let param = make_self_arg(true, lifetime); - let return_expr = quote! { &mut inner.#field_name }; + let return_expr = if is_meta { + quote! { inner.#fn_name() } + } else { + quote! { &mut inner.#field_name } + }; // Pass-through `cfg` attributes as they affect the existence of this field. let cfg_attrs = get_cfg_attrs(&field_data.field.attrs); @@ -760,11 +982,25 @@ fn make_type_ref( fn make_partial_getter( type_name: &Ident, field_data: &FieldData, - field_variants: &[Ident], + field_variants: &[VariantKey], error_opts: &ErrorOpts, mutable: bool, lifetime: Option<&Lifetime>, + is_meta: bool, ) -> proc_macro2::TokenStream { + let field_variants = field_variants + .iter() + .filter_map(|key| { + if is_meta { + key.meta_variant.clone() + } else { + Some(key.variant.clone()) + } + }) + .unique() + .collect_vec(); + let type_name = type_name.clone(); + let field_name = &field_data.name; let renamed_field = field_data .partial_getter_opts @@ -779,7 +1015,9 @@ fn make_partial_getter( let copy = field_data.partial_getter_opts.copy; let self_arg = make_self_arg(mutable, lifetime); let ret_ty = make_type_ref(&field_data.field.ty, mutable, copy, lifetime); - let ret_expr = if mutable { + let ret_expr = if is_meta { + quote! { inner.#fn_name()? } + } else if mutable { quote! { &mut inner.#field_name } } else if copy { quote! { inner.#field_name } @@ -871,3 +1109,39 @@ fn is_attr_with_ident(attr: &Attribute, ident: &str) -> bool { .get_ident() .map_or(false, |attr_ident| attr_ident.to_string() == ident) } + +/// Predicate for determining whether a field should be excluded from a flattened +/// variant combination. +fn should_skip( + variant_names: &[Ident], + meta_variant_names: &[Option], + flatten: &Override>, + variant_key: &VariantKey, +) -> bool { + let variant = &variant_key.variant; + let meta_variant = variant_key.meta_variant.as_ref(); + match flatten { + Override::Inherit => false, + Override::Explicit(map) => { + let contains_variant = map.contains_key(variant); + let contains_meta_variant = meta_variant.map_or(true, |mv| map.contains_key(&mv)); + + let variants_exist = variant_names.iter().any(|v| map.contains_key(v)); + let meta_variants_exist = meta_variant_names + .iter() + .flatten() + .any(|mv| map.contains_key(mv)); + + if contains_variant && !meta_variants_exist { + return false; + } + if contains_meta_variant && !variants_exist { + return false; + } + + let contains_all = contains_variant && contains_meta_variant; + + !map.is_empty() && !contains_all + } + } +} diff --git a/tests/meta_variant.rs b/tests/meta_variant.rs new file mode 100644 index 00000000..8cf264f7 --- /dev/null +++ b/tests/meta_variant.rs @@ -0,0 +1,282 @@ +use superstruct::superstruct; + +#[superstruct( + meta_variants(Read, Write), + variants(Lower, Upper), + variant_attributes(derive(Clone, Debug, PartialEq, Eq)) +)] +#[derive(Clone, Debug, PartialEq, Eq)] +struct InnerMessage { + // Exists on all structs. + pub w: u64, + // Exists on all Read structs. + #[superstruct(meta_only(Read))] + pub x: u64, + // Exists on all LowerCase structs. + #[superstruct(only(Lower))] + pub y: u64, + // Exists only in InnerMessageWriteLower. + #[superstruct(meta_only(Write), only(Upper))] + pub z: u64, +} + +#[test] +fn meta_variant() { + let message_a = InnerMessage::Read(InnerMessageRead::Lower(InnerMessageReadLower { + w: 1, + x: 2, + y: 3, + })); + assert_eq!(*message_a.w(), 1); + assert_eq!(*message_a.x().unwrap(), 2); + assert_eq!(*message_a.y().unwrap(), 3); + assert!(message_a.z().is_err()); + + let message_b = InnerMessage::Read(InnerMessageRead::Upper(InnerMessageReadUpper { + w: 1, + x: 2, + })); + assert_eq!(*message_b.w(), 1); + assert_eq!(*message_b.x().unwrap(), 2); + assert!(message_b.y().is_err()); + assert!(message_b.z().is_err()); + + let message_c = InnerMessage::Write(InnerMessageWrite::Lower(InnerMessageWriteLower { + w: 1, + y: 3, + })); + assert_eq!(*message_c.w(), 1); + assert!(message_c.x().is_err()); + assert_eq!(*message_c.y().unwrap(), 3); + assert!(message_c.z().is_err()); + + let message_d = InnerMessage::Write(InnerMessageWrite::Upper(InnerMessageWriteUpper { + w: 1, + z: 4, + })); + assert_eq!(*message_d.w(), 1); + assert!(message_d.x().is_err()); + assert!(message_d.y().is_err()); + assert_eq!(*message_d.z().unwrap(), 4); +} + +#[superstruct( + meta_variants(Read, Write), + variants(Lower, Upper), + variant_attributes(derive(Debug, PartialEq, Eq)) +)] +#[derive(Debug, PartialEq, Eq)] +struct Message { + // Exists on all variants. + #[superstruct(flatten)] + pub inner_a: InnerMessage, + // Exists on all Upper variants. + #[superstruct(flatten(Upper))] + pub inner_b: InnerMessage, + // Exists on all Read variants. + #[superstruct(flatten(Read))] + pub inner_c: InnerMessage, + // Exists on only the Read + Lower variant. + #[superstruct(flatten(Write, Lower))] + pub inner_d: InnerMessage, +} + +#[test] +fn meta_variant_flatten() { + let inner_a = InnerMessageReadLower { w: 1, x: 2, y: 3 }; + let inner_c = InnerMessageReadLower { w: 4, x: 5, y: 6 }; + let message_e = Message::Read(MessageRead::Lower(MessageReadLower { inner_a, inner_c })); + assert_eq!(message_e.inner_a_read_lower().unwrap().w, 1); + assert!(message_e.inner_a_read_upper().is_err()); + assert!(message_e.inner_a_write_lower().is_err()); + assert!(message_e.inner_a_write_upper().is_err()); + + assert_eq!(message_e.inner_c_read_lower().unwrap().w, 4); + assert!(message_e.inner_c_read_upper().is_err()); + + let inner_a = InnerMessageReadUpper { w: 1, x: 2 }; + let inner_b = InnerMessageReadUpper { w: 3, x: 4 }; + let inner_c = InnerMessageReadUpper { w: 5, x: 6 }; + let message_f = Message::Read(MessageRead::Upper(MessageReadUpper { + inner_a, + inner_b, + inner_c, + })); + assert!(message_f.inner_a_read_lower().is_err()); + assert_eq!(message_f.inner_a_read_upper().unwrap().w, 1); + assert!(message_f.inner_a_write_lower().is_err()); + assert!(message_f.inner_a_write_upper().is_err()); + + assert_eq!(message_f.inner_b_read_upper().unwrap().w, 3); + assert!(message_f.inner_b_write_upper().is_err()); + + assert!(message_f.inner_c_read_lower().is_err()); + assert_eq!(message_f.inner_c_read_upper().unwrap().w, 5); + + let inner_a = InnerMessageWriteLower { w: 1, y: 2 }; + let inner_d = InnerMessageWriteLower { w: 3, y: 4 }; + let message_g = Message::Write(MessageWrite::Lower(MessageWriteLower { inner_a, inner_d })); + assert!(message_g.inner_a_read_lower().is_err()); + assert!(message_g.inner_a_read_upper().is_err()); + assert_eq!(message_g.inner_a_write_lower().unwrap().w, 1); + assert!(message_g.inner_a_write_upper().is_err()); + + assert_eq!(message_g.inner_d_write_lower().unwrap().w, 3); + + let inner_a = InnerMessageWriteUpper { w: 1, z: 2 }; + let inner_b = InnerMessageWriteUpper { w: 3, z: 4 }; + let message_h = Message::Write(MessageWrite::Upper(MessageWriteUpper { inner_a, inner_b })); + assert!(message_h.inner_a_read_lower().is_err()); + assert!(message_h.inner_a_read_upper().is_err()); + assert!(message_h.inner_a_write_lower().is_err()); + assert_eq!(message_h.inner_a_write_upper().unwrap().w, 1); + + assert!(message_h.inner_b_read_upper().is_err()); + assert_eq!(message_h.inner_b_write_upper().unwrap().w, 3); +} + +#[test] +fn meta_variants_map_macro() { + #[superstruct( + meta_variants(Juicy, Sour), + variants(Apple, Orange), + variant_attributes(derive(Debug, PartialEq)) + )] + #[derive(Debug, PartialEq)] + pub struct Fruit { + #[superstruct(getter(copy))] + id: u64, + #[superstruct(only(Apple), partial_getter(copy))] + description: &'static str, + #[superstruct(meta_only(Juicy))] + name: &'static str, + } + + fn increment_id(id: Fruit) -> Fruit { + map_fruit!(id, |mut inner, cons| { + *inner.id_mut() += 1; + cons(inner) + }) + } + + fn get_id_via_ref<'a>(fruit_ref: FruitRef<'a>) -> u64 { + map_fruit_ref!(&'a _, fruit_ref, |inner, _| { inner.id() }) + } + + assert_eq!( + increment_id(Fruit::Juicy(FruitJuicy::Orange(FruitJuicyOrange { + id: 10, + name: "orange" + }))) + .id(), + get_id_via_ref( + Fruit::Juicy(FruitJuicy::Orange(FruitJuicyOrange { + id: 11, + name: "orange" + })) + .to_ref() + ) + ); +} + +#[test] +fn meta_variants_exist_specific_attributes() { + #[superstruct( + meta_variants(One, Two), + variants(IsCopy, IsNotCopy), + variant_attributes(derive(Debug, PartialEq, Clone)), + specific_variant_attributes(IsCopy(derive(Copy))) + )] + #[derive(Clone, PartialEq, Debug)] + pub struct Thing { + pub x: u64, + #[superstruct(only(IsNotCopy))] + pub y: String, + } + + fn copy(t: T) -> (T, T) { + (t, t) + } + + let x = ThingOneIsCopy { x: 0 }; + assert_eq!(copy(x), (x, x)); + let x = ThingTwoIsCopy { x: 0 }; + assert_eq!(copy(x), (x, x)); +} + +#[test] +fn meta_variants_have_specific_attributes() { + #[superstruct( + meta_variants(IsCopy, IsNotCopy), + variants(One, Two), + variant_attributes(derive(Debug, PartialEq, Clone)), + specific_variant_attributes(IsCopy(derive(Copy))) + )] + #[derive(Clone, PartialEq, Debug)] + pub struct Ting { + pub x: u64, + #[superstruct(meta_only(IsNotCopy))] + pub y: String, + } + + fn copy(t: T) -> (T, T) { + (t, t) + } + + let x = TingIsCopyOne { x: 0 }; + assert_eq!(copy(x), (x, x)); + let x = TingIsCopyTwo { x: 0 }; + assert_eq!(copy(x), (x, x)); +} + +#[test] +fn meta_only_flatten() { + #[superstruct( + variants(Merge, Capella), + variant_attributes(derive(Debug, PartialEq, Clone)) + )] + #[derive(Clone, PartialEq, Debug)] + pub struct Payload { + pub transactions: u64, + } + + #[superstruct( + variants(Merge, Capella), + variant_attributes(derive(Debug, PartialEq, Clone)) + )] + #[derive(Clone, PartialEq, Debug)] + pub struct PayloadHeader { + pub transactions_root: u64, + } + + #[superstruct( + meta_variants(Blinded, Full), + variants(Base, Merge, Capella), + variant_attributes(derive(Debug, PartialEq, Clone)) + )] + #[derive(Clone, PartialEq, Debug)] + pub struct Block { + #[superstruct(flatten(Merge, Capella), meta_only(Full))] + pub payload: Payload, + #[superstruct(flatten(Merge, Capella), meta_only(Blinded))] + pub payload_header: PayloadHeader, + } + + let block = Block::Full(BlockFull::Merge(BlockFullMerge { + payload: PayloadMerge { transactions: 1 }, + })); + let blinded_block = Block::Blinded(BlockBlinded::Merge(BlockBlindedMerge { + payload_header: PayloadHeaderMerge { + transactions_root: 1, + }, + })); + + assert_eq!(block.payload_merge().unwrap().transactions, 1); + assert_eq!( + blinded_block + .payload_header_merge() + .unwrap() + .transactions_root, + 1 + ); +}