Skip to content

Commit ac98342

Browse files
committed
Implement arbitrary_enum_discriminant
1 parent 38cd948 commit ac98342

18 files changed

+328
-55
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# `arbitrary_enum_discriminant`
2+
3+
The tracking issue for this feature is: [#60553]
4+
5+
[#60553]: https://github.com/rust-lang/rust/issues/60553
6+
7+
------------------------
8+
9+
The `arbitrary_enum_discriminant` feature permits tuple-like and
10+
struct-like enum variants with `#[repr(<int-type>)]` to have explicit discriminants.
11+
12+
## Examples
13+
14+
```rust
15+
#![feature(arbitrary_enum_discriminant)]
16+
17+
#[allow(dead_code)]
18+
#[repr(u8)]
19+
enum Enum {
20+
Unit = 3,
21+
Tuple(u16) = 2,
22+
Struct {
23+
a: u8,
24+
b: u16,
25+
} = 1,
26+
}
27+
28+
impl Enum {
29+
fn tag(&self) -> u8 {
30+
unsafe { *(self as *const Self as *const u8) }
31+
}
32+
}
33+
34+
assert_eq!(3, Enum::Unit.tag());
35+
assert_eq!(2, Enum::Tuple(5).tag());
36+
assert_eq!(1, Enum::Struct{a: 7, b: 11}.tag());
37+
```

src/librustc_typeck/check/mod.rs

+19
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,25 @@ pub fn check_enum<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, vs: &'tcx [hir::Variant], i
19361936
}
19371937
}
19381938

1939+
if tcx.adt_def(def_id).repr.int.is_none() && tcx.features().arbitrary_enum_discriminant {
1940+
let is_unit =
1941+
|var: &hir::Variant| match var.node.data {
1942+
hir::VariantData::Unit(..) => true,
1943+
_ => false
1944+
};
1945+
1946+
let has_disr = |var: &hir::Variant| var.node.disr_expr.is_some();
1947+
let has_non_units = vs.iter().any(|var| !is_unit(var));
1948+
let disr_units = vs.iter().any(|var| is_unit(&var) && has_disr(&var));
1949+
let disr_non_unit = vs.iter().any(|var| !is_unit(&var) && has_disr(&var));
1950+
1951+
if disr_non_unit || (disr_units && has_non_units) {
1952+
let mut err = struct_span_err!(tcx.sess, sp, E0732,
1953+
"`#[repr(inttype)]` must be specified");
1954+
err.emit();
1955+
}
1956+
}
1957+
19391958
let mut disr_vals: Vec<Discr<'tcx>> = Vec::with_capacity(vs.len());
19401959
for ((_, discr), v) in def.discriminants(tcx).zip(vs) {
19411960
// Check for duplicate discriminant values

src/librustc_typeck/error_codes.rs

+32
Original file line numberDiff line numberDiff line change
@@ -4733,6 +4733,38 @@ if there are multiple variants, it is not clear how the enum should be
47334733
represented.
47344734
"##,
47354735

4736+
E0732: r##"
4737+
An `enum` with a discriminant must specify a `#[repr(inttype)]`.
4738+
4739+
A `#[repr(inttype)]` must be provided on an `enum` if it has a non-unit
4740+
variant with a discriminant, or where there are both unit variants with
4741+
discriminants and non-unit variants. This restriction ensures that there
4742+
is a well-defined way to extract a variant's discriminant from a value;
4743+
for instance:
4744+
4745+
```
4746+
#![feature(arbitrary_enum_discriminant)]
4747+
4748+
#[repr(u8)]
4749+
enum Enum {
4750+
Unit = 3,
4751+
Tuple(u16) = 2,
4752+
Struct {
4753+
a: u8,
4754+
b: u16,
4755+
} = 1,
4756+
}
4757+
4758+
fn discriminant(v : &Enum) -> u8 {
4759+
unsafe { *(v as *const Enum as *const u8) }
4760+
}
4761+
4762+
assert_eq!(3, discriminant(&Enum::Unit));
4763+
assert_eq!(2, discriminant(&Enum::Tuple(5)));
4764+
assert_eq!(1, discriminant(&Enum::Struct{a: 7, b: 11}));
4765+
```
4766+
"##,
4767+
47364768
}
47374769

