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: Variants #52775

Closed
wants to merge 2 commits into from
Closed
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
9 changes: 5 additions & 4 deletions src/doc/unstable-book/src/language-features/non-exhaustive.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ The tracking issue for this feature is: [#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:
on structs, enums and variants. 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 or variants. Structs and variants 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;
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2052,7 +2052,7 @@ where 'x: 'y

E0701: r##"
This error indicates that a `#[non_exhaustive]` attribute was incorrectly placed
on something other than a struct or enum.
on something other than a struct, enum or variant.

Examples of erroneous code:

Expand Down
3 changes: 2 additions & 1 deletion src/librustc/ich/impls_ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ impl_stable_hash_for!(struct ty::VariantDef {
name,
discr,
fields,
ctor_kind
ctor_kind,
can_extend_field_list
});

impl_stable_hash_for!(enum ty::VariantDiscr {
Expand Down
13 changes: 11 additions & 2 deletions src/librustc/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,8 @@ pub struct VariantDef {
pub discr: VariantDiscr,
pub fields: Vec<FieldDef>,
pub ctor_kind: CtorKind,
/// Field list can be extended if this struct/variant is marked as non-exhaustive.
pub can_extend_field_list: bool,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)]
Expand Down Expand Up @@ -1940,9 +1942,16 @@ impl<'a, 'gcx, 'tcx> AdtDef {
self.flags.intersects(AdtFlags::IS_ENUM)
}

/// Variant list can be extended if this enum is marked as non-exhaustive (only `true` if adt
/// represents an enum).
#[inline]
pub fn is_non_exhaustive(&self) -> bool {
self.flags.intersects(AdtFlags::IS_NON_EXHAUSTIVE)
pub fn can_extend_variant_list(&self) -> bool {
// AdtDef represents structs and enums where structs have a single variant.
// We represent a non-exhaustive enum by setting the non-exhaustive flag on the
// AdtDef and non-exhaustive variants and structs by setting the non-exhaustive
// flag on the VariantDef. Therefore, we should double check here that the
// AdtDef represents an enum.
self.is_enum() && self.flags.intersects(AdtFlags::IS_NON_EXHAUSTIVE)
}

/// Returns the kind of the ADT - Struct or Enum.
Expand Down
1 change: 1 addition & 0 deletions src/librustc_metadata/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ impl<'a, 'tcx> CrateMetadata {
}).collect(),
discr: data.discr,
ctor_kind: data.ctor_kind,
can_extend_field_list: data.can_extend_field_list,
}
}

Expand Down
10 changes: 7 additions & 3 deletions src/librustc_metadata/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,8 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
Some(self.lazy(&tcx.fn_sig(def_id)))
} else {
None
}
},
can_extend_field_list: variant.can_extend_field_list,
};

let enum_id = tcx.hir.as_local_node_id(enum_did).unwrap();
Expand Down Expand Up @@ -721,7 +722,8 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
Some(self.lazy(&tcx.fn_sig(def_id)))
} else {
None
}
},
can_extend_field_list: variant.can_extend_field_list,
};

let struct_id = tcx.hir.as_local_node_id(adt_def_id).unwrap();
Expand All @@ -735,7 +737,7 @@ 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 {
if variant.can_extend_field_list && ctor_vis == ty::Visibility::Public {
ctor_vis = ty::Visibility::Restricted(DefId::local(CRATE_DEF_INDEX));
}

Expand Down Expand Up @@ -1089,6 +1091,7 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
discr: variant.discr,
struct_ctor,
ctor_sig: None,
can_extend_field_list: variant.can_extend_field_list,
}), repr_options)
}
hir::ItemKind::Union(..) => {
Expand All @@ -1100,6 +1103,7 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
discr: variant.discr,
struct_ctor: None,
ctor_sig: None,
can_extend_field_list: variant.can_extend_field_list,
}), repr_options)
}
hir::ItemKind::Impl(_, polarity, defaultness, ..) => {
Expand Down
7 changes: 6 additions & 1 deletion src/librustc_metadata/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,13 +455,18 @@ pub struct VariantData<'tcx> {
/// If this is a tuple struct or variant
/// ctor, this is its "function" signature.
pub ctor_sig: Option<Lazy<ty::PolyFnSig<'tcx>>>,

/// Field list can be extended if this struct/variant
/// is marked as non-exhaustive.
pub can_extend_field_list: bool,
}

