Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions num_enum_derive/src/enum_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,29 @@ mod kw {
syn::custom_keyword!(constructor);
syn::custom_keyword!(error_type);
syn::custom_keyword!(name);
syn::custom_keyword!(from_primitive);
syn::custom_keyword!(no_panic);
}

// Example: error_type(name = Foo, constructor = Foo::new)
#[cfg_attr(test, derive(Debug))]
pub(crate) struct Attributes {
pub(crate) error_type: Option<ErrorTypeAttribute>,
pub(crate) from_primitive: Option<FromPrimitiveAttribute>,
}

// Example: error_type(name = Foo, constructor = Foo::new)
#[cfg_attr(test, derive(Debug))]
pub(crate) enum AttributeItem {
ErrorType(ErrorTypeAttribute),
FromPrimitive(FromPrimitiveAttribute),
}

impl Parse for Attributes {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let attribute_items = input.parse_terminated(AttributeItem::parse, syn::Token![,])?;
let mut maybe_error_type = None;
let mut maybe_from_primitive = None;
for attribute_item in &attribute_items {
match attribute_item {
AttributeItem::ErrorType(error_type) => {
Expand All @@ -38,10 +43,20 @@ impl Parse for Attributes {
}
maybe_error_type = Some(error_type.clone());
}
AttributeItem::FromPrimitive(from_primitive) => {
if maybe_from_primitive.is_some() {
return Err(Error::new(
from_primitive.span,
"num_enum attribute must have at most one from_primitive",
));
}
maybe_from_primitive = Some(from_primitive.clone());
}
}
}
Ok(Self {
error_type: maybe_error_type,
from_primitive: maybe_from_primitive,
})
}
}
Expand All @@ -51,6 +66,8 @@ impl Parse for AttributeItem {
let lookahead = input.lookahead1();
if lookahead.peek(kw::error_type) {
input.parse().map(Self::ErrorType)
} else if lookahead.peek(kw::from_primitive) {
input.parse().map(Self::FromPrimitive)
} else {
Err(lookahead.error())
}
Expand Down Expand Up @@ -168,6 +185,74 @@ impl Parse for ErrorTypeConstructorAttribute {
}
}

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub(crate) struct FromPrimitiveAttribute {
pub(crate) no_panic: Option<FromPrimitiveNoPanicAttribute>,

span: Span,
}

impl Parse for FromPrimitiveAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let keyword: kw::from_primitive = input.parse()?;
let span = keyword.span;
let content;
syn::parenthesized!(content in input);
let attribute_values =
content.parse_terminated(FromPrimitiveNamedArgument::parse, syn::Token![,])?;
let mut no_panic = None;
for attribute_value in &attribute_values {
match attribute_value {
FromPrimitiveNamedArgument::NoPanic(no_panic_attr) => {
if no_panic.is_some() {
die!("num_enum from_primitive attribute must have exactly one `no_panic` value");
}
no_panic = Some(no_panic_attr.clone());
}
}
}

Ok(Self { no_panic, span })
}
}

pub(crate) enum FromPrimitiveNamedArgument {
NoPanic(FromPrimitiveNoPanicAttribute),
}

impl Parse for FromPrimitiveNamedArgument {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::no_panic) {
input.parse().map(Self::NoPanic)
} else {
Err(lookahead.error())
}
}
}

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub(crate) struct FromPrimitiveNoPanicAttribute {
pub(crate) no_panic: Option<bool>,
}

impl Parse for FromPrimitiveNoPanicAttribute {
fn parse(input: ParseStream) -> Result<Self> {
input.parse::<kw::no_panic>()?;

let no_panic: Option<bool> = if input.peek(syn::Token![=]) {
input.parse::<syn::Token![=]>()?;
Some(input.parse::<syn::LitBool>()?.value)
} else {
None
};

Ok(Self { no_panic })
}
}

#[cfg(test)]
mod test {
use crate::enum_attributes::Attributes;
Expand Down
7 changes: 7 additions & 0 deletions num_enum_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,19 @@ pub fn derive_from_primitive(input: TokenStream) -> TokenStream {
let expression_idents: Vec<Vec<Ident>> = enum_info.expression_idents();
let variant_expressions: Vec<Vec<Expr>> = enum_info.variant_expressions();

let no_panic_attr = if enum_info.no_panic {
quote!(#[no_panic::no_panic])
} else {
quote!()
};

debug_assert_eq!(variant_idents.len(), variant_expressions.len());

TokenStream::from(quote! {
impl ::#krate::FromPrimitive for #name {
type Primitive = #repr;

#no_panic_attr
fn from_primitive(number: Self::Primitive) -> Self {
// Use intermediate const(s) so that enums defined like
// `Two = ONE + 1u8` work properly.
Expand Down
27 changes: 23 additions & 4 deletions num_enum_derive/src/parsing.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::enum_attributes::ErrorTypeAttribute;
use crate::enum_attributes::{ErrorTypeAttribute, FromPrimitiveAttribute};
use crate::utils::die;
use crate::variant_attributes::{NumEnumVariantAttributeItem, NumEnumVariantAttributes};
use proc_macro2::Span;
Expand All @@ -15,6 +15,7 @@ pub(crate) struct EnumInfo {
pub(crate) repr: Ident,
pub(crate) variants: Vec<VariantInfo>,
pub(crate) error_type_info: ErrorType,
pub(crate) no_panic: bool,
}

impl EnumInfo {
Expand Down Expand Up @@ -89,9 +90,10 @@ impl EnumInfo {

fn parse_attrs<Attrs: Iterator<Item = Attribute>>(
attrs: Attrs,
) -> Result<(Ident, Option<ErrorType>)> {
) -> Result<(Ident, Option<ErrorType>, Option<FromPrimitiveAttribute>)> {
let mut maybe_repr = None;
let mut maybe_error_type = None;
let mut maybe_from_primitive = None;
for attr in attrs {
if let Meta::List(meta_list) = &attr.meta {
if let Some(ident) = meta_list.path.get_ident() {
Expand Down Expand Up @@ -122,14 +124,21 @@ impl EnumInfo {
}
maybe_error_type = Some(error_type.into());
}

if let Some(from_primitive) = attributes.from_primitive {
if maybe_from_primitive.is_some() {
die!(attr => "At most one num_enum from_primitive attribute may be specified");
}
maybe_from_primitive = Some(from_primitive.into());
}
}
}
}
}
if maybe_repr.is_none() {
die!("Missing `#[repr({Integer})]` attribute");
}
Ok((maybe_repr.unwrap(), maybe_error_type))
Ok((maybe_repr.unwrap(), maybe_error_type, maybe_from_primitive))
}
}

Expand All @@ -144,7 +153,8 @@ impl Parse for EnumInfo {
Data::Struct(data) => die!(data.struct_token => "Expected enum but found struct"),
};

let (repr, maybe_error_type) = Self::parse_attrs(input.attrs.into_iter())?;
let (repr, maybe_error_type, maybe_from_primitive) =
Self::parse_attrs(input.attrs.into_iter())?;

let mut variants: Vec<VariantInfo> = vec![];
let mut has_default_variant: bool = false;
Expand Down Expand Up @@ -396,12 +406,21 @@ impl Parse for EnumInfo {
},
}
});

let no_panic = match maybe_from_primitive {
None => false,
Some(from_primitive) => match from_primitive.no_panic {
None => false,
Some(no_panic_attribute) => no_panic_attribute.no_panic.unwrap_or_else(|| true),
},
};

EnumInfo {
name,
repr,
variants,
error_type_info,
no_panic,
}
})
}
Expand Down