Skip to content

Commit

Permalink
add tests,docs,fix the feature
Browse files Browse the repository at this point in the history
  • Loading branch information
realbigsean committed Jan 31, 2024
1 parent d2e114f commit 0f69f8e
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 31 deletions.
42 changes: 42 additions & 0 deletions book/src/config/field.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,45 @@ may be applied in a single attribute, e.g. `#[superstruct(partial_getter(copy, n
The error type for partial getters can currently only be configured on a per-struct basis
via the [`partial_getter_error`](./struct.md#partial-getter-error) attribute, although this may
change in a future release.

## Flatten

```
#[superstruct(flatten)]
```

This attribute can only be applied to enum fields that whose variants match each variant of the
superstruct. This is useful for nesting superstructs whose variant types should be linked.

This will automatically create a partial getter for each variant. The following two examples are equivalent.

Using `flatten`:
```
#[superstruct(variants(A, B))]
struct InnerMessage {
pub x: u64,
pub y: u64,
}
#[superstruct(variants(A, B))]
struct Message {
#[superstruct(flatten)]
pub inner: InnerMessage,
}
```
Equivalent without `flatten`:
```
#[superstruct(variants(A, B))]
struct InnerMessage {
pub x: u64,
pub y: u64,
}
#[superstruct(variants(A, B))]
struct Message {
#[superstruct(only(A), partial_getter(rename = "inner_a"))]
pub inner: InnerMessageA,
#[superstruct(only(B), partial_getter(rename = "inner_b"))]
pub inner: InnerMessageB,
}
```
68 changes: 37 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use quote::{format_ident, quote, ToTokens};
use std::collections::HashMap;
use std::iter::{self, FromIterator};
use syn::{
parse2, parse_macro_input, Attribute, AttributeArgs, Expr, Field, GenericParam, Ident,
ItemEnum, ItemStruct, Lifetime, LifetimeDef, Type, TypeGenerics, TypeParamBound,
parse_macro_input, Attribute, AttributeArgs, Expr, Field, GenericParam, Ident, ItemStruct,
Lifetime, LifetimeDef, Type, TypeGenerics, TypeParamBound,
};

mod attributes;
Expand Down Expand Up @@ -76,7 +76,7 @@ struct FieldOpts {
}

/// Getter configuration for a specific field
#[derive(Debug, Default, FromMeta)]
#[derive(Clone, Debug, Default, FromMeta)]
struct GetterOpts {
#[darling(default)]
copy: bool,
Expand Down Expand Up @@ -208,39 +208,45 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream {
let partial_getter_opts = field_opts.partial_getter.unwrap_or_default();

if field_opts.flatten {
// Parse the inner type, making sure it's an enum.
let ty = &output_field.ty;

let inner_enum: ItemEnum = parse2(output_field.ty.to_token_stream())
.expect(format!("inner type must be an enum {ty:?}").as_str());

// Extract the names of the variants for the inner enum.
let variant_names_inner: Vec<_> = inner_enum
.variants
.iter()
.map(|v| v.ident.clone())
.collect();
for variant in variant_names {
// Update the struct name for this variant.
let mut next_variant_field = output_field.clone();
match &mut next_variant_field.ty {
Type::Path(ref mut p) => {
let first_segment = &mut p
.path
.segments
.first_mut()
.expect("path should have at least one segment");
let inner_ty_name = first_segment.ident.clone();
let next_variant_ty_name = format_ident!("{}{}", inner_ty_name, variant);
first_segment.ident = next_variant_ty_name;
}
_ => panic!("field must be a path"),
};

// Compare the sets of variant names.
assert_eq!(
variant_names, &variant_names_inner,
"variant names must match"
);
// 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()
};

for variant in inner_enum.variants {
assert!(
variant.fields.len() == 1,
"only one field allowed in flattened enum variants"
);
let inner_field = variant.fields.into_iter().next().unwrap();
// Add the variant name as a suffix to the getter name.
fields.push(FieldData {
name: format_ident!("{}_{}", name, variant.ident.to_string().to_lowercase()),
field: inner_field,
only: None,
name: name.clone(),
field: next_variant_field.clone(),
// Make sure the field is only accessible from this variant.
only: Some(vec![variant.clone()]),
getter_opts: <_>::default(),
partial_getter_opts: <_>::default(),
partial_getter_opts,
});

// Update the variant field map
let fields = variant_fields
.get_mut(variant)
.expect("invalid variant name");
*fields.get_mut(0).expect("no fields for variant") = next_variant_field;
}
} else {
fields.push(FieldData {
Expand Down
62 changes: 62 additions & 0 deletions tests/flatten.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use superstruct::superstruct;

#[test]
fn flatten() {
#[superstruct(variants(A, B), variant_attributes(derive(Debug, PartialEq, Eq)))]
#[derive(Debug, PartialEq, Eq)]
struct InnerMessage {
pub x: u64,
#[superstruct(only(B))]
pub y: u64,
}

#[superstruct(variants(A, B), variant_attributes(derive(Debug, PartialEq, Eq)))]
#[derive(Debug, PartialEq, Eq)]
struct Message {
#[superstruct(flatten)]
pub inner: InnerMessage,
}