Skip to content

Commit d762b1d

Browse files
committed
Auto merge of #45394 - davidtwco:rfc-2008, r=petrochenkov
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).
2 parents 98e4b68 + 86c62d0 commit d762b1d

File tree

28 files changed

+769
-9
lines changed

28 files changed

+769
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# `non_exhaustive`
2+
3+
The tracking issue for this feature is: [#44109]
4+
5+
[#44109]: https://github.com/rust-lang/rust/issues/44109
6+
7+
------------------------
8+
9+
The `non_exhaustive` gate allows you to use the `#[non_exhaustive]` attribute
10+
on structs and enums. When applied within a crate, users of the crate will need
11+
to use the `_` pattern when matching enums and use the `..` pattern when
12+
matching structs. Structs marked as `non_exhaustive` will not be able to be
13+
created normally outside of the defining crate. This is demonstrated below:
14+
15+
```rust,ignore (pseudo-Rust)
16+
use std::error::Error as StdError;
17+
18+
#[non_exhaustive]
19+
pub enum Error {
20+
Message(String),
21+
Other,
22+
}
23+
impl StdError for Error {
24+
fn description(&self) -> &str {
25+
// This will not error, despite being marked as non_exhaustive, as this
26+
// enum is defined within the current crate, it can be matched
27+
// exhaustively.
28+
match *self {
29+
Message(ref s) => s,
30+
Other => "other or unknown error",
31+
}
32+
}
33+
}
34+
```
35+
36+
```rust,ignore (pseudo-Rust)
37+
use mycrate::Error;
38+
39+
// This will not error as the non_exhaustive Error enum has been matched with
40+
// a wildcard.
41+
match error {
42+
Message(ref s) => ...,
43+
Other => ...,
44+
_ => ...,
45+
}
46+
```
47+
48+
```rust,ignore (pseudo-Rust)
49+
#[non_exhaustive]
50+
pub struct Config {
51+
pub window_width: u16,
52+
pub window_height: u16,
53+
}
54+
55+
// We can create structs as normal within the defining crate when marked as
56+
// non_exhaustive.
57+
let config = Config { window_width: 640, window_height: 480 };
58+
59+
// We can match structs exhaustively when within the defining crate.
60+
if let Ok(Config { window_width, window_height }) = load_config() {
61+
// ...
62+
}
63+
```
64+
65+
```rust,ignore (pseudo-Rust)
66+
use mycrate::Config;
67+
68+
// We cannot create a struct like normal if it has been marked as
69+
// non_exhaustive.
70+
let config = Config { window_width: 640, window_height: 480 };
71+
// By adding the `..` we can match the config as below outside of the crate
72+
// when marked non_exhaustive.
73+
let &Config { window_width, window_height, .. } = config;
74+
```
75+

src/librustc/ty/mod.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,12 @@ bitflags! {
13261326
const IS_FUNDAMENTAL = 1 << 2;
13271327
const IS_UNION = 1 << 3;
13281328
const IS_BOX = 1 << 4;
1329+
/// Indicates whether this abstract data type will be expanded on in future (new
1330+
/// fields/variants) and as such, whether downstream crates must match exhaustively on the
1331+
/// fields/variants of this data type.
1332+
///
1333+
/// See RFC 2008 (https://github.com/rust-lang/rfcs/pull/2008).
1334+
const IS_NON_EXHAUSTIVE = 1 << 5;
13291335
}
13301336
}
13311337

@@ -1526,6 +1532,9 @@ impl<'a, 'gcx, 'tcx> AdtDef {
15261532
if Some(did) == tcx.lang_items().owned_box() {
15271533
flags = flags | AdtFlags::IS_BOX;
15281534
}
1535+
if tcx.has_attr(did, "non_exhaustive") {
1536+
flags = flags | AdtFlags::IS_NON_EXHAUSTIVE;
1537+
}
15291538
match kind {
15301539
AdtKind::Enum => flags = flags | AdtFlags::IS_ENUM,
15311540
AdtKind::Union => flags = flags | AdtFlags::IS_UNION,
@@ -1554,6 +1563,11 @@ impl<'a, 'gcx, 'tcx> AdtDef {
15541563
self.flags.intersects(AdtFlags::IS_ENUM)
15551564
}
15561565

1566+
#[inline]
1567+
pub fn is_non_exhaustive(&self) -> bool {
1568+
self.flags.intersects(AdtFlags::IS_NON_EXHAUSTIVE)
1569+
}
1570+
15571571
/// Returns the kind of the ADT - Struct or Enum.
15581572
#[inline]
15591573
pub fn adt_kind(&self) -> AdtKind {

src/librustc_const_eval/_match.rs

+69-4
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,20 @@ impl<'a, 'tcx> MatchCheckCtxt<'a, 'tcx> {
208208
}
209209
}
210210

211+
fn is_non_exhaustive_enum(&self, ty: Ty<'tcx>) -> bool {
212+
match ty.sty {
213+
ty::TyAdt(adt_def, ..) => adt_def.is_enum() && adt_def.is_non_exhaustive(),
214+
_ => false,
215+
}
216+
}
217+
218+
fn is_local(&self, ty: Ty<'tcx>) -> bool {
219+
match ty.sty {
220+
ty::TyAdt(adt_def, ..) => adt_def.did.is_local(),
221+
_ => false,
222+
}
223+
}
224+
211225
fn is_variant_uninhabited(&self,
212226
variant: &'tcx ty::VariantDef,
213227
substs: &'tcx ty::subst::Substs<'tcx>)
@@ -628,9 +642,16 @@ pub fn is_useful<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
628642

629643
let is_privately_empty =
630644
all_ctors.is_empty() && !cx.is_uninhabited(pcx.ty);
631-
debug!("missing_ctors={:?} is_privately_empty={:?}", missing_ctors,
632-
is_privately_empty);
633-
if missing_ctors.is_empty() && !is_privately_empty {
645+
let is_declared_nonexhaustive =
646+
cx.is_non_exhaustive_enum(pcx.ty) && !cx.is_local(pcx.ty);
647+
debug!("missing_ctors={:?} is_privately_empty={:?} is_declared_nonexhaustive={:?}",
648+
missing_ctors, is_privately_empty, is_declared_nonexhaustive);
649+
650+
// For privately empty and non-exhaustive enums, we work as if there were an "extra"
651+
// `_` constructor for the type, so we can never match over all constructors.
652+
let is_non_exhaustive = is_privately_empty || is_declared_nonexhaustive;
653+
654+
if missing_ctors.is_empty() && !is_non_exhaustive {
634655
all_ctors.into_iter().map(|c| {
635656
is_useful_specialized(cx, matrix, v, c.clone(), pcx.ty, witness)
636657
}).find(|result| result.is_useful()).unwrap_or(NotUseful)
@@ -645,7 +666,51 @@ pub fn is_useful<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>,
645666
match is_useful(cx, &matrix, &v[1..], witness) {
646667
UsefulWithWitness(pats) => {
647668
let cx = &*cx;
648-
let new_witnesses = if used_ctors.is_empty() {
669+
// In this case, there's at least one "free"
670+
// constructor that is only matched against by
671+
// wildcard patterns.
672+
//
673+
// There are 2 ways we can report a witness here.
674+
// Commonly, we can report all the "free"
675+
// constructors as witnesses, e.g. if we have:
676+
//
677+
// ```
678+
// enum Direction { N, S, E, W }
679+
// let Direction::N = ...;
680+
// ```
681+
//
682+
// we can report 3 witnesses: `S`, `E`, and `W`.
683+
//
684+
// However, there are 2 cases where we don't want
685+
// to do this and instead report a single `_` witness:
686+
//
687+
// 1) If the user is matching against a non-exhaustive
688+
// enum, there is no point in enumerating all possible
689+
// variants, because the user can't actually match
690+
// against them himself, e.g. in an example like:
691+
// ```
692+
// let err: io::ErrorKind = ...;
693+
// match err {
694+
// io::ErrorKind::NotFound => {},
695+
// }
696+
// ```
697+
// we don't want to show every possible IO error,
698+
// but instead have `_` as the witness (this is
699+
// actually *required* if the user specified *all*
700+
// IO errors, but is probably what we want in every
701+
// case).
702+
//
703+
// 2) If the user didn't actually specify a constructor
704+
// in this arm, e.g. in
705+
// ```
706+
// let x: (Direction, Direction, bool) = ...;
707+
// let (_, _, false) = x;
708+
// ```
709+
// we don't want to show all 16 possible witnesses
710+
// `(<direction-1>, <direction-2>, true)` - we are
711+
// satisfied with `(_, _, true)`. In this case,
712+
// `used_ctors` is empty.
713+
let new_witnesses = if is_non_exhaustive || used_ctors.is_empty() {
649714
// All constructors are unused. Add wild patterns
650715
// rather than each individual constructor
651716
pats.into_iter().map(|mut witness| {

src/librustc_metadata/encoder.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,8 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
584584
fn encode_struct_ctor(&mut self, (adt_def_id, def_id): (DefId, DefId)) -> Entry<'tcx> {
585585
debug!("IsolatedEncoder::encode_struct_ctor({:?})", def_id);
586586
let tcx = self.tcx;
587-
let variant = tcx.adt_def(adt_def_id).struct_variant();
587+
let adt_def = tcx.adt_def(adt_def_id);
588+
let variant = adt_def.struct_variant();
588589

589590
let data = VariantData {
590591
ctor_kind: variant.ctor_kind,
@@ -606,6 +607,12 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
606607
}
607608
}
608609

610+
// If the structure is marked as non_exhaustive then lower the visibility
611+
// to within the crate.
612+
if adt_def.is_non_exhaustive() && ctor_vis == ty::Visibility::Public {
613+
ctor_vis = ty::Visibility::Restricted(DefId::local(CRATE_DEF_INDEX));
614+
}
615+
609616
let repr_options = get_repr_options(&tcx, adt_def_id);
610617

611618
Entry {

src/librustc_passes/ast_validation.rs

+10
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ impl<'a> AstValidator<'a> {
4242
}
4343
}
4444

45+
fn invalid_non_exhaustive_attribute(&self, variant: &Variant) {
46+
let has_non_exhaustive = variant.node.attrs.iter()
47+
.any(|attr| attr.check_name("non_exhaustive"));
48+
if has_non_exhaustive {
49+
self.err_handler().span_err(variant.span,
50+
"#[non_exhaustive] is not yet supported on variants");
51+
}
52+
}
53+
4554
fn invalid_visibility(&self, vis: &Visibility, span: Span, note: Option<&str>) {
4655
if vis != &Visibility::Inherited {
4756
let mut err = struct_span_err!(self.session,
@@ -224,6 +233,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
224233
}
225234
ItemKind::Enum(ref def, _) => {
226235
for variant in &def.variants {
236+
self.invalid_non_exhaustive_attribute(variant);
227237
for field in variant.node.data.fields() {
228238
self.invalid_visibility(&field.vis, field.span, None);
229239
}

src/librustc_privacy/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,16 @@ impl<'a, 'tcx> TypePrivacyVisitor<'a, 'tcx> {
627627
ctor_vis = field_vis;
628628
}
629629
}
630+
631+
// If the structure is marked as non_exhaustive then lower the
632+
// visibility to within the crate.
633+
let struct_def_id = self.tcx.hir.get_parent_did(node_id);
634+
let adt_def = self.tcx.adt_def(struct_def_id);
635+
if adt_def.is_non_exhaustive() && ctor_vis == ty::Visibility::Public {
636+
ctor_vis = ty::Visibility::Restricted(
637+
DefId::local(CRATE_DEF_INDEX));
638+
}
639+
630640
return ctor_vis;
631641
}
632642
node => bug!("unexpected node kind: {:?}", node)

src/librustc_resolve/build_reduced_graph.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -338,11 +338,22 @@ impl<'a> Resolver<'a> {
338338
// These items live in both the type and value namespaces.
339339
ItemKind::Struct(ref struct_def, _) => {
340340
// Define a name in the type namespace.
341-
let def = Def::Struct(self.definitions.local_def_id(item.id));
341+
let def_id = self.definitions.local_def_id(item.id);
342+
let def = Def::Struct(def_id);
342343
self.define(parent, ident, TypeNS, (def, vis, sp, expansion));
343344

344-
// Record field names for error reporting.
345345
let mut ctor_vis = vis;
346+
347+
let has_non_exhaustive = item.attrs.iter()
348+
.any(|item| item.check_name("non_exhaustive"));
349+
350+
// If the structure is marked as non_exhaustive then lower the visibility
351+
// to within the crate.
352+
if has_non_exhaustive && vis == ty::Visibility::Public {
353+
ctor_vis = ty::Visibility::Restricted(DefId::local(CRATE_DEF_INDEX));
354+
}
355+
356+
// Record field names for error reporting.
346357
let field_names = struct_def.fields().iter().filter_map(|field| {
347358
let field_vis = self.resolve_visibility(&field.vis);
348359
if ctor_vis.is_at_least(field_vis, &*self) {
@@ -414,6 +425,7 @@ impl<'a> Resolver<'a> {
414425
// value namespace, they are reserved for possible future use.
415426
let ctor_kind = CtorKind::from_ast(&variant.node.data);
416427
let ctor_def = Def::VariantCtor(def_id, ctor_kind);
428+
417429
self.define(parent, ident, ValueNS, (ctor_def, vis, variant.span, expansion));
418430
}
419431

src/librustc_typeck/check/_match.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -825,10 +825,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
825825
def_bm: ty::BindingMode) {
826826
let tcx = self.tcx;
827827

828-
let (substs, kind_name) = match adt_ty.sty {
829-
ty::TyAdt(adt, substs) => (substs, adt.variant_descr()),
828+
let (substs, adt) = match adt_ty.sty {
829+
ty::TyAdt(adt, substs) => (substs, adt),
830830
_ => span_bug!(span, "struct pattern is not an ADT")
831831
};
832+
let kind_name = adt.variant_descr();
832833

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

886+
// Require `..` if struct has non_exhaustive attribute.
887+
if adt.is_struct() && adt.is_non_exhaustive() && !adt.did.is_local() && !etc {
888+
span_err!(tcx.sess, span, E0638,
889+
"`..` required with {} marked as non-exhaustive",
890+
kind_name);
891+
}
892+
885893
// Report an error if incorrect number of the fields were specified.
886894
if kind_name == "union" {
887895
if fields.len() != 1 {

src/librustc_typeck/check/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -3448,6 +3448,15 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
34483448
hir::QPath::TypeRelative(ref qself, _) => qself.span
34493449
};
34503450

3451+
// Prohibit struct expressions when non exhaustive flag is set.
3452+
if let ty::TyAdt(adt, _) = struct_ty.sty {
3453+
if !adt.did.is_local() && adt.is_non_exhaustive() {
3454+
span_err!(self.tcx.sess, expr.span, E0639,
3455+
"cannot create non-exhaustive {} using struct expression",
3456+
adt.variant_descr());
3457+
}
3458+
}
3459+
34513460
self.check_expr_struct_fields(struct_ty, expected, expr.id, path_span, variant, fields,
34523461
base_expr.is_none());
34533462
if let &Some(ref base_expr) = base_expr {

0 commit comments

Comments
 (0)