Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC 2008: Future-proofing enums/structs with #[non_exhaustive] attribute #45394

Merged
merged 5 commits into from
Nov 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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