From 11f8289ed418d91d1b86e5d6dde0db6ddf15158c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jan=20Niemier?= Date: Sun, 18 Sep 2016 20:56:36 +0200 Subject: [PATCH 1/6] Fix `num-macros` `FromPrimitive` implementation Current solution follow syntax proposed in rust-lang/rfcs#1681. Tracking issue rust-lang/rust#35900 Close #227 --- macros/Cargo.toml | 31 +++++--- macros/src/lib.rs | 144 +++++++++---------------------------- macros/tests/test_macro.rs | 23 +++--- 3 files changed, 63 insertions(+), 135 deletions(-) diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 5b58adb..91e24c2 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,17 +1,26 @@ [package] -name = "num-macros" -version = "0.1.33" authors = ["The Rust Project Developers"] -license = "MIT/Apache-2.0" -homepage = "https://github.com/rust-num/num" -repository = "https://github.com/rust-num/num" -documentation = "http://rust-num.github.io/num" -keywords = ["mathematics", "numerics"] description = "Numeric syntax extensions" +documentation = "http://rust-num.github.io/num" +homepage = "https://github.com/rust-num/num" +keywords = ["mathematics", "numerics"] +license = "MIT/Apache-2.0" +name = "num-macros" +repository = "https://github.com/rust-num/num" +version = "0.1.33" -[lib] -name = "num_macros" -plugin = true +[dependencies] +quote = "0.1.3" +syn = "0.5.2" [dev-dependencies] -num = { path = "..", version = "0.1" } + +[dev-dependencies.num] +path = ".." +version = "0.1" + +[lib] +crate-type = ["rustc-macro"] +name = "num_macros" +rustc-macro = true +test = false diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 2d5c67f..78b1a08 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -8,43 +8,24 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(plugin_registrar, rustc_private)] +#![crate_type = "rustc-macro"] +#![feature(rustc_macro, rustc_macro_lib)] -extern crate syntax; -extern crate syntax_ext; -extern crate rustc_plugin; +extern crate syn; +#[macro_use] +extern crate quote; +extern crate rustc_macro; -use syntax::ast::{MetaItem, Expr, BinOpKind}; -use syntax::ast; -use syntax::codemap::Span; -use syntax::ext::base::{ExtCtxt, Annotatable}; -use syntax::ext::build::AstBuilder; -use syntax_ext::deriving::generic::*; -use syntax_ext::deriving::generic::ty::*; -use syntax::parse::token::InternedString; -use syntax::ptr::P; -use syntax::ext::base::MultiDecorator; -use syntax::parse::token; +use rustc_macro::TokenStream; -use rustc_plugin::Registry; +use syn::Body::Enum; -macro_rules! pathvec { - ($($x:ident)::+) => ( - vec![ $( stringify!($x) ),+ ] - ) -} +#[rustc_macro_derive(FromPrimitive)] +pub fn from_primitive(input: TokenStream) -> TokenStream { + let source = input.to_string(); -macro_rules! path { - ($($x:tt)*) => ( - ::syntax_ext::deriving::generic::ty::Path::new( pathvec!( $($x)* ) ) - ) -} - -macro_rules! path_local { - ($x:ident) => ( - ::syntax_ext::deriving::generic::ty::Path::new_local(stringify!($x)) - ) -} + let ast = syn::parse_item(&source).unwrap(); + // panic!("{:?}", ast); macro_rules! pathvec_std { ($cx:expr, $first:ident :: $($rest:ident)::+) => ({ @@ -111,91 +92,32 @@ pub fn expand_deriving_from_primitive(cx: &mut ExtCtxt, supports_unions: false, }; - trait_def.expand(cx, mitem, &item, push) -} + let mut idx = 0; + let variants: Vec<_> = variants.iter() + .map(|variant| { + let ident = &variant.ident; + let tt = quote!(#idx => Some(#name::#ident)); + idx += 1; + tt + }) + .collect(); -fn cs_from(name: &str, cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P { - if substr.nonself_args.len() != 1 { - cx.span_bug(trait_span, "incorrect number of arguments in `derive(FromPrimitive)`") - } + let res = quote! { + #ast - let n = &substr.nonself_args[0]; - - match *substr.fields { - StaticStruct(..) => { - cx.span_err(trait_span, "`FromPrimitive` cannot be derived for structs"); - return cx.expr_fail(trait_span, InternedString::new("")); - } - StaticEnum(enum_def, _) => { - if enum_def.variants.is_empty() { - cx.span_err(trait_span, - "`FromPrimitive` cannot be derived for enums with no variants"); - return cx.expr_fail(trait_span, InternedString::new("")); + impl ::num::traits::FromPrimitive for #name { + fn from_i64(n: i64) -> Option { + Self::from_u64(n as u64) } - let mut arms = Vec::new(); - - for variant in &enum_def.variants { - match variant.node.data { - ast::VariantData::Unit(..) => { - let span = variant.span; - - // expr for `$n == $variant as $name` - let path = cx.path(span, vec![substr.type_ident, variant.node.name]); - let variant = cx.expr_path(path); - let ty = cx.ty_ident(span, cx.ident_of(name)); - let cast = cx.expr_cast(span, variant.clone(), ty); - let guard = cx.expr_binary(span, BinOpKind::Eq, n.clone(), cast); - - // expr for `Some($variant)` - let body = cx.expr_some(span, variant); - - // arm for `_ if $guard => $body` - let arm = ast::Arm { - attrs: vec!(), - pats: vec!(cx.pat_wild(span)), - guard: Some(guard), - body: body, - }; - - arms.push(arm); - } - ast::VariantData::Tuple(..) => { - cx.span_err(trait_span, - "`FromPrimitive` cannot be derived for \ - enum variants with arguments"); - return cx.expr_fail(trait_span, - InternedString::new("")); - } - ast::VariantData::Struct(..) => { - cx.span_err(trait_span, - "`FromPrimitive` cannot be derived for enums \ - with struct variants"); - return cx.expr_fail(trait_span, - InternedString::new("")); - } + fn from_u64(n: u64) -> Option { + match n { + #(variants,)* + _ => None, } } - - // arm for `_ => None` - let arm = ast::Arm { - attrs: vec!(), - pats: vec!(cx.pat_wild(trait_span)), - guard: None, - body: cx.expr_none(trait_span), - }; - arms.push(arm); - - cx.expr_match(trait_span, n.clone(), arms) } - _ => cx.span_bug(trait_span, "expected StaticEnum in derive(FromPrimitive)") - } -} + }; -#[plugin_registrar] -#[doc(hidden)] -pub fn plugin_registrar(reg: &mut Registry) { - reg.register_syntax_extension( - token::intern("derive_NumFromPrimitive"), - MultiDecorator(Box::new(expand_deriving_from_primitive))); + res.to_string().parse().unwrap() } diff --git a/macros/tests/test_macro.rs b/macros/tests/test_macro.rs index 2f582b6..1245266 100644 --- a/macros/tests/test_macro.rs +++ b/macros/tests/test_macro.rs @@ -8,12 +8,13 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(custom_derive, plugin)] -#![plugin(num_macros)] +#![feature(rustc_macro)] extern crate num; +#[macro_use] +extern crate num_macros; -#[derive(Debug, PartialEq, NumFromPrimitive)] +#[derive(Debug, PartialEq, FromPrimitive)] enum Color { Red, Blue, @@ -22,15 +23,11 @@ enum Color { #[test] fn test_from_primitive() { - let v: Vec> = vec![ - num::FromPrimitive::from_u64(0), - num::FromPrimitive::from_u64(1), - num::FromPrimitive::from_u64(2), - num::FromPrimitive::from_u64(3), - ]; + let v: [Option; 4] = [num::FromPrimitive::from_u64(0), + num::FromPrimitive::from_u64(1), + num::FromPrimitive::from_u64(2), + num::FromPrimitive::from_u64(3)]; - assert_eq!( - v, - vec![Some(Color::Red), Some(Color::Blue), Some(Color::Green), None] - ); + assert_eq!(v, + [Some(Color::Red), Some(Color::Blue), Some(Color::Green), None]); } From 43cfa254260521dc7a70814f483cd074d76200b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jan=20Niemier?= Date: Sun, 18 Sep 2016 22:40:58 +0200 Subject: [PATCH 2/6] Add test case for which `syn` currently fails --- macros/src/lib.rs | 1 - macros/tests/{test_macro.rs => trivial.rs} | 2 +- macros/tests/with_custom_values.rs | 33 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) rename macros/tests/{test_macro.rs => trivial.rs} (95%) create mode 100644 macros/tests/with_custom_values.rs diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 78b1a08..db39a45 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -25,7 +25,6 @@ pub fn from_primitive(input: TokenStream) -> TokenStream { let source = input.to_string(); let ast = syn::parse_item(&source).unwrap(); - // panic!("{:?}", ast); macro_rules! pathvec_std { ($cx:expr, $first:ident :: $($rest:ident)::+) => ({ diff --git a/macros/tests/test_macro.rs b/macros/tests/trivial.rs similarity index 95% rename from macros/tests/test_macro.rs rename to macros/tests/trivial.rs index 1245266..709fc2c 100644 --- a/macros/tests/test_macro.rs +++ b/macros/tests/trivial.rs @@ -22,7 +22,7 @@ enum Color { } #[test] -fn test_from_primitive() { +fn test_from_primitive_for_trivial_case() { let v: [Option; 4] = [num::FromPrimitive::from_u64(0), num::FromPrimitive::from_u64(1), num::FromPrimitive::from_u64(2), diff --git a/macros/tests/with_custom_values.rs b/macros/tests/with_custom_values.rs new file mode 100644 index 0000000..7373570 --- /dev/null +++ b/macros/tests/with_custom_values.rs @@ -0,0 +1,33 @@ +// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(rustc_macro)] + +extern crate num; +#[macro_use] +extern crate num_macros; + +#[derive(Debug, PartialEq, FromPrimitive)] +enum Color { + Red, + Blue = 5, + Green, +} + +#[test] +fn test_from_primitive_for_enum_with_custom_value() { + let v: [Option; 4] = [num::FromPrimitive::from_u64(0), + num::FromPrimitive::from_u64(5), + num::FromPrimitive::from_u64(6), + num::FromPrimitive::from_u64(3)]; + + assert_eq!(v, + [Some(Color::Red), Some(Color::Blue), Some(Color::Green), None]); +} From e9768a0a76d4c61af8e8d1760bd91cdb40ca72bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jan=20Niemier?= Date: Sun, 18 Sep 2016 22:45:53 +0200 Subject: [PATCH 3/6] Rebase leftovers --- macros/src/lib.rs | 65 ----------------------------------------------- 1 file changed, 65 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index db39a45..d00f1e8 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -26,71 +26,6 @@ pub fn from_primitive(input: TokenStream) -> TokenStream { let ast = syn::parse_item(&source).unwrap(); -macro_rules! pathvec_std { - ($cx:expr, $first:ident :: $($rest:ident)::+) => ({ - let mut v = pathvec!($($rest)::+); - if let Some(s) = $cx.crate_root { - v.insert(0, s); - } - v - }) -} - -pub fn expand_deriving_from_primitive(cx: &mut ExtCtxt, - span: Span, - mitem: &MetaItem, - item: &Annotatable, - push: &mut FnMut(Annotatable)) -{ - let inline = cx.meta_word(span, InternedString::new("inline")); - let attrs = vec!(cx.attribute(span, inline)); - let trait_def = TraitDef { - is_unsafe: false, - span: span, - attributes: Vec::new(), - path: path!(num::FromPrimitive), - additional_bounds: Vec::new(), - generics: LifetimeBounds::empty(), - methods: vec!( - MethodDef { - name: "from_i64", - is_unsafe: false, - unify_fieldless_variants: false, - generics: LifetimeBounds::empty(), - explicit_self: None, - args: vec!(Literal(path_local!(i64))), - ret_ty: Literal(Path::new_(pathvec_std!(cx, core::option::Option), - None, - vec!(Box::new(Self_)), - true)), - // #[inline] liable to cause code-bloat - attributes: attrs.clone(), - combine_substructure: combine_substructure(Box::new(|c, s, sub| { - cs_from("i64", c, s, sub) - })), - }, - MethodDef { - name: "from_u64", - is_unsafe: false, - unify_fieldless_variants: false, - generics: LifetimeBounds::empty(), - explicit_self: None, - args: vec!(Literal(path_local!(u64))), - ret_ty: Literal(Path::new_(pathvec_std!(cx, core::option::Option), - None, - vec!(Box::new(Self_)), - true)), - // #[inline] liable to cause code-bloat - attributes: attrs, - combine_substructure: combine_substructure(Box::new(|c, s, sub| { - cs_from("u64", c, s, sub) - })), - } - ), - associated_types: Vec::new(), - supports_unions: false, - }; - let mut idx = 0; let variants: Vec<_> = variants.iter() .map(|variant| { From ada17a179309e72a14dcac0f36266b0bd1a36eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jan=20Niemier?= Date: Mon, 26 Sep 2016 20:32:46 +0200 Subject: [PATCH 4/6] Update to syn 0.6.0 --- macros/Cargo.toml | 2 +- macros/src/lib.rs | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 91e24c2..2fa2563 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -11,7 +11,7 @@ version = "0.1.33" [dependencies] quote = "0.1.3" -syn = "0.5.2" +syn = "0.6.0" [dev-dependencies] diff --git a/macros/src/lib.rs b/macros/src/lib.rs index d00f1e8..d85218c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -24,12 +24,21 @@ use syn::Body::Enum; pub fn from_primitive(input: TokenStream) -> TokenStream { let source = input.to_string(); - let ast = syn::parse_item(&source).unwrap(); + let ast = syn::parse_macro_input(&source).unwrap(); + let name = &ast.ident; + + let variants = match ast.body { + Enum(ref variants) => variants, + _ => panic!("`FromPrimitive` can be applied only to the enums, {} is not an enum", name), + }; let mut idx = 0; let variants: Vec<_> = variants.iter() .map(|variant| { let ident = &variant.ident; + if let Some(val) = variant.discriminant { + idx = val.value; + } let tt = quote!(#idx => Some(#name::#ident)); idx += 1; tt From 0c89b893a95bd7200c268ff0fc25222923b098e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jan=20Niemier?= Date: Tue, 27 Sep 2016 11:18:27 +0200 Subject: [PATCH 5/6] Add compiletest crate to test against invalid derivations --- macros/Cargo.toml | 1 + macros/src/lib.rs | 12 ++++++++- macros/tests/compile-fail/derive_on_struct.rs | 25 ++++++++++++++++++ .../compile-fail/enum_with_associated_data.rs | 24 +++++++++++++++++ macros/tests/compiletest.rs | 25 ++++++++++++++++++ macros/tests/empty_enum.rs | 26 +++++++++++++++++++ 6 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 macros/tests/compile-fail/derive_on_struct.rs create mode 100644 macros/tests/compile-fail/enum_with_associated_data.rs create mode 100644 macros/tests/compiletest.rs create mode 100644 macros/tests/empty_enum.rs diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 2fa2563..61e25c6 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -14,6 +14,7 @@ quote = "0.1.3" syn = "0.6.0" [dev-dependencies] +compiletest_rs = "0.2.2" [dev-dependencies.num] path = ".." diff --git a/macros/src/lib.rs b/macros/src/lib.rs index d85218c..3283bb0 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -19,6 +19,7 @@ extern crate rustc_macro; use rustc_macro::TokenStream; use syn::Body::Enum; +use syn::VariantData::Unit; #[rustc_macro_derive(FromPrimitive)] pub fn from_primitive(input: TokenStream) -> TokenStream { @@ -29,13 +30,22 @@ pub fn from_primitive(input: TokenStream) -> TokenStream { let variants = match ast.body { Enum(ref variants) => variants, - _ => panic!("`FromPrimitive` can be applied only to the enums, {} is not an enum", name), + _ => { + panic!("`FromPrimitive` can be applied only to the enums, {} is not an enum", + name) + } }; let mut idx = 0; let variants: Vec<_> = variants.iter() .map(|variant| { let ident = &variant.ident; + match variant.data { + Unit => (), + _ => { + panic!("`FromPrimitive` can be applied only to unitary enums, {}::{} is either struct or tuple", name, ident) + }, + } if let Some(val) = variant.discriminant { idx = val.value; } diff --git a/macros/tests/compile-fail/derive_on_struct.rs b/macros/tests/compile-fail/derive_on_struct.rs new file mode 100644 index 0000000..1f21081 --- /dev/null +++ b/macros/tests/compile-fail/derive_on_struct.rs @@ -0,0 +1,25 @@ + +// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(rustc_macro)] + +extern crate num; +#[macro_use] +extern crate num_macros; + +#[derive(Debug, PartialEq, FromPrimitive)] //~ ERROR +struct Color { + r: u8, + g: u8, + b: u8, +} + +fn main() {} diff --git a/macros/tests/compile-fail/enum_with_associated_data.rs b/macros/tests/compile-fail/enum_with_associated_data.rs new file mode 100644 index 0000000..0fef268 --- /dev/null +++ b/macros/tests/compile-fail/enum_with_associated_data.rs @@ -0,0 +1,24 @@ + +// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(rustc_macro)] + +extern crate num; +#[macro_use] +extern crate num_macros; + +#[derive(Debug, PartialEq, FromPrimitive)] //~ ERROR +enum Color { + Rgb(u8, u8, u8), + Hsv(u8, u8, u8), +} + +fn main() {} diff --git a/macros/tests/compiletest.rs b/macros/tests/compiletest.rs new file mode 100644 index 0000000..27c212b --- /dev/null +++ b/macros/tests/compiletest.rs @@ -0,0 +1,25 @@ +extern crate compiletest_rs as compiletest; + +use std::path::PathBuf; +use std::env::var; + +fn run_mode(mode: &'static str) { + let mut config = compiletest::default_config(); + + let cfg_mode = mode.parse().ok().expect("Invalid mode"); + + config.target_rustcflags = Some("-L target/debug/ -L target/debug/deps/".to_owned()); + if let Ok(name) = var::<&str>("TESTNAME") { + let s : String = name.to_owned(); + config.filter = Some(s) + } + config.mode = cfg_mode; + config.src_base = PathBuf::from(format!("tests/{}", mode)); + + compiletest::run_tests(&config); +} + +#[test] +fn compile_test() { + run_mode("compile-fail"); +} diff --git a/macros/tests/empty_enum.rs b/macros/tests/empty_enum.rs new file mode 100644 index 0000000..3a3d55a --- /dev/null +++ b/macros/tests/empty_enum.rs @@ -0,0 +1,26 @@ + +// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(rustc_macro)] + +extern crate num; +#[macro_use] +extern crate num_macros; + +#[derive(Debug, PartialEq, FromPrimitive)] +enum Color {} + +#[test] +fn test_empty_enum() { + let v: [Option; 1] = [num::FromPrimitive::from_u64(0)]; + + assert_eq!(v, [None]); +} From b7e64074b23db6ddb6b97f44f4b79e5e11367637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jan=20Niemier?= Date: Wed, 28 Sep 2016 13:33:13 +0200 Subject: [PATCH 6/6] Add new crate num-derive as an new replacement for num-macro --- .travis/test_nightly.sh | 1 + derive/Cargo.toml | 27 +++ derive/src/lib.rs | 76 ++++++ .../tests/compile-fail/derive_on_struct.rs | 2 +- .../compile-fail/enum_with_associated_data.rs | 2 +- {macros => derive}/tests/compiletest.rs | 0 {macros => derive}/tests/empty_enum.rs | 2 +- {macros => derive}/tests/trivial.rs | 2 +- .../tests/with_custom_values.rs | 2 +- macros/Cargo.toml | 32 +-- macros/src/lib.rs | 223 ++++++++++++++---- macros/tests/test_macro.rs | 36 +++ 12 files changed, 330 insertions(+), 75 deletions(-) create mode 100644 derive/Cargo.toml create mode 100644 derive/src/lib.rs rename {macros => derive}/tests/compile-fail/derive_on_struct.rs (96%) rename {macros => derive}/tests/compile-fail/enum_with_associated_data.rs (96%) rename {macros => derive}/tests/compiletest.rs (100%) rename {macros => derive}/tests/empty_enum.rs (96%) rename {macros => derive}/tests/trivial.rs (97%) rename {macros => derive}/tests/with_custom_values.rs (97%) create mode 100644 macros/tests/test_macro.rs diff --git a/.travis/test_nightly.sh b/.travis/test_nightly.sh index f2b024c..7ed93dd 100755 --- a/.travis/test_nightly.sh +++ b/.travis/test_nightly.sh @@ -5,6 +5,7 @@ set -ex cargo bench --verbose cargo test --verbose --manifest-path=macros/Cargo.toml +cargo test --verbose --manifest-path=derive/Cargo.toml # Build test for the serde feature cargo build --verbose --features "serde" diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..fbb5154 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,27 @@ +[package] +authors = ["The Rust Project Developers"] +description = "Numeric syntax extensions" +documentation = "http://rust-num.github.io/num" +homepage = "https://github.com/rust-num/num" +keywords = ["mathematics", "numerics"] +license = "MIT/Apache-2.0" +name = "num-derive" +repository = "https://github.com/rust-num/num" +version = "0.1.33" + +[dependencies] +quote = "0.1.3" +syn = "0.7.0" + +[dev-dependencies] +compiletest_rs = "0.2.2" + +[dev-dependencies.num] +path = ".." +version = "0.1" + +[lib] +crate-type = ["rustc-macro"] +name = "num_derive" +rustc-macro = true +test = false diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..3283bb0 --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,76 @@ +// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![crate_type = "rustc-macro"] +#![feature(rustc_macro, rustc_macro_lib)] + +extern crate syn; +#[macro_use] +extern crate quote; +extern crate rustc_macro; + +use rustc_macro::TokenStream; + +use syn::Body::Enum; +use syn::VariantData::Unit; + +#[rustc_macro_derive(FromPrimitive)] +pub fn from_primitive(input: TokenStream) -> TokenStream { + let source = input.to_string(); + + let ast = syn::parse_macro_input(&source).unwrap(); + let name = &ast.ident; + + let variants = match ast.body { + Enum(ref variants) => variants, + _ => { + panic!("`FromPrimitive` can be applied only to the enums, {} is not an enum", + name) + } + }; + + let mut idx = 0; + let variants: Vec<_> = variants.iter() + .map(|variant| { + let ident = &variant.ident; + match variant.data { + Unit => (), + _ => { + panic!("`FromPrimitive` can be applied only to unitary enums, {}::{} is either struct or tuple", name, ident) + }, + } + if let Some(val) = variant.discriminant { + idx = val.value; + } + let tt = quote!(#idx => Some(#name::#ident)); + idx += 1; + tt + }) + .collect(); + + let res = quote! { + #ast + + impl ::num::traits::FromPrimitive for #name { + fn from_i64(n: i64) -> Option { + Self::from_u64(n as u64) + } + + fn from_u64(n: u64) -> Option { + match n { + #(variants,)* + _ => None, + } + } + } + }; + + res.to_string().parse().unwrap() +} diff --git a/macros/tests/compile-fail/derive_on_struct.rs b/derive/tests/compile-fail/derive_on_struct.rs similarity index 96% rename from macros/tests/compile-fail/derive_on_struct.rs rename to derive/tests/compile-fail/derive_on_struct.rs index 1f21081..6ce4cd1 100644 --- a/macros/tests/compile-fail/derive_on_struct.rs +++ b/derive/tests/compile-fail/derive_on_struct.rs @@ -13,7 +13,7 @@ extern crate num; #[macro_use] -extern crate num_macros; +extern crate num_derive; #[derive(Debug, PartialEq, FromPrimitive)] //~ ERROR struct Color { diff --git a/macros/tests/compile-fail/enum_with_associated_data.rs b/derive/tests/compile-fail/enum_with_associated_data.rs similarity index 96% rename from macros/tests/compile-fail/enum_with_associated_data.rs rename to derive/tests/compile-fail/enum_with_associated_data.rs index 0fef268..312f0f7 100644 --- a/macros/tests/compile-fail/enum_with_associated_data.rs +++ b/derive/tests/compile-fail/enum_with_associated_data.rs @@ -13,7 +13,7 @@ extern crate num; #[macro_use] -extern crate num_macros; +extern crate num_derive; #[derive(Debug, PartialEq, FromPrimitive)] //~ ERROR enum Color { diff --git a/macros/tests/compiletest.rs b/derive/tests/compiletest.rs similarity index 100% rename from macros/tests/compiletest.rs rename to derive/tests/compiletest.rs diff --git a/macros/tests/empty_enum.rs b/derive/tests/empty_enum.rs similarity index 96% rename from macros/tests/empty_enum.rs rename to derive/tests/empty_enum.rs index 3a3d55a..6b1c22b 100644 --- a/macros/tests/empty_enum.rs +++ b/derive/tests/empty_enum.rs @@ -13,7 +13,7 @@ extern crate num; #[macro_use] -extern crate num_macros; +extern crate num_derive; #[derive(Debug, PartialEq, FromPrimitive)] enum Color {} diff --git a/macros/tests/trivial.rs b/derive/tests/trivial.rs similarity index 97% rename from macros/tests/trivial.rs rename to derive/tests/trivial.rs index 709fc2c..298ca6d 100644 --- a/macros/tests/trivial.rs +++ b/derive/tests/trivial.rs @@ -12,7 +12,7 @@ extern crate num; #[macro_use] -extern crate num_macros; +extern crate num_derive; #[derive(Debug, PartialEq, FromPrimitive)] enum Color { diff --git a/macros/tests/with_custom_values.rs b/derive/tests/with_custom_values.rs similarity index 97% rename from macros/tests/with_custom_values.rs rename to derive/tests/with_custom_values.rs index 7373570..4ecf672 100644 --- a/macros/tests/with_custom_values.rs +++ b/derive/tests/with_custom_values.rs @@ -12,7 +12,7 @@ extern crate num; #[macro_use] -extern crate num_macros; +extern crate num_derive; #[derive(Debug, PartialEq, FromPrimitive)] enum Color { diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 61e25c6..5b58adb 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,27 +1,17 @@ [package] -authors = ["The Rust Project Developers"] -description = "Numeric syntax extensions" -documentation = "http://rust-num.github.io/num" -homepage = "https://github.com/rust-num/num" -keywords = ["mathematics", "numerics"] -license = "MIT/Apache-2.0" name = "num-macros" -repository = "https://github.com/rust-num/num" version = "0.1.33" - -[dependencies] -quote = "0.1.3" -syn = "0.6.0" - -[dev-dependencies] -compiletest_rs = "0.2.2" - -[dev-dependencies.num] -path = ".." -version = "0.1" +authors = ["The Rust Project Developers"] +license = "MIT/Apache-2.0" +homepage = "https://github.com/rust-num/num" +repository = "https://github.com/rust-num/num" +documentation = "http://rust-num.github.io/num" +keywords = ["mathematics", "numerics"] +description = "Numeric syntax extensions" [lib] -crate-type = ["rustc-macro"] name = "num_macros" -rustc-macro = true -test = false +plugin = true + +[dev-dependencies] +num = { path = "..", version = "0.1" } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 3283bb0..2d5c67f 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -8,69 +8,194 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![crate_type = "rustc-macro"] -#![feature(rustc_macro, rustc_macro_lib)] +#![feature(plugin_registrar, rustc_private)] -extern crate syn; -#[macro_use] -extern crate quote; -extern crate rustc_macro; +extern crate syntax; +extern crate syntax_ext; +extern crate rustc_plugin; -use rustc_macro::TokenStream; +use syntax::ast::{MetaItem, Expr, BinOpKind}; +use syntax::ast; +use syntax::codemap::Span; +use syntax::ext::base::{ExtCtxt, Annotatable}; +use syntax::ext::build::AstBuilder; +use syntax_ext::deriving::generic::*; +use syntax_ext::deriving::generic::ty::*; +use syntax::parse::token::InternedString; +use syntax::ptr::P; +use syntax::ext::base::MultiDecorator; +use syntax::parse::token; -use syn::Body::Enum; -use syn::VariantData::Unit; +use rustc_plugin::Registry; -#[rustc_macro_derive(FromPrimitive)] -pub fn from_primitive(input: TokenStream) -> TokenStream { - let source = input.to_string(); +macro_rules! pathvec { + ($($x:ident)::+) => ( + vec![ $( stringify!($x) ),+ ] + ) +} - let ast = syn::parse_macro_input(&source).unwrap(); - let name = &ast.ident; +macro_rules! path { + ($($x:tt)*) => ( + ::syntax_ext::deriving::generic::ty::Path::new( pathvec!( $($x)* ) ) + ) +} - let variants = match ast.body { - Enum(ref variants) => variants, - _ => { - panic!("`FromPrimitive` can be applied only to the enums, {} is not an enum", - name) +macro_rules! path_local { + ($x:ident) => ( + ::syntax_ext::deriving::generic::ty::Path::new_local(stringify!($x)) + ) +} + +macro_rules! pathvec_std { + ($cx:expr, $first:ident :: $($rest:ident)::+) => ({ + let mut v = pathvec!($($rest)::+); + if let Some(s) = $cx.crate_root { + v.insert(0, s); } + v + }) +} + +pub fn expand_deriving_from_primitive(cx: &mut ExtCtxt, + span: Span, + mitem: &MetaItem, + item: &Annotatable, + push: &mut FnMut(Annotatable)) +{ + let inline = cx.meta_word(span, InternedString::new("inline")); + let attrs = vec!(cx.attribute(span, inline)); + let trait_def = TraitDef { + is_unsafe: false, + span: span, + attributes: Vec::new(), + path: path!(num::FromPrimitive), + additional_bounds: Vec::new(), + generics: LifetimeBounds::empty(), + methods: vec!( + MethodDef { + name: "from_i64", + is_unsafe: false, + unify_fieldless_variants: false, + generics: LifetimeBounds::empty(), + explicit_self: None, + args: vec!(Literal(path_local!(i64))), + ret_ty: Literal(Path::new_(pathvec_std!(cx, core::option::Option), + None, + vec!(Box::new(Self_)), + true)), + // #[inline] liable to cause code-bloat + attributes: attrs.clone(), + combine_substructure: combine_substructure(Box::new(|c, s, sub| { + cs_from("i64", c, s, sub) + })), + }, + MethodDef { + name: "from_u64", + is_unsafe: false, + unify_fieldless_variants: false, + generics: LifetimeBounds::empty(), + explicit_self: None, + args: vec!(Literal(path_local!(u64))), + ret_ty: Literal(Path::new_(pathvec_std!(cx, core::option::Option), + None, + vec!(Box::new(Self_)), + true)), + // #[inline] liable to cause code-bloat + attributes: attrs, + combine_substructure: combine_substructure(Box::new(|c, s, sub| { + cs_from("u64", c, s, sub) + })), + } + ), + associated_types: Vec::new(), + supports_unions: false, }; - let mut idx = 0; - let variants: Vec<_> = variants.iter() - .map(|variant| { - let ident = &variant.ident; - match variant.data { - Unit => (), - _ => { - panic!("`FromPrimitive` can be applied only to unitary enums, {}::{} is either struct or tuple", name, ident) - }, - } - if let Some(val) = variant.discriminant { - idx = val.value; - } - let tt = quote!(#idx => Some(#name::#ident)); - idx += 1; - tt - }) - .collect(); + trait_def.expand(cx, mitem, &item, push) +} - let res = quote! { - #ast +fn cs_from(name: &str, cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P { + if substr.nonself_args.len() != 1 { + cx.span_bug(trait_span, "incorrect number of arguments in `derive(FromPrimitive)`") + } - impl ::num::traits::FromPrimitive for #name { - fn from_i64(n: i64) -> Option { - Self::from_u64(n as u64) + let n = &substr.nonself_args[0]; + + match *substr.fields { + StaticStruct(..) => { + cx.span_err(trait_span, "`FromPrimitive` cannot be derived for structs"); + return cx.expr_fail(trait_span, InternedString::new("")); + } + StaticEnum(enum_def, _) => { + if enum_def.variants.is_empty() { + cx.span_err(trait_span, + "`FromPrimitive` cannot be derived for enums with no variants"); + return cx.expr_fail(trait_span, InternedString::new("")); } - fn from_u64(n: u64) -> Option { - match n { - #(variants,)* - _ => None, + let mut arms = Vec::new(); + + for variant in &enum_def.variants { + match variant.node.data { + ast::VariantData::Unit(..) => { + let span = variant.span; + + // expr for `$n == $variant as $name` + let path = cx.path(span, vec![substr.type_ident, variant.node.name]); + let variant = cx.expr_path(path); + let ty = cx.ty_ident(span, cx.ident_of(name)); + let cast = cx.expr_cast(span, variant.clone(), ty); + let guard = cx.expr_binary(span, BinOpKind::Eq, n.clone(), cast); + + // expr for `Some($variant)` + let body = cx.expr_some(span, variant); + + // arm for `_ if $guard => $body` + let arm = ast::Arm { + attrs: vec!(), + pats: vec!(cx.pat_wild(span)), + guard: Some(guard), + body: body, + }; + + arms.push(arm); + } + ast::VariantData::Tuple(..) => { + cx.span_err(trait_span, + "`FromPrimitive` cannot be derived for \ + enum variants with arguments"); + return cx.expr_fail(trait_span, + InternedString::new("")); + } + ast::VariantData::Struct(..) => { + cx.span_err(trait_span, + "`FromPrimitive` cannot be derived for enums \ + with struct variants"); + return cx.expr_fail(trait_span, + InternedString::new("")); + } } } - } - }; - res.to_string().parse().unwrap() + // arm for `_ => None` + let arm = ast::Arm { + attrs: vec!(), + pats: vec!(cx.pat_wild(trait_span)), + guard: None, + body: cx.expr_none(trait_span), + }; + arms.push(arm); + + cx.expr_match(trait_span, n.clone(), arms) + } + _ => cx.span_bug(trait_span, "expected StaticEnum in derive(FromPrimitive)") + } +} + +#[plugin_registrar] +#[doc(hidden)] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_syntax_extension( + token::intern("derive_NumFromPrimitive"), + MultiDecorator(Box::new(expand_deriving_from_primitive))); } diff --git a/macros/tests/test_macro.rs b/macros/tests/test_macro.rs new file mode 100644 index 0000000..2f582b6 --- /dev/null +++ b/macros/tests/test_macro.rs @@ -0,0 +1,36 @@ +// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(custom_derive, plugin)] +#![plugin(num_macros)] + +extern crate num; + +#[derive(Debug, PartialEq, NumFromPrimitive)] +enum Color { + Red, + Blue, + Green, +} + +#[test] +fn test_from_primitive() { + let v: Vec> = vec![ + num::FromPrimitive::from_u64(0), + num::FromPrimitive::from_u64(1), + num::FromPrimitive::from_u64(2), + num::FromPrimitive::from_u64(3), + ]; + + assert_eq!( + v, + vec![Some(Color::Red), Some(Color::Blue), Some(Color::Green), None] + ); +}