Skip to content

Commit

Permalink
Replace macro panics with syn::Error (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
jplatte committed Oct 19, 2020
1 parent 25eaa5a commit 90047fe
Show file tree
Hide file tree
Showing 15 changed files with 137 additions and 94 deletions.
15 changes: 5 additions & 10 deletions strum_macros/src/helpers/metadata.rs
Expand Up @@ -9,7 +9,7 @@ use syn::{

use super::case_style::CaseStyle;

mod kw {
pub mod kw {
use syn::custom_keyword;

// enum metadata
Expand All @@ -31,29 +31,24 @@ mod kw {

pub enum EnumMeta {
SerializeAll {
serialize_all_kw: kw::serialize_all,
kw: kw::serialize_all,
case_style: CaseStyle,
},
}

impl Parse for EnumMeta {
fn parse(input: ParseStream) -> syn::Result<Self> {
let serialize_all_kw = input.parse::<kw::serialize_all>()?;
let kw = input.parse::<kw::serialize_all>()?;
input.parse::<Token![=]>()?;
let case_style = input.parse()?;
Ok(EnumMeta::SerializeAll {
serialize_all_kw,
case_style,
})
Ok(EnumMeta::SerializeAll { kw, case_style })
}
}

impl Spanned for EnumMeta {
fn span(&self) -> Span {
match self {
EnumMeta::SerializeAll {
serialize_all_kw, ..
} => serialize_all_kw.span(),
EnumMeta::SerializeAll { kw, .. } => kw.span(),
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions strum_macros/src/helpers/mod.rs
Expand Up @@ -6,3 +6,19 @@ pub mod case_style;
mod metadata;
pub mod type_props;
pub mod variant_props;

use proc_macro2::Span;
use quote::ToTokens;

pub fn non_enum_error() -> syn::Error {
syn::Error::new(Span::call_site(), "This macro only supports enums.")
}

pub fn occurrence_error<T: ToTokens>(fst: T, snd: T, attr: &str) -> syn::Error {
let mut e = syn::Error::new_spanned(
snd,
format!("Found multiple occurrences of strum({})", attr),
);
e.combine(syn::Error::new_spanned(fst, "first one here"));
e
}
21 changes: 13 additions & 8 deletions strum_macros/src/helpers/type_props.rs
Expand Up @@ -3,8 +3,9 @@ use quote::quote;
use std::default::Default;
use syn::{DeriveInput, Ident, Path};

use crate::helpers::case_style::CaseStyle;
use crate::helpers::metadata::{DeriveInputExt, EnumDiscriminantsMeta, EnumMeta};
use super::case_style::CaseStyle;
use super::metadata::{DeriveInputExt, EnumDiscriminantsMeta, EnumMeta};
use super::occurrence_error;

pub trait HasTypeProperties {
fn get_type_properties(&self) -> syn::Result<StrumTypeProperties>;
Expand All @@ -25,28 +26,32 @@ impl HasTypeProperties for DeriveInput {
let strum_meta = self.get_metadata()?;
let discriminants_meta = self.get_discriminants_metadata()?;

let mut serialize_all_kw = None;
for meta in strum_meta {
match meta {
EnumMeta::SerializeAll { case_style, .. } => {
if output.case_style.is_some() {
panic!("found multiple values of serialize_all");
EnumMeta::SerializeAll { case_style, kw } => {
if let Some(fst_kw) = serialize_all_kw {
return Err(occurrence_error(fst_kw, kw, "serialize_all"));
}

serialize_all_kw = Some(kw);
output.case_style = Some(case_style);
}
}
}

let mut name_kw = None;
for meta in discriminants_meta {
match meta {
EnumDiscriminantsMeta::Derive { paths, .. } => {
output.discriminant_derives.extend(paths);
}
EnumDiscriminantsMeta::Name { name, .. } => {
if output.discriminant_name.is_some() {
panic!("multiple occurrences of 'name'");
EnumDiscriminantsMeta::Name { name, kw } => {
if let Some(fst_kw) = name_kw {
return Err(occurrence_error(fst_kw, kw, "name"));
}

name_kw = Some(kw);
output.discriminant_name = Some(name);
}
EnumDiscriminantsMeta::Other { path, nested } => {
Expand Down
53 changes: 36 additions & 17 deletions strum_macros/src/helpers/variant_props.rs
@@ -1,17 +1,18 @@
use std::default::Default;
use syn::{Ident, LitStr, Variant};

use crate::helpers::case_style::{CaseStyle, CaseStyleHelpers};
use crate::helpers::metadata::{VariantExt, VariantMeta};
use super::case_style::{CaseStyle, CaseStyleHelpers};
use super::metadata::{kw, VariantExt, VariantMeta};
use super::occurrence_error;

pub trait HasStrumVariantProperties {
fn get_variant_properties(&self) -> syn::Result<StrumVariantProperties>;
}

#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct StrumVariantProperties {
pub is_disabled: bool,
pub default: bool,
pub disabled: Option<kw::disabled>,
pub default: Option<kw::default>,
pub message: Option<LitStr>,
pub detailed_message: Option<LitStr>,
pub string_props: Vec<(LitStr, LitStr)>,
Expand Down Expand Up @@ -59,37 +60,55 @@ impl HasStrumVariantProperties for Variant {
let mut output = StrumVariantProperties::default();
output.ident = Some(self.ident.clone());

let mut message_kw = None;
let mut detailed_message_kw = None;
let mut to_string_kw = None;
let mut disabled_kw = None;
let mut default_kw = None;
for meta in self.get_metadata()? {
match meta {
VariantMeta::Message { value, .. } => {
if output.message.is_some() {
panic!("message is set twice on the same variant");
VariantMeta::Message { value, kw } => {
if let Some(fst_kw) = message_kw {
return Err(occurrence_error(fst_kw, kw, "message"));
}

message_kw = Some(kw);
output.message = Some(value);
}
VariantMeta::DetailedMessage { value, .. } => {
if output.detailed_message.is_some() {
panic!("detailed message set twice on the same variant");
VariantMeta::DetailedMessage { value, kw } => {
if let Some(fst_kw) = detailed_message_kw {
return Err(occurrence_error(fst_kw, kw, "detailed_message"));
}

detailed_message_kw = Some(kw);
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");
VariantMeta::ToString { value, kw } => {
if let Some(fst_kw) = to_string_kw {
return Err(occurrence_error(fst_kw, kw, "to_string"));
}

to_string_kw = Some(kw);
output.to_string = Some(value);
}
VariantMeta::Disabled(_) => {
output.is_disabled = true;
VariantMeta::Disabled(kw) => {
if let Some(fst_kw) = disabled_kw {
return Err(occurrence_error(fst_kw, kw, "disabled"));
}

disabled_kw = Some(kw);
output.disabled = Some(kw);
}
VariantMeta::Default(_) => {
output.default = true;
VariantMeta::Default(kw) => {
if let Some(fst_kw) = default_kw {
return Err(occurrence_error(fst_kw, kw, "default"));
}

default_kw = Some(kw);
output.default = Some(kw);
}
VariantMeta::Props { props, .. } => {
output.string_props.extend(props);
Expand Down
3 changes: 2 additions & 1 deletion strum_macros/src/lib.rs
Expand Up @@ -658,7 +658,8 @@ pub fn enum_discriminants(input: proc_macro::TokenStream) -> proc_macro::TokenSt
)]
pub fn enum_count(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as DeriveInput);
let toks = macros::enum_count::enum_count_inner(&ast);
let toks =
macros::enum_count::enum_count_inner(&ast).unwrap_or_else(|err| err.to_compile_error());
debug_print_generated(&ast, &toks);
toks.into()
}
10 changes: 6 additions & 4 deletions strum_macros/src/macros/enum_count.rs
Expand Up @@ -2,10 +2,12 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput};

pub(crate) fn enum_count_inner(ast: &DeriveInput) -> TokenStream {
use crate::helpers::non_enum_error;

pub(crate) fn enum_count_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let n = match &ast.data {
Data::Enum(v) => v.variants.len(),
_ => panic!("EnumCount can only be used with enums"),
_ => return Err(non_enum_error()),
};

// Used in the quasi-quotation below as `#name`
Expand All @@ -14,10 +16,10 @@ pub(crate) fn enum_count_inner(ast: &DeriveInput) -> TokenStream {
// Helper is provided for handling complex generic types correctly and effortlessly
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

quote! {
Ok(quote! {
// Implementation
impl #impl_generics ::strum::EnumCount for #name #ty_generics #where_clause {
const COUNT: usize = #n;
}
}
})
}
4 changes: 2 additions & 2 deletions strum_macros/src/macros/enum_discriminants.rs
Expand Up @@ -3,7 +3,7 @@ use quote::quote;
use syn::parse_quote;
use syn::{Data, DeriveInput};

use crate::helpers::HasTypeProperties;
use crate::helpers::{non_enum_error, HasTypeProperties};

/// Attributes to copy from the main enum's variants to the discriminant enum's variants.
///
Expand All @@ -17,7 +17,7 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {

let variants = match &ast.data {
Data::Enum(v) => &v.variants,
_ => panic!("EnumDiscriminants only works on Enums"),
_ => return Err(non_enum_error()),
};

// Derives for the generated enum
Expand Down
17 changes: 9 additions & 8 deletions strum_macros/src/macros/enum_iter.rs
@@ -1,8 +1,8 @@
use proc_macro2::TokenStream;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Data, DeriveInput, Ident};

use crate::helpers::HasStrumVariantProperties;
use crate::helpers::{non_enum_error, HasStrumVariantProperties};

pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
Expand All @@ -11,10 +11,11 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let vis = &ast.vis;

if gen.lifetimes().count() > 0 {
panic!(
"Enum Iterator isn't supported on Enums with lifetimes. The resulting enums would \
be unbounded."
);
return Err(syn::Error::new(
Span::call_site(),
"This macro doesn't support enums with lifetimes. \
The resulting enums would be unbounded.",
));
}

let phantom_data = if gen.type_params().count() > 0 {
Expand All @@ -26,15 +27,15 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {

let variants = match &ast.data {
Data::Enum(v) => &v.variants,
_ => panic!("EnumIter only works on Enums"),
_ => return Err(non_enum_error()),
};

let mut arms = Vec::new();
let mut idx = 0usize;
for variant in variants {
use syn::Fields::*;

if variant.get_variant_properties()?.is_disabled {
if variant.get_variant_properties()?.disabled.is_some() {
continue;
}

Expand Down
6 changes: 3 additions & 3 deletions strum_macros/src/macros/enum_messages.rs
Expand Up @@ -2,14 +2,14 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput};

use crate::helpers::{HasStrumVariantProperties, HasTypeProperties};
use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};

pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let variants = match &ast.data {
Data::Enum(v) => &v.variants,
_ => panic!("EnumMessage only works on Enums"),
_ => return Err(non_enum_error()),
};

let type_properties = ast.get_type_properties()?;
Expand Down Expand Up @@ -46,7 +46,7 @@ pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}

// But you can disable the messages.
if variant_properties.is_disabled {
if variant_properties.disabled.is_some() {
continue;
}

Expand Down
6 changes: 3 additions & 3 deletions strum_macros/src/macros/enum_properties.rs
Expand Up @@ -2,14 +2,14 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput};

use crate::helpers::HasStrumVariantProperties;
use crate::helpers::{non_enum_error, HasStrumVariantProperties};

pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let variants = match &ast.data {
Data::Enum(v) => &v.variants,
_ => panic!("EnumProp only works on Enums"),
_ => return Err(non_enum_error()),
};

let mut arms = Vec::new();
Expand All @@ -20,7 +20,7 @@ pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let mut bool_arms = Vec::new();
let mut num_arms = Vec::new();
// But you can disable the messages.
if variant_properties.is_disabled {
if variant_properties.disabled.is_some() {
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions strum_macros/src/macros/enum_variant_names.rs
Expand Up @@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput};

use crate::helpers::{HasStrumVariantProperties, HasTypeProperties};
use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};

pub fn enum_variant_names_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
let name = &ast.ident;
Expand All @@ -11,7 +11,7 @@ pub fn enum_variant_names_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {

let variants = match &ast.data {
Data::Enum(v) => &v.variants,
_ => panic!("EnumVariantNames only works on Enums"),
_ => return Err(non_enum_error()),
};

// Derives for the generated enum
Expand Down

0 comments on commit 90047fe

Please sign in to comment.