Skip to content

Commit

Permalink
Allow conversion of primitive enums to their repr
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
illicitonion committed Dec 18, 2020
1 parent fee693d commit ec50f6a
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 2 deletions.
134 changes: 134 additions & 0 deletions compiler/rustc_builtin_macros/src/deriving/into.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use rustc_ast::ast::DUMMY_NODE_ID;
use rustc_ast::ptr::P;
use rustc_ast::{
AngleBracketedArg, AngleBracketedArgs, AssocItem, AssocItemKind, Const, Defaultness, FnHeader,
FnRetTy, FnSig, GenericArg, GenericArgs, Generics, ImplPolarity, ItemKind, MetaItem, Path,
PathSegment, Unsafe, Visibility, VisibilityKind,
};
use rustc_errors::struct_span_err;
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_span::symbol::{sym, Ident};
use rustc_span::{Span, DUMMY_SP};

macro_rules! invalid_derive {
($cx:ident, $span:ident) => {
struct_span_err!(
&$cx.sess.parse_sess.span_diagnostic,
$span,
FIXME,
"`Into` can only be derived for enums with an explicit integer representation"
)
.emit();
};
}

pub fn expand_deriving_into(
cx: &mut ExtCtxt<'_>,
span: Span,
_mitem: &MetaItem,
item: &Annotatable,
push: &mut dyn FnMut(Annotatable),
) {
match *item {
Annotatable::Item(ref annitem) => match annitem.kind {
ItemKind::Enum(_, _) => {
let reprs: Vec<_> = annitem
.attrs
.iter()
.filter_map(|attr| {
for r in rustc_attr::find_repr_attrs(&cx.sess, attr) {
use rustc_attr::*;
match r {
ReprInt(rustc_attr::IntType::UnsignedInt(int_type)) => {
return Some(int_type.name());
}
ReprInt(rustc_attr::IntType::SignedInt(int_type)) => {
return Some(int_type.name());
}
ReprC | ReprPacked(..) | ReprSimd | ReprTransparent
| ReprAlign(..) | ReprNoNiche => {}
}
}
None
})
.collect();
if reprs.len() != 1 {
invalid_derive!(cx, span);
return;
}

let repr_ident = Ident { name: reprs[0], span: DUMMY_SP };
let repr_ty = cx.ty_ident(DUMMY_SP, repr_ident);

let ty = cx.ty_ident(DUMMY_SP, annitem.ident);

let param_ident = Ident::from_str("value");

let decl = cx.fn_decl(
vec![cx.param(DUMMY_SP, param_ident, ty)],
FnRetTy::Ty(repr_ty.clone()),
);

let fn_item = P(AssocItem {
attrs: vec![cx.attribute(cx.meta_word(span, sym::inline))],
id: DUMMY_NODE_ID,
span: DUMMY_SP,
vis: Visibility { kind: VisibilityKind::Inherited, span, tokens: None },
ident: Ident::from_str("from"),

kind: AssocItemKind::Fn(
Defaultness::Final,
FnSig { header: FnHeader::default(), decl, span: DUMMY_SP },
Generics::default(),
Some(cx.block_expr(cx.expr_cast(
DUMMY_SP,
cx.expr_path(Path::from_ident(param_ident)),
repr_ty.clone(),
))),
),
tokens: None,
});

let mut trait_path = Path {
span: DUMMY_SP,
segments: cx
.std_path(&[sym::convert])
.into_iter()
.map(PathSegment::from_ident)
.collect(),
tokens: None,
};
trait_path.segments.push(PathSegment {
ident: Ident { name: sym::From, span: DUMMY_SP },
id: DUMMY_NODE_ID,
args: Some(P(GenericArgs::AngleBracketed(AngleBracketedArgs {
span: DUMMY_SP,
args: vec![AngleBracketedArg::Arg(GenericArg::Type(
cx.ty_ident(DUMMY_SP, annitem.ident),
))],
}))),
});

let trait_item = Annotatable::Item(cx.item(
DUMMY_SP,
Ident::invalid(),
Vec::new(),
ItemKind::Impl {
unsafety: Unsafe::No,
polarity: ImplPolarity::Positive,
defaultness: Defaultness::Final,
constness: Const::No,
generics: Generics::default(),
of_trait: Some(cx.trait_ref(trait_path)),
self_ty: repr_ty,
items: vec![fn_item],
},
));

push(trait_item);
}
_ => invalid_derive!(cx, span),
},
_ => invalid_derive!(cx, span),
}
}
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/deriving/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub mod decodable;
pub mod default;
pub mod encodable;
pub mod hash;
pub mod into;

