diff --git a/compiler/rustc_builtin_macros/src/deriving/into_underlying.rs b/compiler/rustc_builtin_macros/src/deriving/into_underlying.rs new file mode 100644 index 0000000000000..30a72f2372d92 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/into_underlying.rs @@ -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), + } +} diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs index 1651180817b9d..efabe3a9b72bc 100644 --- a/compiler/rustc_builtin_macros/src/deriving/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs @@ -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; diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 97cadb913cacf..58054585fe9da 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -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, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 99a523c3f3bb4..065671d3b7e51 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -156,6 +156,7 @@ symbols! { Implied, Input, IntoIterator, + IntoUnderlying, Is, ItemContext, Iterator, diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index 3f7110b34cc67..3f8a653ab5337 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -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. //! @@ -281,6 +283,29 @@ pub trait Into: 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`]. /// diff --git a/library/core/src/prelude/v1.rs b/library/core/src/prelude/v1.rs index b4fff3d67b555..336fec6bb79df 100644 --- a/library/core/src/prelude/v1.rs +++ b/library/core/src/prelude/v1.rs @@ -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; diff --git a/library/std/src/prelude/v1.rs b/library/std/src/prelude/v1.rs index 0fbd6b62f18ff..b1e0c7d5dc691 100644 --- a/library/std/src/prelude/v1.rs +++ b/library/std/src/prelude/v1.rs @@ -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")] diff --git a/src/test/ui/deriving/deriving-into-underlying-bad.rs b/src/test/ui/deriving/deriving-into-underlying-bad.rs new file mode 100644 index 0000000000000..6b23f4d6780a5 --- /dev/null +++ b/src/test/ui/deriving/deriving-into-underlying-bad.rs @@ -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() {} diff --git a/src/test/ui/deriving/deriving-into-underlying-bad.stderr b/src/test/ui/deriving/deriving-into-underlying-bad.stderr new file mode 100644 index 0000000000000..5cd9baf051c14 --- /dev/null +++ b/src/test/ui/deriving/deriving-into-underlying-bad.stderr @@ -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 + diff --git a/src/test/ui/deriving/deriving-into-underlying-enum.rs b/src/test/ui/deriving/deriving-into-underlying-enum.rs new file mode 100644 index 0000000000000..265dae6dcf349 --- /dev/null +++ b/src/test/ui/deriving/deriving-into-underlying-enum.rs @@ -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); +}