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 some 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`.
3. Not all enums _want_ to expose conversion to their underlying repr as
   part of their public API.

Instead, by allowing an `IntoUnderlying` 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#3046 for more information.
  • Loading branch information
illicitonion committed Dec 27, 2020
1 parent dc6121c commit 961fcbd
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 0 deletions.
149 changes: 149 additions & 0 deletions compiler/rustc_builtin_macros/src/deriving/into_underlying.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use rustc_ast::ast::DUMMY_NODE_ID;
use rustc_ast::ptr::P;
use rustc_ast::{
AssocItem, AssocItemKind, AttrVec, Const, Defaultness, FnHeader, FnRetTy, FnSig, Generics,
ImplPolarity, ItemKind, MetaItem, Mutability, Param, Path, PathSegment, SelfKind, Unsafe,
Visibility, VisibilityKind,
};
use rustc_errors::struct_span_err;
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_span::source_map::dummy_spanned;
use rustc_span::symbol::{kw::SelfLower, 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,
"`IntoUnderlying` can only be derived for enums with an explicit integer representation"
)
.emit();
};
}

pub fn expand_deriving_into_underlying(
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 self_ident = Ident::with_dummy_span(SelfLower).with_span_pos(DUMMY_SP);

let decl = cx.fn_decl(
vec![Param::from_self(
AttrVec::default(),
dummy_spanned(SelfKind::Value(Mutability::Not)),
self_ident,
)],
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("into_underlying"),

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(self_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::IntoUnderlying, span: DUMMY_SP },
id: DUMMY_NODE_ID,
args: None,
});

let associated_type = P(AssocItem {
attrs: vec![],
id: DUMMY_NODE_ID,
span: DUMMY_SP,
vis: Visibility { kind: VisibilityKind::Inherited, span, tokens: None },
ident: Ident::from_str("Underlying"),
kind: AssocItemKind::TyAlias(
Defaultness::Final,
Generics::default(),
vec![],
Some(repr_ty),
),
tokens: None,
});

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: ty,
items: vec![associated_type, 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_underlying;

#[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,
IntoUnderlying: into_underlying::expand_deriving_into_underlying,
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 @@ -156,6 +156,7 @@ symbols! {
Implied,
Input,
IntoIterator,
IntoUnderlying,
Is,
ItemContext,
Iterator,
Expand Down
25 changes: 25 additions & 0 deletions library/core/src/convert/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
//! - Implement the [`From`] trait for consuming value-to-value conversions
//! - Implement the [`Into`] trait for consuming value-to-value conversions to types
//! outside the current crate
//! - Implement the [`IntoUnderlying`] trait for cheaply consuming a value which wraps, or is
//! represented by, another value.
//! - The [`TryFrom`] and [`TryInto`] traits behave like [`From`] and [`Into`],
//! but should be implemented when the conversion can fail.
//!
Expand Down Expand Up @@ -281,6 +283,29 @@ pub trait Into<T>: Sized {
fn into(self) -> T;
}

/// Used to consume a type with a natural underlying representation into that representation.
/// This trait should only be implemented where conversion is trivial; for non-trivial conversions,
/// prefer to implement [`Into`].
#[stable(feature = "into_underlying", since = "1.49.0")]
pub trait IntoUnderlying {
/// The underlying type.
#[stable(feature = "into_underlying", since = "1.49.0")]
type Underlying;

/// Performs the conversion.
#[stable(feature = "into_underlying", since = "1.49.0")]
fn into_underlying(self) -> Self::Underlying;
}

/// Derive macro generating an impl of the trait `IntoUnderlying`.
#[cfg(not(bootstrap))]
#[rustc_builtin_macro]
#[stable(feature = "into_underlying", since = "1.49.0")]
#[allow_internal_unstable(core_intrinsics)]
pub macro IntoUnderlying($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
5 changes: 5 additions & 0 deletions library/core/src/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ pub use crate::clone::Clone;
#[stable(feature = "core_prelude", since = "1.4.0")]
#[doc(no_inline)]
pub use crate::cmp::{Eq, Ord, PartialEq, PartialOrd};
#[cfg(not(bootstrap))]
#[stable(feature = "into_underlying", since = "1.49.0")]
#[doc(no_inline)]
pub use crate::convert::IntoUnderlying;
#[stable(feature = "core_prelude", since = "1.4.0")]
#[doc(no_inline)]
pub use crate::convert::{AsMut, AsRef, From, Into};

#[stable(feature = "core_prelude", since = "1.4.0")]
#[doc(no_inline)]
pub use crate::default::Default;
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ pub use crate::option::Option::{self, None, Some};
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(no_inline)]
pub use crate::result::Result::{self, Err, Ok};
#[cfg(not(bootstrap))]
#[stable(feature = "into_underlying", since = "1.48.0")]
#[doc(no_inline)]
pub use core::convert::IntoUnderlying;

// Re-exported built-in macros
#[stable(feature = "builtin_macro_prelude", since = "1.38.0")]
Expand Down
24 changes: 24 additions & 0 deletions src/test/ui/deriving/deriving-into-underlying-bad.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Test that IntoUnderlying cannot be derived other than for enums with an int repr.

#![allow(unused)]

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

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

#[derive(IntoUnderlying)]
//~^ ERROR `IntoUnderlying` 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-underlying-bad.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
error[FIXME]: `IntoUnderlying` can only be derived for enums with an explicit integer representation
--> $DIR/deriving-into-underlying-bad.rs:5:10
|
LL | #[derive(IntoUnderlying)]
| ^^^^^^^^^^^^^^
|
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

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

error[FIXME]: `IntoUnderlying` can only be derived for enums with an explicit integer representation
--> $DIR/deriving-into-underlying-bad.rs:17:10
|
LL | #[derive(IntoUnderlying)]
| ^^^^^^^^^^^^^^
|
= 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

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

// run-pass

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

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

fn main() {
let n = PositiveNumber::Zero.into_underlying();
assert_eq!(n, 0_u8);
let n = PositiveNumber::One.into_underlying();
assert_eq!(n, 1_u8);

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

0 comments on commit 961fcbd

Please sign in to comment.