47384770
register_diagnostics! {

src/libsyntax/feature_gate.rs

+32-5
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ use crate::source_map::Spanned;
2525
use crate::edition::{ALL_EDITIONS, Edition};
2626
use crate::visit::{self, FnKind, Visitor};
2727
use crate::parse::{token, ParseSess};
28+
use crate::parse::parser::Parser;
2829
use crate::symbol::{Symbol, sym};
2930
use crate::tokenstream::TokenTree;
3031

3132
use errors::{Applicability, DiagnosticBuilder, Handler};
3233
use rustc_data_structures::fx::FxHashMap;
3334
use rustc_target::spec::abi::Abi;
34-
use syntax_pos::{Span, DUMMY_SP};
35+
use syntax_pos::{Span, DUMMY_SP, MultiSpan};
3536
use log::debug;
3637
use lazy_static::lazy_static;
3738

@@ -566,6 +567,9 @@ declare_features! (
566567
// #[repr(transparent)] on unions.
567568
(active, transparent_unions, "1.37.0", Some(60405), None),
568569

570+
// Allows explicit discriminants on non-unit enum variants.
571+
(active, arbitrary_enum_discriminant, "1.37.0", Some(60553), None),
572+
569573
// -------------------------------------------------------------------------
570574
// feature-group-end: actual feature gates
571575
// -------------------------------------------------------------------------
@@ -1705,20 +1709,20 @@ pub fn emit_feature_err(
17051709
feature_err(sess, feature, span, issue, explain).emit();
17061710
}
17071711

1708-
pub fn feature_err<'a>(
1712+
pub fn feature_err<'a, S: Into<MultiSpan>>(
17091713
sess: &'a ParseSess,
17101714
feature: Symbol,
1711-
span: Span,
1715+
span: S,
17121716
issue: GateIssue,
17131717
explain: &str,
17141718
) -> DiagnosticBuilder<'a> {
17151719
leveled_feature_err(sess, feature, span, issue, explain, GateStrength::Hard)
17161720
}
17171721

1718-
fn leveled_feature_err<'a>(
1722+
fn leveled_feature_err<'a, S: Into<MultiSpan>>(
17191723
sess: &'a ParseSess,
17201724
feature: Symbol,
1721-
span: Span,
1725+
span: S,
17221726
issue: GateIssue,
17231727
explain: &str,
17241728
level: GateStrength,
@@ -2033,6 +2037,29 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
20332037
}
20342038
}
20352039