impl_stable_hash_for!(struct VariantData<'tcx> {
ctor_kind,
discr,
struct_ctor,
ctor_sig
ctor_sig,
can_extend_field_list,
});

#[derive(RustcEncodable, RustcDecodable)]
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/hair/pattern/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ 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(),
ty::TyAdt(adt_def, ..) => adt_def.can_extend_variant_list(),
_ => false,
}
}
Expand Down
9 changes: 0 additions & 9 deletions src/librustc_passes/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,6 @@ impl<'a> AstValidator<'a> {
}
}

fn invalid_non_exhaustive_attribute(&self, variant: &Variant) {
let has_non_exhaustive = attr::contains_name(&variant.node.attrs, "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, note: Option<&str>) {
if let VisibilityKind::Inherited = vis.node {
return
Expand Down Expand Up @@ -309,7 +301,6 @@ 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, None);
}
Expand Down
8 changes: 5 additions & 3 deletions src/librustc_privacy/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,9 +674,11 @@ impl<'a, 'tcx> TypePrivacyVisitor<'a, 'tcx> {
// 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));
if let Some(variant) = adt_def.variants.first() {
if variant.can_extend_field_list && ctor_vis == ty::Visibility::Public {
ctor_vis = ty::Visibility::Restricted(
DefId::local(CRATE_DEF_INDEX));
}
}

return ctor_vis;
Expand Down
25 changes: 16 additions & 9 deletions src/librustc_typeck/check/_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use rustc::hir::def::{Def, CtorKind};
use rustc::hir::pat_util::EnumerateAndAdjustIterator;
use rustc::infer;
use rustc::infer::type_variable::TypeVariableOrigin;
use rustc::session::Session;
use rustc::traits::ObligationCauseCode;
use rustc::ty::{self, Ty, TypeFoldable};
use check::{FnCtxt, Expectation, Diverges, Needs};
Expand Down Expand Up @@ -789,7 +790,7 @@ https://doc.rust-lang.org/reference/types.html#trait-objects");

// Resolve the path and check the definition for errors.
let (def, opt_ty, segments) = self.resolve_ty_and_def_ufcs(qpath, pat.id, pat.span);
let variant = match def {
let (variant, kind_name) = match def {
Def::Err => {
self.set_tainted_by_errors();
on_error();
Expand All @@ -799,10 +800,8 @@ https://doc.rust-lang.org/reference/types.html#trait-objects");
report_unexpected_def(def);
return tcx.types.err;
}
Def::VariantCtor(_, CtorKind::Fn) |
Def::StructCtor(_, CtorKind::Fn) => {
tcx.expect_variant_def(def)
}
Def::VariantCtor(_, CtorKind::Fn) => (tcx.expect_variant_def(def), "variant"),
Def::StructCtor(_, CtorKind::Fn) => (tcx.expect_variant_def(def), "struct"),
_ => bug!("unexpected pattern definition: {:?}", def)
};

Expand All @@ -814,6 +813,11 @@ https://doc.rust-lang.org/reference/types.html#trait-objects");

self.demand_eqtype(pat.span, expected, pat_ty);

// Require `..` if tuple struct/variant has non_exhaustive attribute.
if variant.can_extend_field_list && !variant.did.is_local() && ddpos.is_none() {
self.report_non_exhaustive_error(&tcx.sess, &pat.span, kind_name);
}

// Type check subpatterns.
if subpats.len() == variant.fields.len() ||
subpats.len() < variant.fields.len() && ddpos.is_some() {
Expand Down Expand Up @@ -948,10 +952,8 @@ https://doc.rust-lang.org/reference/types.html#trait-objects");
}

// 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);
if variant.can_extend_field_list && !variant.did.is_local() && !etc {
self.report_non_exhaustive_error(&tcx.sess, &span, kind_name);
}

// Report an error if incorrect number of the fields were specified.
Expand Down Expand Up @@ -998,4 +1000,9 @@ https://doc.rust-lang.org/reference/types.html#trait-objects");
}
no_field_errors
}

fn report_non_exhaustive_error(&self, sess: &Session, span: &Span, kind_name: &str) {
span_err!(sess, *span, E0638, "`..` required with {} marked as non-exhaustive",
kind_name);
}
}
13 changes: 8 additions & 5 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3573,12 +3573,15 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
};

// Prohibit struct expressions when non exhaustive flag is set.
let mut is_struct = false;
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());
}
is_struct = adt.is_struct();
}

