Skip to content

Commit

Permalink
Fix num-macros FromPrimitive implementation
Browse files Browse the repository at this point in the history
Current solution follow syntax proposed in rust-lang/rfcs#1681.

Tracking issue rust-lang/rust#35900

Close #227
  • Loading branch information
hauleth committed Sep 18, 2016
1 parent c8ed8ff commit decfedb
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 198 deletions.
29 changes: 19 additions & 10 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
description = "Numeric syntax extensions"
documentation = "http://rust-num.github.io/num"
homepage = "https://github.com/rust-num/num"
keywords = ["mathematics", "numerics"]
description = "Numeric syntax extensions"
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
214 changes: 39 additions & 175 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,193 +8,57 @@
// 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) ),+ ]
)
}

macro_rules! path {
($($x:tt)*) => (
::syntax_ext::deriving::generic::ty::Path::new( pathvec!( $($x)* ) )
)
}
#[rustc_macro_derive(FromPrimitive)]
pub fn from_primitive(input: TokenStream) -> TokenStream {
let source = input.to_string();

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)::+) => ({
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(),
let name = &ast.ident;
let variants: &[_] = match ast.body {
Enum(ref variants) => variants,
_ => panic!("#[derive(FromPrimitive)] works only with enums, struct given!"),
};

trait_def.expand(cx, mitem, &item, push)
}

fn cs_from(name: &str, cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P<Expr> {
if substr.nonself_args.len() != 1 {
cx.span_bug(trait_span, "incorrect number of arguments in `derive(FromPrimitive)`")
}

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(""));
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();

let res = quote! {
#ast

impl ::num::traits::FromPrimitive for #name {
fn from_i64(n: i64) -> Option<Self> {
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<Self> {
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()
}
23 changes: 10 additions & 13 deletions macros/tests/test_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -22,15 +23,11 @@ enum Color {

#[test]
fn test_from_primitive() {
let v: Vec<Option<Color>> = vec![
num::FromPrimitive::from_u64(0),
num::FromPrimitive::from_u64(1),
num::FromPrimitive::from_u64(2),
num::FromPrimitive::from_u64(3),
];
let v: [Option<Color>; 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]);
}

0 comments on commit decfedb

Please sign in to comment.