forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow conversion of primitive enums to their repr
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
1 parent
dc6121c
commit 961fcbd
Showing
10 changed files
with
268 additions
and
0 deletions.
There are no files selected for viewing
149 changes: 149 additions & 0 deletions
149
compiler/rustc_builtin_macros/src/deriving/into_underlying.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -156,6 +156,7 @@ symbols! { | |
Implied, | ||
Input, | ||
IntoIterator, | ||
IntoUnderlying, | ||
Is, | ||
ItemContext, | ||
Iterator, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |