Skip to content

Commit 030d7b5

Browse files
author
Peter Glotfelty
committed
Merge branch 'bobozaur-258-transparent-attr'
2 parents 24e7a4f + 7606dd5 commit 030d7b5

File tree

11 files changed

+267
-97
lines changed

11 files changed

+267
-97
lines changed

strum/src/additional_attributes.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@
7474
//! The plugin will fail if the data doesn't implement From<&str>. You can only have one `default`
7575
//! on your enum.
7676
//!
77+
//! - `transparent`: Signals that the inner field's implementation should be used, instead of generating
78+
//! one for this variant. Only applicable to enum variants with a single field. Compatible with the
79+
//! `AsRefStr`, `Display` and `IntoStaticStr` derive macros.
80+
//!
7781
//! - `disabled`: removes variant from generated code.
7882
//!
7983
//! - `ascii_case_insensitive`: makes the comparison to this variant case insensitive (ASCII only).

strum_macros/src/helpers/metadata.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod kw {
3232
custom_keyword!(detailed_message);
3333
custom_keyword!(serialize);
3434
custom_keyword!(to_string);
35+
custom_keyword!(transparent);
3536
custom_keyword!(disabled);
3637
custom_keyword!(default);
3738
custom_keyword!(default_with);
@@ -62,7 +63,7 @@ pub enum EnumMeta {
6263
kw: kw::parse_err_fn,
6364
path: Path,
6465
},
65-
ConstIntoStr(kw::const_into_str)
66+
ConstIntoStr(kw::const_into_str),
6667
}
6768

