Skip to content

Commit 60e9acd

Browse files
committed
Merge #3
3: Update deps and derivation algorithm r=cuviper a=hcpl Fixes #2. An updated version of rust-num/num#353 which includes suggestions outlined [here](rust-num/num#353 (review)) and [here](https://github.com/rust-num/num/pull/353/files/76b5b2189f2b45e864e14c38c7856be578125931#r157100221): - Update `quote` and `syn` to parse new Rust syntax; - Update `compiletest_rs` to solve Manishearth/compiletest-rs#86; - Add support for arbitrary constant expressions as enum discriminants; - Remove the need to `extern crate num` just for deriving. Some notes: - `#[derive(FromPrimitive)]` now uses if-else to do its job because non-literal expressions are not allowed for pattern matching. - I added tests for self-containment of `#[derive]` alongside the code testing derivation functionality to keep the tests small. Would it be better to separate concerns? - `with_custom_value` should have all three tests like `trivial`.
2 parents 14f9a98 + 0f58c1c commit 60e9acd

File tree

8 files changed

+188
-76
lines changed

8 files changed

+188
-76
lines changed

Cargo.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,23 @@ version = "0.1.41"
1212
readme = "README.md"
1313

1414
[dependencies]
15-
quote = "0.1.3"
16-
syn = "0.7.0"
15+
proc-macro2 = "0.2.1"
16+
quote = "0.4.2"
17+
syn = "0.12.7"
1718

1819
[dev-dependencies]
19-
compiletest_rs = "0.2.5"
20+
compiletest_rs = "0.3.5"
2021

2122
[dev-dependencies.num]
2223
version = "0.1"
2324

25+
[dev-dependencies.num-derive]
26+
path = "."
27+
features = ["full-syntax"]
28+
29+
[features]
30+
full-syntax = ["syn/full"]
31+
2432
[lib]
2533
name = "num_derive"
2634
proc-macro = true

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ Add this to your `Cargo.toml`:
1212

1313
```toml
1414
[dependencies]
15-
num-derive= "0.1"
15+
num = "0.1"
16+
num-derive = "0.1"
1617
```
1718

