Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ error: num_enum attribute must have at most one error_type
29 | #[num_enum(error_type(name = CustomError, constructor = CustomError::new), error_type(name = CustomError, constructor = CustomError::new))]
| ^^^^^^^^^^

error: At most one num_enum error_type attribute may be specified
--> tests/try_build/compile_fail/custom_error_type_parsing.rs:39:1
error: num_enum attribute must have at most one error_type
--> tests/try_build/compile_fail/custom_error_type_parsing.rs:39:12
|
39 | #[num_enum(error_type(name = CustomError, constructor = CustomError::new))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
error: `num_enum` only supports unit variants (with no associated data), but `Number::NonZero` was not a unit variant.
error: `::num_enum` only supports unit variants (with no associated data), but `Number::NonZero` was not a unit variant.
--> $DIR/variants_with_fields.rs:7:5
|
7 | NonZero(u8),
| ^^^^^^^^^^^

error: `num_enum` only supports unit variants (with no associated data), but `Colour::Red` was not a unit variant.
error: `::num_enum` only supports unit variants (with no associated data), but `Colour::Red` was not a unit variant.
--> $DIR/variants_with_fields.rs:13:5
|
13 | Red { intensity: u8 },
| ^^^^^^^^^^^^^^^^^^^^^

error: `num_enum` only supports unit variants (with no associated data), but `Meaningless::Beep` was not a unit variant.
error: `::num_enum` only supports unit variants (with no associated data), but `Meaningless::Beep` was not a unit variant.
--> $DIR/variants_with_fields.rs:19:5
|
19 | Beep(),
Expand Down
2 changes: 1 addition & 1 deletion num_enum_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ features = ["external_doc"]
proc-macro2 = "1.0.60"
proc-macro-crate = { version = ">= 1, <= 3", optional = true }
quote = "1"
syn = { version = "2", features = ["parsing"] }
syn = { version = "2", features = ["derive", "extra-traits", "parsing"] }

[dev-dependencies]
syn = { version = "2", features = ["extra-traits", "parsing"] }
72 changes: 70 additions & 2 deletions num_enum_derive/src/enum_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,50 @@ mod kw {

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

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

impl Attributes {
pub(crate) fn exclusive_union(&mut self, other: Self) -> Result<()> {
if self.crate_path.is_some() {
if let Some(other) = &other.crate_path {
return Err(Error::new(
other.span,
"num_enum attribute must have at most one crate",
));
}
} else {
self.crate_path = other.crate_path;
}
if self.error_type.is_some() {
if let Some(other) = &other.error_type {
return Err(Error::new(
other.span,
"num_enum attribute must have at most one error_type",
));
}
} else {
self.error_type = other.error_type;
}
Ok(())
}
}

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_krate_path = None;
for attribute_item in &attribute_items {
match attribute_item {
AttributeItem::ErrorType(error_type) => {
Expand All @@ -38,10 +68,20 @@ impl Parse for Attributes {
}
maybe_error_type = Some(error_type.clone());
}
AttributeItem::CratePath(krate_path) => {
if maybe_krate_path.is_some() {
return Err(Error::new(
krate_path.span,
"num_enum attribute must have at most one crate",
));
}
maybe_krate_path = Some(krate_path.clone());
}
}
}
Ok(Self {
error_type: maybe_error_type,
crate_path: maybe_krate_path,
})
}
}
Expand All @@ -51,6 +91,8 @@ impl Parse for AttributeItem {
let lookahead = input.lookahead1();
if lookahead.peek(kw::error_type) {
input.parse().map(Self::ErrorType)
} else if lookahead.peek(syn::token::Crate) {
input.parse().map(Self::CratePath)
} else {
Err(lookahead.error())
}
Expand Down Expand Up @@ -168,6 +210,24 @@ impl Parse for ErrorTypeConstructorAttribute {
}
}

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub(crate) struct CrateAttribute {
pub(crate) path: syn::Path,

span: Span,
}

impl Parse for CrateAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let span = input.span();
let _: syn::token::Crate = input.parse()?;
let _: syn::token::Eq = input.parse()?;
let path = syn::Path::parse_mod_style(input)?;
Ok(Self { path, span })
}
}

