提交 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
}
}
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());
for ((_, discr), v) in def.discriminants(tcx).zip(vs) {
// Check for duplicate discriminant values
......
......@@ -4733,6 +4733,38 @@ enum Status { // error: transparent enum needs exactly one variant, but has 2
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! {
......
......@@ -25,13 +25,14 @@
use crate::edition::{ALL_EDITIONS, Edition};
use crate::visit::{self, FnKind, Visitor};
use crate::parse::{token, ParseSess};
use crate::parse::parser::Parser;
use crate::symbol::{Symbol, sym};
use crate::tokenstream::TokenTree;
use errors::{Applicability, DiagnosticBuilder, Handler};
use rustc_data_structures::fx::FxHashMap;
use rustc_target::spec::abi::Abi;
use syntax_pos::{Span, DUMMY_SP};
use syntax_pos::{Span, DUMMY_SP, MultiSpan};
use log::debug;
use lazy_static::lazy_static;
......@@ -569,6 +570,9 @@ pub fn walk_feature_fields<F>(&self, mut f: F)
// #[repr(transparent)] on unions.
(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
// -------------------------------------------------------------------------
......@@ -1709,20 +1713,20 @@ pub fn emit_feature_err(
feature_err(sess, feature, span, issue, explain).emit();
}
pub fn feature_err<'a>(
pub fn feature_err<'a, S: Into<MultiSpan>>(
sess: &'a ParseSess,
feature: Symbol,
span: Span,
span: S,
issue: GateIssue,
explain: &str,
) -> DiagnosticBuilder<'a> {
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,
feature: Symbol,
span: Span,
span: S,
issue: GateIssue,
explain: &str,
level: GateStrength,
......@@ -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, _, _, _, _) => {
if polarity == ast::ImplPolarity::Negative {
gate_feature_post!(&self, optin_builtin_traits,
......
......@@ -2,7 +2,7 @@
self, Arg, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item, ItemKind,
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::token::{self, TokenKind};
use crate::print::pprust;
......@@ -539,8 +539,7 @@ fn tokens_to_string(tokens: &[TokenType]) -> String {
}
crate fn maybe_report_invalid_custom_discriminants(
&mut self,
discriminant_spans: Vec<Span>,
sess: &ParseSess,
variants: &[Spanned<ast::Variant_>],
) {
let has_fields = variants.iter().any(|variant| match variant.node.data {
......@@ -548,28 +547,39 @@ fn tokens_to_string(tokens: &[TokenType]) -> String {
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 {
let mut err = self.struct_span_err(
let mut err = crate::feature_gate::feature_err(
sess,
sym::arbitrary_enum_discriminant,
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 {
err.span_label(sp, "invalid custom discriminant");
err.span_label(sp, "disallowed custom discriminant");
}
for variant in variants.iter() {
if let VariantData::Struct(fields, ..) | VariantData::Tuple(fields, ..) =
&variant.node.data
{
let fields = if fields.len() > 1 {
"fields"
} else {
"a field"
};
err.span_label(
variant.span,
&format!("variant with {fields} defined here", fields = fields),
);
match &variant.node.data {
VariantData::Struct(..) => {
err.span_label(
variant.span,
"struct variant defined here",
);
}
VariantData::Tuple(..) => {
err.span_label(
variant.span,
"tuple variant defined here",
);
}
VariantData::Unit(..) => {}
}
}
err.emit();
......
......@@ -6946,36 +6946,34 @@ fn parse_existential_or_alias(
/// Parses the part of an enum declaration following the `{`.
fn parse_enum_def(&mut self, _generics: &ast::Generics) -> PResult<'a, EnumDef> {
let mut variants = Vec::new();
let mut any_disr = vec![];
while self.token != token::CloseDelim(token::Brace) {
let variant_attrs = self.parse_outer_attributes()?;
let vlo = self.token.span;
let struct_def;
let mut disr_expr = None;
self.eat_bad_pub();
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.
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)) {
struct_def = VariantData::Tuple(
VariantData::Tuple(
self.parse_tuple_struct_body()?,
ast::DUMMY_NODE_ID,
);
} else if self.eat(&token::Eq) {
disr_expr = Some(AnonConst {
)
} else {
VariantData::Unit(ast::DUMMY_NODE_ID)
};
let disr_expr = if self.eat(&token::Eq) {
Some(AnonConst {
id: ast::DUMMY_NODE_ID,
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 {
struct_def = VariantData::Unit(ast::DUMMY_NODE_ID);
}
None
};
let vr = ast::Variant_ {
ident,
......@@ -7003,7 +7001,6 @@ fn parse_enum_def(&mut self, _generics: &ast::Generics) -> PResult<'a, EnumDef>
}
}
self.expect(&token::CloseDelim(token::Brace))?;
self.maybe_report_invalid_custom_discriminants(any_disr, &variants);
Ok(ast::EnumDef { variants })
}
......
......@@ -135,6 +135,7 @@
always,
and,
any,
arbitrary_enum_discriminant,
arbitrary_self_types,
Arguments,
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)]
#![feature(core, core_intrinsics)]
#![feature(arbitrary_enum_discriminant, core, core_intrinsics)]
extern crate core;
use core::intrinsics::discriminant_value;
......@@ -38,6 +39,17 @@ enum NullablePointer {
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() {
unsafe {
......@@ -64,5 +76,9 @@ pub fn main() {
assert_eq!(discriminant_value(&10), 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 {
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)
}
......
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
|
LL | A = 3,
| ^ invalid custom discriminant
| ^ disallowed custom discriminant
LL |
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
For more information about this error, try `rustc --explain E0658`.
enum Color {
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,
Blue = 0x0000ff,
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
|
LL | Red = 0xff0000,
| ^^^^^^^^ invalid custom discriminant
| ^^^^^^^^ disallowed custom discriminant
LL |
LL | Green = 0x00ff00,
| ^^^^^^^^ invalid custom discriminant
| ^^^^^^^^ disallowed custom discriminant
LL | Blue = 0x0000ff,
| ^^^^^^^^ invalid custom discriminant
| ^^^^^^^^ disallowed custom discriminant
LL | Black = 0x000000,
| ^^^^^^^^ invalid custom discriminant
| ^^^^^^^^ disallowed custom discriminant
LL | White = 0xffffff,
| ^^^^^^^^ invalid custom discriminant
| ^^^^^^^^ disallowed custom discriminant
LL | Other(usize),
| ------------ variant with a field defined here
| ------------ tuple variant defined here
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
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.
先完成此消息的编辑!
想要评论请 注册