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

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

merged 5 commits into from
Nov 4, 2017

Conversation

davidtwco
Copy link
Member

@davidtwco davidtwco commented Oct 19, 2017

This work-in-progress pull request contains my changes to implement RFC 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.

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @steveklabnik (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@nikomatsakis
Copy link
Contributor

r? @nikomatsakis

@petrochenkov petrochenkov self-assigned this Oct 19, 2017
@@ -1371,6 +1371,7 @@ pub struct AdtDef {
pub variants: Vec<VariantDef>,
flags: AdtFlags,
pub repr: ReprOptions,
pub non_exhaustive: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a comment briefly explaining what this is, perhaps linking to the RFC? I know none of the other fields have comments, but that's no reason not to do better... =)

Copy link
Member Author

Choose a reason for hiding this comment

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

I definitely will, I plan on doing another pass to make sure everything is well commented, just wanted to get that pushed up before I called it a night. Will do this ASAP.

Copy link
Member Author

Choose a reason for hiding this comment

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

Just pushed up a comment here.

Copy link
Contributor

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

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

All the error-cases have a trivial solution. I think it would be cool if we also produced structured suggestions showing how to fix it

use enums::FieldEnum;

// This errors as we cannot create a enum variant marked non_exhaustive
// from an external crate as if the create added new fields it would be
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: create -> crate

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.


fn match_enum(ne: &NormalEnum) -> &str {
match *ne {
//~^ ERROR `..` required with variant marked as 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.

The error message is wrong, the enum is marked as non-exhaustive, not the variant

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed this part of the test.

if tcx.sess.features.borrow().non_exhaustive {
// Require `..` if structure or enum has non_exhaustive attribute.
if adt.non_exhaustive && !adt.did.is_local() && !etc {
tcx.sess.span_err(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this error should have its own error number.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added error numbers in a WIP commit that I couldn't get compiling locally - messages regarding the diagnostic code not being registered despite my not being able to spot a difference in how they are being added. Would appreciate any thoughts here.

let descr = self.tcx.adt_def(variant.did).variant_descr();

// Prohibit struct expressions when non exhaustive flag is set.
self.tcx.sess.span_err(expr.span, &format!("cannot create 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.

Same here, but a different new error code

Copy link
Member Author

Choose a reason for hiding this comment

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

See other comment w/r/t error codes.

@oli-obk
Copy link
Contributor

oli-obk commented Oct 20, 2017

tidy failed:

[00:03:26] tidy error: /checkout/src/test/compile-fail/rfc-2008-non-exhaustive/functional_record.rs:24: line longer than 100 chars
[00:03:26] tidy error: /checkout/src/librustc_privacy/lib.rs:632: line longer than 100 chars
[00:03:26] tidy error: /checkout/src/librustc_privacy/lib.rs:635: line longer than 100 chars

@davidtwco
Copy link
Member Author

davidtwco commented Oct 20, 2017

@oli-obk Thanks for the comments, I'll get on these ASAP.

Update: pushed up changes that fixed most everything mentioned including the tidy.

@kennytm kennytm added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Oct 20, 2017
@@ -1939,6 +1939,65 @@ fn foo<'a>(x: &'a i32, y: &i32) -> &'a i32 {
```
"##,

E0638: r##"
Copy link
Contributor

Choose a reason for hiding this comment

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

You need to add this to the diagnostics file of librustc_typeck

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, I've pushed up a fix for this. I wasn't aware that there was a diagnostics file in the other crates, my bad.

/// fields/variants of this data type.
///
/// See RFC 2008 (https://github.com/rust-lang/rfcs/pull/2008).
pub non_exhaustive: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is better merged into the flags: AdtFlags field.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@@ -648,6 +649,14 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
}
}

if tcx.sess.features.borrow().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.

This condition seems unnecessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@@ -625,6 +625,18 @@ impl<'a, 'tcx> TypePrivacyVisitor<'a, 'tcx> {
ctor_vis = field_vis;
}
}

if self.tcx.sess.features.borrow().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.

Unnecessary condition again.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@@ -337,8 +337,20 @@ impl<'a> Resolver<'a> {

// These items live in both the type and value namespaces.
ItemKind::Struct(ref struct_def, _) => {
if self.session.features.borrow().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.

Unnecessary condition again, you don't have to check this all the time, one feature check in feature_gate.rs is enough.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed. Wasn't aware that I didn't need it everywhere - I misinterpreted the instructions on the Rust forge.

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

Choose a reason for hiding this comment

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

Hmm, this doesn't look correct - constructor's visibility below (ctor_vis) should be lowered to pub(crate), not visibility of the struct itself.

Copy link
Member Author

Choose a reason for hiding this comment

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

Adjusted this.

@@ -867,6 +868,15 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
self.check_pat_walk(&field.pat, field_ty, def_bm, true);
}

if tcx.sess.features.borrow().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.

Unnecessary feature check.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Member Author

Choose a reason for hiding this comment

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

After doing this, I noticed a regression in creating the structs - wasn't able to work out what was causing it.

@@ -3408,6 +3408,19 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
return self.tcx.types.err;
};

if self.tcx.sess.features.borrow().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.

Unnecessary feature check.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@@ -867,6 +868,15 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
self.check_pat_walk(&field.pat, field_ty, def_bm, true);
}

if tcx.sess.features.borrow().non_exhaustive {
// Require `..` if structure or enum has non_exhaustive attribute.
Copy link
Contributor

Choose a reason for hiding this comment

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

Require .. if structure or variant has the non_exhaustive attribute.
It looks like this patch doesn't implement non_exhaustive for variants yet, but non_exhaustive on enum should not restrict use of its variants.

Copy link
Member Author

Choose a reason for hiding this comment

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

Adjusted this comment.

if self.tcx.sess.features.borrow().non_exhaustive {
let is_local = variant.did.is_local();

if !is_local {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like the check for non_exhaustive is forgotten here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also see https://github.com/rust-lang/rust/pull/45394/files#r146130454, non_exhaustive on enum should not prohibit struct expressions using that enum's variants.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added check.


pub enum FieldEnum {
WithoutFields,
#[non_exhaustive] WithAnonStruct { field: u32 },
Copy link
Contributor

Choose a reason for hiding this comment

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

Since non_exhaustive on variants is not implemented, could you make it an error? (Currently it's silently ignored.)

Copy link
Member Author

Choose a reason for hiding this comment

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

I've not gotten around to doing this yet.

pub enum FieldEnum {
WithoutFields,
#[non_exhaustive] WithAnonStruct { field: u32 },
#[non_exhaustive] WithTuple(u32)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to see all of these configurations tested:

#[non_exhaustive]
enum NonExhaustiveEnum {
    Unit,
    Tuple(u8),
    Struct { field: u8 },
}

enum NonExhaustiveVariants {
    #[non_exhaustive] Unit,
    #[non_exhaustive] Tuple(u8),
    #[non_exhaustive] Struct { field: u8 },
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Now testing with these enums.

// option. This file may not be copied, modified, or distributed
// except according to those terms.

// FIXME issue #44109, PR #45394
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: This can probably be omitted (also FIXME seems out-of-place).

Copy link
Contributor

Choose a reason for hiding this comment

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

Same with other tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

// except according to those terms.

// FIXME issue #44109, PR #45394
// gate-test-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.

This marker is used for feature gate tests, but this test is not a feature gate test.

Also, could you actually add a feature gate test? (Making sure a feature error is reported in #[non_exhaustive] is used without feature(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.

Same with other tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added a proper feature gate test.


// FIXME issue #44109, PR #45394
// gate-test-non_exhaustive
#![feature(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.

Unnecessary feature, feature(non_exhaustive) is required only in crated defining something non_exhaustive, not using it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same with other tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed the unnecessary feature usages.


// Prohibit struct expressions when non exhaustive flag is set.
span_err!(self.tcx.sess, expr.span, E0639,
"cannot create 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.

Nit: "cannot create non-exhaustive {} using struct expression"?
I mean you can obtain non-exhaustive structs using other means, e.g. function calls.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.


fn match_struct(us: &UnitStruct) {
let &UnitStruct { } = us;
//~^ ERROR `..` required with struct marked as 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.

You can merge several test files testing similar things into one file with several //~ ERROR markers, it's usually more convenient that way (both for writing better more exhaustive tests, and for reviewing them).

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed this. It's a lot easier to keep track of now.

@davidtwco
Copy link
Member Author

@petrochenkov Thanks a bunch for the comments. Just fixed a bunch of them and I think there's one that I'm still working to resolve.

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

if cx.is_non_exhaustive(pcx.ty) && !cx.is_local(pcx.ty) {
Copy link
Contributor

@arielb1 arielb1 Oct 26, 2017

Choose a reason for hiding this comment

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

It's confusing to mutate this variable, especially after the debug print

Maybe have

let is_privately_empty =
    all_ctors.is_empty() && !cx.is_uninhabited(pcx.ty);
let is_declared_nonexhaustive =
    cx.is_non_exhaustive(pcx.ty) && !cx.is_local(pcx.ty);
// For privately empty and nonexhaustive enums, we work as
// if there were an "extra" `_` constructor for the type, so we
// can never match over all constructors.
let is_nonexhaustive =
    is_privately_empty || is_declared_nonexhaustive;

if missing_ctors.is_empty() && !is_nonexhaustive {

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed this, thanks.

@petrochenkov
Copy link
Contributor

Almost ready, r=me after addressing the last round of comments and squashing commits.

@davidtwco
Copy link
Member Author

@petrochenkov That's all your comments resolved.

@bors r=petrochenkov

@bors
Copy link
Contributor

bors commented Nov 3, 2017

@davidtwco: 🔑 Insufficient privileges: Not in reviewers

@petrochenkov
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Nov 3, 2017

📋 Looks like this PR is still in progress, ignoring approval

@petrochenkov petrochenkov changed the title [WIP] RFC 2008: Future-proofing enums/structs with #[non_exhaustive] attribute RFC 2008: Future-proofing enums/structs with #[non_exhaustive] attribute Nov 3, 2017
@petrochenkov
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Nov 3, 2017

📌 Commit d3babe5 has been approved by petrochenkov

@bors
Copy link
Contributor

bors commented Nov 4, 2017

⌛ Testing commit d3babe5 with merge 5fe8cd7aa66c92d1936d8286e6205e24e566aa7c...

@bors
Copy link
Contributor

bors commented Nov 4, 2017

💔 Test failed - status-travis

@kennytm
Copy link
Member

kennytm commented Nov 4, 2017

pretty test of run-pass/rfc-2008-non-exhaustive/enums.rs failed.

[01:16:34] ---- [pretty] run-pass/rfc-2008-non-exhaustive/enums.rs stdout ----
[01:16:34] 	
[01:16:34] error: pretty-printed source does not match expected source

Diff (a single empty line 😕):

--- expected
+++ actual
@@ -20,6 +20,7 @@
 [01:16:34]         NonExhaustiveEnum::Unit => 1,
 [01:16:34]         NonExhaustiveEnum::Tuple(_) => 2,
 [01:16:34] 
+[01:16:34] 
 [01:16:34]         // This particular arm tests that a enum marked as non-exhaustive
 [01:16:34]         // will not error if its variants are matched exhaustively.
 [01:16:34]         NonExhaustiveEnum::Struct { field } => field,

@petrochenkov
Copy link
Contributor

This is a pretty-printer bug (#37199).
// ignore-pretty issue #37199 in the test will fix the issue.

@petrochenkov
Copy link
Contributor

@bors r+

@bors
Copy link
Contributor

bors commented Nov 4, 2017

📌 Commit 86c62d0 has been approved by petrochenkov

@bors
Copy link
Contributor

bors commented Nov 4, 2017

⌛ Testing commit 86c62d0 with merge d762b1d...

bors added a commit that referenced this pull request Nov 4, 2017
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).
@bors
Copy link
Contributor

bors commented Nov 4, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: petrochenkov
Pushing d762b1d to master...

@bors bors merged commit 86c62d0 into rust-lang:master Nov 4, 2017
@davidtwco davidtwco deleted the rfc-2008 branch November 4, 2017 20:35
@nikomatsakis
Copy link
Contributor

Just saw that this landed, nice work @davidtwco !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants