Skip to content

Commit 040b4df

Browse files
authored
Add support for adding attributes to enum variants (#46)
* Add support for adding attributes to enum variants * Add test for `enum_variant_attributes` If `enum_variant_attributes(serde(borrow))` is removed or commented, the compilation will fail with `error: lifetime may not live long enough`. * Add documentation for enum_variant_attributes * Add documentation for specific_enum_variant_attributes Also fixes some small docs issues (unlinked page, wrong link, double word) * Improve the enum_variant_attributes test to exercise specific_enum_variant_attributes --------- Co-authored-by: Christiaan Biesterbosch <[email protected]>
1 parent 7d5df69 commit 040b4df

File tree

6 files changed

+129
-5
lines changed

6 files changed

+129
-5
lines changed

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [Top-level enum](./codegen/enum.md)
88
- [`Ref` and `RefMut`](./codegen/ref-and-refmut.md)
99
- [Mapping macros](./codegen/map-macros.md)
10+
- [Meta variant structs and enums](./codegen/meta-variants.md)
1011
- [Configuration](./config.md)
1112
- [Struct attributes](./config/struct.md)
1213
- [Field attributes](./config/field.md)

book/src/codegen.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ You should visit each of the sub-pages in order to understand how the generated
88
2. [Top-level enum](./codegen/enum.md).
99
3. [`Ref` and `RefMut`](./codegen/ref-and-refmut.md).
1010
4. [Mapping macros](./codegen/map-macros.md).
11+
5. [Meta variant structs and enums](./codegen/meta-variants.md).
1112

1213
## Example
1314

book/src/codegen/enum.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
SuperStruct generates an enum that combines all of the generated variant structs.
44

5-
Consider the the `MyStruct` example from the previous page:
5+
Consider the `MyStruct` example from the previous page:
66

77
```rust,no_run,no_playground
88
#[superstruct(variants(Foo, Bar))]
@@ -68,3 +68,37 @@ are described [here](./ref-and-refmut.md).
6868
The top-level enum has `From` implementations for converting (owned) variant structs, i.e.
6969

7070
* `impl From<{VariantStruct}> for {BaseName}` for all variants
71+
72+
## Attributes on the enum variants
73+
74+
To add attributes to the enum variants, `enum_variant_attributes` and `specific_enum_variant_attributes`
75+
can be used.
76+
77+
Consider a variant of the `MyStruct` example where you want to derive `serde::Serialize`. However, one
78+
of the fields has a lifetime thus the `#[serde(borrow)]` attribute is required on the enum variants.
79+
In addition, you want to change the name of one of the enum variants when it's serialized:
80+
```rust,no_run,no_playground
81+
#[superstruct(
82+
variants(Foo, Bar),
83+
enum_variant_attributes(serde(borrow)),
84+
specific_enum_variant_attributes(Bar(serde(rename = "Baz"))),
85+
)]
86+
#[derive(serde::Serialize)]
87+
struct MyStruct<'a> {
88+
name: &'a str,
89+
#[superstruct(only(Foo))]
90+
location: u16,
91+
}
92+
```
93+
94+
The generated enum is:
95+
96+
```rust,no_run,no_playground
97+
#[derive(serde::Serialize)]
98+
enum MyStruct<'a> {
99+
#[serde(borrow)]
100+
Foo(MyStructFoo<'a>),
101+
#[serde(borrow, rename = "Baz")]
102+
Bar(MyStructBar<'a>),
103+
}
104+
```

book/src/config/struct.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,30 @@ procedural macro is being invoked on the variant struct which requires different
6767

6868
**Format**: zero or more variant names, with variant attributes nested in parens
6969

70+
## Enum variant attributes
71+
72+
```
73+
#[superstruct(enum_variant_attributes(...))]
74+
```
75+
76+
Provide a list of attributes to be applied verbatim to each of the enum variants.
77+
78+
This is useful when using another proc-macro on the enum and needing to add an attribute
79+
to all enum variants.
80+
81+
**Format**: any.
82+
83+
## Specific enum variant attributes
84+
85+
```
86+
#[superstruct(specific_enum_variant_attributes(A(...), B(...), ...))]
87+
```
88+
89+
Similar to `enum_variant_attributes`, but applies the attributes only to the named enum variants.
90+
This is useful if e.g. one enum variant needs an attribute while the others cannot.
91+
92+
**Format**: zero or more variant names, with enum variant attributes nested in parens.
93+
7094
## `Ref` attributes
7195

7296
```
@@ -106,7 +130,7 @@ Disable generation of the top-level enum, and all code except the
106130

107131
Generate mapping macros from the top-level enum, the `Ref` type or the `RefMut` type as appropriate.
108132

109-
Please see the documentation on [Mapping into other types](./codegen/map-macros.md#mapping-into-other-types)
133+
Please see the documentation on [Mapping into other types](../codegen/map-macros.md#mapping-into-other-types)
110134
for an explanation of how these macros operate.
111135

112136
**Format**: one or more `superstruct` type names

src/lib.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use from::{
99
generate_from_enum_trait_impl_for_ref, generate_from_variant_trait_impl,
1010
generate_from_variant_trait_impl_for_ref,
1111
};
12-
use itertools::Itertools;
12+
use itertools::{izip, Itertools};
1313
use macros::generate_all_map_macros;
1414
use proc_macro::TokenStream;
1515
use proc_macro2::{Span, TokenStream as TokenStream2};
@@ -39,6 +39,12 @@ struct StructOpts {
3939
/// List of attributes to apply to a selection of named variant structs.
4040
#[darling(default)]
4141
specific_variant_attributes: Option<HashMap<Ident, NestedMetaList>>,
42+
/// List of attributes to apply to the enum variants.
43+
#[darling(default)]
44+
enum_variant_attributes: Option<NestedMetaList>,
45+
/// List of attributes to apply to a selection of named enum variants.
46+
#[darling(default)]
47+
specific_enum_variant_attributes: Option<HashMap<Ident, NestedMetaList>>,
4248
/// List of attributes to apply to the generated Ref type.
4349
#[darling(default)]
4450
ref_attributes: Option<NestedMetaList>,
@@ -480,6 +486,11 @@ pub fn superstruct(args: TokenStream, input: TokenStream) -> TokenStream {
480486

481487
// If the `no_enum` attribute is set, stop after generating variant structs.
482488
if opts.no_enum {
489+
if opts.enum_variant_attributes.is_some() {
490+
panic!("can't set both `no_enum` and `enum_variant_attributes`")
491+
} else if opts.specific_enum_variant_attributes.is_some() {
492+
panic!("can't set both `no_enum` and `enum_specific_variant_attributes`")
493+
}
483494
return TokenStream::from_iter(output_items);
484495
}
485496

@@ -542,11 +553,35 @@ fn generate_wrapper_enums(
542553
is_meta: bool,
543554
) {
544555
let visibility = &item.vis;
556+
let enum_variant_attributes = opts
557+
.enum_variant_attributes
558+
.as_ref()
559+
.map_or(&[][..], |attrs| &attrs.metas);
545560
// Extract the generics to use for the top-level type and all variant structs.
546561
let decl_generics = &item.generics;
547562
// Generics used for the impl block.
548563
let (impl_generics, ty_generics, where_clause) = &item.generics.split_for_impl();
549564

565+
// Construct the enum variants
566+
let mut variants = Vec::new();
567+
for (variant_name, struct_name) in izip!(variant_names, struct_names) {
568+
let specific_enum_variant_attributes = opts
569+
.specific_enum_variant_attributes
570+
.as_ref()
571+
.and_then(|map| map.get(variant_name))
572+
.map_or(&[][..], |attrs| &attrs.metas);
573+
let variant = quote! {
574+
#(
575+
#[#enum_variant_attributes]
576+
)*
577+
#(
578+
#[#specific_enum_variant_attributes]
579+
)*
580+
#variant_name(#struct_name #ty_generics),
581+
};
582+
variants.push(variant);
583+
}
584+
550585
// Construct the top-level enum.
551586
let top_level_attrs = discard_superstruct_attrs(&item.attrs);
552587
let enum_item = quote! {
@@ -555,7 +590,7 @@ fn generate_wrapper_enums(
555590
)*
556591
#visibility enum #type_name #decl_generics #where_clause {
557592
#(
558-
#variant_names(#struct_names #ty_generics),
593+
#variants
559594
)*
560595
}
561596
};

tests/basic.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![allow(non_local_definitions)] // for macros on structs within test functions
22

3-
use serde::Deserialize;
3+
use serde::{Deserialize, Serialize};
4+
use std::borrow::Cow;
45
use superstruct::superstruct;
56

67
#[test]
@@ -152,3 +153,31 @@ fn no_getter() {
152153
pub x: String,
153154
}
154155
}
156+
157+
#[test]
158+
#[allow(dead_code)]
159+
fn enum_variant_attribute() {
160+
#[superstruct(
161+
variants(A, B),
162+
variant_attributes(derive(Deserialize, Serialize)),
163+
enum_variant_attributes(serde(borrow)),
164+
specific_enum_variant_attributes(B(serde(rename = "C")))
165+
)]
166+
#[derive(Deserialize, Serialize)]
167+
struct EnumVariantAttribute<'a> {
168+
#[superstruct(only(A))]
169+
#[serde(borrow)]
170+
pub x: Cow<'a, str>,
171+
#[superstruct(only(B))]
172+
#[serde(borrow)]
173+
pub y: Cow<'a, [u8]>,
174+
}
175+
176+
assert_eq!(
177+
serde_json::to_string(&EnumVariantAttribute::B(EnumVariantAttributeB {
178+
y: Cow::Borrowed(&[0])
179+
}))
180+
.unwrap(),
181+
r#"{"C":{"y":[0]}}"#
182+
);
183+
}

0 commit comments

Comments
 (0)