From fc8f0020479de2afd160232d259da0b19a3627da Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 10 Oct 2020 20:25:10 +0200 Subject: [PATCH 1/6] Remove unused method NestedMetaHelpers::expect_lit --- strum_macros/src/helpers/meta_helpers.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/strum_macros/src/helpers/meta_helpers.rs b/strum_macros/src/helpers/meta_helpers.rs index a52eecf3..c0c7907b 100644 --- a/strum_macros/src/helpers/meta_helpers.rs +++ b/strum_macros/src/helpers/meta_helpers.rs @@ -31,7 +31,6 @@ impl MetaHelpers for Meta { pub trait NestedMetaHelpers { fn expect_meta(&self, msg: &str) -> syn::Result<&Meta>; - fn expect_lit(&self, msg: &str) -> syn::Result<&Lit>; } impl NestedMetaHelpers for NestedMeta { @@ -41,12 +40,6 @@ impl NestedMetaHelpers for NestedMeta { _ => Err(syn::Error::new_spanned(self, msg)), } } - fn expect_lit(&self, msg: &str) -> syn::Result<&Lit> { - match self { - NestedMeta::Lit(l) => Ok(l), - _ => Err(syn::Error::new_spanned(self, msg)), - } - } } pub trait LitHelpers { From 85e76296ca613fa9d6ffce85a3e95ca50212ce95 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 10 Oct 2020 21:11:50 +0200 Subject: [PATCH 2/6] Use references match instead of ref patterns --- strum_macros/src/helpers/type_props.rs | 4 ++-- strum_macros/src/helpers/variant_props.rs | 4 ++-- strum_macros/src/macros/enum_count.rs | 4 ++-- strum_macros/src/macros/enum_discriminants.rs | 10 +++++----- strum_macros/src/macros/enum_iter.rs | 10 +++++----- strum_macros/src/macros/enum_messages.rs | 4 ++-- strum_macros/src/macros/enum_properties.rs | 4 ++-- strum_macros/src/macros/enum_variant_names.rs | 4 ++-- strum_macros/src/macros/strings/as_ref_str.rs | 4 ++-- strum_macros/src/macros/strings/display.rs | 4 ++-- strum_macros/src/macros/strings/from_string.rs | 12 ++++++------ strum_macros/src/macros/strings/to_string.rs | 4 ++-- 12 files changed, 34 insertions(+), 34 deletions(-) diff --git a/strum_macros/src/helpers/type_props.rs b/strum_macros/src/helpers/type_props.rs index 0bc91024..1bcb2105 100644 --- a/strum_macros/src/helpers/type_props.rs +++ b/strum_macros/src/helpers/type_props.rs @@ -48,8 +48,8 @@ impl HasTypeProperties for DeriveInput { } for meta in discriminants_meta { - match meta { - Meta::List(ref ls) => { + match &meta { + Meta::List(ls) => { if ls.path.is_ident("derive") { let paths = ls .nested diff --git a/strum_macros/src/helpers/variant_props.rs b/strum_macros/src/helpers/variant_props.rs index 12f626a1..7281c6c6 100644 --- a/strum_macros/src/helpers/variant_props.rs +++ b/strum_macros/src/helpers/variant_props.rs @@ -24,7 +24,7 @@ pub struct StrumVariantProperties { impl StrumVariantProperties { pub fn get_preferred_name(&self, case_style: Option) -> String { - if let Some(ref to_string) = self.to_string { + if let Some(to_string) = &self.to_string { to_string.clone() } else { let mut serialized = self.serialize.clone(); @@ -42,7 +42,7 @@ impl StrumVariantProperties { pub fn get_serializations(&self, case_style: Option) -> Vec { let mut attrs = self.serialize.clone(); - if let Some(ref to_string) = self.to_string { + if let Some(to_string) = &self.to_string { attrs.push(to_string.clone()); } diff --git a/strum_macros/src/macros/enum_count.rs b/strum_macros/src/macros/enum_count.rs index e487f1e2..4d492897 100644 --- a/strum_macros/src/macros/enum_count.rs +++ b/strum_macros/src/macros/enum_count.rs @@ -3,8 +3,8 @@ use quote::quote; use syn::{Data, DeriveInput}; pub(crate) fn enum_count_inner(ast: &DeriveInput) -> TokenStream { - let n = match ast.data { - Data::Enum(ref v) => v.variants.len(), + let n = match &ast.data { + Data::Enum(v) => v.variants.len(), _ => panic!("EnumCount can only be used with enums"), }; diff --git a/strum_macros/src/macros/enum_discriminants.rs b/strum_macros/src/macros/enum_discriminants.rs index 5e9baf6a..e5665324 100644 --- a/strum_macros/src/macros/enum_discriminants.rs +++ b/strum_macros/src/macros/enum_discriminants.rs @@ -15,8 +15,8 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let vis = &ast.vis; - let variants = match ast.data { - Data::Enum(ref v) => &v.variants, + let variants = match &ast.data { + Data::Enum(v) => &v.variants, _ => panic!("EnumDiscriminants only works on Enums"), }; @@ -84,12 +84,12 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result { let ident = &variant.ident; use syn::Fields::*; - let params = match variant.fields { + let params = match &variant.fields { Unit => quote! {}, - Unnamed(ref _fields) => { + Unnamed(_fields) => { quote! { (..) } } - Named(ref _fields) => { + Named(_fields) => { quote! { { .. } } } }; diff --git a/strum_macros/src/macros/enum_iter.rs b/strum_macros/src/macros/enum_iter.rs index 0ed464bb..bbef9c51 100644 --- a/strum_macros/src/macros/enum_iter.rs +++ b/strum_macros/src/macros/enum_iter.rs @@ -24,8 +24,8 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result { quote! { < () > } }; - let variants = match ast.data { - Data::Enum(ref v) => &v.variants, + let variants = match &ast.data { + Data::Enum(v) => &v.variants, _ => panic!("EnumIter only works on Enums"), }; @@ -39,14 +39,14 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result { } let ident = &variant.ident; - let params = match variant.fields { + let params = match &variant.fields { Unit => quote! {}, - Unnamed(ref fields) => { + Unnamed(fields) => { let defaults = ::std::iter::repeat(quote!(::std::default::Default::default())) .take(fields.unnamed.len()); quote! { (#(#defaults),*) } } - Named(ref fields) => { + Named(fields) => { let fields = fields .named .iter() diff --git a/strum_macros/src/macros/enum_messages.rs b/strum_macros/src/macros/enum_messages.rs index 74015cc2..67ad6bc9 100644 --- a/strum_macros/src/macros/enum_messages.rs +++ b/strum_macros/src/macros/enum_messages.rs @@ -7,8 +7,8 @@ use crate::helpers::{HasStrumVariantProperties, HasTypeProperties}; pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - let variants = match ast.data { - Data::Enum(ref v) => &v.variants, + let variants = match &ast.data { + Data::Enum(v) => &v.variants, _ => panic!("EnumMessage only works on Enums"), }; diff --git a/strum_macros/src/macros/enum_properties.rs b/strum_macros/src/macros/enum_properties.rs index ccc37a11..ccb2b7fe 100644 --- a/strum_macros/src/macros/enum_properties.rs +++ b/strum_macros/src/macros/enum_properties.rs @@ -7,8 +7,8 @@ use crate::helpers::HasStrumVariantProperties; pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - let variants = match ast.data { - Data::Enum(ref v) => &v.variants, + let variants = match &ast.data { + Data::Enum(v) => &v.variants, _ => panic!("EnumProp only works on Enums"), }; diff --git a/strum_macros/src/macros/enum_variant_names.rs b/strum_macros/src/macros/enum_variant_names.rs index a8ce2702..868f9892 100644 --- a/strum_macros/src/macros/enum_variant_names.rs +++ b/strum_macros/src/macros/enum_variant_names.rs @@ -9,8 +9,8 @@ pub fn enum_variant_names_inner(ast: &DeriveInput) -> syn::Result { let gen = &ast.generics; let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); - let variants = match ast.data { - Data::Enum(ref v) => &v.variants, + let variants = match &ast.data { + Data::Enum(v) => &v.variants, _ => panic!("EnumVariantNames only works on Enums"), }; diff --git a/strum_macros/src/macros/strings/as_ref_str.rs b/strum_macros/src/macros/strings/as_ref_str.rs index 88c0e886..9e26413d 100644 --- a/strum_macros/src/macros/strings/as_ref_str.rs +++ b/strum_macros/src/macros/strings/as_ref_str.rs @@ -7,8 +7,8 @@ use crate::helpers::{HasStrumVariantProperties, HasTypeProperties}; fn get_arms(ast: &DeriveInput) -> syn::Result> { let name = &ast.ident; let mut arms = Vec::new(); - let variants = match ast.data { - Data::Enum(ref v) => &v.variants, + let variants = match &ast.data { + Data::Enum(v) => &v.variants, _ => panic!("This macro only works on Enums"), }; diff --git a/strum_macros/src/macros/strings/display.rs b/strum_macros/src/macros/strings/display.rs index 3b24b39d..1aefe789 100644 --- a/strum_macros/src/macros/strings/display.rs +++ b/strum_macros/src/macros/strings/display.rs @@ -7,8 +7,8 @@ use crate::helpers::{HasStrumVariantProperties, HasTypeProperties}; pub fn display_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - let variants = match ast.data { - Data::Enum(ref v) => &v.variants, + let variants = match &ast.data { + Data::Enum(v) => &v.variants, _ => panic!("Display only works on Enums"), }; diff --git a/strum_macros/src/macros/strings/from_string.rs b/strum_macros/src/macros/strings/from_string.rs index 7447461f..5b1d7c19 100644 --- a/strum_macros/src/macros/strings/from_string.rs +++ b/strum_macros/src/macros/strings/from_string.rs @@ -7,8 +7,8 @@ use crate::helpers::{HasStrumVariantProperties, HasTypeProperties}; pub fn from_string_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - let variants = match ast.data { - Data::Enum(ref v) => &v.variants, + let variants = match &ast.data { + Data::Enum(v) => &v.variants, _ => panic!("FromString only works on Enums"), }; @@ -32,7 +32,7 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result { panic!("Can't have multiple default variants"); } - if let Unnamed(ref fields) = variant.fields { + if let Unnamed(fields) = &variant.fields { if fields.unnamed.len() != 1 { panic!("Default only works on unit structs with a single String parameter"); } @@ -51,14 +51,14 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result { // If we don't have any custom variants, add the default serialized name. let attrs = variant_properties.get_serializations(type_properties.case_style); - let params = match variant.fields { + let params = match &variant.fields { Unit => quote! {}, - Unnamed(ref fields) => { + Unnamed(fields) => { let defaults = ::std::iter::repeat(quote!(Default::default())).take(fields.unnamed.len()); quote! { (#(#defaults),*) } } - Named(ref fields) => { + Named(fields) => { let fields = fields .named .iter() diff --git a/strum_macros/src/macros/strings/to_string.rs b/strum_macros/src/macros/strings/to_string.rs index fbac7e8f..7c05f506 100644 --- a/strum_macros/src/macros/strings/to_string.rs +++ b/strum_macros/src/macros/strings/to_string.rs @@ -7,8 +7,8 @@ use crate::helpers::{HasStrumVariantProperties, HasTypeProperties}; pub fn to_string_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - let variants = match ast.data { - Data::Enum(ref v) => &v.variants, + let variants = match &ast.data { + Data::Enum(v) => &v.variants, _ => panic!("ToString only works on Enums"), }; From 15596f68516f2e6728fb7cd59507f3bb63482d21 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 11 Oct 2020 02:11:10 +0200 Subject: [PATCH 3/6] Use custom meta types instead of syn::Meta This should improve both readability of the code and the quality of error messages. --- strum_macros/src/helpers/case_style.rs | 63 +++-- strum_macros/src/helpers/has_metadata.rs | 56 ---- strum_macros/src/helpers/meta_helpers.rs | 56 ---- strum_macros/src/helpers/metadata.rs | 241 ++++++++++++++++++ strum_macros/src/helpers/mod.rs | 4 +- strum_macros/src/helpers/type_props.rs | 83 ++---- strum_macros/src/helpers/variant_props.rs | 120 ++++----- strum_macros/src/macros/enum_discriminants.rs | 14 +- 8 files changed, 360 insertions(+), 277 deletions(-) delete mode 100644 strum_macros/src/helpers/has_metadata.rs delete mode 100644 strum_macros/src/helpers/meta_helpers.rs create mode 100644 strum_macros/src/helpers/metadata.rs diff --git a/strum_macros/src/helpers/case_style.rs b/strum_macros/src/helpers/case_style.rs index 6295a778..09629aee 100644 --- a/strum_macros/src/helpers/case_style.rs +++ b/strum_macros/src/helpers/case_style.rs @@ -1,5 +1,9 @@ use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase}; -use syn::Ident; +use std::convert::TryFrom; +use syn::{ + parse::{Parse, ParseStream}, + Ident, LitStr, +}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum CaseStyle { @@ -15,9 +19,41 @@ pub enum CaseStyle { PascalCase, } -impl<'s> From<&'s str> for CaseStyle { - fn from(text: &'s str) -> CaseStyle { - match text { +const VALID_CASE_STYLES: &[&str] = &[ + "camelCase", + "PascalCase", + "kebab-case", + "snake_case", + "SCREAMING_SNAKE_CASE", + "SCREAMING-KEBAB-CASE", + "lowercase", + "UPPERCASE", + "title_case", + "mixed_case", +]; + +impl Parse for CaseStyle { + fn parse(input: ParseStream) -> syn::Result { + let text = input.parse::()?; + let val = text.value(); + + Self::try_from(val.as_str()).map_err(|_| { + syn::Error::new_spanned( + &text, + format!( + "Unexpected case style for serialize_all: `{}`. Valid values are: `{:?}`", + val, VALID_CASE_STYLES + ), + ) + }) + } +} + +impl TryFrom<&'_ str> for CaseStyle { + type Error = (); + + fn try_from(text: &str) -> Result { + Ok(match text { "camel_case" | "PascalCase" => CaseStyle::PascalCase, "camelCase" => CaseStyle::CamelCase, "snake_case" | "snek_case" => CaseStyle::SnakeCase, @@ -30,23 +66,8 @@ impl<'s> From<&'s str> for CaseStyle { "mixed_case" => CaseStyle::MixedCase, "lowercase" => CaseStyle::LowerCase, "UPPERCASE" => CaseStyle::UpperCase, - _ => panic!( - "Unexpected case style for serialize_all: `{}`. Valid values are: `{:?}`", - text, - [ - "camelCase", - "PascalCase", - "kebab-case", - "snake_case", - "SCREAMING_SNAKE_CASE", - "SCREAMING-KEBAB-CASE", - "lowercase", - "UPPERCASE", - "title_case", - "mixed_case", - ] - ), - } + _ => return Err(()), + }) } } diff --git a/strum_macros/src/helpers/has_metadata.rs b/strum_macros/src/helpers/has_metadata.rs deleted file mode 100644 index b8cab6fb..00000000 --- a/strum_macros/src/helpers/has_metadata.rs +++ /dev/null @@ -1,56 +0,0 @@ -use syn::{Attribute, DeriveInput, Meta, NestedMeta, Variant}; - -/// Represents a type that can have strum metadata associated with it. -pub trait HasMetadata { - /// Get all the metadata associated with a specific "tag". - /// All of strum's metadata is nested inside a path such as - /// #[strum(...)] so this let's us quickly filter down to only our metadata. - fn get_metadata(&self, ident: &str) -> syn::Result>; -} - -fn get_metadata_inner<'a>( - ident: &str, - it: impl IntoIterator, -) -> syn::Result> { - let mut res = Vec::new(); - - for attr in it { - if !attr.path.is_ident(ident) { - continue; - } - - let meta = attr.parse_meta()?; - let nested = match meta { - Meta::List(syn::MetaList { nested, .. }) => nested, - _ => { - return Err(syn::Error::new_spanned( - meta, - "unrecognized strum attribute form", - )) - } - }; - - for nested_meta in nested { - match nested_meta { - NestedMeta::Meta(meta) => res.push(meta), - NestedMeta::Lit(lit) => { - return Err(syn::Error::new_spanned(lit, "unexpected literal")) - } - } - } - } - - Ok(res) -} - -impl HasMetadata for Variant { - fn get_metadata(&self, ident: &str) -> syn::Result> { - get_metadata_inner(ident, &self.attrs) - } -} - -impl HasMetadata for DeriveInput { - fn get_metadata(&self, ident: &str) -> syn::Result> { - get_metadata_inner(ident, &self.attrs) - } -} diff --git a/strum_macros/src/helpers/meta_helpers.rs b/strum_macros/src/helpers/meta_helpers.rs deleted file mode 100644 index c0c7907b..00000000 --- a/strum_macros/src/helpers/meta_helpers.rs +++ /dev/null @@ -1,56 +0,0 @@ -use syn::{Lit, Meta, MetaList, MetaNameValue, NestedMeta, Path}; - -pub trait MetaHelpers { - fn expect_metalist(&self, msg: &str) -> syn::Result<&MetaList>; - fn expect_path(&self, msg: &str) -> syn::Result<&Path>; - fn expect_namevalue(&self, msg: &str) -> syn::Result<&MetaNameValue>; -} - -impl MetaHelpers for Meta { - fn expect_metalist(&self, msg: &str) -> syn::Result<&MetaList> { - match self { - Meta::List(list) => Ok(list), - _ => Err(syn::Error::new_spanned(self, msg)), - } - } - - fn expect_path(&self, msg: &str) -> syn::Result<&Path> { - match self { - Meta::Path(path) => Ok(path), - _ => Err(syn::Error::new_spanned(self, msg)), - } - } - - fn expect_namevalue(&self, msg: &str) -> syn::Result<&MetaNameValue> { - match self { - Meta::NameValue(pair) => Ok(pair), - _ => Err(syn::Error::new_spanned(self, msg)), - } - } -} - -pub trait NestedMetaHelpers { - fn expect_meta(&self, msg: &str) -> syn::Result<&Meta>; -} - -impl NestedMetaHelpers for NestedMeta { - fn expect_meta(&self, msg: &str) -> syn::Result<&Meta> { - match self { - NestedMeta::Meta(m) => Ok(m), - _ => Err(syn::Error::new_spanned(self, msg)), - } - } -} - -pub trait LitHelpers { - fn expect_string(&self, msg: &str) -> syn::Result; -} - -impl LitHelpers for Lit { - fn expect_string(&self, msg: &str) -> syn::Result { - match self { - Lit::Str(s) => Ok(s.value()), - _ => Err(syn::Error::new_spanned(self, msg)), - } - } -} diff --git a/strum_macros/src/helpers/metadata.rs b/strum_macros/src/helpers/metadata.rs new file mode 100644 index 00000000..6f76481f --- /dev/null +++ b/strum_macros/src/helpers/metadata.rs @@ -0,0 +1,241 @@ +use proc_macro2::{Span, TokenStream}; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Attribute, DeriveInput, Ident, LitStr, Path, Token, Variant, +}; + +use super::case_style::CaseStyle; + +mod kw { + use syn::custom_keyword; + + // enum metadata + custom_keyword!(serialize_all); + + // enum discriminant metadata + custom_keyword!(derive); + custom_keyword!(name); + + // variant metadata + custom_keyword!(message); + custom_keyword!(detailed_message); + custom_keyword!(serialize); + custom_keyword!(to_string); + custom_keyword!(disabled); + custom_keyword!(default); + custom_keyword!(props); +} + +pub enum EnumMeta { + SerializeAll { + serialize_all_kw: kw::serialize_all, + case_style: CaseStyle, + }, +} + +impl Parse for EnumMeta { + fn parse(input: ParseStream) -> syn::Result { + let serialize_all_kw = input.parse::()?; + input.parse::()?; + let case_style = input.parse()?; + Ok(Self::SerializeAll { + serialize_all_kw, + case_style, + }) + } +} + +impl Spanned for EnumMeta { + fn span(&self) -> Span { + match self { + Self::SerializeAll { + serialize_all_kw, .. + } => serialize_all_kw.span(), + } + } +} + +pub enum EnumDiscriminantsMeta { + Derive { kw: kw::derive, paths: Vec }, + Name { kw: kw::name, name: Ident }, + Other { path: Path, nested: TokenStream }, +} + +impl Parse for EnumDiscriminantsMeta { + fn parse(input: ParseStream) -> syn::Result { + if input.peek(kw::derive) { + let kw = input.parse()?; + let content; + parenthesized!(content in input); + let paths = content.parse_terminated::<_, Token![,]>(Path::parse)?; + Ok(Self::Derive { + kw, + paths: paths.into_iter().collect(), + }) + } else if input.peek(kw::name) { + let kw = input.parse()?; + let content; + parenthesized!(content in input); + let name = content.parse()?; + Ok(Self::Name { kw, name }) + } else { + let path = input.parse()?; + let content; + parenthesized!(content in input); + let nested = content.parse()?; + Ok(Self::Other { path, nested }) + } + } +} + +impl Spanned for EnumDiscriminantsMeta { + fn span(&self) -> Span { + match self { + Self::Derive { kw, .. } => kw.span, + Self::Name { kw, .. } => kw.span, + Self::Other { path, .. } => path.span(), + } + } +} + +pub trait DeriveInputExt { + /// Get all the strum metadata associated with an enum. + fn get_metadata(&self) -> syn::Result>; + + /// Get all the strum_discriminants metadata associated with an enum. + fn get_discriminants_metadata(&self) -> syn::Result>; +} + +impl DeriveInputExt for DeriveInput { + fn get_metadata(&self) -> syn::Result> { + get_metadata_inner("strum", &self.attrs) + } + + fn get_discriminants_metadata(&self) -> syn::Result> { + get_metadata_inner("strum_discriminants", &self.attrs) + } +} + +pub enum VariantMeta { + Message { + kw: kw::message, + value: LitStr, + }, + DetailedMessage { + kw: kw::detailed_message, + value: LitStr, + }, + Serialize { + kw: kw::serialize, + value: LitStr, + }, + ToString { + kw: kw::to_string, + value: LitStr, + }, + Disabled(kw::disabled), + Default(kw::default), + Props { + kw: kw::props, + props: Vec<(LitStr, LitStr)>, + }, +} + +impl Parse for VariantMeta { + fn parse(input: ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::message) { + let kw = input.parse()?; + let _: Token![=] = input.parse()?; + let value = input.parse()?; + Ok(Self::Message { kw, value }) + } else if lookahead.peek(kw::detailed_message) { + let kw = input.parse()?; + let _: Token![=] = input.parse()?; + let value = input.parse()?; + Ok(Self::DetailedMessage { kw, value }) + } else if lookahead.peek(kw::serialize) { + let kw = input.parse()?; + let _: Token![=] = input.parse()?; + let value = input.parse()?; + Ok(Self::Serialize { kw, value }) + } else if lookahead.peek(kw::to_string) { + let kw = input.parse()?; + let _: Token![=] = input.parse()?; + let value = input.parse()?; + Ok(Self::ToString { kw, value }) + } else if lookahead.peek(kw::disabled) { + Ok(Self::Disabled(input.parse()?)) + } else if lookahead.peek(kw::default) { + Ok(Self::Default(input.parse()?)) + } else if lookahead.peek(kw::props) { + let kw = input.parse()?; + let content; + parenthesized!(content in input); + let props = content.parse_terminated::<_, Token![,]>(Prop::parse)?; + Ok(Self::Props { + kw, + props: props + .into_iter() + .map(|Prop(k, v)| (LitStr::new(&k.to_string(), k.span()), v)) + .collect(), + }) + } else { + Err(lookahead.error()) + } + } +} + +struct Prop(Ident, LitStr); + +impl Parse for Prop { + fn parse(input: ParseStream) -> syn::Result { + use syn::ext::IdentExt; + + let k = Ident::parse_any(&input)?; + let _: Token![=] = input.parse()?; + let v = input.parse()?; + + Ok(Self(k, v)) + } +} + +impl Spanned for VariantMeta { + fn span(&self) -> Span { + match self { + Self::Message { kw, .. } => kw.span, + Self::DetailedMessage { kw, .. } => kw.span, + Self::Serialize { kw, .. } => kw.span, + Self::ToString { kw, .. } => kw.span, + Self::Disabled(kw) => kw.span, + Self::Default(kw) => kw.span, + Self::Props { kw, .. } => kw.span, + } + } +} + +pub trait VariantExt { + /// Get all the metadata associated with an enum variant. + fn get_metadata(&self) -> syn::Result>; +} + +impl VariantExt for Variant { + fn get_metadata(&self) -> syn::Result> { + get_metadata_inner("strum", &self.attrs) + } +} + +fn get_metadata_inner<'a, T: Parse + Spanned>( + ident: &str, + it: impl IntoIterator, +) -> syn::Result> { + it.into_iter() + .filter(|attr| attr.path.is_ident(ident)) + .try_fold(Vec::new(), |mut vec, attr| { + vec.extend(attr.parse_args_with(Punctuated::::parse_terminated)?); + Ok(vec) + }) +} diff --git a/strum_macros/src/helpers/mod.rs b/strum_macros/src/helpers/mod.rs index 5fbda5b1..747ad745 100644 --- a/strum_macros/src/helpers/mod.rs +++ b/strum_macros/src/helpers/mod.rs @@ -1,10 +1,8 @@ pub use self::case_style::CaseStyleHelpers; -pub use self::meta_helpers::{LitHelpers, MetaHelpers, NestedMetaHelpers}; pub use self::type_props::HasTypeProperties; pub use self::variant_props::HasStrumVariantProperties; pub mod case_style; -mod has_metadata; -mod meta_helpers; +mod metadata; pub mod type_props; pub mod variant_props; diff --git a/strum_macros/src/helpers/type_props.rs b/strum_macros/src/helpers/type_props.rs index 1bcb2105..d9bda387 100644 --- a/strum_macros/src/helpers/type_props.rs +++ b/strum_macros/src/helpers/type_props.rs @@ -1,87 +1,56 @@ -use std::convert::From; +use proc_macro2::TokenStream; +use quote::quote; use std::default::Default; -use syn::{DeriveInput, Lit, Meta, Path}; +use syn::{DeriveInput, Ident, Path}; use crate::helpers::case_style::CaseStyle; -use crate::helpers::has_metadata::HasMetadata; -use crate::helpers::{MetaHelpers, NestedMetaHelpers}; +use crate::helpers::metadata::{DeriveInputExt, EnumDiscriminantsMeta, EnumMeta}; pub trait HasTypeProperties { fn get_type_properties(&self) -> syn::Result; } -#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[derive(Debug, Clone, Default)] pub struct StrumTypeProperties { pub case_style: Option, pub discriminant_derives: Vec, - pub discriminant_name: Option, - pub discriminant_others: Vec, + pub discriminant_name: Option, + pub discriminant_others: Vec, } impl HasTypeProperties for DeriveInput { fn get_type_properties(&self) -> syn::Result { let mut output = StrumTypeProperties::default(); - let strum_meta = self.get_metadata("strum")?; - let discriminants_meta = self.get_metadata("strum_discriminants")?; + let strum_meta = self.get_metadata()?; + let discriminants_meta = self.get_discriminants_metadata()?; for meta in strum_meta { - let meta = match meta { - Meta::NameValue(mv) => mv, - _ => panic!("strum on types only supports key-values"), - }; - - if meta.path.is_ident("serialize_all") { - let style = match meta.lit { - Lit::Str(s) => s.value(), - _ => panic!("expected string value for 'serialize_all'"), - }; + match meta { + EnumMeta::SerializeAll { case_style, .. } => { + if output.case_style.is_some() { + panic!("found multiple values of serialize_all"); + } - if output.case_style.is_some() { - panic!("found multiple values of serialize_all"); + output.case_style = Some(case_style); } - - output.case_style = Some(CaseStyle::from(&*style)); - } else { - panic!("unrecognized attribue found on strum(..)"); } } for meta in discriminants_meta { - match &meta { - Meta::List(ls) => { - if ls.path.is_ident("derive") { - let paths = ls - .nested - .iter() - .map(|meta| { - let meta = meta.expect_meta("unexpected literal")?; - Ok(meta.path().clone()) - }) - .collect::>>()?; - - output.discriminant_derives.extend(paths); - } else if ls.path.is_ident("name") { - if ls.nested.len() != 1 { - panic!("name expects exactly 1 value"); - } - - let value = ls.nested.first().expect("unexpected error"); - let name = value - .expect_meta("unexpected literal")? - .expect_path("name must be an identifier")?; - - if output.discriminant_name.is_some() { - panic!("multiple occurrences of 'name'"); - } - - output.discriminant_name = Some(name.clone()); - } else { - output.discriminant_others.push(meta.clone()); + match meta { + EnumDiscriminantsMeta::Derive { paths, .. } => { + output.discriminant_derives.extend(paths); + } + EnumDiscriminantsMeta::Name { name, .. } => { + if output.discriminant_name.is_some() { + panic!("multiple occurrences of 'name'"); } + + output.discriminant_name = Some(name); } - _ => { - output.discriminant_others.push(meta); + EnumDiscriminantsMeta::Other { path, nested } => { + output.discriminant_others.push(quote! { #path(#nested) }); } } } diff --git a/strum_macros/src/helpers/variant_props.rs b/strum_macros/src/helpers/variant_props.rs index 7281c6c6..ca6b3f0b 100644 --- a/strum_macros/src/helpers/variant_props.rs +++ b/strum_macros/src/helpers/variant_props.rs @@ -1,10 +1,8 @@ -use std::collections::HashMap; use std::default::Default; -use syn::{Ident, Meta, Variant}; +use syn::{Ident, LitStr, Variant}; use crate::helpers::case_style::{CaseStyle, CaseStyleHelpers}; -use crate::helpers::has_metadata::HasMetadata; -use crate::helpers::{LitHelpers, MetaHelpers, NestedMetaHelpers}; +use crate::helpers::metadata::{VariantExt, VariantMeta}; pub trait HasStrumVariantProperties { fn get_variant_properties(&self) -> syn::Result; @@ -14,45 +12,42 @@ pub trait HasStrumVariantProperties { pub struct StrumVariantProperties { pub is_disabled: bool, pub default: bool, - pub message: Option, - pub detailed_message: Option, - pub string_props: HashMap, - serialize: Vec, - to_string: Option, + pub message: Option, + pub detailed_message: Option, + pub string_props: Vec<(LitStr, LitStr)>, + serialize: Vec, + to_string: Option, ident: Option, } impl StrumVariantProperties { - pub fn get_preferred_name(&self, case_style: Option) -> String { + fn ident_as_str(&self, case_style: Option) -> LitStr { + let ident = self.ident.as_ref().expect("identifier"); + LitStr::new(&ident.convert_case(case_style), ident.span()) + } + + pub fn get_preferred_name(&self, case_style: Option) -> LitStr { if let Some(to_string) = &self.to_string { to_string.clone() } else { let mut serialized = self.serialize.clone(); - serialized.sort_by_key(|s| s.len()); + serialized.sort_by_key(|s| s.value().len()); if let Some(n) = serialized.pop() { n } else { - self.ident - .as_ref() - .expect("identifier") - .convert_case(case_style) + self.ident_as_str(case_style) } } } - pub fn get_serializations(&self, case_style: Option) -> Vec { + pub fn get_serializations(&self, case_style: Option) -> Vec { let mut attrs = self.serialize.clone(); if let Some(to_string) = &self.to_string { attrs.push(to_string.clone()); } if attrs.is_empty() { - attrs.push( - self.ident - .as_ref() - .expect("identifier") - .convert_case(case_style), - ); + attrs.push(self.ident_as_str(case_style)); } attrs @@ -64,65 +59,40 @@ impl HasStrumVariantProperties for Variant { let mut output = StrumVariantProperties::default(); output.ident = Some(self.ident.clone()); - for meta in self.get_metadata("strum")? { + for meta in self.get_metadata()? { match meta { - Meta::NameValue(syn::MetaNameValue { path, lit, .. }) => { - if path.is_ident("message") { - if output.message.is_some() { - panic!("message is set twice on the same variant"); - } - - output.message = Some(lit.expect_string("expected string")?); - } else if path.is_ident("detailed_message") { - if output.detailed_message.is_some() { - panic!("detailed message set twice on the same variant"); - } - - output.detailed_message = Some(lit.expect_string("expected string")?); - } else if path.is_ident("serialize") { - output.serialize.push(lit.expect_string("expected string")?); - } else if path.is_ident("to_string") { - if output.to_string.is_some() { - panic!("to_string is set twice on the same variant"); - } - - output.to_string = Some(lit.expect_string("expected string")?); - } else if path.is_ident("disabled") { - panic!("this method is deprecated. Prefer #[strum(disabled)] instead of #[strum(disabled=\"true\")]"); - } else if path.is_ident("default") { - panic!("this method is deprecated. Prefer #[strum(default)] instead of #[strum(default=\"true\")]"); - } else { - panic!("unrecognized value in strum(..) attribute"); + VariantMeta::Message { value, .. } => { + if output.message.is_some() { + panic!("message is set twice on the same variant"); } + + output.message = Some(value); } - Meta::Path(p) => { - if p.is_ident("disabled") { - output.is_disabled = true; - } else if p.is_ident("default") { - output.default = true; - } else { - panic!("unrecognized value in strum(..) attribute"); + VariantMeta::DetailedMessage { value, .. } => { + if output.detailed_message.is_some() { + panic!("detailed message set twice on the same variant"); } - } - Meta::List(syn::MetaList { path, nested, .. }) => { - if path.is_ident("props") { - for p in nested { - let p = p - .expect_meta("unexpected literal found in props")? - .expect_namevalue("props must be key-value pairs")?; - let key = p - .path - .get_ident() - .expect("key must be an identifier") - .to_string(); - - let value = p.lit.expect_string("expected string")?; - output.string_props.insert(key, value); - } - } else { - panic!("unrecognized value in strum(..) attribute"); + output.detailed_message = Some(value); + } + VariantMeta::Serialize { value, .. } => { + output.serialize.push(value); + } + VariantMeta::ToString { value, .. } => { + if output.to_string.is_some() { + panic!("to_string is set twice on the same variant"); } + + output.to_string = Some(value); + } + VariantMeta::Disabled(_) => { + output.is_disabled = true; + } + VariantMeta::Default(_) => { + output.default = true; + } + VariantMeta::Props { props, .. } => { + output.string_props.extend(props); } } } diff --git a/strum_macros/src/macros/enum_discriminants.rs b/strum_macros/src/macros/enum_discriminants.rs index e5665324..f8430450 100644 --- a/strum_macros/src/macros/enum_discriminants.rs +++ b/strum_macros/src/macros/enum_discriminants.rs @@ -1,6 +1,6 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{parse_quote, Path}; +use syn::parse_quote; use syn::{Data, DeriveInput}; use crate::helpers::HasTypeProperties; @@ -30,19 +30,15 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result { }; // Work out the name - let default_name = Path::from(syn::Ident::new( + let default_name = syn::Ident::new( &format!("{}Discriminants", name.to_string()), Span::call_site(), - )); + ); let discriminants_name = type_properties.discriminant_name.unwrap_or(default_name); // Pass through all other attributes - let pass_though_attributes = type_properties - .discriminant_others - .into_iter() - .map(|meta| quote! { #[ #meta ] }) - .collect::>(); + let pass_though_attributes = type_properties.discriminant_others; // Add the variants without fields, but exclude the `strum` meta item let mut discriminants = Vec::new(); @@ -130,7 +126,7 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result { Ok(quote! { /// Auto-generated discriminant enum variants #derives - #(#pass_though_attributes)* + #(#[ #pass_though_attributes ])* #vis enum #discriminants_name { #(#discriminants),* } From 67e8c7bcd57882c1e763973b777aba9ef7c271fb Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 12 Oct 2020 21:04:49 +0200 Subject: [PATCH 4/6] Replace TryFrom<&str> with FromStr --- strum_macros/src/helpers/case_style.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/strum_macros/src/helpers/case_style.rs b/strum_macros/src/helpers/case_style.rs index 09629aee..352a277e 100644 --- a/strum_macros/src/helpers/case_style.rs +++ b/strum_macros/src/helpers/case_style.rs @@ -1,5 +1,5 @@ use heck::{CamelCase, KebabCase, MixedCase, ShoutySnakeCase, SnakeCase, TitleCase}; -use std::convert::TryFrom; +use std::str::FromStr; use syn::{ parse::{Parse, ParseStream}, Ident, LitStr, @@ -37,7 +37,7 @@ impl Parse for CaseStyle { let text = input.parse::()?; let val = text.value(); - Self::try_from(val.as_str()).map_err(|_| { + val.as_str().parse().map_err(|_| { syn::Error::new_spanned( &text, format!( @@ -49,10 +49,10 @@ impl Parse for CaseStyle { } } -impl TryFrom<&'_ str> for CaseStyle { - type Error = (); +impl FromStr for CaseStyle { + type Err = (); - fn try_from(text: &str) -> Result { + fn from_str(text: &str) -> Result { Ok(match text { "camel_case" | "PascalCase" => CaseStyle::PascalCase, "camelCase" => CaseStyle::CamelCase, From f29c088b12797d05b12da3e0d9257dc7d8ab88db Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 15 Oct 2020 13:05:01 +0200 Subject: [PATCH 5/6] Restore Rust 1.31.1 compatibility --- strum_macros/src/helpers/metadata.rs | 46 ++++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/strum_macros/src/helpers/metadata.rs b/strum_macros/src/helpers/metadata.rs index 6f76481f..cbf06289 100644 --- a/strum_macros/src/helpers/metadata.rs +++ b/strum_macros/src/helpers/metadata.rs @@ -41,7 +41,7 @@ impl Parse for EnumMeta { let serialize_all_kw = input.parse::()?; input.parse::()?; let case_style = input.parse()?; - Ok(Self::SerializeAll { + Ok(EnumMeta::SerializeAll { serialize_all_kw, case_style, }) @@ -51,7 +51,7 @@ impl Parse for EnumMeta { impl Spanned for EnumMeta { fn span(&self) -> Span { match self { - Self::SerializeAll { + EnumMeta::SerializeAll { serialize_all_kw, .. } => serialize_all_kw.span(), } @@ -71,7 +71,7 @@ impl Parse for EnumDiscriminantsMeta { let content; parenthesized!(content in input); let paths = content.parse_terminated::<_, Token![,]>(Path::parse)?; - Ok(Self::Derive { + Ok(EnumDiscriminantsMeta::Derive { kw, paths: paths.into_iter().collect(), }) @@ -80,13 +80,13 @@ impl Parse for EnumDiscriminantsMeta { let content; parenthesized!(content in input); let name = content.parse()?; - Ok(Self::Name { kw, name }) + Ok(EnumDiscriminantsMeta::Name { kw, name }) } else { let path = input.parse()?; let content; parenthesized!(content in input); let nested = content.parse()?; - Ok(Self::Other { path, nested }) + Ok(EnumDiscriminantsMeta::Other { path, nested }) } } } @@ -94,9 +94,9 @@ impl Parse for EnumDiscriminantsMeta { impl Spanned for EnumDiscriminantsMeta { fn span(&self) -> Span { match self { - Self::Derive { kw, .. } => kw.span, - Self::Name { kw, .. } => kw.span, - Self::Other { path, .. } => path.span(), + EnumDiscriminantsMeta::Derive { kw, .. } => kw.span, + EnumDiscriminantsMeta::Name { kw, .. } => kw.span, + EnumDiscriminantsMeta::Other { path, .. } => path.span(), } } } @@ -151,32 +151,32 @@ impl Parse for VariantMeta { let kw = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; - Ok(Self::Message { kw, value }) + Ok(VariantMeta::Message { kw, value }) } else if lookahead.peek(kw::detailed_message) { let kw = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; - Ok(Self::DetailedMessage { kw, value }) + Ok(VariantMeta::DetailedMessage { kw, value }) } else if lookahead.peek(kw::serialize) { let kw = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; - Ok(Self::Serialize { kw, value }) + Ok(VariantMeta::Serialize { kw, value }) } else if lookahead.peek(kw::to_string) { let kw = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; - Ok(Self::ToString { kw, value }) + Ok(VariantMeta::ToString { kw, value }) } else if lookahead.peek(kw::disabled) { - Ok(Self::Disabled(input.parse()?)) + Ok(VariantMeta::Disabled(input.parse()?)) } else if lookahead.peek(kw::default) { - Ok(Self::Default(input.parse()?)) + Ok(VariantMeta::Default(input.parse()?)) } else if lookahead.peek(kw::props) { let kw = input.parse()?; let content; parenthesized!(content in input); let props = content.parse_terminated::<_, Token![,]>(Prop::parse)?; - Ok(Self::Props { + Ok(VariantMeta::Props { kw, props: props .into_iter() @@ -199,20 +199,20 @@ impl Parse for Prop { let _: Token![=] = input.parse()?; let v = input.parse()?; - Ok(Self(k, v)) + Ok(Prop(k, v)) } } impl Spanned for VariantMeta { fn span(&self) -> Span { match self { - Self::Message { kw, .. } => kw.span, - Self::DetailedMessage { kw, .. } => kw.span, - Self::Serialize { kw, .. } => kw.span, - Self::ToString { kw, .. } => kw.span, - Self::Disabled(kw) => kw.span, - Self::Default(kw) => kw.span, - Self::Props { kw, .. } => kw.span, + VariantMeta::Message { kw, .. } => kw.span, + VariantMeta::DetailedMessage { kw, .. } => kw.span, + VariantMeta::Serialize { kw, .. } => kw.span, + VariantMeta::ToString { kw, .. } => kw.span, + VariantMeta::Disabled(kw) => kw.span, + VariantMeta::Default(kw) => kw.span, + VariantMeta::Props { kw, .. } => kw.span, } } } From 77fc768265ee819ecd54fb98ff143dcafcf822a4 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 15 Oct 2020 13:07:25 +0200 Subject: [PATCH 6/6] Re-run `cargo fmt` --- strum/src/lib.rs | 2 +- strum_tests/build.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/strum/src/lib.rs b/strum/src/lib.rs index 7d7964fd..f89bb0b0 100644 --- a/strum/src/lib.rs +++ b/strum/src/lib.rs @@ -201,7 +201,7 @@ macro_rules! DocumentMacroRexports { // for docsrs. You can do a weird thing where you rename the macro // and then reference it through strum. The renaming feature should be deprecated now that // 2018 edition is almost 2 years old, but we'll need to give people some time to do that. -DocumentMacroRexports!{ +DocumentMacroRexports! { AsRefStr, AsStaticStr, Display, diff --git a/strum_tests/build.rs b/strum_tests/build.rs index 23e46c5b..9cf597e5 100644 --- a/strum_tests/build.rs +++ b/strum_tests/build.rs @@ -1,7 +1,6 @@ - fn main() { // Check if version of rustc is >= 1.34 if let Some(true) = version_check::is_min_version("1.34.0") { println!("cargo:rustc-cfg=try_from"); } -} \ No newline at end of file +}