#[cfg(test)]
mod test {
use crate::enum_attributes::Attributes;
Expand All @@ -178,11 +238,15 @@ mod test {
fn parse_num_enum_attr() {
let expected_name: Path = parse_quote! { Foo };
let expected_constructor: Path = parse_quote! { ::foo::Foo::<u8>::new };
let expected_krate: Path = parse_quote! { ::num_enum };

let attributes: Attributes =
syn::parse_str("error_type(name = Foo, constructor = ::foo::Foo::<u8>::new)").unwrap();
let attributes: Attributes = syn::parse_str(
"error_type(name = Foo, constructor = ::foo::Foo::<u8>::new), crate = ::num_enum",
)
.unwrap();
assert!(attributes.error_type.is_some());
let error_type = attributes.error_type.unwrap();
let krate_path = attributes.crate_path.unwrap();
assert_eq!(
error_type.name.path.to_token_stream().to_string(),
expected_name.to_token_stream().to_string()
Expand All @@ -191,6 +255,10 @@ mod test {
error_type.constructor.path.to_token_stream().to_string(),
expected_constructor.to_token_stream().to_string()
);
assert_eq!(
krate_path.path.to_token_stream().to_string(),
expected_krate.to_token_stream().to_string()
);
}

#[test]
Expand Down
23 changes: 11 additions & 12 deletions num_enum_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use syn::{parse_macro_input, Expr, Ident};

mod enum_attributes;
mod parsing;
use parsing::{get_crate_name, EnumInfo};
use parsing::{get_crate_path, EnumInfo};
mod utils;
mod variant_attributes;

Expand Down Expand Up @@ -90,7 +90,7 @@ pub fn derive_into_primitive(input: TokenStream) -> TokenStream {
#[proc_macro_derive(FromPrimitive, attributes(num_enum, default, catch_all))]
pub fn derive_from_primitive(input: TokenStream) -> TokenStream {
let enum_info: EnumInfo = parse_macro_input!(input);
let krate = Ident::new(&get_crate_name(), Span::call_site());
let krate = get_crate_path(enum_info.crate_path.clone());

let is_naturally_exhaustive = enum_info.is_naturally_exhaustive();
let catch_all_body = match is_naturally_exhaustive {
Expand Down Expand Up @@ -124,7 +124,7 @@ pub fn derive_from_primitive(input: TokenStream) -> TokenStream {
debug_assert_eq!(variant_idents.len(), variant_expressions.len());

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

fn from_primitive(number: Self::Primitive) -> Self {
Expand Down Expand Up @@ -153,12 +153,12 @@ pub fn derive_from_primitive(input: TokenStream) -> TokenStream {
fn from (
number: #repr,
) -> Self {
::#krate::FromPrimitive::from_primitive(number)
#krate::FromPrimitive::from_primitive(number)
}
}

#[doc(hidden)]
impl ::#krate::CannotDeriveBothFromPrimitiveAndTryFromPrimitive for #name {}
impl #krate::CannotDeriveBothFromPrimitiveAndTryFromPrimitive for #name {}
})
}

Expand Down Expand Up @@ -190,8 +190,7 @@ pub fn derive_from_primitive(input: TokenStream) -> TokenStream {
#[proc_macro_derive(TryFromPrimitive, attributes(num_enum))]
pub fn derive_try_from_primitive(input: TokenStream) -> TokenStream {
let enum_info: EnumInfo = parse_macro_input!(input);
let krate = Ident::new(&get_crate_name(), Span::call_site());

let krate = get_crate_path(enum_info.crate_path.clone());
let EnumInfo {
ref name,
ref repr,
Expand All @@ -209,7 +208,7 @@ pub fn derive_try_from_primitive(input: TokenStream) -> TokenStream {
let error_constructor = &error_type_info.constructor;

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

Expand Down Expand Up @@ -251,12 +250,12 @@ pub fn derive_try_from_primitive(input: TokenStream) -> TokenStream {
number: #repr,
) -> ::core::result::Result<Self, #error_type>
{
::#krate::TryFromPrimitive::try_from_primitive(number)
#krate::TryFromPrimitive::try_from_primitive(number)
}
}

#[doc(hidden)]
impl ::#krate::CannotDeriveBothFromPrimitiveAndTryFromPrimitive for #name {}
impl #krate::CannotDeriveBothFromPrimitiveAndTryFromPrimitive for #name {}
})
}

Expand Down Expand Up @@ -302,14 +301,14 @@ pub fn derive_try_from_primitive(input: TokenStream) -> TokenStream {
#[proc_macro_derive(UnsafeFromPrimitive, attributes(num_enum))]
pub fn derive_unsafe_from_primitive(stream: TokenStream) -> TokenStream {
let enum_info = parse_macro_input!(stream as EnumInfo);
let krate = Ident::new(&get_crate_name(), Span::call_site());
let krate = get_crate_path(enum_info.crate_path);

let EnumInfo {
ref name, ref repr, ..
} = enum_info;

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

unsafe fn unchecked_transmute_from(number: Self::Primitive) -> Self {
Expand Down
Loading
Loading