Skip to content

Commit

Permalink
Implement arbitrary_enum_discriminant
Browse files Browse the repository at this point in the history
  • Loading branch information
jswrenn committed Jun 21, 2019
1 parent 38cd948 commit ac98342
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# `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());
```
19 changes: 19 additions & 0 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,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
Expand Down
32 changes: 32 additions & 0 deletions src/librustc_typeck/error_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4733,6 +4733,38 @@ if there are multiple variants, it is not clear how the enum should be
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! {
Expand Down
37 changes: 32 additions & 5 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ use crate::source_map::Spanned;
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;

Expand Down Expand Up @@ -566,6 +567,9 @@ declare_features! (
// #[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
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -1705,20 +1709,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,
Expand Down Expand Up @@ -2033,6 +2037,29 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
}
}

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,
Expand Down
48 changes: 29 additions & 19 deletions src/libsyntax/parse/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::ast::{
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;
Expand Down Expand Up @@ -539,37 +539,47 @@ impl<'a> Parser<'a> {
}

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 {
VariantData::Tuple(..) | VariantData::Struct(..) => true,
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();
Expand Down
31 changes: 14 additions & 17 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6963,36 +6963,34 @@ impl<'a> Parser<'a> {
/// 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,
Expand Down Expand Up @@ -7020,7 +7018,6 @@ impl<'a> Parser<'a> {
}
}
self.expect(&token::CloseDelim(token::Brace))?;
self.maybe_report_invalid_custom_discriminants(any_disr, &variants);

Ok(ast::EnumDef { variants })
}
Expand Down
1 change: 1 addition & 0 deletions src/libsyntax_pos/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ symbols! {
always,
and,
any,
arbitrary_enum_discriminant,
arbitrary_self_types,
Arguments,
ArgumentV1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![crate_type="lib"]
#![feature(arbitrary_enum_discriminant)]

enum Enum {
//~^ ERROR `#[repr(inttype)]` must be specified
Unit = 1,
Tuple() = 2,
Struct{} = 3,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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`.
Loading

0 comments on commit ac98342

Please sign in to comment.