2040+
ast::ItemKind::Enum(ast::EnumDef{ref variants, ..}, ..) => {
2041+
for variant in variants {
2042+
match (&variant.node.data, &variant.node.disr_expr) {
2043+
(ast::VariantData::Unit(..), _) => {},
2044+
(_, Some(disr_expr)) =>
2045+
gate_feature_post!(
2046+
&self,
2047+
arbitrary_enum_discriminant,
2048+
disr_expr.value.span,
2049+
"discriminants on non-unit variants are experimental"),
2050+
_ => {},
2051+
}
2052+
}
2053+
2054+
let has_feature = self.context.features.arbitrary_enum_discriminant;
2055+
if !has_feature && !i.span.allows_unstable(sym::arbitrary_enum_discriminant) {
2056+
Parser::maybe_report_invalid_custom_discriminants(
2057+
self.context.parse_sess,
2058+
&variants,
2059+
);
2060+
}
2061+
}
2062+
20362063
ast::ItemKind::Impl(_, polarity, defaultness, _, _, _, _) => {
20372064
if polarity == ast::ImplPolarity::Negative {
20382065
gate_feature_post!(&self, optin_builtin_traits,

src/libsyntax/parse/diagnostics.rs

+29-19
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::ast::{
22
self, Arg, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item, ItemKind,
33
Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind, VariantData,
44
};
5-
use crate::parse::{SeqSep, PResult, Parser};
5+
use crate::parse::{SeqSep, PResult, Parser, ParseSess};
66
use crate::parse::parser::{BlockMode, PathStyle, SemiColonMode, TokenType, TokenExpectType};
77
use crate::parse::token::{self, TokenKind};
88
use crate::print::pprust;
@@ -539,37 +539,47 @@ impl<'a> Parser<'a> {
539539
}
540540

541541
crate fn maybe_report_invalid_custom_discriminants(
542-
&mut self,
543-
discriminant_spans: Vec<Span>,
542+
sess: &ParseSess,
544543
variants: &[Spanned<ast::Variant_>],
545544
) {
546545
let has_fields = variants.iter().any(|variant| match variant.node.data {
547546
VariantData::Tuple(..) | VariantData::Struct(..) => true,
548547
VariantData::Unit(..) => false,
549548
});
550549

550+
let discriminant_spans = variants.iter().filter(|variant| match variant.node.data {
551+
VariantData::Tuple(..) | VariantData::Struct(..) => false,
552+
VariantData::Unit(..) => true,
553+
})
554+
.filter_map(|variant| variant.node.disr_expr.as_ref().map(|c| c.value.span))
555+
.collect::<Vec<_>>();
556+
551557
if !discriminant_spans.is_empty() && has_fields {
552-
let mut err = self.struct_span_err(
558+
let mut err = crate::feature_gate::feature_err(
559+
sess,
560+
sym::arbitrary_enum_discriminant,
553561
discriminant_spans.clone(),
554-
"custom discriminant values are not allowed in enums with fields",
562+
crate::feature_gate::GateIssue::Language,
563+
"custom discriminant values are not allowed in enums with tuple or struct variants",
555564
);
556565
for sp in discriminant_spans {
557-
err.span_label(sp, "invalid custom discriminant");
566+
err.span_label(sp, "disallowed custom discriminant");
558567
}
559568
for variant in variants.iter() {
560-
if let VariantData::Struct(fields, ..) | VariantData::Tuple(fields, ..) =
561-
&variant.node.data
562-
{
563-
let fields = if fields.len() > 1 {
564-
"fields"
565-
} else {
566-
"a field"
567-
};
568-
err.span_label(
569-
variant.span,
570-
&format!("variant with {fields} defined here", fields = fields),
571-
);
572-
569+
match &variant.node.data {
570+
VariantData::Struct(..) => {
571+
err.span_label(
572+
variant.span,
573+
"struct variant defined here",
574+
);
575+
}
576+
VariantData::Tuple(..) => {
577+
err.span_label(
578+
variant.span,
579+
"tuple variant defined here",
580+
);
581+
}
582+
VariantData::Unit(..) => {}
573583
}
574584
}
575585
err.emit();

src/libsyntax/parse/parser.rs

+14-17
Original file line numberDiff line numberDiff line change
@@ -6963,36 +6963,34 @@ impl<'a> Parser<'a> {
69636963
/// Parses the part of an enum declaration following the `{`.
69646964
fn parse_enum_def(&mut self, _generics: &ast::Generics) -> PResult<'a, EnumDef> {
69656965
let mut variants = Vec::new();
6966-
let mut any_disr = vec![];
69676966
while self.token != token::CloseDelim(token::Brace) {
69686967
let variant_attrs = self.parse_outer_attributes()?;
69696968
let vlo = self.token.span;
69706969

6971-
let struct_def;
6972-
let mut disr_expr = None;
69736970
self.eat_bad_pub();
69746971
let ident = self.parse_ident()?;
6975-
if self.check(&token::OpenDelim(token::Brace)) {
6972+
6973+
let struct_def = if self.check(&token::OpenDelim(token::Brace)) {
69766974
// Parse a struct variant.
69776975
let (fields, recovered) = self.parse_record_struct_body()?;
6978-
struct_def = VariantData::Struct(fields, recovered);
6976+
VariantData::Struct(fields, recovered)
69796977
} else if self.check(&token::OpenDelim(token::Paren)) {
6980-
struct_def = VariantData::Tuple(
6978+
VariantData::Tuple(
69816979
self.parse_tuple_struct_body()?,
69826980
ast::DUMMY_NODE_ID,
6983-
);
6984-
} else if self.eat(&token::Eq) {
6985-
disr_expr = Some(AnonConst {
6981+
)
6982+
} else {
6983+
VariantData::Unit(ast::DUMMY_NODE_ID)
6984+
};
6985+
6986+
let disr_expr = if self.eat(&token::Eq) {
6987+
Some(AnonConst {
69866988
id: ast::DUMMY_NODE_ID,
69876989
value: self.parse_expr()?,
6988-
});
6989-
if let Some(sp) = disr_expr.as_ref().map(|c| c.value.span) {
6990-
any_disr.push(sp);
6991-
}
6992-
struct_def = VariantData::Unit(ast::DUMMY_NODE_ID);
6990+
})
69936991
} else {
6994-
struct_def = VariantData::Unit(ast::DUMMY_NODE_ID);
6995-
}
6992+
None
6993+
};
69966994

69976995
let vr = ast::Variant_ {
69986996
ident,
@@ -7020,7 +7018,6 @@ impl<'a> Parser<'a> {
70207018
}
70217019
}
70227020
self.expect(&token::CloseDelim(token::Brace))?;
7023-
self.maybe_report_invalid_custom_discriminants(any_disr, &variants);
70247021

70257022
Ok(ast::EnumDef { variants })
70267023
}

src/libsyntax_pos/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ symbols! {
135135
always,
136136
and,
137137
any,
138+
arbitrary_enum_discriminant,
138139
arbitrary_self_types,
139140
Arguments,
140141
ArgumentV1,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![crate_type="lib"]
2+
#![feature(arbitrary_enum_discriminant)]
3+
4+
enum Enum {
5+
//~^ ERROR `#[repr(inttype)]` must be specified
6+
Unit = 1,
7+
Tuple() = 2,
8+
Struct{} = 3,
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0732]: `#[repr(inttype)]` must be specified
2+
--> $DIR/arbitrary_enum_discriminant-no-repr.rs:4:1
3+
|
4+
LL | / enum Enum {
5+
LL | |
6+
LL | | Unit = 1,
7+
LL | | Tuple() = 2,
8+
LL | | Struct{} = 3,
9+
LL | | }
10+
| |_^
11+
12+
error: aborting due to previous error
13+
14+
For more information about this error, try `rustc --explain E0732`.

0 commit comments

Comments
 (0)