Skip to content

Commit

Permalink
Auto merge of #45394 - davidtwco:rfc-2008, r=petrochenkov
Browse files Browse the repository at this point in the history
RFC 2008: Future-proofing enums/structs with #[non_exhaustive] attribute

This work-in-progress pull request contains my changes to implement [RFC 2008](rust-lang/rfcs#2008). The related tracking issue is #44109.

As of writing, enum-related functionality is not included and there are some issues related to tuple/unit structs. Enum related tests are currently ignored.

WIP PR requested by @nikomatsakis [in Gitter](https://gitter.im/rust-impl-period/WG-compiler-middle?at=59e90e6297cedeb0482ade3e).
  • Loading branch information
bors committed Nov 4, 2017
2 parents 98e4b68 + 86c62d0 commit d762b1d
Show file tree
Hide file tree
Showing 28 changed files with 769 additions and 9 deletions.
75 changes: 75 additions & 0 deletions src/doc/unstable-book/src/language-features/non-exhaustive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# `non_exhaustive`

The tracking issue for this feature is: [#44109]

[#44109]: https://github.com/rust-lang/rust/issues/44109

------------------------

The `non_exhaustive` gate allows you to use the `#[non_exhaustive]` attribute
on structs and enums. When applied within a crate, users of the crate will need
to use the `_` pattern when matching enums and use the `..` pattern when
matching structs. Structs marked as `non_exhaustive` will not be able to be
created normally outside of the defining crate. This is demonstrated below:

```rust,ignore (pseudo-Rust)
use std::error::Error as StdError;
#[non_exhaustive]
pub enum Error {
Message(String),
Other,
}
impl StdError for Error {
fn description(&self) -> &str {
// This will not error, despite being marked as non_exhaustive, as this
// enum is defined within the current crate, it can be matched
// exhaustively.
match *self {
Message(ref s) => s,
Other => "other or unknown error",
}
}
}
```

```rust,ignore (pseudo-Rust)
use mycrate::Error;
// This will not error as the non_exhaustive Error enum has been matched with
// a wildcard.
match error {
Message(ref s) => ...,
Other => ...,
_ => ...,
}
```

```rust,ignore (pseudo-Rust)
#[non_exhaustive]
pub struct Config {
pub window_width: u16,
pub window_height: u16,
}
// We can create structs as normal within the defining crate when marked as
// non_exhaustive.
let config = Config { window_width: 640, window_height: 480 };
// We can match structs exhaustively when within the defining crate.
if let Ok(Config { window_width, window_height }) = load_config() {
// ...
}
```

```rust,ignore (pseudo-Rust)
use mycrate::Config;
// We cannot create a struct like normal if it has been marked as
// non_exhaustive.
let config = Config { window_width: 640, window_height: 480 };
// By adding the `..` we can match the config as below outside of the crate
// when marked non_exhaustive.
let &Config { window_width, window_height, .. } = config;
```

14 changes: 14 additions & 0 deletions src/librustc/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,12 @@ bitflags! {
const IS_FUNDAMENTAL = 1 << 2;
const IS_UNION = 1 << 3;
const IS_BOX = 1 << 4;
/// Indicates whether this abstract data type will be expanded on in future (new
/// fields/variants) and as such, whether downstream crates must match exhaustively on the
/// fields/variants of this data type.
///
/// See RFC 2008 (https://github.com/rust-lang/rfcs/pull/2008).
const IS_NON_EXHAUSTIVE = 1 << 5;
}
}

Expand Down Expand Up @@ -1526,6 +1532,9 @@ impl<'a, 'gcx, 'tcx> AdtDef {
if Some(did) == tcx.lang_items().owned_box() {
flags = flags | AdtFlags::IS_BOX;
}
if tcx.has_attr(did, "non_exhaustive") {
flags = flags | AdtFlags::IS_NON_EXHAUSTIVE;
}
match kind {
AdtKind::Enum => flags = flags | AdtFlags::IS_ENUM,
AdtKind::Union => flags = flags | AdtFlags::IS_UNION,
Expand Down Expand Up @@ -1554,6 +1563,11 @@ impl<'a, 'gcx, 'tcx> AdtDef {
self.flags.intersects(AdtFlags::IS_ENUM)
}

#[inline]
pub fn is_non_exhaustive(&self) -> bool {
self.flags.intersects(AdtFlags::IS_NON_EXHAUSTIVE)
}

/// Returns the kind of the ADT - Struct or Enum.
#[inline]
pub fn adt_kind(&self) -> AdtKind {
Expand Down
73 changes: 69 additions & 4 deletions src/librustc_const_eval/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ impl<'a, 'tcx> MatchCheckCtxt<'a, 'tcx> {
}
}

fn is_non_exhaustive_enum(&self, ty: Ty<'tcx>) -> bool {
match ty.sty {
ty::TyAdt(adt_def, ..) => adt_def.is_enum() && adt_def.is_non_exhaustive(),
_ => false,
}
}

fn is_local(&self, ty: Ty<'tcx>) -> bool {
match ty.sty {
ty::TyAdt(adt_def, ..) => adt_def.did.is_local(),
_ => false,
}
}

fn is_variant_uninhabited(&self,
variant: &'tcx ty::VariantDef,
substs: &'tcx ty::subst::Substs<'tcx>)
Expand Down Expand Up @@ -628,9 +642,16 @@ pub fn is_useful<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,

let is_privately_empty =
all_ctors.is_empty() && !cx.is_uninhabited(pcx.ty);
debug!("missing_ctors={:?} is_privately_empty={:?}", missing_ctors,
is_privately_empty);
if missing_ctors.is_empty() && !is_privately_empty {
let is_declared_nonexhaustive =
cx.is_non_exhaustive_enum(pcx.ty) && !cx.is_local(pcx.ty);
debug!("missing_ctors={:?} is_privately_empty={:?} is_declared_nonexhaustive={:?}",
missing_ctors, is_privately_empty, is_declared_nonexhaustive);

// For privately empty and non-exhaustive enums, we work as if there were an "extra"
// `_` constructor for the type, so we can never match over all constructors.
let is_non_exhaustive = is_privately_empty || is_declared_nonexhaustive;

if missing_ctors.is_empty() && !is_non_exhaustive {
all_ctors.into_iter().map(|c| {
is_useful_specialized(cx, matrix, v, c.clone(), pcx.ty, witness)
}).find(|result| result.is_useful()).unwrap_or(NotUseful)
Expand All @@ -645,7 +666,51 @@ pub fn is_useful<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
match is_useful(cx, &matrix, &v[1..], witness) {
UsefulWithWitness(pats) => {
let cx = &*cx;
let new_witnesses = if used_ctors.is_empty() {
// In this case, there's at least one "free"
// constructor that is only matched against by
// wildcard patterns.
//
// There are 2 ways we can report a witness here.
// Commonly, we can report all the "free"
// constructors as witnesses, e.g. if we have:
//
// ```
// enum Direction { N, S, E, W }
// let Direction::N = ...;
// ```
//
// we can report 3 witnesses: `S`, `E`, and `W`.
//
// However, there are 2 cases where we don't want
// to do this and instead report a single `_` witness:
//
// 1) If the user is matching against a non-exhaustive
// enum, there is no point in enumerating all possible
// variants, because the user can't actually match
// against them himself, e.g. in an example like:
// ```
// let err: io::ErrorKind = ...;
// match err {
// io::ErrorKind::NotFound => {},
// }
// ```
// we don't want to show every possible IO error,
// but instead have `_` as the witness (this is
// actually *required* if the user specified *all*
// IO errors, but is probably what we want in every
// case).
//
// 2) If the user didn't actually specify a constructor
// in this arm, e.g. in
// ```
// let x: (Direction, Direction, bool) = ...;
// let (_, _, false) = x;
// ```
// we don't want to show all 16 possible witnesses
// `(<direction-1>, <direction-2>, true)` - we are
// satisfied with `(_, _, true)`. In this case,
// `used_ctors` is empty.
let new_witnesses = if is_non_exhaustive || used_ctors.is_empty() {
// All constructors are unused. Add wild patterns
// rather than each individual constructor
pats.into_iter().map(|mut witness| {
Expand Down
9 changes: 8 additions & 1 deletion src/librustc_metadata/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,8 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
fn encode_struct_ctor(&mut self, (adt_def_id, def_id): (DefId, DefId)) -> Entry<'tcx> {
debug!("IsolatedEncoder::encode_struct_ctor({:?})", def_id);
let tcx = self.tcx;
let variant = tcx.adt_def(adt_def_id).struct_variant();
let adt_def = tcx.adt_def(adt_def_id);
let variant = adt_def.struct_variant();

let data = VariantData {
ctor_kind: variant.ctor_kind,
Expand All @@ -606,6 +607,12 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
}
}

// If the structure is marked as non_exhaustive then lower the visibility
// to within the crate.
if adt_def.is_non_exhaustive() && ctor_vis == ty::Visibility::Public {
ctor_vis = ty::Visibility::Restricted(DefId::local(CRATE_DEF_INDEX));
}

let repr_options = get_repr_options(&tcx, adt_def_id);

Entry {
Expand Down
10 changes: 10 additions & 0 deletions src/librustc_passes/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ impl<'a> AstValidator<'a> {
}
}

fn invalid_non_exhaustive_attribute(&self, variant: &Variant) {
let has_non_exhaustive = variant.node.attrs.iter()
.any(|attr| attr.check_name("non_exhaustive"));
if has_non_exhaustive {
self.err_handler().span_err(variant.span,
"#[non_exhaustive] is not yet supported on variants");
}
}

fn invalid_visibility(&self, vis: &Visibility, span: Span, note: Option<&str>) {
if vis != &Visibility::Inherited {
let mut err = struct_span_err!(self.session,
Expand Down Expand Up @@ -224,6 +233,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
}
ItemKind::Enum(ref def, _) => {
for variant in &def.variants {
self.invalid_non_exhaustive_attribute(variant);
for field in variant.node.data.fields() {
self.invalid_visibility(&field.vis, field.span, None);
}
Expand Down
10 changes: 10 additions & 0 deletions src/librustc_privacy/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,16 @@ impl<'a, 'tcx> TypePrivacyVisitor<'a, 'tcx> {
ctor_vis = field_vis;
}
}

// If the structure is marked as non_exhaustive then lower the
// visibility to within the crate.
let struct_def_id = self.tcx.hir.get_parent_did(node_id);
let adt_def = self.tcx.adt_def(struct_def_id);
if adt_def.is_non_exhaustive() && ctor_vis == ty::Visibility::Public {
ctor_vis = ty::Visibility::Restricted(
DefId::local(CRATE_DEF_INDEX));
}

return ctor_vis;
}
node => bug!("unexpected node kind: {:?}", node)
Expand Down
16 changes: 14 additions & 2 deletions src/librustc_resolve/build_reduced_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,22 @@ impl<'a> Resolver<'a> {
// These items live in both the type and value namespaces.
ItemKind::Struct(ref struct_def, _) => {
// Define a name in the type namespace.
let def = Def::Struct(self.definitions.local_def_id(item.id));
let def_id = self.definitions.local_def_id(item.id);
let def = Def::Struct(def_id);
self.define(parent, ident, TypeNS, (def, vis, sp, expansion));

// Record field names for error reporting.
let mut ctor_vis = vis;

let has_non_exhaustive = item.attrs.iter()
.any(|item| item.check_name("non_exhaustive"));

// If the structure is marked as non_exhaustive then lower the visibility
// to within the crate.
if has_non_exhaustive && vis == ty::Visibility::Public {
ctor_vis = ty::Visibility::Restricted(DefId::local(CRATE_DEF_INDEX));
}

// Record field names for error reporting.
let field_names = struct_def.fields().iter().filter_map(|field| {
let field_vis = self.resolve_visibility(&field.vis);
if ctor_vis.is_at_least(field_vis, &*self) {
Expand Down Expand Up @@ -414,6 +425,7 @@ impl<'a> Resolver<'a> {
// value namespace, they are reserved for possible future use.
let ctor_kind = CtorKind::from_ast(&variant.node.data);
let ctor_def = Def::VariantCtor(def_id, ctor_kind);

self.define(parent, ident, ValueNS, (ctor_def, vis, variant.span, expansion));
}

Expand Down
12 changes: 10 additions & 2 deletions src/librustc_typeck/check/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,10 +825,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
def_bm: ty::BindingMode) {
let tcx = self.tcx;

let (substs, kind_name) = match adt_ty.sty {
ty::TyAdt(adt, substs) => (substs, adt.variant_descr()),
let (substs, adt) = match adt_ty.sty {
ty::TyAdt(adt, substs) => (substs, adt),
_ => span_bug!(span, "struct pattern is not an ADT")
};
let kind_name = adt.variant_descr();

// Index the struct fields' types.
let field_map = variant.fields
Expand Down Expand Up @@ -882,6 +883,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
self.check_pat_walk(&field.pat, field_ty, def_bm, true);
}

// Require `..` if struct has non_exhaustive attribute.
if adt.is_struct() && adt.is_non_exhaustive() && !adt.did.is_local() && !etc {
span_err!(tcx.sess, span, E0638,
"`..` required with {} marked as non-exhaustive",
kind_name);
}

// Report an error if incorrect number of the fields were specified.
if kind_name == "union" {
if fields.len() != 1 {
Expand Down
9 changes: 9 additions & 0 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3448,6 +3448,15 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
hir::QPath::TypeRelative(ref qself, _) => qself.span
};

// Prohibit struct expressions when non exhaustive flag is set.
if let ty::TyAdt(adt, _) = struct_ty.sty {
if !adt.did.is_local() && adt.is_non_exhaustive() {
span_err!(self.tcx.sess, expr.span, E0639,
"cannot create non-exhaustive {} using struct expression",
adt.variant_descr());
}
}

self.check_expr_struct_fields(struct_ty, expected, expr.id, path_span, variant, fields,
base_expr.is_none());
if let &Some(ref base_expr) = base_expr {
Expand Down
Loading

0 comments on commit d762b1d

Please sign in to comment.