Skip to content

Commit ec50f6a

Browse files
committed
Allow conversion of primitive enums to their repr
Currently this tends to be done using `as`, which has two flaws: 1. It will silently truncate. e.g. if you have a `repr(u64)` enum, and you write `value as u8` (perhaps because the repr changed), you get no warning or error that truncation will occur. 2. You cannot use enums in code which is generic over `From` or `Into`. Instead, by allowing a `From` (and accordingly `Into`) implementation to be easily derived, we can avoid these issues. There are a number of crates which provide macros to provide this implementation, as well as `TryFrom` implementations. I believe this is enough of a foot-gun that it should be rolled into `std`. See rust-lang/rfcs#3040 for more information.
1 parent fee693d commit ec50f6a

File tree

10 files changed

+263
-2
lines changed

10 files changed

+263
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use rustc_ast::ast::DUMMY_NODE_ID;
2+
use rustc_ast::ptr::P;
3+
use rustc_ast::{
4+
AngleBracketedArg, AngleBracketedArgs, AssocItem, AssocItemKind, Const, Defaultness, FnHeader,
5+
FnRetTy, FnSig, GenericArg, GenericArgs, Generics, ImplPolarity, ItemKind, MetaItem, Path,
6+
PathSegment, Unsafe, Visibility, VisibilityKind,
7+
};
8+
use rustc_errors::struct_span_err;
9+
use rustc_expand::base::{Annotatable, ExtCtxt};
10+
use rustc_span::symbol::{sym, Ident};
11+
use rustc_span::{Span, DUMMY_SP};
12+
13+
macro_rules! invalid_derive {
14+
($cx:ident, $span:ident) => {
15+
struct_span_err!(
16+
&$cx.sess.parse_sess.span_diagnostic,
17+
$span,
18+
FIXME,
19+
"`Into` can only be derived for enums with an explicit integer representation"
20+
)
21+
.emit();
22+
};
23+
}
24+
25+
pub fn expand_deriving_into(
26+
cx: &mut ExtCtxt<'_>,
27+
span: Span,
28+
_mitem: &MetaItem,
29+
item: &Annotatable,
30+
push: &mut dyn FnMut(Annotatable),
31+
) {
32+
match *item {
33+
Annotatable::Item(ref annitem) => match annitem.kind {
34+
ItemKind::Enum(_, _) => {
35+
let reprs: Vec<_> = annitem
36+
.attrs
37+
.iter()
38+
.filter_map(|attr| {
39+
for r in rustc_attr::find_repr_attrs(&cx.sess, attr) {
40+
use rustc_attr::*;
41+
match r {
42+
ReprInt(rustc_attr::IntType::UnsignedInt(int_type)) => {
43+
return Some(int_type.name());
44+
}
45+
ReprInt(rustc_attr::IntType::SignedInt(int_type)) => {
46+
return Some(int_type.name());
47+
}
48+
ReprC | ReprPacked(..) | ReprSimd | ReprTransparent
49+
| ReprAlign(..) | ReprNoNiche => {}
50+
}
51+
}
52+
None
53+
})
54+
.collect();
55+
if reprs.len() != 1 {
56+
invalid_derive!(cx, span);
57+
return;
58+
}
59+
60+
let repr_ident = Ident { name: reprs[0], span: DUMMY_SP };
61+
let repr_ty = cx.ty_ident(DUMMY_SP, repr_ident);
62+
63+
let ty = cx.ty_ident(DUMMY_SP, annitem.ident);
64+
65+
let param_ident = Ident::from_str("value");
66+
67+
let decl = cx.fn_decl(
68+
vec![cx.param(DUMMY_SP, param_ident, ty)],
69+
FnRetTy::Ty(repr_ty.clone()),
70+
);
71+
72+
let fn_item = P(AssocItem {
73+
attrs: vec![cx.attribute(cx.meta_word(span, sym::inline))],
74+
id: DUMMY_NODE_ID,
75+
span: DUMMY_SP,
76+
vis: Visibility { kind: VisibilityKind::Inherited, span, tokens: None },
77+
ident: Ident::from_str("from"),
78+
79+
kind: AssocItemKind::Fn(
80+
Defaultness::Final,
81+
FnSig { header: FnHeader::default(), decl, span: DUMMY_SP },
82+
Generics::default(),
83+
Some(cx.block_expr(cx.expr_cast(
84+
DUMMY_SP,
85+
cx.expr_path(Path::from_ident(param_ident)),
86+
repr_ty.clone(),
87+
))),
88+
),
89+
tokens: None,
90+
});
91+
92+
let mut trait_path = Path {
93+
span: DUMMY_SP,
94+
segments: cx
95+
.std_path(&[sym::convert])
96+
.into_iter()
97+
.map(PathSegment::from_ident)
98+
.collect(),
99+
tokens: None,
100+
};
101+
trait_path.segments.push(PathSegment {
102+
ident: Ident { name: sym::From, span: DUMMY_SP },
103+
id: DUMMY_NODE_ID,
104+
args: Some(P(GenericArgs::AngleBracketed(AngleBracketedArgs {
105+
span: DUMMY_SP,
106+
args: vec![AngleBracketedArg::Arg(GenericArg::Type(
107+
cx.ty_ident(DUMMY_SP, annitem.ident),
108+
))],
109+
}))),
110+
});
111+
112+
let trait_item = Annotatable::Item(cx.item(
113+
DUMMY_SP,
114+
Ident::invalid(),
115+
Vec::new(),
116+
ItemKind::Impl {
117+
unsafety: Unsafe::No,
118+
polarity: ImplPolarity::Positive,
119+
defaultness: Defaultness::Final,
120+
constness: Const::No,
121+
generics: Generics::default(),
122+
of_trait: Some(cx.trait_ref(trait_path)),
123+
self_ty: repr_ty,
124+
items: vec![fn_item],
125+
},
126+
));
127+
128+
push(trait_item);
129+
}
130+
_ => invalid_derive!(cx, span),
131+
},
132+
_ => invalid_derive!(cx, span),
133+
}
134+
}

