提交 303f77ee 编写于 作者: B bors

Auto merge of #60732 - jswrenn:arbitrary_enum_discriminant, r=pnkfelix

Implement arbitrary_enum_discriminant

Implements RFC rust-lang/rfcs#2363 (tracking issue #60553).
# `arbitrary_enum_discriminant`
The tracking issue for this feature is: [#60553]
[#60553]: https://github.com/rust-lang/rust/issues/60553
------------------------
The `arbitrary_enum_discriminant` feature permits tuple-like and
struct-like enum variants with `#[repr(<int-type>)]` to have explicit discriminants.
## Examples
```rust
#![feature(arbitrary_enum_discriminant)]
#[allow(dead_code)]
#[repr(u8)]
enum Enum {
Unit = 3,
Tuple(u16) = 2,
Struct {
a: u8,
b: u16,
} = 1,
}
impl Enum {
fn tag(&self) -> u8 {
unsafe { *(self as *const Self as *const u8) }
}
}
assert_eq!(3, Enum::Unit.tag());
assert_eq!(2, Enum::Tuple(5).tag());
assert_eq!(1, Enum::Struct{a: 7, b: 11}.tag());
```
...@@ -1935,6 +1935,25 @@ pub fn check_enum<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, vs: &'tcx [hir::Variant], i ...@@ -1935,6 +1935,25 @@ pub fn check_enum<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, vs: &'tcx [hir::Variant], i
} }
} }
if tcx.adt_def(def_id).repr.int.is_none() && tcx.features().arbitrary_enum_discriminant {
let is_unit =
|var: &hir::Variant| match var.node.data {
hir::VariantData::Unit(..) => true,
_ => false
};
let has_disr = |var: &hir::Variant| var.node.disr_expr.is_some();
let has_non_units = vs.iter().any(|var| !is_unit(var));
let disr_units = vs.iter().any(|var| is_unit(&var) && has_disr(&var));
let disr_non_unit = vs.iter().any(|var| !is_unit(&var) && has_disr(&var));
if disr_non_unit || (disr_units && has_non_units) {
let mut err = struct_span_err!(tcx.sess, sp, E0732,
"`#[repr(inttype)]` must be specified");
err.emit();
}
}
let mut disr_vals: Vec<Discr<'tcx>> = Vec::with_capacity(vs.len()); let mut disr_vals: Vec<Discr<'tcx>> = Vec::with_capacity(vs.len());
for ((_, discr), v) in def.discriminants(tcx).zip(vs) { for ((_, discr), v) in def.discriminants(tcx).zip(vs) {
// Check for duplicate discriminant values // Check for duplicate discriminant values
......
...@@ -4733,6 +4733,38 @@ enum Status { // error: transparent enum needs exactly one variant, but has 2 ...@@ -4733,6 +4733,38 @@ enum Status { // error: transparent enum needs exactly one variant, but has 2
represented. represented.
"##, "##,
E0732: r##"
An `enum` with a discriminant must specify a `#[repr(inttype)]`.
A `#[repr(inttype)]` must be provided on an `enum` if it has a non-unit
variant with a discriminant, or where there are both unit variants with
discriminants and non-unit variants. This restriction ensures that there
is a well-defined way to extract a variant's discriminant from a value;
for instance:
```
#![feature(arbitrary_enum_discriminant)]
#[repr(u8)]
enum Enum {
Unit = 3,
Tuple(u16) = 2,
Struct {
a: u8,
b: u16,
} = 1,
}
fn discriminant(v : &Enum) -> u8 {
unsafe { *(v as *const Enum as *const u8) }
}
assert_eq!(3, discriminant(&Enum::Unit));
assert_eq!(2, discriminant(&Enum::Tuple(5)));
assert_eq!(1, discriminant(&Enum::Struct{a: 7, b: 11}));
```
"##,
} }
register_diagnostics! { register_diagnostics! {
......
...@@ -25,13 +25,14 @@ ...@@ -25,13 +25,14 @@
use crate::edition::{ALL_EDITIONS, Edition}; use crate::edition::{ALL_EDITIONS, Edition};
use crate::visit::{self, FnKind, Visitor}; use crate::visit::{self, FnKind, Visitor};
use crate::parse::{token, ParseSess}; use crate::parse::{token, ParseSess};
use crate::parse::parser::Parser;
use crate::symbol::{Symbol, sym}; use crate::symbol::{Symbol, sym};
use crate::tokenstream::TokenTree; use crate::tokenstream::TokenTree;
use errors::{Applicability, DiagnosticBuilder, Handler}; use errors::{Applicability, DiagnosticBuilder, Handler};
use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::fx::FxHashMap;
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
use syntax_pos::{Span, DUMMY_SP}; use syntax_pos::{Span, DUMMY_SP, MultiSpan};
use log::debug; use log::debug;
use lazy_static::lazy_static; use lazy_static::lazy_static;
...@@ -569,6 +570,9 @@ pub fn walk_feature_fields<F>(&self, mut f: F) ...@@ -569,6 +570,9 @@ pub fn walk_feature_fields<F>(&self, mut f: F)
// #[repr(transparent)] on unions. // #[repr(transparent)] on unions.
(active, transparent_unions, "1.37.0", Some(60405), None), (active, transparent_unions, "1.37.0", Some(60405), None),
// Allows explicit discriminants on non-unit enum variants.
(active, arbitrary_enum_discriminant, "1.37.0", Some(60553), None),
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// feature-group-end: actual feature gates // feature-group-end: actual feature gates
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
...@@ -1709,20 +1713,20 @@ pub fn emit_feature_err( ...@@ -1709,20 +1713,20 @@ pub fn emit_feature_err(
feature_err(sess, feature, span, issue, explain).emit(); feature_err(sess, feature, span, issue, explain).emit();
} }
pub fn feature_err<'a>( pub fn feature_err<'a, S: Into<MultiSpan>>(
sess: &'a ParseSess, sess: &'a ParseSess,
feature: Symbol, feature: Symbol,
span: Span, span: S,
issue: GateIssue, issue: GateIssue,
explain: &str, explain: &str,
) -> DiagnosticBuilder<'a> { ) -> DiagnosticBuilder<'a> {
leveled_feature_err(sess, feature, span, issue, explain, GateStrength::Hard) leveled_feature_err(sess, feature, span, issue, explain, GateStrength::Hard)
} }
fn leveled_feature_err<'a>( fn leveled_feature_err<'a, S: Into<MultiSpan>>(
sess: &'a ParseSess, sess: &'a ParseSess,
feature: Symbol, feature: Symbol,
span: Span, span: S,
issue: GateIssue, issue: GateIssue,
explain: &str, explain: &str,
level: GateStrength, level: GateStrength,
...@@ -2037,6 +2041,29 @@ fn visit_item(&mut self, i: &'a ast::Item) { ...@@ -2037,6 +2041,29 @@ fn visit_item(&mut self, i: &'a ast::Item) {
} }
} }
ast::ItemKind::Enum(ast::EnumDef{ref variants, ..}, ..) => {
for variant in variants {
match (&variant.node.data, &variant.node.disr_expr) {
(ast::VariantData::Unit(..), _) => {},
(_, Some(disr_expr)) =>
gate_feature_post!(
&self,
arbitrary_enum_discriminant,
disr_expr.value.span,
"discriminants on non-unit variants are experimental"),
_ => {},
}
}
let has_feature = self.context.features.arbitrary_enum_discriminant;
if !has_feature && !i.span.allows_unstable(sym::arbitrary_enum_discriminant) {
Parser::maybe_report_invalid_custom_discriminants(
self.context.parse_sess,
&variants,
);
}
}
ast::ItemKind::Impl(_, polarity, defaultness, _, _, _, _) => { ast::ItemKind::Impl(_, polarity, defaultness, _, _, _, _) => {
if polarity == ast::ImplPolarity::Negative { if polarity == ast::ImplPolarity::Negative {
gate_feature_post!(&self, optin_builtin_traits, gate_feature_post!(&self, optin_builtin_traits,
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
self, Arg, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item, ItemKind, self, Arg, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item, ItemKind,
Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind, VariantData, Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind, VariantData,
}; };
use crate::parse::{SeqSep, PResult, Parser}; use crate::parse::{SeqSep, PResult, Parser, ParseSess};
use crate::parse::parser::{BlockMode, PathStyle, SemiColonMode, TokenType, TokenExpectType}; use crate::parse::parser::{BlockMode, PathStyle, SemiColonMode, TokenType, TokenExpectType};
use crate::parse::token::{self, TokenKind}; use crate::parse::token::{self, TokenKind};
use crate::print::pprust; use crate::print::pprust;
...@@ -539,8 +539,7 @@ fn tokens_to_string(tokens: &[TokenType]) -> String { ...@@ -539,8 +539,7 @@ fn tokens_to_string(tokens: &[TokenType]) -> String {
} }
crate fn maybe_report_invalid_custom_discriminants( crate fn maybe_report_invalid_custom_discriminants(
&mut self, sess: &ParseSess,
discriminant_spans: Vec<Span>,
variants: &[Spanned<ast::Variant_>], variants: &[Spanned<ast::Variant_>],
) { ) {
let has_fields = variants.iter().any(|variant| match variant.node.data { let has_fields = variants.iter().any(|variant| match variant.node.data {
...@@ -548,28 +547,39 @@ fn tokens_to_string(tokens: &[TokenType]) -> String { ...@@ -548,28 +547,39 @@ fn tokens_to_string(tokens: &[TokenType]) -> String {
VariantData::Unit(..) => false, VariantData::Unit(..) => false,
}); });
let discriminant_spans = variants.iter().filter(|variant| match variant.node.data {
VariantData::Tuple(..) | VariantData::Struct(..) => false,
VariantData::Unit(..) => true,
})
.filter_map(|variant| variant.node.disr_expr.as_ref().map(|c| c.value.span))
.collect::<Vec<_>>();
if !discriminant_spans.is_empty() && has_fields { if !discriminant_spans.is_empty() && has_fields {
let mut err = self.struct_span_err( let mut err = crate::feature_gate::feature_err(
sess,
sym::arbitrary_enum_discriminant,
discriminant_spans.clone(), discriminant_spans.clone(),
"custom discriminant values are not allowed in enums with fields", crate::feature_gate::GateIssue::Language,
"custom discriminant values are not allowed in enums with tuple or struct variants",
); );
for sp in discriminant_spans { for sp in discriminant_spans {
err.span_label(sp, "invalid custom discriminant"); err.span_label(sp, "disallowed custom discriminant");
} }
for variant in variants.iter() { for variant in variants.iter() {
if let VariantData::Struct(fields, ..) | VariantData::Tuple(fields, ..) = match &variant.node.data {
&variant.node.data VariantData::Struct(..) => {
{ err.span_label(
let fields = if fields.len() > 1 { variant.span,
"fields" "struct variant defined here",
} else { );
"a field" }
}; VariantData::Tuple(..) => {
err.span_label( err.span_label(
variant.span, variant.span,
&format!("variant with {fields} defined here", fields = fields), "tuple variant defined here",
); );
}
VariantData::Unit(..) => {}
} }
} }
err.emit(); err.emit();
......
...@@ -6946,36 +6946,34 @@ fn parse_existential_or_alias( ...@@ -6946,36 +6946,34 @@ fn parse_existential_or_alias(
/// Parses the part of an enum declaration following the `{`. /// Parses the part of an enum declaration following the `{`.
fn parse_enum_def(&mut self, _generics: &ast::Generics) -> PResult<'a, EnumDef> { fn parse_enum_def(&mut self, _generics: &ast::Generics) -> PResult<'a, EnumDef> {
let mut variants = Vec::new(); let mut variants = Vec::new();
let mut any_disr = vec![];
while self.token != token::CloseDelim(token::Brace) { while self.token != token::CloseDelim(token::Brace) {
let variant_attrs = self.parse_outer_attributes()?; let variant_attrs = self.parse_outer_attributes()?;
let vlo = self.token.span; let vlo = self.token.span;
let struct_def;
let mut disr_expr = None;
self.eat_bad_pub(); self.eat_bad_pub();
let ident = self.parse_ident()?; let ident = self.parse_ident()?;
if self.check(&token::OpenDelim(token::Brace)) {
let struct_def = if self.check(&token::OpenDelim(token::Brace)) {
// Parse a struct variant. // Parse a struct variant.
let (fields, recovered) = self.parse_record_struct_body()?; let (fields, recovered) = self.parse_record_struct_body()?;
struct_def = VariantData::Struct(fields, recovered); VariantData::Struct(fields, recovered)
} else if self.check(&token::OpenDelim(token::Paren)) { } else if self.check(&token::OpenDelim(token::Paren)) {
struct_def = VariantData::Tuple( VariantData::Tuple(
self.parse_tuple_struct_body()?, self.parse_tuple_struct_body()?,
ast::DUMMY_NODE_ID, ast::DUMMY_NODE_ID,
); )
} else if self.eat(&token::Eq) { } else {
disr_expr = Some(AnonConst { VariantData::Unit(ast::DUMMY_NODE_ID)
};
let disr_expr = if self.eat(&token::Eq) {
Some(AnonConst {
id: ast::DUMMY_NODE_ID, id: ast::DUMMY_NODE_ID,
value: self.parse_expr()?, value: self.parse_expr()?,
}); })
if let Some(sp) = disr_expr.as_ref().map(|c| c.value.span) {
any_disr.push(sp);
}
struct_def = VariantData::Unit(ast::DUMMY_NODE_ID);
} else { } else {
struct_def = VariantData::Unit(ast::DUMMY_NODE_ID); None
} };
let vr = ast::Variant_ { let vr = ast::Variant_ {
ident, ident,
...@@ -7003,7 +7001,6 @@ fn parse_enum_def(&mut self, _generics: &ast::Generics) -> PResult<'a, EnumDef> ...@@ -7003,7 +7001,6 @@ fn parse_enum_def(&mut self, _generics: &ast::Generics) -> PResult<'a, EnumDef>
} }
} }
self.expect(&token::CloseDelim(token::Brace))?; self.expect(&token::CloseDelim(token::Brace))?;
self.maybe_report_invalid_custom_discriminants(any_disr, &variants);
Ok(ast::EnumDef { variants }) Ok(ast::EnumDef { variants })
} }
......
...@@ -135,6 +135,7 @@ ...@@ -135,6 +135,7 @@
always, always,
and, and,
any, any,
arbitrary_enum_discriminant,
arbitrary_self_types, arbitrary_self_types,
Arguments, Arguments,
ArgumentV1, ArgumentV1,
......
#![crate_type="lib"]
#![feature(arbitrary_enum_discriminant)]
enum Enum {
//~^ ERROR `#[repr(inttype)]` must be specified
Unit = 1,
Tuple() = 2,
Struct{} = 3,
}
error[E0732]: `#[repr(inttype)]` must be specified
--> $DIR/arbitrary_enum_discriminant-no-repr.rs:4:1
|
LL | / enum Enum {
LL | |
LL | | Unit = 1,
LL | | Tuple() = 2,
LL | | Struct{} = 3,
LL | | }
| |_^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0732`.
// run-pass
#![feature(arbitrary_enum_discriminant, const_raw_ptr_deref, test)]
extern crate test;
use test::black_box;
#[allow(dead_code)]
#[repr(u8)]
enum Enum {
Unit = 3,
Tuple(u16) = 2,
Struct {
a: u8,
b: u16,
} = 1,
}
impl Enum {
const unsafe fn tag(&self) -> u8 {
*(self as *const Self as *const u8)
}
}
#[allow(dead_code)]
#[repr(u8)]
enum FieldlessEnum {
Unit = 3,
Tuple() = 2,
Struct {} = 1,
}
fn main() {
const UNIT: Enum = Enum::Unit;
const TUPLE: Enum = Enum::Tuple(5);
const STRUCT: Enum = Enum::Struct{a: 7, b: 11};
// Ensure discriminants are correct during runtime execution
assert_eq!(3, unsafe { black_box(UNIT).tag() });
assert_eq!(2, unsafe { black_box(TUPLE).tag() });
assert_eq!(1, unsafe { black_box(STRUCT).tag() });
// Ensure discriminants are correct during CTFE
const UNIT_TAG: u8 = unsafe { UNIT.tag() };
const TUPLE_TAG: u8 = unsafe { TUPLE.tag() };
const STRUCT_TAG: u8 = unsafe { STRUCT.tag() };
assert_eq!(3, UNIT_TAG);
assert_eq!(2, TUPLE_TAG);
assert_eq!(1, STRUCT_TAG);
// Ensure `as` conversions are correct
assert_eq!(3, FieldlessEnum::Unit as u8);
assert_eq!(2, FieldlessEnum::Tuple() as u8);
assert_eq!(1, FieldlessEnum::Struct{} as u8);
}
// run-pass
#![allow(stable_features)] #![allow(stable_features)]
#![feature(core, core_intrinsics)] #![feature(arbitrary_enum_discriminant, core, core_intrinsics)]
extern crate core; extern crate core;
use core::intrinsics::discriminant_value; use core::intrinsics::discriminant_value;
...@@ -38,6 +39,17 @@ enum NullablePointer { ...@@ -38,6 +39,17 @@ enum NullablePointer {
static CONST : u32 = 0xBEEF; static CONST : u32 = 0xBEEF;
#[allow(dead_code)]
#[repr(isize)]
enum Mixed {
Unit = 3,
Tuple(u16) = 2,
Struct {
a: u8,
b: u16,
} = 1,
}
pub fn main() { pub fn main() {
unsafe { unsafe {
...@@ -64,5 +76,9 @@ pub fn main() { ...@@ -64,5 +76,9 @@ pub fn main() {
assert_eq!(discriminant_value(&10), 0); assert_eq!(discriminant_value(&10), 0);
assert_eq!(discriminant_value(&"test"), 0); assert_eq!(discriminant_value(&"test"), 0);
assert_eq!(3, discriminant_value(&Mixed::Unit));
assert_eq!(2, discriminant_value(&Mixed::Tuple(5)));
assert_eq!(1, discriminant_value(&Mixed::Struct{a: 7, b: 11}));
} }
} }
#![crate_type="lib"]
enum Enum {
Unit = 1,
//~^ ERROR custom discriminant values are not allowed in enums with tuple or struct variants
Tuple() = 2,
//~^ ERROR discriminants on non-unit variants are experimental
Struct{} = 3,
//~^ ERROR discriminants on non-unit variants are experimental
}
error[E0658]: discriminants on non-unit variants are experimental
--> $DIR/feature-gate-arbitrary_enum_discriminant.rs:6:13
|
LL | Tuple() = 2,
| ^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/60553
= help: add #![feature(arbitrary_enum_discriminant)] to the crate attributes to enable
error[E0658]: discriminants on non-unit variants are experimental
--> $DIR/feature-gate-arbitrary_enum_discriminant.rs:8:14
|
LL | Struct{} = 3,
| ^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/60553
= help: add #![feature(arbitrary_enum_discriminant)] to the crate attributes to enable
error[E0658]: custom discriminant values are not allowed in enums with tuple or struct variants
--> $DIR/feature-gate-arbitrary_enum_discriminant.rs:4:10
|
LL | Unit = 1,
| ^ disallowed custom discriminant
LL |
LL | Tuple() = 2,
| ----------- tuple variant defined here
LL |
LL | Struct{} = 3,
| ------------ struct variant defined here
|
= note: for more information, see https://github.com/rust-lang/rust/issues/60553
= help: add #![feature(arbitrary_enum_discriminant)] to the crate attributes to enable
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0658`.
enum X { enum X {
A = 3, A = 3,
//~^ ERROR custom discriminant values are not allowed in enums with fields //~^ ERROR custom discriminant values are not allowed in enums with tuple or struct variants
B(usize) B(usize)
} }
......
error: custom discriminant values are not allowed in enums with fields error[E0658]: custom discriminant values are not allowed in enums with tuple or struct variants
--> $DIR/issue-17383.rs:2:9 --> $DIR/issue-17383.rs:2:9
| |
LL | A = 3, LL | A = 3,
| ^ invalid custom discriminant | ^ disallowed custom discriminant
LL | LL |
LL | B(usize) LL | B(usize)
| -------- variant with a field defined here | -------- tuple variant defined here
|
= note: for more information, see https://github.com/rust-lang/rust/issues/60553
= help: add #![feature(arbitrary_enum_discriminant)] to the crate attributes to enable
error: aborting due to previous error error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.
enum Color { enum Color {
Red = 0xff0000, Red = 0xff0000,
//~^ ERROR custom discriminant values are not allowed in enums with fields //~^ ERROR custom discriminant values are not allowed in enums with tuple or struct variants
Green = 0x00ff00, Green = 0x00ff00,
Blue = 0x0000ff, Blue = 0x0000ff,
Black = 0x000000, Black = 0x000000,
......
error: custom discriminant values are not allowed in enums with fields error[E0658]: custom discriminant values are not allowed in enums with tuple or struct variants
--> $DIR/tag-variant-disr-non-nullary.rs:2:11 --> $DIR/tag-variant-disr-non-nullary.rs:2:11
| |
LL | Red = 0xff0000, LL | Red = 0xff0000,
| ^^^^^^^^ invalid custom discriminant | ^^^^^^^^ disallowed custom discriminant
LL | LL |
LL | Green = 0x00ff00, LL | Green = 0x00ff00,
| ^^^^^^^^ invalid custom discriminant | ^^^^^^^^ disallowed custom discriminant
LL | Blue = 0x0000ff, LL | Blue = 0x0000ff,
| ^^^^^^^^ invalid custom discriminant | ^^^^^^^^ disallowed custom discriminant
LL | Black = 0x000000, LL | Black = 0x000000,
| ^^^^^^^^ invalid custom discriminant | ^^^^^^^^ disallowed custom discriminant
LL | White = 0xffffff, LL | White = 0xffffff,
| ^^^^^^^^ invalid custom discriminant | ^^^^^^^^ disallowed custom discriminant
LL | Other(usize), LL | Other(usize),
| ------------ variant with a field defined here | ------------ tuple variant defined here
LL | Other2(usize, usize), LL | Other2(usize, usize),
| -------------------- variant with fields defined here | -------------------- tuple variant defined here
|
= note: for more information, see https://github.com/rust-lang/rust/issues/60553
= help: add #![feature(arbitrary_enum_discriminant)] to the crate attributes to enable
error: aborting due to previous error error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册