6869
impl Parse for EnumMeta {
@@ -188,6 +189,7 @@ pub enum VariantMeta {
188189
kw: kw::to_string,
189190
value: LitStr,
190191
},
192+
Transparent(kw::transparent),
191193
Disabled(kw::disabled),
192194
Default(kw::default),
193195
DefaultWith {
@@ -227,6 +229,8 @@ impl Parse for VariantMeta {
227229
let _: Token![=] = input.parse()?;
228230
let value = input.parse()?;
229231
Ok(VariantMeta::ToString { kw, value })
232+
} else if lookahead.peek(kw::transparent) {
233+
Ok(VariantMeta::Transparent(input.parse()?))
230234
} else if lookahead.peek(kw::disabled) {
231235
Ok(VariantMeta::Disabled(input.parse()?))
232236
} else if lookahead.peek(kw::default) {

strum_macros/src/helpers/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ pub fn non_unit_variant_error() -> syn::Error {
3232
)
3333
}
3434

35+
pub fn non_single_field_variant_error(attr: &str) -> syn::Error {
36+
syn::Error::new(
37+
Span::call_site(),
38+
format_args!(
39+
"The [`{}`] attribute only supports enum variants with a single field",
40+
attr
41+
),
42+
)
43+
}
44+
3545
pub fn strum_discriminants_passthrough_error(span: &impl Spanned) -> syn::Error {
3646
syn::Error::new(
3747
span.span(),

strum_macros/src/helpers/variant_props.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub trait HasStrumVariantProperties {
1111

1212
#[derive(Clone, Default)]
1313
pub struct StrumVariantProperties {
14+
pub transparent: Option<kw::transparent>,
1415
pub disabled: Option<kw::disabled>,
1516
pub default: Option<kw::default>,
1617
pub default_with: Option<LitStr>,
@@ -73,6 +74,7 @@ impl HasStrumVariantProperties for Variant {
7374

7475
let mut message_kw = None;
7576
let mut detailed_message_kw = None;
77+
let mut transparent_kw = None;
7678
let mut disabled_kw = None;
7779
let mut default_kw = None;
7880
let mut default_with_kw = None;
@@ -110,6 +112,14 @@ impl HasStrumVariantProperties for Variant {
110112
to_string_kw = Some(kw);
111113
output.to_string = Some(value);
112114
}
115+
VariantMeta::Transparent(kw) => {
116+
if let Some(fst_kw) = transparent_kw {
117+
return Err(occurrence_error(fst_kw, kw, "transparent"));
118+
}
119+
120+
transparent_kw = Some(kw);
121+
output.transparent = Some(kw);
122+
}
113123
VariantMeta::Disabled(kw) => {
114124
if let Some(fst_kw) = disabled_kw {
115125
return Err(occurrence_error(fst_kw, kw, "disabled"));

strum_macros/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ fn debug_print_generated(ast: &DeriveInput, toks: &TokenStream) {
5656
///
5757
/// The default error type is `strum::ParseError`. This can be overriden by applying both the
5858
/// `parse_err_ty` and `parse_err_fn` attributes at the type level. `parse_error_fn` should be a
59-
/// function that accepts an `&str` and returns the type `parse_error_ty`. See
59+
/// function that accepts an `&str` and returns the type `parse_error_ty`. See
6060
/// [this test case](https://github.com/Peternator7/strum/blob/9db3c4dc9b6f585aeb9f5f15f9cc18b6cf4fd780/strum_tests/tests/from_str.rs#L233)
6161
/// for an example.
6262
///

strum_macros/src/macros/strings/as_ref_str.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ use proc_macro2::TokenStream;
22
use quote::quote;
33
use syn::{parse_quote, Data, DeriveInput, Fields};
44

5-
use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
6-
7-
fn get_arms(ast: &DeriveInput) -> syn::Result<Vec<TokenStream>> {
5+
use crate::helpers::{
6+
non_enum_error, non_single_field_variant_error, HasStrumVariantProperties, HasTypeProperties,
7+
};
8+
9+
fn get_arms<F>(ast: &DeriveInput, transparent_fn: F) -> syn::Result<Vec<TokenStream>>
10+
where
11+
F: Fn(&TokenStream) -> TokenStream,
12+
{
813
let name = &ast.ident;
914
let mut arms = Vec::new();
1015
let variants = match &ast.data {
@@ -22,12 +27,21 @@ fn get_arms(ast: &DeriveInput) -> syn::Result<Vec<TokenStream>> {
2227
continue;
2328
}
2429

30+
if let Some(..) = variant_properties.transparent {
31+
let arm = super::extract_single_field_variant_and_then(name, variant, |tok| {
32+
transparent_fn(tok)
33+
})
34+
.map_err(|_| non_single_field_variant_error("transparent"))?;
35+
36+
arms.push(arm);
37+
continue;
38+
}
39+
2540
// Look at all the serialize attributes.
2641
// Use `to_string` attribute (not `as_ref_str` or something) to keep things consistent
2742
// (i.e. always `enum.as_ref().to_string() == enum.to_string()`).
2843
let output = variant_properties
2944
.get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref());
30-
3145
let params = match variant.fields {
3246
Fields::Unit => quote! {},
3347
Fields::Unnamed(..) => quote! { (..) },
@@ -52,7 +66,10 @@ fn get_arms(ast: &DeriveInput) -> syn::Result<Vec<TokenStream>> {
5266
pub fn as_ref_str_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
5367
let name = &ast.ident;
5468
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
55-
let arms = get_arms(ast)?;
69+
let arms = get_arms(ast, |tok| {
70+
quote! { ::core::convert::AsRef::<str>::as_ref(#tok) }
71+
})?;
72+
5673
Ok(quote! {
5774
impl #impl_generics ::core::convert::AsRef<str> for #name #ty_generics #where_clause {
5875
#[inline]
@@ -76,7 +93,10 @@ pub fn as_static_str_inner(
7693
) -> syn::Result<TokenStream> {
7794
let name = &ast.ident;
7895
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
79-
let arms = get_arms(ast)?;
96+
let arms = get_arms(ast, |tok| {
97+
quote! { ::core::convert::From::from(#tok) }
98+
})?;
99+
80100
let type_properties = ast.get_type_properties()?;
81101
let strum_module_path = type_properties.crate_module_path();
82102

@@ -119,7 +139,7 @@ pub fn as_static_str_inner(
119139
}
120140
}
121141
},
122-
GenerateTraitVariant::From => quote! {
142+
GenerateTraitVariant::From => quote! {
123143
impl #impl_generics #name #ty_generics #where_clause {
124144
pub const fn into_str(&self) -> &'static str {
125145
match self {

strum_macros/src/macros/strings/display.rs

Lines changed: 99 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use proc_macro2::{Ident, TokenStream};
22
use quote::quote;
33
use syn::{punctuated::Punctuated, Data, DeriveInput, Fields, LitStr, Token};
44

5-
use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
5+
use crate::helpers::{
6+
non_enum_error, non_single_field_variant_error, HasStrumVariantProperties, HasTypeProperties,
7+
};
68

79
pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
810
let name = &ast.ident;
@@ -23,6 +25,16 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
2325
continue;
2426
}
2527

28+
if let Some(..) = variant_properties.transparent {
29+
let arm = super::extract_single_field_variant_and_then(name, variant, |tok| {
30+
quote! { ::core::fmt::Display::fmt(#tok, f)}
31+
})
32+
.map_err(|_| non_single_field_variant_error("transparent"))?;
33+
34+
arms.push(arm);
35+
continue;
36+
}
37+
2638
// Look at all the serialize attributes.
2739
let output = variant_properties
2840
.get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref());
@@ -37,7 +49,8 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
3749
.enumerate()
3850
.map(|(index, field)| {
3951
assert!(field.ident.is_none());
40-
let ident = syn::parse_str::<Ident>(format!("field{}", index).as_str()).unwrap();
52+
let ident =
53+
syn::parse_str::<Ident>(format!("field{}", index).as_str()).unwrap();
4154
quote! { ref #ident }
4255
})
4356
.collect();
@@ -59,86 +72,87 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
5972
};
6073

6174
if variant_properties.to_string.is_none() && variant_properties.default.is_some() {
62-
match &variant.fields {
63-
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
64-
arms.push(quote! { #name::#ident(ref s) => ::core::fmt::Display::fmt(s, f) });
75+
let arm = super::extract_single_field_variant_and_then(name, variant, |tok| {
76+
quote! { ::core::fmt::Display::fmt(#tok, f)}
77+
})
78+
.map_err(|_| {
79+
syn::Error::new_spanned(
80+
variant,
81+
"Default only works on newtype structs with a single String field",
82+
)
83+
})?;
84+
85+
arms.push(arm);
86+
continue;
87+
}
88+
89+
let arm = match variant.fields {
90+
Fields::Named(ref field_names) => {
91+
let used_vars = capture_format_string_idents(&output)?;
92+
if used_vars.is_empty() {
93+
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
94+
} else {
95+
// Create args like 'name = name, age = age' for format macro
96+
let args: Punctuated<_, Token!(,)> = field_names
97+
.named
98+
.iter()
99+
.filter_map(|field| {
100+
let ident = field.ident.as_ref().unwrap();
101+
// Only contain variables that are used in format string
102+
if !used_vars.contains(ident) {
103+
None
104+
} else {
105+
Some(quote! { #ident = #ident })
106+
}
107+
})
108+
.collect();
109+
110+
quote! {
111+
#[allow(unused_variables)]
112+
#name::#ident #params => ::core::fmt::Display::fmt(&format_args!(#output, #args), f)
113+
}
65114
}
66-
_ => {
115+
}
116+
Fields::Unnamed(ref unnamed_fields) => {
117+
let used_vars = capture_format_strings(&output)?;
118+
if used_vars.iter().any(String::is_empty) {
67119
return Err(syn::Error::new_spanned(
68-
variant,
69-
"Default only works on newtype structs with a single String field",
70-
))
120+
&output,
121+
"Empty {} is not allowed; Use manual numbering ({0})",
122+
));
71123
}
72-
}
73-
} else {
74-
let arm = match variant.fields {
75-
Fields::Named(ref field_names) => {
76-
let used_vars = capture_format_string_idents(&output)?;
77-
if used_vars.is_empty() {
78-
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
79-
} else {
80-
// Create args like 'name = name, age = age' for format macro
81-
let args: Punctuated<_, Token!(,)> = field_names
82-
.named
83-
.iter()
84-
.filter_map(|field| {
85-
let ident = field.ident.as_ref().unwrap();
86-
// Only contain variables that are used in format string
87-
if !used_vars.contains(ident) {
88-
None
89-
} else {
90-
Some(quote! { #ident = #ident })
91-
}
92-
})
93-
.collect();
94-
95-
quote! {
96-
#[allow(unused_variables)]
97-
#name::#ident #params => ::core::fmt::Display::fmt(&format_args!(#output, #args), f)
98-
}
99-
}
100-
},
101-
Fields::Unnamed(ref unnamed_fields) => {
102-
let used_vars = capture_format_strings(&output)?;
103-
if used_vars.iter().any(String::is_empty) {
104-
return Err(syn::Error::new_spanned(
105-
&output,
106-
"Empty {} is not allowed; Use manual numbering ({0})",
107-
))
108-
}
109-
if used_vars.is_empty() {
110-
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
111-
} else {
112-
let args: Punctuated<_, Token!(,)> = unnamed_fields
113-
.unnamed
114-
.iter()
115-
.enumerate()
116-
.map(|(index, field)| {
117-
assert!(field.ident.is_none());
118-
syn::parse_str::<Ident>(format!("field{}", index).as_str()).unwrap()
119-
})
120-
.collect();
121-
quote! {
122-
#[allow(unused_variables)]
123-
#name::#ident #params => ::core::fmt::Display::fmt(&format!(#output, #args), f)
124-
}
124+
if used_vars.is_empty() {
125+
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
126+
} else {
127+
let args: Punctuated<_, Token!(,)> = unnamed_fields
128+
.unnamed
129+
.iter()
130+
.enumerate()
131+
.map(|(index, field)| {
132+
assert!(field.ident.is_none());
133+
syn::parse_str::<Ident>(format!("field{}", index).as_str()).unwrap()
134+
})
135+
.collect();
136+
quote! {
137+
#[allow(unused_variables)]
138+
#name::#ident #params => ::core::fmt::Display::fmt(&format!(#output, #args), f)
125139
}
126140
}
127-
Fields::Unit => {
128-
let used_vars = capture_format_strings(&output)?;
129-
if !used_vars.is_empty() {
130-
return Err(syn::Error::new_spanned(
131-
&output,
132-
"Unit variants do not support interpolation",
133-
));
134-
}
135-
136-
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
141+
}
142+
Fields::Unit => {
143+
let used_vars = capture_format_strings(&output)?;
144+
if !used_vars.is_empty() {
145+
return Err(syn::Error::new_spanned(
146+
&output,
147+
"Unit variants do not support interpolation",
148+
));
137149
}
138-
};
139150

140-
arms.push(arm);
141-
}
151+
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
152+
}
153+
};
154+
155+
arms.push(arm);
142156
}
143157

144158
if arms.len() < variants.len() {
@@ -157,14 +171,17 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
157171
}
158172

159173
fn capture_format_string_idents(string_literal: &LitStr) -> syn::Result<Vec<Ident>> {
160-
capture_format_strings(string_literal)?.into_iter().map(|ident| {
161-
syn::parse_str::<Ident>(ident.as_str()).map_err(|_| {
162-
syn::Error::new_spanned(
163-
string_literal,
164-
"Invalid identifier inside format string bracket",
165-
)
174+
capture_format_strings(string_literal)?
175+
.into_iter()
176+
.map(|ident| {
177+
syn::parse_str::<Ident>(ident.as_str()).map_err(|_| {
178+
syn::Error::new_spanned(
179+
string_literal,
180+
"Invalid identifier inside format string bracket",
181+
)
182+
})
166183
})
167-
}).collect()
184+
.collect()
168185
}
169186

170187
fn capture_format_strings(string_literal: &LitStr) -> syn::Result<Vec<String>> {

0 commit comments

Comments
 (0)