diff --git a/Cargo.toml b/Cargo.toml index be4586a..649031a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ categories = [ "science" ] license = "MIT/Apache-2.0" name = "num-derive" repository = "https://github.com/rust-num/num-derive" -version = "0.3.0" +version = "0.3.1" readme = "README.md" exclude = ["/bors.toml", "/ci/*", "/.github/*"] edition = "2018" diff --git a/RELEASES.md b/RELEASES.md index 371f6fb..6ae1cad 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,9 @@ +# Release 0.3.1 (2020-07-28) + +- [Provide nicer parse errors and suggest "full-syntax"][39] + +[39]: https://github.com/rust-num/num-derive/pull/39 + # Release 0.3.0 (2019-09-27) - [Updated the `proc-macro2`, `quote`, and `syn` dependencies to 1.0][28], diff --git a/src/lib.rs b/src/lib.rs index 138b752..dad8370 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,26 @@ use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use syn::{Data, Fields, Ident}; +/// Try to parse the tokens, or else return a compilation error +/// suggesting "full-syntax" if that's not already enabled. +macro_rules! parse { + ($tokens:ident as $type:ty) => { + match syn::parse::<$type>($tokens) { + Ok(parsed) => parsed, + Err(mut error) => { + if cfg!(not(feature = "full-syntax")) { + let hint = syn::Error::new( + Span::call_site(), + r#"this might need the "full-syntax" feature of `num-derive`"#, + ); + error.combine(hint); + } + return TokenStream::from(error.to_compile_error()); + } + } + }; +} + // Within `exp`, you can bring things into scope with `extern crate`. // // We don't want to assume that `num_traits::` is in scope - the user may have imported it under a @@ -230,7 +250,7 @@ impl NumTraits { /// ``` #[proc_macro_derive(FromPrimitive, attributes(num_traits))] pub fn from_primitive(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); + let ast = parse!(input as syn::DeriveInput); let name = &ast.ident; let import = NumTraits::new(&ast); @@ -388,7 +408,7 @@ pub fn from_primitive(input: TokenStream) -> TokenStream { /// ``` #[proc_macro_derive(ToPrimitive, attributes(num_traits))] pub fn to_primitive(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); + let ast = parse!(input as syn::DeriveInput); let name = &ast.ident; let import = NumTraits::new(&ast); @@ -509,7 +529,7 @@ const NEWTYPE_ONLY: &str = "This trait can only be derived for newtypes"; /// `Output=Self`. #[proc_macro_derive(NumOps)] pub fn num_ops(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); + let ast = parse!(input as syn::DeriveInput); let name = &ast.ident; let inner_ty = newtype_inner(&ast.data).expect(NEWTYPE_ONLY); let impl_ = quote! { @@ -553,7 +573,7 @@ pub fn num_ops(input: TokenStream) -> TokenStream { /// [num_cast]: https://docs.rs/num-traits/0.2/num_traits/cast/trait.NumCast.html #[proc_macro_derive(NumCast, attributes(num_traits))] pub fn num_cast(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); + let ast = parse!(input as syn::DeriveInput); let name = &ast.ident; let inner_ty = newtype_inner(&ast.data).expect(NEWTYPE_ONLY); @@ -575,7 +595,7 @@ pub fn num_cast(input: TokenStream) -> TokenStream { /// [zero]: https://docs.rs/num-traits/0.2/num_traits/identities/trait.Zero.html #[proc_macro_derive(Zero, attributes(num_traits))] pub fn zero(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); + let ast = parse!(input as syn::DeriveInput); let name = &ast.ident; let inner_ty = newtype_inner(&ast.data).expect(NEWTYPE_ONLY); @@ -600,7 +620,7 @@ pub fn zero(input: TokenStream) -> TokenStream { /// [one]: https://docs.rs/num-traits/0.2/num_traits/identities/trait.One.html #[proc_macro_derive(One, attributes(num_traits))] pub fn one(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); + let ast = parse!(input as syn::DeriveInput); let name = &ast.ident; let inner_ty = newtype_inner(&ast.data).expect(NEWTYPE_ONLY); @@ -625,7 +645,7 @@ pub fn one(input: TokenStream) -> TokenStream { /// [num]: https://docs.rs/num-traits/0.2/num_traits/trait.Num.html #[proc_macro_derive(Num, attributes(num_traits))] pub fn num(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); + let ast = parse!(input as syn::DeriveInput); let name = &ast.ident; let inner_ty = newtype_inner(&ast.data).expect(NEWTYPE_ONLY); @@ -649,7 +669,7 @@ pub fn num(input: TokenStream) -> TokenStream { /// [float]: https://docs.rs/num-traits/0.2/num_traits/float/trait.Float.html #[proc_macro_derive(Float, attributes(num_traits))] pub fn float(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); + let ast = parse!(input as syn::DeriveInput); let name = &ast.ident; let inner_ty = newtype_inner(&ast.data).expect(NEWTYPE_ONLY); @@ -834,3 +854,5 @@ pub fn float(input: TokenStream) -> TokenStream { import.wrap("Float", &name, impl_).into() } + +mod test; diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..c4cd7fe --- /dev/null +++ b/src/test.rs @@ -0,0 +1,31 @@ +//! This module uses doc-tests on modules for `compile_fail` + +// We need "syn/full" to parse macros. +// Use `--nocapture` to check the quality of the error message. +#[cfg(not(feature = "full-syntax"))] +/// ```compile_fail +/// macro_rules! get_an_isize { +/// () => (0_isize) +/// } +/// +/// #[derive(num_derive::FromPrimitive)] +/// pub enum CLikeEnum { +/// VarA = get_an_isize!(), // error without "syn/full" +/// VarB = 2, +/// } +/// ``` +mod issue16 {} + +#[cfg(feature = "full-syntax")] +/// ``` +/// macro_rules! get_an_isize { +/// () => (0_isize) +/// } +/// +/// #[derive(num_derive::FromPrimitive)] +/// pub enum CLikeEnum { +/// VarA = get_an_isize!(), // ok with "syn/full" +/// VarB = 2, +/// } +/// ``` +mod issue16 {}