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] + ); +}