#[path = "cmp/eq.rs"]
pub mod eq;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand, edition: Editi
Default: default::expand_deriving_default,
Eq: eq::expand_deriving_eq,
Hash: hash::expand_deriving_hash,
Into: into::expand_deriving_into,
Ord: ord::expand_deriving_ord,
PartialEq: partial_eq::expand_deriving_partial_eq,
PartialOrd: partial_ord::expand_deriving_partial_ord,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ symbols! {
Hasher,
Implied,
Input,
Into,
IntoIterator,
Is,
ItemContext,
Expand Down
9 changes: 9 additions & 0 deletions library/core/src/convert/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,15 @@ pub trait Into<T>: Sized {
fn into(self) -> T;
}

/// Derive macro generating an impl of the trait `Into` for primitive enums.
#[cfg(not(bootstrap))]
#[rustc_builtin_macro]
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
#[allow_internal_unstable(core_intrinsics)]
pub macro Into($item:item) {
/* compiler built-in */
}

/// Used to do value-to-value conversions while consuming the input value. It is the reciprocal of
/// [`Into`].
///
Expand Down
12 changes: 11 additions & 1 deletion library/core/src/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@ pub use crate::clone::Clone;
pub use crate::cmp::{Eq, Ord, PartialEq, PartialOrd};
#[stable(feature = "core_prelude", since = "1.4.0")]
#[doc(no_inline)]
pub use crate::convert::{AsMut, AsRef, From, Into};
pub use crate::convert::{AsMut, AsRef, From};

#[cfg(bootstrap)]
#[stable(feature = "core_prelude", since = "1.4.0")]
#[doc(no_inline)]
pub use crate::convert::Into;
#[cfg(not(bootstrap))]
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
#[doc(no_inline)]
pub use crate::convert::Into;

#[stable(feature = "core_prelude", since = "1.4.0")]
#[doc(no_inline)]
pub use crate::default::Default;
Expand Down
13 changes: 12 additions & 1 deletion library/std/src/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ pub use crate::mem::drop;
// Re-exported types and traits
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(no_inline)]
pub use crate::convert::{AsMut, AsRef, From, Into};
pub use crate::convert::{AsMut, AsRef, From};

#[cfg(bootstrap)]
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(no_inline)]
pub use core::prelude::v1::Into;
#[cfg(not(bootstrap))]
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
#[allow(deprecated)]
#[doc(hidden)]
pub use core::prelude::v1::Into;

#[stable(feature = "rust1", since = "1.0.0")]
#[doc(no_inline)]
pub use crate::iter::{DoubleEndedIterator, ExactSizeIterator};
Expand Down
24 changes: 24 additions & 0 deletions src/test/ui/deriving/deriving-into-bad.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Test that From/Into cannot be derived other than for enums with an int repr.

#![allow(unused)]

#[derive(Into)]
//~^ ERROR `Into` can only be derived for enums with an explicit integer representation [FIXME]
struct Struct {}

#[derive(Into)]
//~^ ERROR `Into` can only be derived for enums with an explicit integer representation [FIXME]
#[repr(C)]
enum NumberC {
Zero,
One,
}

#[derive(Into)]
//~^ ERROR `Into` can only be derived for enums with an explicit integer representation [FIXME]
enum NumberNoRepr {
Zero,
One,
}

fn main() {}
26 changes: 26 additions & 0 deletions src/test/ui/deriving/deriving-into-bad.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
error[FIXME]: `Into` can only be derived for enums with an explicit integer representation
--> $DIR/deriving-into-bad.rs:5:10
|
LL | #[derive(Into)]
| ^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[FIXME]: `Into` can only be derived for enums with an explicit integer representation
--> $DIR/deriving-into-bad.rs:9:10
|
LL | #[derive(Into)]
| ^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[FIXME]: `Into` can only be derived for enums with an explicit integer representation
--> $DIR/deriving-into-bad.rs:17:10
|
LL | #[derive(Into)]
| ^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 3 previous errors

44 changes: 44 additions & 0 deletions src/test/ui/deriving/deriving-into-enum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Test that From/Into can be derived to convert an int-repr'd enum into its repr.

// run-pass

#[derive(Into)]
#[repr(u8)]
enum PositiveNumber {
Zero,
One,
}

#[derive(Into)]
#[repr(i8)]
enum Number {
MinusOne = -1,
Zero,
One,
}

fn main() {
let n = u8::from(PositiveNumber::Zero);
assert_eq!(n, 0);
let n = u8::from(PositiveNumber::One);
assert_eq!(n, 1);

let n: u8 = PositiveNumber::Zero.into();
assert_eq!(n, 0);
let n: u8 = PositiveNumber::One.into();
assert_eq!(n, 1);

let n = i8::from(Number::MinusOne);
assert_eq!(n, -1);
let n = i8::from(Number::Zero);
assert_eq!(n, 0);
let n = i8::from(Number::One);
assert_eq!(n, 1);

let n: i8 = Number::MinusOne.into();
assert_eq!(n, -1);
let n: i8 = Number::Zero.into();
assert_eq!(n, 0);
let n: i8 = Number::One.into();
assert_eq!(n, 1);
}

0 comments on commit ec50f6a

Please sign in to comment.