Skip to content

Commit 303f77e

Browse files
committed
Auto merge of #60732 - jswrenn:arbitrary_enum_discriminant, r=pnkfelix
Implement arbitrary_enum_discriminant Implements RFC rust-lang/rfcs#2363 (tracking issue #60553).
2 parents 40ab9d2 + ac98342 commit 303f77e

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
@@ -1935,6 +1935,25 @@ pub fn check_enum<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, vs: &'tcx [hir::Variant], i
19351935
}
19361936
}
19371937

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

@@ -569,6 +570,9 @@ declare_features! (
569570
// #[repr(transparent)] on unions.
570571
(active, transparent_unions, "1.37.0", Some(60405), None),
571572

573+
// Allows explicit discriminants on non-unit enum variants.
574+
(active, arbitrary_enum_discriminant, "1.37.0", Some(60553), None),
575+
572576
// -------------------------------------------------------------------------
573577
// feature-group-end: actual feature gates
574578
// -------------------------------------------------------------------------
@@ -1709,20 +1713,20 @@ pub fn emit_feature_err(
17091713
feature_err(sess, feature, span, issue, explain).emit();
17101714
}
17111715

1712-
pub fn feature_err<'a>(
1716+
pub fn feature_err<'a, S: Into<MultiSpan>>(
17131717
sess: &'a ParseSess,
17141718
feature: Symbol,
1715-
span: Span,
1719+
span: S,
17161720
issue: GateIssue,
17171721
explain: &str,
17181722
) -> DiagnosticBuilder<'a> {
17191723
leveled_feature_err(sess, feature, span, issue, explain, GateStrength::Hard)
17201724
}
17211725

1722-
fn leveled_feature_err<'a>(
1726+
fn leveled_feature_err<'a, S: Into<MultiSpan>>(
17231727
sess: &'a ParseSess,
17241728
feature: Symbol,
1725-
span: Span,
1729+
span: S,
17261730
issue: GateIssue,
17271731
explain: &str,
17281732
level: GateStrength,
@@ -2037,6 +2041,29 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
20372041
}
20382042
}
20392043

2044+
ast::ItemKind::Enum(ast::EnumDef{ref variants, ..}, ..) => {
2045+
for variant in variants {
2046+
match (&variant.node.data, &variant.node.disr_expr) {
2047+
(ast::VariantData::Unit(..), _) => {},
2048+
(_, Some(disr_expr)) =>
2049+
gate_feature_post!(
2050+
&self,
2051+
arbitrary_enum_discriminant,
2052+
disr_expr.value.span,
2053+
"discriminants on non-unit variants are experimental"),
2054+
_ => {},
2055+
}
2056+
}
2057+
2058+
let has_feature = self.context.features.arbitrary_enum_discriminant;
2059+
if !has_feature && !i.span.allows_unstable(sym::arbitrary_enum_discriminant) {
2060+
Parser::maybe_report_invalid_custom_discriminants(
2061+
self.context.parse_sess,
2062+
&variants,
2063+
);
2064+
}
2065+
}
2066+
20402067
ast::ItemKind::Impl(_, polarity, defaultness, _, _, _, _) => {
20412068
if polarity == ast::ImplPolarity::Negative {
20422069
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
@@ -6946,36 +6946,34 @@ impl<'a> Parser<'a> {
69466946
/// Parses the part of an enum declaration following the `{`.
69476947
fn parse_enum_def(&mut self, _generics: &ast::Generics) -> PResult<'a, EnumDef> {
69486948
let mut variants = Vec::new();
6949-
let mut any_disr = vec![];
69506949
while self.token != token::CloseDelim(token::Brace) {
69516950
let variant_attrs = self.parse_outer_attributes()?;
69526951
let vlo = self.token.span;
69536952

6954-
let struct_def;
6955-
let mut disr_expr = None;
69566953
self.eat_bad_pub();
69576954
let ident = self.parse_ident()?;
6958-
if self.check(&token::OpenDelim(token::Brace)) {
6955+
6956+
let struct_def = if self.check(&token::OpenDelim(token::Brace)) {
69596957
// Parse a struct variant.
69606958
let (fields, recovered) = self.parse_record_struct_body()?;
6961-
struct_def = VariantData::Struct(fields, recovered);
6959+
VariantData::Struct(fields, recovered)
69626960
} else if self.check(&token::OpenDelim(token::Paren)) {
6963-
struct_def = VariantData::Tuple(
6961+
VariantData::Tuple(
69646962
self.parse_tuple_struct_body()?,
69656963
ast::DUMMY_NODE_ID,
6966-
);
6967-
} else if self.eat(&token::Eq) {
6968-
disr_expr = Some(AnonConst {
6964+
)
6965+
} else {
6966+
VariantData::Unit(ast::DUMMY_NODE_ID)
6967+
};
6968+
6969+
let disr_expr = if self.eat(&token::Eq) {
6970+
Some(AnonConst {
69696971
id: ast::DUMMY_NODE_ID,
69706972
value: self.parse_expr()?,
6971-
});
6972-
if let Some(sp) = disr_expr.as_ref().map(|c| c.value.span) {
6973-
any_disr.push(sp);
6974-
}
6975-
struct_def = VariantData::Unit(ast::DUMMY_NODE_ID);
6973+
})
69766974
} else {
6977-
struct_def = VariantData::Unit(ast::DUMMY_NODE_ID);
6978-
}
6975+
None
6976+
};
69796977

69806978
let vr = ast::Variant_ {
69816979
ident,
@@ -7003,7 +7001,6 @@ impl<'a> Parser<'a> {
70037001
}
70047002
}
70057003
self.expect(&token::CloseDelim(token::Brace))?;
7006-
self.maybe_report_invalid_custom_discriminants(any_disr, &variants);
70077004

70087005
Ok(ast::EnumDef { variants })
70097006
}

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)