if !variant.did.is_local() && variant.can_extend_field_list {
span_err!(self.tcx.sess, expr.span, E0639,
"cannot create non-exhaustive {} using struct expression",
if is_struct { "struct" } else { "variant" });
}

let error_happened = self.check_expr_struct_fields(struct_ty, expected, expr.id, path_span,
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_typeck/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,12 +546,14 @@ fn convert_struct_variant<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
vis: ty::Visibility::from_hir(&f.vis, node_id, tcx)
}
}).collect();

ty::VariantDef {
did,
name,
discr,
fields,
ctor_kind: CtorKind::from_hir(def),
can_extend_field_list: tcx.has_attr(did, "non_exhaustive"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One weird thing -- I'm not sure how this will work -- the did in the case of a tuple-struct is I think the synthetic def-id creator for the struct constructor? We just need to be sure to test #[non_exhaustive] pub struct Foo(pub u32)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well I guess we have some tests for that already so... seems ok.

}
}

Expand Down
15 changes: 8 additions & 7 deletions src/librustc_typeck/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4446,11 +4446,12 @@ foo.method(); // Ok!
"##,

E0638: r##"
This error indicates that the struct or enum must be matched non-exhaustively
as it has been marked as `non_exhaustive`.
This error indicates that the struct, enum or variant must be matched
non-exhaustively as it has been marked as `non_exhaustive`.

When applied within a crate, downstream users of the crate will need to use the
`_` pattern when matching enums and use the `..` pattern when matching structs.
`_` pattern when matching enums and use the `..` pattern when matching structs
or variants.

For example, in the below example, since the enum is marked as
`non_exhaustive`, it is required that downstream crates match non-exhaustively
Expand Down Expand Up @@ -4495,10 +4496,10 @@ Similarly, for structs, match with `..` to avoid this error.
"##,

E0639: r##"
This error indicates that the struct or enum cannot be instantiated from
outside of the defining crate as it has been marked as `non_exhaustive` and as
such more fields/variants may be added in future that could cause adverse side
effects for this code.
This error indicates that the struct, enum or variant cannot be instantiated
from outside of the defining crate as it has been marked as `non_exhaustive`
and as such more fields/variants may be added in future that could cause
adverse side effects for this code.

It is recommended that you look for a `new` function or equivalent in the
crate's documentation.
Expand Down
12 changes: 6 additions & 6 deletions src/test/compile-fail/rfc-2008-non-exhaustive/variants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ extern crate variants;

use variants::NonExhaustiveVariants;

/*
* The initial implementation of #[non_exhaustive] (RFC 2008) does not include support for
* variants. See issue #44109 and PR 45394.
*/
// ignore-test

fn main() {
let variant_struct = NonExhaustiveVariants::Struct { field: 640 };
//~^ ERROR cannot create non-exhaustive variant

let variant_tuple = NonExhaustiveVariants::Tuple { 0: 640 };
//~^ ERROR cannot create non-exhaustive variant

match variant_struct {
NonExhaustiveVariants::Tuple { 0: fe_tpl } => "",
//~^ ERROR `..` required with variant marked as non-exhaustive
_ => "",
};

match variant_struct {
NonExhaustiveVariants::Unit => "",
NonExhaustiveVariants::Tuple(fe_tpl) => "",
Expand Down
27 changes: 0 additions & 27 deletions src/test/compile-fail/rfc-2008-non-exhaustive/variants_create.rs

This file was deleted.

16 changes: 6 additions & 10 deletions src/test/run-pass/rfc-2008-non-exhaustive/variants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,15 @@ extern crate variants;

use variants::NonExhaustiveVariants;

/*
* The initial implementation of #[non_exhaustive] (RFC 2008) does not include support for
* variants. See issue #44109 and PR 45394.
*/
// ignore-test
// We only test matching here as we cannot create non-exhaustive
// variants from another crate. ie. they'll never pass in run-pass tests.

fn main() {
let variant_tuple = NonExhaustiveVariants::Tuple { 0: 340 };
let variant_struct = NonExhaustiveVariants::Struct { field: 340 };

match variant_struct {
fn match_variants(non_exhaustive_enum: NonExhaustiveVariants) {
match non_exhaustive_enum {
NonExhaustiveVariants::Unit => "",
NonExhaustiveVariants::Struct { field, .. } => "",
NonExhaustiveVariants::Tuple(fe_tpl, ..) => ""
};
}

fn main() {}
Loading