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 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
1 parent
fee693d
commit ec50f6a
Showing
10 changed files
with
263 additions
and
2 deletions.
There are no files selected for viewing
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,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), | ||
} | ||
} |
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 |
---|---|---|
|
@@ -155,6 +155,7 @@ symbols! { | |
Hasher, | ||
Implied, | ||
Input, | ||
Into, | ||
IntoIterator, | ||
Is, | ||
ItemContext, | ||
|
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 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() {} |
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]: `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 | ||
|
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,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); | ||
} |