compiler/rustc_builtin_macros/src/deriving/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub mod decodable;
2626
pub mod default;
2727
pub mod encodable;
2828
pub mod hash;
29+
pub mod into;
2930

3031
#[path = "cmp/eq.rs"]
3132
pub mod eq;

compiler/rustc_builtin_macros/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand, edition: Editi
101101
Default: default::expand_deriving_default,
102102
Eq: eq::expand_deriving_eq,
103103
Hash: hash::expand_deriving_hash,
104+
Into: into::expand_deriving_into,
104105
Ord: ord::expand_deriving_ord,
105106
PartialEq: partial_eq::expand_deriving_partial_eq,
106107
PartialOrd: partial_ord::expand_deriving_partial_ord,

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ symbols! {
155155
Hasher,
156156
Implied,
157157
Input,
158+
Into,
158159
IntoIterator,
159160
Is,
160161
ItemContext,

library/core/src/convert/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,15 @@ pub trait Into<T>: Sized {
281281
fn into(self) -> T;
282282
}
283283

284+
/// Derive macro generating an impl of the trait `Into` for primitive enums.
285+
#[cfg(not(bootstrap))]
286+
#[rustc_builtin_macro]
287+
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
288+
#[allow_internal_unstable(core_intrinsics)]
289+
pub macro Into($item:item) {
290+
/* compiler built-in */
291+
}
292+
284293
/// Used to do value-to-value conversions while consuming the input value. It is the reciprocal of
285294
/// [`Into`].
286295
///

library/core/src/prelude/v1.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,17 @@ pub use crate::clone::Clone;
2828
pub use crate::cmp::{Eq, Ord, PartialEq, PartialOrd};
2929
#[stable(feature = "core_prelude", since = "1.4.0")]
3030
#[doc(no_inline)]
31-
pub use crate::convert::{AsMut, AsRef, From, Into};
31+
pub use crate::convert::{AsMut, AsRef, From};
32+
33+
#[cfg(bootstrap)]
34+
#[stable(feature = "core_prelude", since = "1.4.0")]
35+
#[doc(no_inline)]
36+
pub use crate::convert::Into;
37+
#[cfg(not(bootstrap))]
38+
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
39+
#[doc(no_inline)]
40+
pub use crate::convert::Into;
41+
3242
#[stable(feature = "core_prelude", since = "1.4.0")]
3343
#[doc(no_inline)]
3444
pub use crate::default::Default;

library/std/src/prelude/v1.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@ pub use crate::mem::drop;
2020
// Re-exported types and traits
2121
#[stable(feature = "rust1", since = "1.0.0")]
2222
#[doc(no_inline)]
23-
pub use crate::convert::{AsMut, AsRef, From, Into};
23+
pub use crate::convert::{AsMut, AsRef, From};
24+
25+
#[cfg(bootstrap)]
26+
#[stable(feature = "rust1", since = "1.0.0")]
27+
#[doc(no_inline)]
28+
pub use core::prelude::v1::Into;
29+
#[cfg(not(bootstrap))]
30+
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
31+
#[allow(deprecated)]
32+
#[doc(hidden)]
33+
pub use core::prelude::v1::Into;
34+
2435
#[stable(feature = "rust1", since = "1.0.0")]
2536
#[doc(no_inline)]
2637
pub use crate::iter::{DoubleEndedIterator, ExactSizeIterator};
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Test that From/Into cannot be derived other than for enums with an int repr.
2+
3+
#![allow(unused)]
4+
5+
#[derive(Into)]
6+
//~^ ERROR `Into` can only be derived for enums with an explicit integer representation [FIXME]
7+
struct Struct {}
8+
9+
#[derive(Into)]
10+
//~^ ERROR `Into` can only be derived for enums with an explicit integer representation [FIXME]
11+
#[repr(C)]
12+
enum NumberC {
13+
Zero,
14+
One,
15+
}
16+
17+
#[derive(Into)]
18+
//~^ ERROR `Into` can only be derived for enums with an explicit integer representation [FIXME]
19+
enum NumberNoRepr {
20+
Zero,
21+
One,
22+
}
23+
24+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error[FIXME]: `Into` can only be derived for enums with an explicit integer representation
2+
--> $DIR/deriving-into-bad.rs:5:10
3+
|
4+
LL | #[derive(Into)]
5+
| ^^^^
6+
|
7+
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
error[FIXME]: `Into` can only be derived for enums with an explicit integer representation
10+
--> $DIR/deriving-into-bad.rs:9:10
11+
|
12+
LL | #[derive(Into)]
13+
| ^^^^
14+
|
15+
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
16+
17+
error[FIXME]: `Into` can only be derived for enums with an explicit integer representation
18+
--> $DIR/deriving-into-bad.rs:17:10
19+
|
20+
LL | #[derive(Into)]
21+
| ^^^^
22+
|
23+
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
24+
25+
error: aborting due to 3 previous errors
26+
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Test that From/Into can be derived to convert an int-repr'd enum into its repr.
2+
3+
// run-pass
4+
5+
#[derive(Into)]
6+
#[repr(u8)]
7+
enum PositiveNumber {
8+
Zero,
9+
One,
10+
}
11+
12+
#[derive(Into)]
13+
#[repr(i8)]
14+
enum Number {
15+
MinusOne = -1,
16+
Zero,
17+
One,
18+
}
19+
20+
fn main() {
21+
let n = u8::from(PositiveNumber::Zero);
22+
assert_eq!(n, 0);
23+
let n = u8::from(PositiveNumber::One);
24+
assert_eq!(n, 1);
25+
26+
let n: u8 = PositiveNumber::Zero.into();
27+
assert_eq!(n, 0);
28+
let n: u8 = PositiveNumber::One.into();
29+
assert_eq!(n, 1);
30+
31+
let n = i8::from(Number::MinusOne);
32+
assert_eq!(n, -1);
33+
let n = i8::from(Number::Zero);
34+
assert_eq!(n, 0);
35+
let n = i8::from(Number::One);
36+
assert_eq!(n, 1);
37+
38+
let n: i8 = Number::MinusOne.into();
39+
assert_eq!(n, -1);
40+
let n: i8 = Number::Zero.into();
41+
assert_eq!(n, 0);
42+
let n: i8 = Number::One.into();
43+
assert_eq!(n, 1);
44+
}

0 commit comments

Comments
 (0)