Skip to content
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

Meta variants #35

Merged
merged 8 commits into from
Feb 5, 2024
Merged
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
73 changes: 73 additions & 0 deletions book/src/codegen/meta-variants.md
Original file line number Diff line number Diff line change
@@ -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).
13 changes: 13 additions & 0 deletions book/src/config/struct.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion src/attributes.rs
Original file line number Diff line number Diff line change
@@ -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<Ident>,
}
384 changes: 329 additions & 55 deletions src/lib.rs

Large diffs are not rendered by default.

282 changes: 282 additions & 0 deletions tests/meta_variant.rs
Original file line number Diff line number Diff line change
@@ -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: 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: 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
);
}