1819
and this to your crate root:
@@ -22,6 +23,16 @@ and this to your crate root:
2223
extern crate num_derive;
2324
```
2425

26+
## Optional features
27+
28+
- **`full-syntax`** — Enables `num-derive` to handle enum discriminants
29+
represented by complex expressions. Usually can be avoided by
30+
[utilizing constants], so only use this feature if namespace pollution is
31+
undesired and [compile time doubling] is acceptable.
32+
33+
[utilizing constants]: https://github.com/rust-num/num-derive/pull/3#issuecomment-359044704
34+
[compile time doubling]: https://github.com/rust-num/num-derive/pull/3#issuecomment-359172588
35+
2536
## Compatibility
2637

2738
The `num-derive` crate is tested for rustc 1.15 and greater.

src/lib.rs

Lines changed: 99 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,109 +11,156 @@
1111
#![crate_type = "proc-macro"]
1212
#![doc(html_root_url = "https://docs.rs/num-derive/0.1")]
1313

14-
extern crate syn;
14+
extern crate proc_macro;
15+
16+
extern crate proc_macro2;
1517
#[macro_use]
1618
extern crate quote;
17-
extern crate proc_macro;
19+
extern crate syn;
1820

1921
use proc_macro::TokenStream;
22+
use proc_macro2::Span;
2023

21-
use syn::Body::Enum;
22-
use syn::VariantData::Unit;
24+
use syn::{Data, Fields, Ident};
2325

2426
#[proc_macro_derive(FromPrimitive)]
2527
pub fn from_primitive(input: TokenStream) -> TokenStream {
26-
let source = input.to_string();
27-
28-
let ast = syn::parse_macro_input(&source).unwrap();
28+
let ast: syn::DeriveInput = syn::parse(input).unwrap();
2929
let name = &ast.ident;
30+
let dummy_const = Ident::new(&format!("_IMPL_NUM_FROM_PRIMITIVE_FOR_{}", name), Span::call_site());
3031

31-
let variants = match ast.body {
32-
Enum(ref variants) => variants,
32+
let variants = match ast.data {
33+
Data::Enum(ref data_enum) => &data_enum.variants,
3334
_ => panic!("`FromPrimitive` can be applied only to the enums, {} is not an enum", name)
3435
};
3536

36-
let mut idx = 0;
37-
let variants: Vec<_> = variants.iter()
37+
let from_u64_var = quote! { n };
38+
let mut expr = quote! { 0isize };
39+
let mut offset = 0isize;
40+
let clauses: Vec<_> = variants.iter()
3841
.map(|variant| {
3942
let ident = &variant.ident;
40-
match variant.data {
41-
Unit => (),
43+
match variant.fields {
44+
Fields::Unit => (),
4245
_ => {
4346
panic!("`FromPrimitive` can be applied only to unitary enums, {}::{} is either struct or tuple", name, ident)
4447
},
4548
}
46-
if let Some(val) = variant.discriminant {
47-
idx = val.value;
49+
50+
let discriminant_expr = match variant.discriminant {
51+
Some((_, ref const_expr)) => {
52+
expr = quote! { (#const_expr) as isize };
53+
offset = 1;
54+
expr.clone()
55+
}
56+
None => {
57+
let tt = quote! { #expr + #offset };
58+
offset += 1;
59+
tt
60+
}
61+
};
62+
63+
quote! {
64+
if #from_u64_var as isize == #discriminant_expr {
65+
Some(#name::#ident)
66+
}
4867
}
49-
let tt = quote!(#idx => Some(#name::#ident));
50-
idx += 1;
51-
tt
5268
})
5369
.collect();
5470

71+
let from_u64_var = if clauses.is_empty() { quote!(_) } else { from_u64_var };
72+
5573
let res = quote! {
56-
impl ::num::traits::FromPrimitive for #name {
57-
fn from_i64(n: i64) -> Option<Self> {
58-
Self::from_u64(n as u64)
59-
}
74+
#[allow(non_upper_case_globals)]
75+
const #dummy_const: () = {
76+
extern crate num as _num;
77+
78+
impl _num::traits::FromPrimitive for #name {
79+
fn from_i64(n: i64) -> Option<Self> {
80+
Self::from_u64(n as u64)
81+
}
6082

61-
fn from_u64(n: u64) -> Option<Self> {
62-
match n {
63-
#(variants,)*
64-
_ => None,
83+
fn from_u64(#from_u64_var: u64) -> Option<Self> {
84+
#(#clauses else)* {
85+
None
86+
}
6587
}
6688
}
67-
}
89+
};
6890
};
6991

70-
res.to_string().parse().unwrap()
92+
res.into()
7193
}
7294

7395
#[proc_macro_derive(ToPrimitive)]
7496
pub fn to_primitive(input: TokenStream) -> TokenStream {
75-
let source = input.to_string();
76-
77-
let ast = syn::parse_macro_input(&source).unwrap();
97+
let ast: syn::DeriveInput = syn::parse(input).unwrap();
7898
let name = &ast.ident;
99+
let dummy_const = Ident::new(&format!("_IMPL_NUM_TO_PRIMITIVE_FOR_{}", name), Span::call_site());
79100

80-
let variants = match ast.body {
81-
Enum(ref variants) => variants,
101+
let variants = match ast.data {
102+
Data::Enum(ref data_enum) => &data_enum.variants,
82103
_ => panic!("`ToPrimitive` can be applied only to the enums, {} is not an enum", name)
83104
};
84105

85-
let mut idx = 0;
106+
let mut expr = quote! { 0isize };
107+
let mut offset = 0isize;
86108
let variants: Vec<_> = variants.iter()
87109
.map(|variant| {
88110
let ident = &variant.ident;
89-
match variant.data {
90-
Unit => (),
111+
match variant.fields {
112+
Fields::Unit => (),
91113
_ => {
92114
panic!("`ToPrimitive` can be applied only to unitary enums, {}::{} is either struct or tuple", name, ident)
93115
},
94116
}
95-
if let Some(val) = variant.discriminant {
96-
idx = val.value;
97-
}
98-
let tt = quote!(#name::#ident => #idx);
99-
idx += 1;
100-
tt
117+
118+
let discriminant_expr = match variant.discriminant {
119+
Some((_, ref const_expr)) => {
120+
expr = quote! { (#const_expr) as isize };
121+
offset = 1;
122+
expr.clone()
123+
}
124+
None => {
125+
let tt = quote! { #expr + #offset };
126+
offset += 1;
127+
tt
128+
}
129+
};
130+
131+
quote!(#name::#ident => (#discriminant_expr) as u64)
101132
})
102133
.collect();
103134

135+
let match_expr = if variants.is_empty() {
136+
// No variants found, so do not use Some to not to trigger `unreachable_code` lint
137+
quote! {
138+
match *self {}
139+
}
140+
} else {
141+
quote! {
142+
Some(match *self {
143+
#(#variants,)*
144+
})
145+
}
146+
};
147+
104148
let res = quote! {
105-
impl ::num::traits::ToPrimitive for #name {
106-
fn to_i64(&self) -> Option<i64> {
107-
self.to_u64().map(|x| x as i64)
108-
}
149+
#[allow(non_upper_case_globals)]
150+
const #dummy_const: () = {
151+
extern crate num as _num;
109152

110-
fn to_u64(&self) -> Option<u64> {
111-
Some(match *self {
112-
#(variants,)*
113-
})
153+
impl _num::traits::ToPrimitive for #name {
154+
fn to_i64(&self) -> Option<i64> {
155+
self.to_u64().map(|x| x as i64)
156+
}
157+
158+
fn to_u64(&self) -> Option<u64> {
159+
#match_expr
160+
}
114161
}
115-
}
162+
};
116163
};
117164

118-
res.to_string().parse().unwrap()
165+
res.into()
119166
}

tests/compiletest.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ extern crate compiletest_rs as compiletest;
33
use std::path::PathBuf;
44
use std::env::var;
55

6+
use compiletest::Config;
7+
68
fn run_mode(mode: &'static str) {
7-
let mut config = compiletest::default_config();
9+
let mut config = Config::default();
810

911
let cfg_mode = mode.parse().ok().expect("Invalid mode");
1012

tests/empty_enum.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
extern crate num;
11+
extern crate num as num_renamed;
1212
#[macro_use]
1313
extern crate num_derive;
1414

@@ -17,7 +17,7 @@ enum Color {}
1717

1818
#[test]
1919
fn test_empty_enum() {
20-
let v: [Option<Color>; 1] = [num::FromPrimitive::from_u64(0)];
20+
let v: [Option<Color>; 1] = [num_renamed::FromPrimitive::from_u64(0)];
2121

2222
assert_eq!(v, [None]);
2323
}

tests/num_derive_without_num.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#[macro_use]
12+
extern crate num_derive;
13+
14+
#[derive(Debug, FromPrimitive, ToPrimitive)]
15+
enum Direction {
16+
Up,
17+
Down,
18+
Left,
19+
Right,
20+
}

tests/trivial.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
extern crate num;
11+
extern crate num as num_renamed;
1212
#[macro_use]
1313
extern crate num_derive;
1414

@@ -21,20 +21,20 @@ enum Color {
2121

2222
#[test]
2323
fn test_from_primitive_for_trivial_case() {
24-
let v: [Option<Color>; 4] = [num::FromPrimitive::from_u64(0),
25-
num::FromPrimitive::from_u64(1),
26-
num::FromPrimitive::from_u64(2),
27-
num::FromPrimitive::from_u64(3)];
24+
let v: [Option<Color>; 4] = [num_renamed::FromPrimitive::from_u64(0),
25+
num_renamed::FromPrimitive::from_u64(1),
26+
num_renamed::FromPrimitive::from_u64(2),
27+
num_renamed::FromPrimitive::from_u64(3)];
2828

2929
assert_eq!(v,
3030
[Some(Color::Red), Some(Color::Blue), Some(Color::Green), None]);
3131
}
3232

3333
#[test]
3434
fn test_to_primitive_for_trivial_case() {
35-
let v: [Option<u64>; 3] = [num::ToPrimitive::to_u64(&Color::Red),
36-
num::ToPrimitive::to_u64(&Color::Blue),
37-
num::ToPrimitive::to_u64(&Color::Green)];
35+
let v: [Option<u64>; 3] = [num_renamed::ToPrimitive::to_u64(&Color::Red),
36+
num_renamed::ToPrimitive::to_u64(&Color::Blue),
37+
num_renamed::ToPrimitive::to_u64(&Color::Green)];
3838

3939
assert_eq!(v, [Some(0), Some(1), Some(2)]);
4040
}
@@ -43,8 +43,8 @@ fn test_to_primitive_for_trivial_case() {
4343
fn test_reflexive_for_trivial_case() {
4444
let before: [u64; 3] = [0, 1, 2];
4545
let after: Vec<Option<u64>> = before.iter()
46-
.map(|&x| -> Option<Color> { num::FromPrimitive::from_u64(x) })
47-
.map(|x| x.and_then(|x| num::ToPrimitive::to_u64(&x)))
46+
.map(|&x| -> Option<Color> { num_renamed::FromPrimitive::from_u64(x) })
47+
.map(|x| x.and_then(|x| num_renamed::ToPrimitive::to_u64(&x)))
4848
.collect();
4949
let before = before.into_iter().cloned().map(Some).collect::<Vec<_>>();
5050

0 commit comments

Comments
 (0)