Skip to content

Commit

Permalink
Rollup merge of #128934 - Nadrieril:fix-empty-non-exhaustive, r=compi…
Browse files Browse the repository at this point in the history
…ler-errors

Non-exhaustive structs may be empty

This is a follow-up to a discrepancy noticed in #122792: today, the following struct is considered inhabited (non-empty) outside its defining crate:
```rust
#[non_exhaustive]
pub struct UninhabitedStruct {
    pub never: !,
    // other fields
}
```

`#[non_exhaustive]` on a struct should mean that adding fields to it isn't a breaking change. There is no way that adding fields to this struct could make it non-empty since the `never` field must stay and is inconstructible. I suspect this was implemented this way due to confusion with `#[non_exhaustive]` enums, which indeed should be considered non-empty outside their defining crate.

I propose that we consider such a struct uninhabited (empty), just like it would be without the `#[non_exhaustive]` annotation.

Code that doesn't pass today and will pass after this:
```rust
// In a different crate
fn empty_match_on_empty_struct<T>(x: UninhabitedStruct) -> T {
    match x {}
}
```

This is not a breaking change.

r? ``@compiler-errors``
  • Loading branch information
matthiaskrgr authored Sep 3, 2024
2 parents 51c686f + 6f6a6bc commit e7504ac
Show file tree
Hide file tree
Showing 16 changed files with 135 additions and 207 deletions.
4 changes: 0 additions & 4 deletions compiler/rustc_middle/src/ty/inhabitedness/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ impl<'tcx> VariantDef {
adt: ty::AdtDef<'_>,
) -> InhabitedPredicate<'tcx> {
debug_assert!(!adt.is_union());
if self.is_field_list_non_exhaustive() && !self.def_id.is_local() {
// Non-exhaustive variants from other crates are always considered inhabited.
return InhabitedPredicate::True;
}
InhabitedPredicate::all(
tcx,
self.fields.iter().map(|field| {
Expand Down
8 changes: 1 addition & 7 deletions compiler/rustc_pattern_analysis/src/rustc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,17 +229,11 @@ impl<'p, 'tcx: 'p> RustcPatCtxt<'p, 'tcx> {
} else {
let variant =
&adt.variant(RustcPatCtxt::variant_index_for_adt(&ctor, *adt));

// In the cases of either a `#[non_exhaustive]` field list or a non-public
// field, we skip uninhabited fields in order not to reveal the
// uninhabitedness of the whole variant.
let is_non_exhaustive =
variant.is_field_list_non_exhaustive() && !adt.did().is_local();
let tys = cx.variant_sub_tys(ty, variant).map(|(field, ty)| {
let is_visible =
adt.is_enum() || field.vis.is_accessible_from(cx.module, cx.tcx);
let is_uninhabited = cx.is_uninhabited(*ty);
let skip = is_uninhabited && (!is_visible || is_non_exhaustive);
let skip = is_uninhabited && !is_visible;
(ty, PrivateUninhabitedField(skip))
});
cx.dropless_arena.alloc_from_iter(tys)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ pub enum UninhabitedEnum {

#[non_exhaustive]
pub struct UninhabitedStruct {
_priv: !,
pub never: !,
_priv: (),
}

#[non_exhaustive]
pub struct UninhabitedTupleStruct(!);
pub struct PrivatelyUninhabitedStruct {
never: !,
}

#[non_exhaustive]
pub struct UninhabitedTupleStruct(pub !);

pub enum UninhabitedVariants {
#[non_exhaustive] Tuple(!),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LL | match x {}
| ^
|
note: `IndirectUninhabitedEnum` defined here
--> $DIR/auxiliary/uninhabited.rs:26:1
--> $DIR/auxiliary/uninhabited.rs:32:1
|
LL | pub struct IndirectUninhabitedEnum(UninhabitedEnum);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -24,7 +24,7 @@ LL | match x {}
| ^
|
note: `IndirectUninhabitedStruct` defined here
--> $DIR/auxiliary/uninhabited.rs:28:1
--> $DIR/auxiliary/uninhabited.rs:34:1
|
LL | pub struct IndirectUninhabitedStruct(UninhabitedStruct);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -43,7 +43,7 @@ LL | match x {}
| ^
|
note: `IndirectUninhabitedTupleStruct` defined here
--> $DIR/auxiliary/uninhabited.rs:30:1
--> $DIR/auxiliary/uninhabited.rs:36:1
|
LL | pub struct IndirectUninhabitedTupleStruct(UninhabitedTupleStruct);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -62,7 +62,7 @@ LL | match x {}
| ^
|
note: `IndirectUninhabitedVariants` defined here
--> $DIR/auxiliary/uninhabited.rs:32:1
--> $DIR/auxiliary/uninhabited.rs:38:1
|
LL | pub struct IndirectUninhabitedVariants(UninhabitedVariants);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LL | match x {}
| ^
|
note: `IndirectUninhabitedEnum` defined here
--> $DIR/auxiliary/uninhabited.rs:26:1
--> $DIR/auxiliary/uninhabited.rs:32:1
|
LL | pub struct IndirectUninhabitedEnum(UninhabitedEnum);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -24,7 +24,7 @@ LL | match x {}
| ^
|
note: `IndirectUninhabitedStruct` defined here
--> $DIR/auxiliary/uninhabited.rs:28:1
--> $DIR/auxiliary/uninhabited.rs:34:1
|
LL | pub struct IndirectUninhabitedStruct(UninhabitedStruct);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -43,7 +43,7 @@ LL | match x {}
| ^
|
note: `IndirectUninhabitedTupleStruct` defined here
--> $DIR/auxiliary/uninhabited.rs:30:1
--> $DIR/auxiliary/uninhabited.rs:36:1
|
LL | pub struct IndirectUninhabitedTupleStruct(UninhabitedTupleStruct);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -62,7 +62,7 @@ LL | match x {}
| ^
|
note: `IndirectUninhabitedVariants` defined here
--> $DIR/auxiliary/uninhabited.rs:32:1
--> $DIR/auxiliary/uninhabited.rs:38:1
|
LL | pub struct IndirectUninhabitedVariants(UninhabitedVariants);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ use uninhabited::PartiallyInhabitedVariants;

pub fn foo(x: PartiallyInhabitedVariants) {
match x {
PartiallyInhabitedVariants::Struct { .. } => {},
PartiallyInhabitedVariants::Struct { .. } => {},
PartiallyInhabitedVariants::Struct { .. } => {}
//~^ ERROR unreachable pattern
_ => {},
PartiallyInhabitedVariants::Struct { .. } => {}
//~^ ERROR unreachable pattern
_ => {}
}
}

fn main() { }
fn main() {}
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
error: unreachable pattern
--> $DIR/issue-65157-repeated-match-arm.rs:15:9
--> $DIR/issue-65157-repeated-match-arm.rs:14:9
|
LL | PartiallyInhabitedVariants::Struct { .. } => {},
| ----------------------------------------- matches all the relevant values
LL | PartiallyInhabitedVariants::Struct { .. } => {},
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no value can reach this
LL | PartiallyInhabitedVariants::Struct { .. } => {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ matches no values because `PartiallyInhabitedVariants` is uninhabited
|
= note: to learn more about uninhabited types, see https://doc.rust-lang.org/nomicon/exotic-sizes.html#empty-types
note: the lint level is defined here
--> $DIR/issue-65157-repeated-match-arm.rs:2:9
|
LL | #![deny(unreachable_patterns)]
| ^^^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error
error: unreachable pattern
--> $DIR/issue-65157-repeated-match-arm.rs:16:9
|
LL | PartiallyInhabitedVariants::Struct { .. } => {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ matches no values because `PartiallyInhabitedVariants` is uninhabited
|
= note: to learn more about uninhabited types, see https://doc.rust-lang.org/nomicon/exotic-sizes.html#empty-types

error: aborting due to 2 previous errors

21 changes: 10 additions & 11 deletions tests/ui/rfcs/rfc-2008-non-exhaustive/uninhabited/match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@

extern crate uninhabited;

use uninhabited::{
UninhabitedEnum,
UninhabitedStruct,
UninhabitedTupleStruct,
UninhabitedVariants,
};
use uninhabited::*;

struct A;

Expand All @@ -19,16 +14,20 @@ fn cannot_empty_match_on_empty_enum_to_anything(x: UninhabitedEnum) -> A {
match x {} //~ ERROR non-exhaustive patterns
}

fn cannot_empty_match_on_empty_struct_to_anything(x: UninhabitedStruct) -> A {
match x {} //~ ERROR non-exhaustive patterns
fn empty_match_on_empty_struct(x: UninhabitedStruct) -> A {
match x {}
}

fn cannot_empty_match_on_empty_tuple_struct_to_anything(x: UninhabitedTupleStruct) -> A {
fn cannot_empty_match_on_privately_empty_struct(x: PrivatelyUninhabitedStruct) -> A {
match x {} //~ ERROR non-exhaustive patterns
}

fn cannot_empty_match_on_enum_with_empty_variants_struct_to_anything(x: UninhabitedVariants) -> A {
match x {} //~ ERROR non-exhaustive patterns
fn empty_match_on_empty_tuple_struct(x: UninhabitedTupleStruct) -> A {
match x {}
}

fn empty_match_on_enum_with_empty_variants_struct(x: UninhabitedVariants) -> A {
match x {}
}

fn main() {}
66 changes: 12 additions & 54 deletions tests/ui/rfcs/rfc-2008-non-exhaustive/uninhabited/match.stderr
Original file line number Diff line number Diff line change
@@ -1,83 +1,41 @@
error[E0004]: non-exhaustive patterns: type `UninhabitedEnum` is non-empty
--> $DIR/match.rs:19:11
error[E0004]: non-exhaustive patterns: type `uninhabited::UninhabitedEnum` is non-empty
--> $DIR/match.rs:14:11
|
LL | match x {}
| ^
|
note: `UninhabitedEnum` defined here
note: `uninhabited::UninhabitedEnum` defined here
--> $DIR/auxiliary/uninhabited.rs:5:1
|
LL | pub enum UninhabitedEnum {
| ^^^^^^^^^^^^^^^^^^^^^^^^
= note: the matched value is of type `UninhabitedEnum`, which is marked as non-exhaustive
= note: the matched value is of type `uninhabited::UninhabitedEnum`, which is marked as non-exhaustive
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
|
LL ~ match x {
LL + _ => todo!(),
LL ~ }
|

error[E0004]: non-exhaustive patterns: type `UninhabitedStruct` is non-empty
--> $DIR/match.rs:23:11
error[E0004]: non-exhaustive patterns: type `uninhabited::PrivatelyUninhabitedStruct` is non-empty
--> $DIR/match.rs:22:11
|
LL | match x {}
| ^
|
note: `UninhabitedStruct` defined here
--> $DIR/auxiliary/uninhabited.rs:9:1
note: `uninhabited::PrivatelyUninhabitedStruct` defined here
--> $DIR/auxiliary/uninhabited.rs:15:1
|
LL | pub struct UninhabitedStruct {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: the matched value is of type `UninhabitedStruct`
LL | pub struct PrivatelyUninhabitedStruct {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: the matched value is of type `uninhabited::PrivatelyUninhabitedStruct`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
|
LL ~ match x {
LL + _ => todo!(),
LL ~ }
|

error[E0004]: non-exhaustive patterns: type `UninhabitedTupleStruct` is non-empty
--> $DIR/match.rs:27:11
|
LL | match x {}
| ^
|
note: `UninhabitedTupleStruct` defined here
--> $DIR/auxiliary/uninhabited.rs:14:1
|
LL | pub struct UninhabitedTupleStruct(!);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: the matched value is of type `UninhabitedTupleStruct`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown
|
LL ~ match x {
LL + _ => todo!(),
LL ~ }
|

error[E0004]: non-exhaustive patterns: `UninhabitedVariants::Tuple(_)` and `UninhabitedVariants::Struct { .. }` not covered
--> $DIR/match.rs:31:11
|
LL | match x {}
| ^ patterns `UninhabitedVariants::Tuple(_)` and `UninhabitedVariants::Struct { .. }` not covered
|
note: `UninhabitedVariants` defined here
--> $DIR/auxiliary/uninhabited.rs:16:1
|
LL | pub enum UninhabitedVariants {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | #[non_exhaustive] Tuple(!),
| ----- not covered
LL | #[non_exhaustive] Struct { x: ! }
| ------ not covered
= note: the matched value is of type `UninhabitedVariants`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
|
LL ~ match x {
LL + UninhabitedVariants::Tuple(_) | UninhabitedVariants::Struct { .. } => todo!(),
LL ~ }
|

error: aborting due to 4 previous errors
error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0004`.
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,28 @@
extern crate uninhabited;

use uninhabited::{
UninhabitedEnum,
UninhabitedStruct,
UninhabitedTupleStruct,
UninhabitedVariants,
UninhabitedEnum, UninhabitedStruct, UninhabitedTupleStruct, UninhabitedVariants,
};

struct A;

// This test checks that an empty match on a non-exhaustive uninhabited type from an extern crate
// will not compile. In particular, this enables the `exhaustive_patterns` feature as this can
// change the branch used in the compiler to determine this.

fn cannot_empty_match_on_empty_enum_to_anything(x: UninhabitedEnum) -> A {
// This test checks that non-exhaustive enums are never considered uninhabited outside their
// defining crate, and non-exhaustive structs are considered uninhabited the same way as normal
// ones.
fn cannot_empty_match_on_non_exhaustive_empty_enum(x: UninhabitedEnum) -> A {
match x {} //~ ERROR non-exhaustive patterns
}

fn cannot_empty_match_on_empty_struct_to_anything(x: UninhabitedStruct) -> A {
match x {} //~ ERROR non-exhaustive patterns
fn empty_match_on_empty_struct(x: UninhabitedStruct) -> A {
match x {}
}

fn cannot_empty_match_on_empty_tuple_struct_to_anything(x: UninhabitedTupleStruct) -> A {
match x {} //~ ERROR non-exhaustive patterns
fn empty_match_on_empty_tuple_struct(x: UninhabitedTupleStruct) -> A {
match x {}
}

fn cannot_empty_match_on_enum_with_empty_variants_struct_to_anything(x: UninhabitedVariants) -> A {
match x {} //~ ERROR non-exhaustive patterns
fn empty_match_on_enum_with_empty_variants_struct(x: UninhabitedVariants) -> A {
match x {}
}

fn main() {}
Loading

0 comments on commit e7504ac

Please sign in to comment.