From 2bed859494e8b31b5bd1d8c6e3d055195a91852b Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 16 May 2023 01:59:47 +0200 Subject: [PATCH 01/40] Add some tests --- .../omitted-patterns.rs | 32 +++++++++++++- .../omitted-patterns.stderr | 42 ++++++++++++------- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs index 3482af74752f8..bed3ca526c1b9 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs @@ -13,8 +13,8 @@ use enums::{ EmptyNonExhaustiveEnum, NestedNonExhaustive, NonExhaustiveEnum, NonExhaustiveSingleVariant, VariantNonExhaustive, }; -use unstable::{UnstableEnum, OnlyUnstableEnum, UnstableStruct, OnlyUnstableStruct}; use structs::{FunctionalRecord, MixedVisFields, NestedStruct, NormalStruct}; +use unstable::{OnlyUnstableEnum, OnlyUnstableStruct, UnstableEnum, UnstableStruct}; #[non_exhaustive] #[derive(Default)] @@ -127,11 +127,22 @@ fn main() { _ => {} } + // No variants are mentioned #[deny(non_exhaustive_omitted_patterns)] match NonExhaustiveSingleVariant::A(true) { _ => {} } //~^^ some variants are not matched explicitly + #[deny(non_exhaustive_omitted_patterns)] + match Some(NonExhaustiveSingleVariant::A(true)) { + Some(_) => {} + None => {} + } + #[deny(non_exhaustive_omitted_patterns)] + match Some(&NonExhaustiveSingleVariant::A(true)) { + Some(_) => {} + None => {} + } // Ok: we don't lint on `if let` expressions #[deny(non_exhaustive_omitted_patterns)] @@ -202,6 +213,25 @@ fn main() { _ => {} } //~^^ some variants are not matched explicitly + + #[deny(non_exhaustive_omitted_patterns)] + match (true, &non_enum) { + (true, NonExhaustiveEnum::Unit) => {} + _ => {} + } + + #[deny(non_exhaustive_omitted_patterns)] + match (&non_enum, true) { + (NonExhaustiveEnum::Unit, true) => {} + _ => {} + } + //~^^ some variants are not matched explicitly + + #[deny(non_exhaustive_omitted_patterns)] + match Some(&non_enum) { + Some(NonExhaustiveEnum::Unit | NonExhaustiveEnum::Tuple(_)) => {} + _ => {} + } } #[deny(non_exhaustive_omitted_patterns)] diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr index 923394474b262..b604646e88e63 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr @@ -50,7 +50,7 @@ LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = Nested = note: the pattern is of type `NestedStruct` and the `non_exhaustive_omitted_patterns` attribute was found warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:173:9 + --> $DIR/omitted-patterns.rs:184:9 | LL | let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable2` not listed @@ -58,13 +58,13 @@ LL | let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `OnlyUnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:172:12 + --> $DIR/omitted-patterns.rs:183:12 | LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:181:9 + --> $DIR/omitted-patterns.rs:192:9 | LL | let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable` not listed @@ -72,7 +72,7 @@ LL | let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `UnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:180:12 + --> $DIR/omitted-patterns.rs:191:12 | LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -143,7 +143,7 @@ LL | _ => {} = note: the matched value is of type `NestedNonExhaustive` and the `non_exhaustive_omitted_patterns` attribute was found error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:132:9 + --> $DIR/omitted-patterns.rs:133:9 | LL | _ => {} | ^ pattern `NonExhaustiveSingleVariant::A(_)` not covered @@ -151,13 +151,13 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveSingleVariant` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:130:12 + --> $DIR/omitted-patterns.rs:131:12 | LL | #[deny(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:144:9 + --> $DIR/omitted-patterns.rs:155:9 | LL | _ => {} | ^ pattern `UnstableEnum::Unstable` not covered @@ -165,13 +165,13 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `UnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:143:16 + --> $DIR/omitted-patterns.rs:154:16 | LL | #[deny(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:168:9 + --> $DIR/omitted-patterns.rs:179:9 | LL | _ => {} | ^ pattern `OnlyUnstableEnum::Unstable2` not covered @@ -179,13 +179,13 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `OnlyUnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:165:12 + --> $DIR/omitted-patterns.rs:176:12 | LL | #[deny(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0005]: refutable pattern in local binding - --> $DIR/omitted-patterns.rs:194:9 + --> $DIR/omitted-patterns.rs:205:9 | LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit; | ^^^^^^^^^^^^^^^ pattern `_` not covered @@ -199,7 +199,7 @@ LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit | ++++++++++++++++ error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:202:9 + --> $DIR/omitted-patterns.rs:213:9 | LL | _ => {} | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered @@ -207,11 +207,25 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:198:12 + --> $DIR/omitted-patterns.rs:209:12 | LL | #[deny(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 10 previous errors; 6 warnings emitted +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:226:9 + | +LL | _ => {} + | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered + | + = help: ensure that all variants are matched explicitly by adding the suggested match arms + = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:223:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 11 previous errors; 6 warnings emitted For more information about this error, try `rustc --explain E0005`. From 0332402e4f655c76f16935d034b9b112d0772fee Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 16 May 2023 14:21:58 +0200 Subject: [PATCH 02/40] Don't use per-arm lint levels This makes things unnecessarily complicated for now --- compiler/rustc_lint_defs/src/builtin.rs | 2 +- .../src/thir/pattern/deconstruct_pat.rs | 1 - .../src/thir/pattern/usefulness.rs | 2 +- .../omitted-patterns.rs | 50 ++----- .../omitted-patterns.stderr | 127 +++++------------- .../stable-omitted-patterns.rs | 6 +- .../stable-omitted-patterns.stderr | 6 +- 7 files changed, 51 insertions(+), 143 deletions(-) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 6e9dc880a7dee..acc1caa5f81ae 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3817,9 +3817,9 @@ declare_lint! { /// // in crate B /// #![feature(non_exhaustive_omitted_patterns_lint)] /// + /// #[warn(non_exhaustive_omitted_patterns)] /// match Bar::A { /// Bar::A => {}, - /// #[warn(non_exhaustive_omitted_patterns)] /// _ => {}, /// } /// ``` diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 6a77146138bb5..8337199ff189b 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -1596,7 +1596,6 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { self.collect_unreachable_spans(&mut spans); spans } - fn collect_unreachable_spans(&self, spans: &mut Vec) { // We don't look at subpatterns if we already reported the whole pattern as unreachable. if !self.is_reachable() { diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index e5b6350690609..9603ee1f40fe6 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -988,7 +988,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( .map(|arm| { debug!(?arm); let v = PatStack::from_pattern(arm.pat); - is_useful(cx, &matrix, &v, RealArm, arm.hir_id, arm.has_guard, true); + is_useful(cx, &matrix, &v, RealArm, lint_root, arm.has_guard, true); if !arm.has_guard { matrix.push(v); } diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs index bed3ca526c1b9..87cfa3b868b06 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs @@ -1,6 +1,7 @@ // Test that the `non_exhaustive_omitted_patterns` lint is triggered correctly. #![feature(non_exhaustive_omitted_patterns_lint, unstable_test_feature)] +#![deny(unreachable_patterns)] // aux-build:enums.rs extern crate enums; @@ -31,6 +32,17 @@ pub enum Bar { C, } +fn no_lint() { + let non_enum = NonExhaustiveEnum::Unit; + // Ok: without the attribute + match non_enum { + NonExhaustiveEnum::Unit => {} + NonExhaustiveEnum::Tuple(_) => {} + _ => {} + } +} + +#[warn(non_exhaustive_omitted_patterns)] fn main() { let enumeration = Bar::A; @@ -38,30 +50,20 @@ fn main() { match enumeration { Bar::A => {} Bar::B => {} - #[deny(non_exhaustive_omitted_patterns)] _ => {} } let non_enum = NonExhaustiveEnum::Unit; - // Ok: without the attribute - match non_enum { - NonExhaustiveEnum::Unit => {} - NonExhaustiveEnum::Tuple(_) => {} - _ => {} - } - match non_enum { NonExhaustiveEnum::Unit => {} NonExhaustiveEnum::Tuple(_) => {} - #[deny(non_exhaustive_omitted_patterns)] _ => {} } //~^^ some variants are not matched explicitly match non_enum { NonExhaustiveEnum::Unit | NonExhaustiveEnum::Struct { .. } => {} - #[deny(non_exhaustive_omitted_patterns)] _ => {} } //~^^ some variants are not matched explicitly @@ -71,22 +73,18 @@ fn main() { NonExhaustiveEnum::Unit if x > 10 => {} NonExhaustiveEnum::Tuple(_) => {} NonExhaustiveEnum::Struct { .. } => {} - #[deny(non_exhaustive_omitted_patterns)] _ => {} } //~^^ some variants are not matched explicitly // Ok: all covered and not `unreachable-patterns` - #[deny(unreachable_patterns)] match non_enum { NonExhaustiveEnum::Unit => {} NonExhaustiveEnum::Tuple(_) => {} NonExhaustiveEnum::Struct { .. } => {} - #[deny(non_exhaustive_omitted_patterns)] _ => {} } - #[deny(non_exhaustive_omitted_patterns)] match NestedNonExhaustive::B { NestedNonExhaustive::A(NonExhaustiveEnum::Unit) => {} NestedNonExhaustive::A(_) => {} @@ -96,68 +94,56 @@ fn main() { //~^^ some variants are not matched explicitly //~^^^^^ some variants are not matched explicitly - #[warn(non_exhaustive_omitted_patterns)] match VariantNonExhaustive::Baz(1, 2) { VariantNonExhaustive::Baz(_, _) => {} VariantNonExhaustive::Bar { x, .. } => {} } //~^^ some fields are not explicitly listed - #[warn(non_exhaustive_omitted_patterns)] let FunctionalRecord { first_field, second_field, .. } = FunctionalRecord::default(); //~^ some fields are not explicitly listed // Ok: this is local - #[warn(non_exhaustive_omitted_patterns)] let Foo { a, b, .. } = Foo::default(); - #[warn(non_exhaustive_omitted_patterns)] let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = NestedStruct::default(); //~^ some fields are not explicitly listed //~^^ some fields are not explicitly listed // Ok: this tests https://github.com/rust-lang/rust/issues/89382 - #[warn(non_exhaustive_omitted_patterns)] let MixedVisFields { a, b, .. } = MixedVisFields::default(); // Ok: because this only has 1 variant - #[deny(non_exhaustive_omitted_patterns)] match NonExhaustiveSingleVariant::A(true) { NonExhaustiveSingleVariant::A(true) => {} _ => {} } // No variants are mentioned - #[deny(non_exhaustive_omitted_patterns)] match NonExhaustiveSingleVariant::A(true) { _ => {} } //~^^ some variants are not matched explicitly - #[deny(non_exhaustive_omitted_patterns)] match Some(NonExhaustiveSingleVariant::A(true)) { Some(_) => {} None => {} } - #[deny(non_exhaustive_omitted_patterns)] match Some(&NonExhaustiveSingleVariant::A(true)) { Some(_) => {} None => {} } // Ok: we don't lint on `if let` expressions - #[deny(non_exhaustive_omitted_patterns)] if let NonExhaustiveEnum::Tuple(_) = non_enum {} match UnstableEnum::Stable { UnstableEnum::Stable => {} UnstableEnum::Stable2 => {} - #[deny(non_exhaustive_omitted_patterns)] _ => {} } //~^^ some variants are not matched explicitly // Ok: the feature is on and all variants are matched - #[deny(non_exhaustive_omitted_patterns)] match UnstableEnum::Stable { UnstableEnum::Stable => {} UnstableEnum::Stable2 => {} @@ -166,47 +152,38 @@ fn main() { } // Ok: the feature is on and both variants are matched - #[deny(non_exhaustive_omitted_patterns)] match OnlyUnstableEnum::Unstable { OnlyUnstableEnum::Unstable => {} OnlyUnstableEnum::Unstable2 => {} _ => {} } - #[deny(non_exhaustive_omitted_patterns)] match OnlyUnstableEnum::Unstable { OnlyUnstableEnum::Unstable => {} _ => {} } //~^^ some variants are not matched explicitly - #[warn(non_exhaustive_omitted_patterns)] let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); //~^ some fields are not explicitly listed // OK: both unstable fields are matched with feature on - #[warn(non_exhaustive_omitted_patterns)] let OnlyUnstableStruct { unstable, unstable2, .. } = OnlyUnstableStruct::new(); - #[warn(non_exhaustive_omitted_patterns)] let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); //~^ some fields are not explicitly listed // OK: both unstable and stable fields are matched with feature on - #[warn(non_exhaustive_omitted_patterns)] let UnstableStruct { stable, stable2, unstable, .. } = UnstableStruct::default(); // Ok: local bindings are allowed - #[deny(non_exhaustive_omitted_patterns)] let local = NonExhaustiveEnum::Unit; // Ok: missing patterns will be blocked by the pattern being refutable - #[deny(non_exhaustive_omitted_patterns)] let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit; //~^ refutable pattern in local binding // Check that matching on a reference results in a correctly spanned diagnostic - #[deny(non_exhaustive_omitted_patterns)] match &non_enum { NonExhaustiveEnum::Unit => {} NonExhaustiveEnum::Tuple(_) => {} @@ -214,20 +191,17 @@ fn main() { } //~^^ some variants are not matched explicitly - #[deny(non_exhaustive_omitted_patterns)] match (true, &non_enum) { (true, NonExhaustiveEnum::Unit) => {} _ => {} } - #[deny(non_exhaustive_omitted_patterns)] match (&non_enum, true) { (NonExhaustiveEnum::Unit, true) => {} _ => {} } //~^^ some variants are not matched explicitly - #[deny(non_exhaustive_omitted_patterns)] match Some(&non_enum) { Some(NonExhaustiveEnum::Unit | NonExhaustiveEnum::Tuple(_)) => {} _ => {} diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr index b604646e88e63..1b7b80cc0b006 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr @@ -1,5 +1,5 @@ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:102:9 + --> $DIR/omitted-patterns.rs:99:9 | LL | VariantNonExhaustive::Bar { x, .. } => {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `y` not listed @@ -7,41 +7,31 @@ LL | VariantNonExhaustive::Bar { x, .. } => {} = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `VariantNonExhaustive` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:99:12 + --> $DIR/omitted-patterns.rs:45:8 | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:107:9 + --> $DIR/omitted-patterns.rs:103:9 | LL | let FunctionalRecord { first_field, second_field, .. } = FunctionalRecord::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `third_field` not listed | = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `FunctionalRecord` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:106:12 - | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:115:29 + --> $DIR/omitted-patterns.rs:109:29 | LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = NestedStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `second_field` not listed | = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `NormalStruct` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:114:12 - | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:115:9 + --> $DIR/omitted-patterns.rs:109:9 | LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = NestedStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `foo` not listed @@ -50,91 +40,61 @@ LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = Nested = note: the pattern is of type `NestedStruct` and the `non_exhaustive_omitted_patterns` attribute was found warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:184:9 + --> $DIR/omitted-patterns.rs:167:9 | LL | let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable2` not listed | = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `OnlyUnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:183:12 - | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:192:9 + --> $DIR/omitted-patterns.rs:173:9 | LL | let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable` not listed | = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `UnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:191:12 - | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:58:9 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:61:9 | LL | _ => {} | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:57:16 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:65:9 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:67:9 | LL | _ => {} | ^ pattern `NonExhaustiveEnum::Tuple(_)` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:64:16 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:75:9 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:76:9 | LL | _ => {} | ^ pattern `NonExhaustiveEnum::Unit` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:74:16 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:92:32 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:90:32 | LL | NestedNonExhaustive::A(_) => {} | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:89:12 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:94:9 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:92:9 | LL | _ => {} | ^ pattern `NestedNonExhaustive::C` not covered @@ -142,50 +102,35 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NestedNonExhaustive` and the `non_exhaustive_omitted_patterns` attribute was found -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:133:9 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:124:9 | LL | _ => {} | ^ pattern `NonExhaustiveSingleVariant::A(_)` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveSingleVariant` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:131:12 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:155:9 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:142:9 | LL | _ => {} | ^ pattern `UnstableEnum::Unstable` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `UnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:154:16 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:179:9 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:163:9 | LL | _ => {} | ^ pattern `OnlyUnstableEnum::Unstable2` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `OnlyUnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:176:12 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0005]: refutable pattern in local binding - --> $DIR/omitted-patterns.rs:205:9 + --> $DIR/omitted-patterns.rs:183:9 | LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit; | ^^^^^^^^^^^^^^^ pattern `_` not covered @@ -198,34 +143,24 @@ help: you might want to use `let else` to handle the variant that isn't matched LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit else { todo!() }; | ++++++++++++++++ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:213:9 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:190:9 | LL | _ => {} | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:209:12 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:226:9 +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:201:9 | LL | _ => {} | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found -note: the lint level is defined here - --> $DIR/omitted-patterns.rs:223:12 - | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 11 previous errors; 6 warnings emitted +error: aborting due to previous error; 16 warnings emitted For more information about this error, try `rustc --explain E0005`. diff --git a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs index 82ee68687ed00..20b51a5cf3f72 100644 --- a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs +++ b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs @@ -6,20 +6,20 @@ // aux-build:unstable.rs extern crate unstable; -use unstable::{UnstableEnum, OnlyUnstableEnum, UnstableStruct, OnlyUnstableStruct}; +use unstable::{OnlyUnstableEnum, OnlyUnstableStruct, UnstableEnum, UnstableStruct}; fn main() { // OK: this matches all the stable variants + #[deny(non_exhaustive_omitted_patterns)] match UnstableEnum::Stable { UnstableEnum::Stable => {} UnstableEnum::Stable2 => {} - #[deny(non_exhaustive_omitted_patterns)] _ => {} } + #[deny(non_exhaustive_omitted_patterns)] match UnstableEnum::Stable { UnstableEnum::Stable => {} - #[deny(non_exhaustive_omitted_patterns)] _ => {} } //~^^ some variants are not matched explicitly diff --git a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr index f38368590fb1c..5b75cfb69fe0c 100644 --- a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr +++ b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr @@ -21,10 +21,10 @@ LL | _ => {} = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `UnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/stable-omitted-patterns.rs:22:16 + --> $DIR/stable-omitted-patterns.rs:20:12 | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to previous error; 1 warning emitted From 2058de53a70b2df9a8fd96162fce4c19e8797310 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 16 May 2023 20:00:51 +0200 Subject: [PATCH 03/40] Implement lint as a separate columns-wise pass --- compiler/rustc_lint_defs/src/builtin.rs | 21 +- .../src/thir/pattern/check_match.rs | 7 +- .../src/thir/pattern/deconstruct_pat.rs | 45 +++-- .../src/thir/pattern/usefulness.rs | 183 ++++++++++++------ ...te-non_exhaustive_omitted_patterns_lint.rs | 25 ++- ...on_exhaustive_omitted_patterns_lint.stderr | 111 ++++++++--- .../omitted-patterns.rs | 27 +-- .../omitted-patterns.stderr | 107 +++++----- .../stable-omitted-patterns.rs | 2 +- .../stable-omitted-patterns.stderr | 6 +- 10 files changed, 334 insertions(+), 200 deletions(-) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index acc1caa5f81ae..d6ad54c3fa66b 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3801,8 +3801,10 @@ declare_lint! { } declare_lint! { - /// The `non_exhaustive_omitted_patterns` lint detects when a wildcard (`_` or `..`) in a - /// pattern for a `#[non_exhaustive]` struct or enum is reachable. + /// The `non_exhaustive_omitted_patterns` lint detects when some variants or fields of a + /// `#[non_exhaustive]` struct or enum are not mentioned explicitly in a pattern. This allows + /// downstream crates to be warned when new variants or fields are added to the upstream struct + /// or enum. /// /// ### Example /// @@ -3830,8 +3832,8 @@ declare_lint! { /// warning: reachable patterns not covered of non exhaustive enum /// --> $DIR/reachable-patterns.rs:70:9 /// | - /// LL | _ => {} - /// | ^ pattern `B` not covered + /// LL | match Bar::A { + /// | ^ pattern `Bar::B` not covered /// | /// note: the lint level is defined here /// --> $DIR/reachable-patterns.rs:69:16 @@ -3844,12 +3846,11 @@ declare_lint! { /// /// ### Explanation /// - /// Structs and enums tagged with `#[non_exhaustive]` force the user to add a - /// (potentially redundant) wildcard when pattern-matching, to allow for future - /// addition of fields or variants. The `non_exhaustive_omitted_patterns` lint - /// detects when such a wildcard happens to actually catch some fields/variants. - /// In other words, when the match without the wildcard would not be exhaustive. - /// This lets the user be informed if new fields/variants were added. + /// Structs and enums tagged with `#[non_exhaustive]` force the user to add a (potentially + /// redundant) wildcard when pattern-matching, to allow for future addition of fields or + /// variants. The `non_exhaustive_omitted_patterns` lint detects when such a wildcard happens to + /// actually catch some fields/variants. This lets the user be informed if new fields/variants + /// were added. pub NON_EXHAUSTIVE_OMITTED_PATTERNS, Allow, "detect when patterns of types marked `non_exhaustive` are missed", diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index ca25f83e6437e..4049bb7b65797 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -254,7 +254,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { let scrut = &self.thir[scrut]; let scrut_ty = scrut.ty; - let report = compute_match_usefulness(&cx, &tarms, self.lint_level, scrut_ty); + let report = compute_match_usefulness(&cx, &tarms, self.lint_level, scrut_ty, scrut.span); match source { // Don't report arm reachability of desugared `match $iter.into_iter() { iter => .. }` @@ -416,7 +416,8 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { let pattern = self.lower_pattern(&mut cx, pat); let pattern_ty = pattern.ty(); let arm = MatchArm { pat: pattern, hir_id: self.lint_level, has_guard: false }; - let report = compute_match_usefulness(&cx, &[arm], self.lint_level, pattern_ty); + let report = + compute_match_usefulness(&cx, &[arm], self.lint_level, pattern_ty, pattern.span()); // Note: we ignore whether the pattern is unreachable (i.e. whether the type is empty). We // only care about exhaustiveness here. @@ -584,7 +585,7 @@ fn is_let_irrefutable<'p, 'tcx>( pat: &'p DeconstructedPat<'p, 'tcx>, ) -> bool { let arms = [MatchArm { pat, hir_id: pat_id, has_guard: false }]; - let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty()); + let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty(), pat.span()); // Report if the pattern is unreachable, which can only occur when the type is uninhabited. // This also reports unreachable sub-patterns though, so we can't just replace it with an diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 8337199ff189b..04115f72c1fdb 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -643,9 +643,8 @@ pub(super) enum Constructor<'tcx> { /// for those types for which we cannot list constructors explicitly, like `f64` and `str`. NonExhaustive, /// Stands for constructors that are not seen in the matrix, as explained in the documentation - /// for [`SplitWildcard`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns` - /// lint. - Missing { nonexhaustive_enum_missing_real_variants: bool }, + /// for [`SplitWildcard`]. + Missing, /// Wildcard pattern. Wildcard, /// Or-pattern. @@ -1053,6 +1052,16 @@ impl<'tcx> SplitWildcard<'tcx> { self.all_ctors.iter().filter(move |ctor| !ctor.is_covered_by_any(pcx, &self.matrix_ctors)) } + /// Iterate over the constructors for this type that are present in the matrix. This has the + /// effect of deduplicating present constructors. + /// WARNING: this omits special constructors like `Wildcard` and `Opaque`. + pub(super) fn iter_present<'a, 'p>( + &'a self, + pcx: &'a PatCtxt<'a, 'p, 'tcx>, + ) -> impl Iterator> + Captures<'p> { + self.all_ctors.iter().filter(move |ctor| ctor.is_covered_by_any(pcx, &self.matrix_ctors)) + } + /// Return the set of constructors resulting from splitting the wildcard. As explained at the /// top of the file, if any constructors are missing we can ignore the present ones. fn into_ctors(self, pcx: &PatCtxt<'_, '_, 'tcx>) -> SmallVec<[Constructor<'tcx>; 1]> { @@ -1086,15 +1095,7 @@ impl<'tcx> SplitWildcard<'tcx> { // sometimes prefer reporting the list of constructors instead of just `_`. let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); let ctor = if !self.matrix_ctors.is_empty() || report_when_all_missing { - if pcx.is_non_exhaustive { - Missing { - nonexhaustive_enum_missing_real_variants: self - .iter_missing(pcx) - .any(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))), - } - } else { - Missing { nonexhaustive_enum_missing_real_variants: false } - } + Missing } else { Wildcard }; @@ -1240,9 +1241,10 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { /// Values and patterns can be represented as a constructor applied to some fields. This represents /// a pattern in this form. -/// This also keeps track of whether the pattern has been found reachable during analysis. For this -/// reason we should be careful not to clone patterns for which we care about that. Use -/// `clone_and_forget_reachability` if you're sure. +/// This also uses interior mutability to keep track of whether the pattern has been found reachable +/// during analysis. For this reason we should be careful not to clone patterns for which we care +/// about that. +#[derive(Clone)] pub(crate) struct DeconstructedPat<'p, 'tcx> { ctor: Constructor<'tcx>, fields: Fields<'p, 'tcx>, @@ -1273,12 +1275,6 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { DeconstructedPat::new(ctor, fields, pcx.ty, pcx.span) } - /// Clone this value. This method emphasizes that cloning loses reachability information and - /// should be done carefully. - pub(super) fn clone_and_forget_reachability(&self) -> Self { - DeconstructedPat::new(self.ctor.clone(), self.fields, self.ty, self.span) - } - pub(crate) fn from_pat(cx: &MatchCheckCtxt<'p, 'tcx>, pat: &Pat<'tcx>) -> Self { let mkpat = |pat| DeconstructedPat::from_pat(cx, pat); let ctor; @@ -1522,6 +1518,13 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { pub(super) fn is_or_pat(&self) -> bool { matches!(self.ctor, Or) } + pub(super) fn flatten_or_pat(&'p self) -> SmallVec<[&'p Self; 1]> { + if self.is_or_pat() { + self.iter_fields().flat_map(|p| p.flatten_or_pat()).collect() + } else { + smallvec![self] + } + } pub(super) fn ctor(&self) -> &Constructor<'tcx> { &self.ctor diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 9603ee1f40fe6..263c51756c6a9 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -658,14 +658,7 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> { .into_iter() .flat_map(|witness| { new_patterns.iter().map(move |pat| { - Witness( - witness - .0 - .iter() - .chain(once(pat)) - .map(DeconstructedPat::clone_and_forget_reachability) - .collect(), - ) + Witness(witness.0.iter().chain(once(pat)).cloned().collect()) }) }) .collect() @@ -858,7 +851,6 @@ fn is_useful<'p, 'tcx>( } // We split the head constructor of `v`. let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - let is_non_exhaustive_and_wild = is_non_exhaustive && v_ctor.is_wildcard(); // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. let start_matrix = &matrix; @@ -879,56 +871,6 @@ fn is_useful<'p, 'tcx>( ) }); let usefulness = usefulness.apply_constructor(pcx, start_matrix, &ctor); - - // When all the conditions are met we have a match with a `non_exhaustive` enum - // that has the potential to trigger the `non_exhaustive_omitted_patterns` lint. - // To understand the workings checkout `Constructor::split` and `SplitWildcard::new/into_ctors` - if is_non_exhaustive_and_wild - // Only emit a lint on refutable patterns. - && cx.refutable - // We check that the match has a wildcard pattern and that wildcard is useful, - // meaning there are variants that are covered by the wildcard. Without the check - // for `witness_preference` the lint would trigger on `if let NonExhaustiveEnum::A = foo {}` - && usefulness.is_useful() && matches!(witness_preference, RealArm) - && matches!( - &ctor, - Constructor::Missing { nonexhaustive_enum_missing_real_variants: true } - ) - { - let patterns = { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - // Construct for each missing constructor a "wild" version of this - // constructor, that matches everything that can be built with - // it. For example, if `ctor` is a `Constructor::Variant` for - // `Option::Some`, we get the pattern `Some(_)`. - split_wildcard - .iter_missing(pcx) - // Filter out the `NonExhaustive` because we want to list only real - // variants. Also remove any unstable feature gated variants. - // Because of how we computed `nonexhaustive_enum_missing_real_variants`, - // this will not return an empty `Vec`. - .filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))) - .cloned() - .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)) - .collect::>() - }; - - // Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns` - // is not exhaustive enough. - // - // NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`. - cx.tcx.emit_spanned_lint( - NON_EXHAUSTIVE_OMITTED_PATTERNS, - lint_root, - pcx.span, - NonExhaustiveOmittedPattern { - scrut_ty: pcx.ty, - uncovered: Uncovered::new(pcx.span, pcx.cx, patterns), - }, - ); - } - ret.extend(usefulness); } } @@ -940,6 +882,96 @@ fn is_useful<'p, 'tcx>( ret } +/// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned +/// in a given column. This traverses patterns column-by-column, where a column is the intuitive +/// notion of "subpatterns that inspect the same subvalue". +/// Despite similarities with `is_useful`, this traversal is different. Notably this is linear in the +/// depth of patterns, whereas `is_useful` is worst-case exponential (exhaustiveness is NP-complete). +fn collect_nonexhaustive_missing_variants<'p, 'tcx>( + cx: &MatchCheckCtxt<'p, 'tcx>, + column: &[&DeconstructedPat<'p, 'tcx>], +) -> Vec> { + let mut witnesses = Vec::new(); + // If the column is all wildcards, we don't want to collect anything and we can't recurse + // further. + let is_all_wildcards = column.iter().all(|p| p.ctor().is_wildcard()); + if column.is_empty() || is_all_wildcards { + return witnesses; + } + + let ty = column[0].ty(); + let span = column[0].span(); + let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); + let pcx = &PatCtxt { cx, ty, span, is_top_level: false, is_non_exhaustive }; + + let mut split_wildcard = SplitWildcard::new(pcx); + split_wildcard.split(pcx, column.iter().map(|p| p.ctor())); + + if is_non_exhaustive { + witnesses.extend( + split_wildcard + .iter_missing(pcx) + // Filter out the `NonExhaustive` ctor because we want to list only real variants. + // Also remove any unstable feature gated variants. + .filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))) + .cloned() + .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)), + ) + } + + // Recurse into the fields. + for ctor in split_wildcard.iter_present(pcx) { + let arity = ctor.arity(pcx); + if arity == 0 { + continue; + } + + // We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These + // columns may not have the same length in the presence of or-patterns (this is why we can't + // reuse what `Matrix` does). + let mut specialized_columns: Vec> = (0..arity).map(|_| Vec::new()).collect(); + let relevant_patterns = column.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor())); + for pat in relevant_patterns { + let specialized = pat.specialize(pcx, &ctor); + for (subpat, sub_column) in specialized.iter().zip(&mut specialized_columns) { + if subpat.is_or_pat() { + sub_column.extend(subpat.iter_fields()) + } else { + sub_column.push(subpat) + } + } + } + assert!( + !specialized_columns[0].is_empty(), + "ctor {ctor:?} was listed as present but isn't" + ); + + for (i, col_i) in specialized_columns.iter().enumerate() { + // Compute witnesses for each column. + let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i.as_slice()); + if wits_for_col_i.is_empty() { + continue; + } + // For each witness, we build a new pattern in the shape of `ctor(_, _, wit, _, _)`, + // adding enough wildcards to match `arity`. + let fields = Fields::wildcards(pcx, &ctor); + for wit in wits_for_col_i { + let fields_with_wit = Fields::from_iter( + cx, + fields + .iter_patterns() + .enumerate() + .map(|(j, wild)| if i == j { &wit } else { wild }) + .cloned(), + ); + let pat = DeconstructedPat::new(ctor.clone(), fields_with_wit, ty, DUMMY_SP); + witnesses.push(pat); + } + } + } + witnesses +} + /// The arm of a match expression. #[derive(Clone, Copy, Debug)] pub(crate) struct MatchArm<'p, 'tcx> { @@ -980,6 +1012,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( arms: &[MatchArm<'p, 'tcx>], lint_root: HirId, scrut_ty: Ty<'tcx>, + scrut_span: Span, ) -> UsefulnessReport<'p, 'tcx> { let mut matrix = Matrix::empty(); let arm_usefulness: Vec<_> = arms @@ -1004,9 +1037,39 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); let v = PatStack::from_pattern(wild_pattern); let usefulness = is_useful(cx, &matrix, &v, FakeExtraWildcard, lint_root, false, true); - let non_exhaustiveness_witnesses = match usefulness { + let non_exhaustiveness_witnesses: Vec<_> = match usefulness { WithWitnesses(pats) => pats.into_iter().map(|w| w.single_pattern()).collect(), NoWitnesses { .. } => bug!(), }; + + // Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hittin + // `if let`s. Only run if the match is exhaustive otherwise the error is redundant. + if cx.refutable + && non_exhaustiveness_witnesses.is_empty() + && !matches!( + cx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, lint_root).0, + rustc_session::lint::Level::Allow + ) + { + let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::>(); + let witnesses = collect_nonexhaustive_missing_variants(cx, &pat_column); + + if !witnesses.is_empty() { + // Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns` + // is not exhaustive enough. + // + // NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`. + cx.tcx.emit_spanned_lint( + NON_EXHAUSTIVE_OMITTED_PATTERNS, + lint_root, + scrut_span, + NonExhaustiveOmittedPattern { + scrut_ty, + uncovered: Uncovered::new(scrut_span, cx, witnesses), + }, + ); + } + } + UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses } } diff --git a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs index 9b646060adfd9..c14fa88507746 100644 --- a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs +++ b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs @@ -3,13 +3,17 @@ #![deny(non_exhaustive_omitted_patterns)] //~^ WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` +//~| WARNING unknown lint: `non_exhaustive_omitted_patterns` #![allow(non_exhaustive_omitted_patterns)] //~^ WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` +//~| WARNING unknown lint: `non_exhaustive_omitted_patterns` fn main() { enum Foo { - A, B, C, + A, + B, + C, } #[allow(non_exhaustive_omitted_patterns)] @@ -17,18 +21,21 @@ fn main() { //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` + //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` match Foo::A { - Foo::A => {} - Foo::B => {} + _ => {} } - //~^^^^ ERROR non-exhaustive patterns: `Foo::C` not covered + #[warn(non_exhaustive_omitted_patterns)] + //~^ WARNING unknown lint: `non_exhaustive_omitted_patterns` + //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` + //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` + //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` + //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` match Foo::A { - Foo::A => {} - Foo::B => {} - #[warn(non_exhaustive_omitted_patterns)] _ => {} } - //~^^^ WARNING unknown lint: `non_exhaustive_omitted_patterns` - //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` + + match Foo::A {} + //~^ ERROR non-exhaustive patterns } diff --git a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr index fb39c404c207e..b17e9a1a25d6e 100644 --- a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr +++ b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr @@ -10,7 +10,7 @@ LL | #![deny(non_exhaustive_omitted_patterns)] = note: `#[warn(unknown_lints)]` on by default warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:6:1 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:7:1 | LL | #![allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,7 +20,7 @@ LL | #![allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:15:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:15:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,10 +40,20 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:9 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the `non_exhaustive_omitted_patterns` lint is unstable + = note: see issue #89554 for more information + = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable + +warning: unknown lint: `non_exhaustive_omitted_patterns` + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 + | +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the `non_exhaustive_omitted_patterns` lint is unstable = note: see issue #89554 for more information @@ -60,7 +70,7 @@ LL | #![deny(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:6:1 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:7:1 | LL | #![allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -70,7 +80,7 @@ LL | #![allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:15:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -80,7 +90,57 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:15:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 + | +LL | #[allow(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the `non_exhaustive_omitted_patterns` lint is unstable + = note: see issue #89554 for more information + = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable + +warning: unknown lint: `non_exhaustive_omitted_patterns` + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 + | +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the `non_exhaustive_omitted_patterns` lint is unstable + = note: see issue #89554 for more information + = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable + +warning: unknown lint: `non_exhaustive_omitted_patterns` + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 + | +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the `non_exhaustive_omitted_patterns` lint is unstable + = note: see issue #89554 for more information + = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable + +warning: unknown lint: `non_exhaustive_omitted_patterns` + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:3:1 + | +LL | #![deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the `non_exhaustive_omitted_patterns` lint is unstable + = note: see issue #89554 for more information + = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable + +warning: unknown lint: `non_exhaustive_omitted_patterns` + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:7:1 + | +LL | #![allow(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the `non_exhaustive_omitted_patterns` lint is unstable + = note: see issue #89554 for more information + = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable + +warning: unknown lint: `non_exhaustive_omitted_patterns` + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -90,35 +150,40 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:9 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the `non_exhaustive_omitted_patterns` lint is unstable = note: see issue #89554 for more information = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable -error[E0004]: non-exhaustive patterns: `Foo::C` not covered - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:20:11 +error[E0004]: non-exhaustive patterns: `Foo::A`, `Foo::B` and `Foo::C` not covered + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:39:11 | -LL | match Foo::A { - | ^^^^^^ pattern `Foo::C` not covered +LL | match Foo::A {} + | ^^^^^^ patterns `Foo::A`, `Foo::B` and `Foo::C` not covered | note: `Foo` defined here - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:12:15 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:14:9 | LL | enum Foo { | --- -LL | A, B, C, - | ^ not covered +LL | A, + | ^ not covered +LL | B, + | ^ not covered +LL | C, + | ^ not covered = note: the matched value is of type `Foo` -help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown +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 ~ Foo::B => {}, -LL + Foo::C => todo!() +LL ~ match Foo::A { +LL + Foo::A | Foo::B | Foo::C => todo!(), +LL + } | -error: aborting due to previous error; 10 warnings emitted +error: aborting due to previous error; 16 warnings emitted For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs index 87cfa3b868b06..17e1b3363c567 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs @@ -56,17 +56,17 @@ fn main() { let non_enum = NonExhaustiveEnum::Unit; match non_enum { + //~^ some variants are not matched explicitly NonExhaustiveEnum::Unit => {} NonExhaustiveEnum::Tuple(_) => {} _ => {} } - //~^^ some variants are not matched explicitly match non_enum { + //~^ some variants are not matched explicitly NonExhaustiveEnum::Unit | NonExhaustiveEnum::Struct { .. } => {} _ => {} } - //~^^ some variants are not matched explicitly let x = 5; match non_enum { @@ -75,7 +75,6 @@ fn main() { NonExhaustiveEnum::Struct { .. } => {} _ => {} } - //~^^ some variants are not matched explicitly // Ok: all covered and not `unreachable-patterns` match non_enum { @@ -86,13 +85,12 @@ fn main() { } match NestedNonExhaustive::B { + //~^ some variants are not matched explicitly NestedNonExhaustive::A(NonExhaustiveEnum::Unit) => {} NestedNonExhaustive::A(_) => {} NestedNonExhaustive::B => {} _ => {} } - //~^^ some variants are not matched explicitly - //~^^^^^ some variants are not matched explicitly match VariantNonExhaustive::Baz(1, 2) { VariantNonExhaustive::Baz(_, _) => {} @@ -119,11 +117,10 @@ fn main() { _ => {} } - // No variants are mentioned + // Don't lint if no variant is mentioned, because we can't do it consistently. match NonExhaustiveSingleVariant::A(true) { _ => {} } - //~^^ some variants are not matched explicitly match Some(NonExhaustiveSingleVariant::A(true)) { Some(_) => {} None => {} @@ -137,11 +134,11 @@ fn main() { if let NonExhaustiveEnum::Tuple(_) = non_enum {} match UnstableEnum::Stable { + //~^ some variants are not matched explicitly UnstableEnum::Stable => {} UnstableEnum::Stable2 => {} _ => {} } - //~^^ some variants are not matched explicitly // Ok: the feature is on and all variants are matched match UnstableEnum::Stable { @@ -159,10 +156,10 @@ fn main() { } match OnlyUnstableEnum::Unstable { + //~^ some variants are not matched explicitly OnlyUnstableEnum::Unstable => {} _ => {} } - //~^^ some variants are not matched explicitly let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); //~^ some fields are not explicitly listed @@ -183,26 +180,32 @@ fn main() { let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit; //~^ refutable pattern in local binding - // Check that matching on a reference results in a correctly spanned diagnostic + // Check that matching on a reference results in a correct diagnostic match &non_enum { + //~^ some variants are not matched explicitly + //~| pattern `&NonExhaustiveEnum::Struct { .. }` not covered NonExhaustiveEnum::Unit => {} NonExhaustiveEnum::Tuple(_) => {} _ => {} } - //~^^ some variants are not matched explicitly match (true, &non_enum) { + //~^ some variants are not matched explicitly + //~| patterns `(_, &NonExhaustiveEnum::Tuple(_))` and `(_, &NonExhaustiveEnum::Struct { .. })` not covered (true, NonExhaustiveEnum::Unit) => {} _ => {} } match (&non_enum, true) { + //~^ some variants are not matched explicitly + //~| patterns `(&NonExhaustiveEnum::Tuple(_), _)` and `(&NonExhaustiveEnum::Struct { .. }, _)` not covered (NonExhaustiveEnum::Unit, true) => {} _ => {} } - //~^^ some variants are not matched explicitly match Some(&non_enum) { + //~^ some variants are not matched explicitly + //~| pattern `Some(&NonExhaustiveEnum::Struct { .. })` not covered Some(NonExhaustiveEnum::Unit | NonExhaustiveEnum::Tuple(_)) => {} _ => {} } diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr index 1b7b80cc0b006..3bdad4a360620 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr @@ -1,5 +1,5 @@ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:99:9 + --> $DIR/omitted-patterns.rs:97:9 | LL | VariantNonExhaustive::Bar { x, .. } => {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `y` not listed @@ -13,7 +13,7 @@ LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:103:9 + --> $DIR/omitted-patterns.rs:101:9 | LL | let FunctionalRecord { first_field, second_field, .. } = FunctionalRecord::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `third_field` not listed @@ -22,7 +22,7 @@ LL | let FunctionalRecord { first_field, second_field, .. } = FunctionalReco = note: the pattern is of type `FunctionalRecord` and the `non_exhaustive_omitted_patterns` attribute was found warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:109:29 + --> $DIR/omitted-patterns.rs:107:29 | LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = NestedStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `second_field` not listed @@ -31,7 +31,7 @@ LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = Nested = note: the pattern is of type `NormalStruct` and the `non_exhaustive_omitted_patterns` attribute was found warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:109:9 + --> $DIR/omitted-patterns.rs:107:9 | LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = NestedStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `foo` not listed @@ -40,7 +40,7 @@ LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = Nested = note: the pattern is of type `NestedStruct` and the `non_exhaustive_omitted_patterns` attribute was found warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:167:9 + --> $DIR/omitted-patterns.rs:164:9 | LL | let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable2` not listed @@ -49,7 +49,7 @@ LL | let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); = note: the pattern is of type `OnlyUnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:173:9 + --> $DIR/omitted-patterns.rs:170:9 | LL | let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable` not listed @@ -58,79 +58,52 @@ LL | let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); = note: the pattern is of type `UnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:61:9 + --> $DIR/omitted-patterns.rs:58:11 | -LL | _ => {} - | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered +LL | match non_enum { + | ^^^^^^^^ pattern `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:67:9 + --> $DIR/omitted-patterns.rs:65:11 | -LL | _ => {} - | ^ pattern `NonExhaustiveEnum::Tuple(_)` not covered +LL | match non_enum { + | ^^^^^^^^ pattern `NonExhaustiveEnum::Tuple(_)` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:76:9 + --> $DIR/omitted-patterns.rs:87:11 | -LL | _ => {} - | ^ pattern `NonExhaustiveEnum::Unit` not covered - | - = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found - -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:90:32 - | -LL | NestedNonExhaustive::A(_) => {} - | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered - | - = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found - -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:92:9 - | -LL | _ => {} - | ^ pattern `NestedNonExhaustive::C` not covered +LL | match NestedNonExhaustive::B { + | ^^^^^^^^^^^^^^^^^^^^^^ patterns `NestedNonExhaustive::C`, `NestedNonExhaustive::A(NonExhaustiveEnum::Tuple(_))` and `NestedNonExhaustive::A(NonExhaustiveEnum::Struct { .. })` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NestedNonExhaustive` and the `non_exhaustive_omitted_patterns` attribute was found warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:124:9 + --> $DIR/omitted-patterns.rs:136:11 | -LL | _ => {} - | ^ pattern `NonExhaustiveSingleVariant::A(_)` not covered - | - = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `NonExhaustiveSingleVariant` and the `non_exhaustive_omitted_patterns` attribute was found - -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:142:9 - | -LL | _ => {} - | ^ pattern `UnstableEnum::Unstable` not covered +LL | match UnstableEnum::Stable { + | ^^^^^^^^^^^^^^^^^^^^ pattern `UnstableEnum::Unstable` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `UnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:163:9 + --> $DIR/omitted-patterns.rs:158:11 | -LL | _ => {} - | ^ pattern `OnlyUnstableEnum::Unstable2` not covered +LL | match OnlyUnstableEnum::Unstable { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ pattern `OnlyUnstableEnum::Unstable2` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `OnlyUnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found error[E0005]: refutable pattern in local binding - --> $DIR/omitted-patterns.rs:183:9 + --> $DIR/omitted-patterns.rs:180:9 | LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit; | ^^^^^^^^^^^^^^^ pattern `_` not covered @@ -144,23 +117,41 @@ LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit | ++++++++++++++++ warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:190:9 + --> $DIR/omitted-patterns.rs:184:11 | -LL | _ => {} - | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered +LL | match &non_enum { + | ^^^^^^^^^ pattern `&NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found + = note: the matched value is of type `&NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:201:9 + --> $DIR/omitted-patterns.rs:192:11 | -LL | _ => {} - | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered +LL | match (true, &non_enum) { + | ^^^^^^^^^^^^^^^^^ patterns `(_, &NonExhaustiveEnum::Tuple(_))` and `(_, &NonExhaustiveEnum::Struct { .. })` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found + = note: the matched value is of type `(bool, &NonExhaustiveEnum)` and the `non_exhaustive_omitted_patterns` attribute was found + +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:199:11 + | +LL | match (&non_enum, true) { + | ^^^^^^^^^^^^^^^^^ patterns `(&NonExhaustiveEnum::Tuple(_), _)` and `(&NonExhaustiveEnum::Struct { .. }, _)` not covered + | + = help: ensure that all variants are matched explicitly by adding the suggested match arms + = note: the matched value is of type `(&NonExhaustiveEnum, bool)` and the `non_exhaustive_omitted_patterns` attribute was found + +warning: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:206:11 + | +LL | match Some(&non_enum) { + | ^^^^^^^^^^^^^^^ pattern `Some(&NonExhaustiveEnum::Struct { .. })` not covered + | + = help: ensure that all variants are matched explicitly by adding the suggested match arms + = note: the matched value is of type `Option<&NonExhaustiveEnum>` and the `non_exhaustive_omitted_patterns` attribute was found -error: aborting due to previous error; 16 warnings emitted +error: aborting due to previous error; 15 warnings emitted For more information about this error, try `rustc --explain E0005`. diff --git a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs index 20b51a5cf3f72..1828fdef90139 100644 --- a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs +++ b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs @@ -19,10 +19,10 @@ fn main() { #[deny(non_exhaustive_omitted_patterns)] match UnstableEnum::Stable { + //~^ some variants are not matched explicitly UnstableEnum::Stable => {} _ => {} } - //~^^ some variants are not matched explicitly // Ok: although this is a bit odd, we don't have anything to report // since there is no stable variants and the feature is off diff --git a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr index 5b75cfb69fe0c..27939176f75ed 100644 --- a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr +++ b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr @@ -13,10 +13,10 @@ LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: some variants are not matched explicitly - --> $DIR/stable-omitted-patterns.rs:23:9 + --> $DIR/stable-omitted-patterns.rs:21:11 | -LL | _ => {} - | ^ pattern `UnstableEnum::Stable2` not covered +LL | match UnstableEnum::Stable { + | ^^^^^^^^^^^^^^^^^^^^ pattern `UnstableEnum::Stable2` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `UnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found From 0361bf2b08e91b534d44ffe42b182656efbdaf3f Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 15 May 2023 18:37:02 +0200 Subject: [PATCH 04/40] Abstract over list of witnesses --- .../src/thir/pattern/usefulness.rs | 227 ++++++++++-------- 1 file changed, 132 insertions(+), 95 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 263c51756c6a9..750116a53135c 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -322,7 +322,6 @@ use rustc_span::{Span, DUMMY_SP}; use smallvec::{smallvec, SmallVec}; use std::fmt; -use std::iter::once; pub(crate) struct MatchCheckCtxt<'p, 'tcx> { pub(crate) tcx: TyCtxt<'tcx>, @@ -563,21 +562,21 @@ enum Usefulness<'p, 'tcx> { NoWitnesses { useful: bool }, /// Carries a list of witnesses of non-exhaustiveness. If empty, indicates that the whole /// pattern is unreachable. - WithWitnesses(Vec>), + WithWitnesses(WitnessMatrix<'p, 'tcx>), } impl<'p, 'tcx> Usefulness<'p, 'tcx> { fn new_useful(preference: ArmType) -> Self { match preference { // A single (empty) witness of reachability. - FakeExtraWildcard => WithWitnesses(vec![Witness(vec![])]), + FakeExtraWildcard => WithWitnesses(WitnessMatrix::new_unit()), RealArm => NoWitnesses { useful: true }, } } fn new_not_useful(preference: ArmType) -> Self { match preference { - FakeExtraWildcard => WithWitnesses(vec![]), + FakeExtraWildcard => WithWitnesses(WitnessMatrix::new_empty()), RealArm => NoWitnesses { useful: false }, } } @@ -613,62 +612,9 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> { ) -> Self { match self { NoWitnesses { .. } => self, - WithWitnesses(ref witnesses) if witnesses.is_empty() => self, - WithWitnesses(witnesses) => { - let new_witnesses = if let Constructor::Missing { .. } = ctor { - // We got the special `Missing` constructor, so each of the missing constructors - // gives a new pattern that is not caught by the match. We list those patterns. - let new_patterns = if pcx.is_non_exhaustive { - // Here we don't want the user to try to list all variants, we want them to add - // a wildcard, so we only suggest that. - vec![DeconstructedPat::wildcard(pcx.ty, pcx.span)] - } else { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - - // This lets us know if we skipped any variants because they are marked - // `doc(hidden)` or they are unstable feature gate (only stdlib types). - let mut hide_variant_show_wild = false; - // Construct for each missing constructor a "wild" version of this - // constructor, that matches everything that can be built with - // it. For example, if `ctor` is a `Constructor::Variant` for - // `Option::Some`, we get the pattern `Some(_)`. - let mut new: Vec> = split_wildcard - .iter_missing(pcx) - .filter_map(|missing_ctor| { - // Check if this variant is marked `doc(hidden)` - if missing_ctor.is_doc_hidden_variant(pcx) - || missing_ctor.is_unstable_variant(pcx) - { - hide_variant_show_wild = true; - return None; - } - Some(DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone())) - }) - .collect(); - - if hide_variant_show_wild { - new.push(DeconstructedPat::wildcard(pcx.ty, pcx.span)); - } - - new - }; - - witnesses - .into_iter() - .flat_map(|witness| { - new_patterns.iter().map(move |pat| { - Witness(witness.0.iter().chain(once(pat)).cloned().collect()) - }) - }) - .collect() - } else { - witnesses - .into_iter() - .map(|witness| witness.apply_constructor(pcx, &ctor)) - .collect() - }; - WithWitnesses(new_witnesses) + WithWitnesses(mut witnesses) => { + witnesses.apply_constructor(pcx, matrix, ctor); + WithWitnesses(witnesses) } } } @@ -680,16 +626,14 @@ enum ArmType { RealArm, } -/// A witness of non-exhaustiveness for error reporting, represented -/// as a list of patterns (in reverse order of construction) with -/// wildcards inside to represent elements that can take any inhabitant -/// of the type as a value. +/// A partially-constructed witness of non-exhaustiveness for error reporting, represented as a list +/// of patterns (in reverse order of construction). This is very similar to `PatStack`, with two +/// differences: the patterns are stored in the opposite order for historical reasons, and where the +/// API of `PatStack` is oriented towards deconstructing it by peeling layers off, this is oriented +/// towards reconstructing by rewrapping layers. /// -/// A witness against a list of patterns should have the same types -/// and length as the pattern matched against. Because Rust `match` -/// is always against a single pattern, at the end the witness will -/// have length 1, but in the middle of the algorithm, it can contain -/// multiple patterns. +/// This should have the same length and types as the `PatStack`s and `Matrix` it is constructed +/// nearby of. At the end of the algorithm this will have length 1 and represent and actual pattern. /// /// For example, if we are constructing a witness for the match against /// @@ -705,50 +649,143 @@ enum ArmType { /// /// We'll perform the following steps: /// 1. Start with an empty witness -/// `Witness(vec![])` +/// `WitnessStack(vec![])` /// 2. Push a witness `true` against the `false` -/// `Witness(vec![true])` +/// `WitnessStack(vec![true])` /// 3. Push a witness `Some(_)` against the `None` -/// `Witness(vec![true, Some(_)])` +/// `WitnessStack(vec![true, Some(_)])` /// 4. Apply the `Pair` constructor to the witnesses -/// `Witness(vec![Pair(Some(_), true)])` +/// `WitnessStack(vec![Pair(Some(_), true)])` /// /// The final `Pair(Some(_), true)` is then the resulting witness. -#[derive(Debug)] -pub(crate) struct Witness<'p, 'tcx>(Vec>); +#[derive(Debug, Clone)] +struct WitnessStack<'p, 'tcx>(Vec>); -impl<'p, 'tcx> Witness<'p, 'tcx> { +impl<'p, 'tcx> WitnessStack<'p, 'tcx> { /// Asserts that the witness contains a single pattern, and returns it. fn single_pattern(self) -> DeconstructedPat<'p, 'tcx> { assert_eq!(self.0.len(), 1); self.0.into_iter().next().unwrap() } - /// Constructs a partial witness for a pattern given a list of - /// patterns expanded by the specialization step. - /// - /// When a pattern P is discovered to be useful, this function is used bottom-up - /// to reconstruct a complete witness, e.g., a pattern P' that covers a subset - /// of values, V, where each value in that set is not covered by any previously - /// used patterns and is covered by the pattern P'. Examples: + /// Reverses specialization. Given a witness obtained after specialization, this constructs a + /// new witness valid for before specialization. Examples: /// - /// left_ty: tuple of 3 elements - /// pats: [10, 20, _] => (10, 20, _) + /// ctor: tuple of 2 elements + /// pats: [false, "foo", _, true] + /// result: [(false, "foo"), _, true] /// - /// left_ty: struct X { a: (bool, &'static str), b: usize} - /// pats: [(false, "foo"), 42] => X { a: (false, "foo"), b: 42 } - fn apply_constructor(mut self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>) -> Self { - let pat = { - let len = self.0.len(); - let arity = ctor.arity(pcx); + /// ctor: Enum::Variant { a: (bool, &'static str), b: usize} + /// pats: [(false, "foo"), _, true] + /// result: [Enum::Variant { a: (false, "foo"), b: _ }, true] + fn apply_constructor(&mut self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>) { + let len = self.0.len(); + let arity = ctor.arity(pcx); + let fields = { let pats = self.0.drain((len - arity)..).rev(); - let fields = Fields::from_iter(pcx.cx, pats); - DeconstructedPat::new(ctor.clone(), fields, pcx.ty, pcx.span) + Fields::from_iter(pcx.cx, pats) }; + let pat = DeconstructedPat::new(ctor.clone(), fields, pcx.ty, pcx.span); self.0.push(pat); + } + + /// Reverses specialization by the `Missing` constructor by pushing a whole new pattern. + fn push_pattern(&mut self, pat: DeconstructedPat<'p, 'tcx>) { + self.0.push(pat); + } +} + +/// Represents a set of partially-constructed witnesses of non-exhaustiveness for error reporting. +/// This has similar invariants as `Matrix` does. +/// Throughout the exhaustiveness phase of the algorithm, `is_useful` maintains the invariant that +/// the union of the `Matrix` and the `WitnessMatrix` together matches the type exhaustively. By the +/// end of the algorithm, this has a single column, which contains the patterns that are missing for +/// the match to be exhaustive. +#[derive(Debug, Clone)] +pub struct WitnessMatrix<'p, 'tcx>(Vec>); + +impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { + /// New matrix with no rows. + fn new_empty() -> Self { + WitnessMatrix(vec![]) + } + /// New matrix with one row and no columns. + fn new_unit() -> Self { + WitnessMatrix(vec![WitnessStack(vec![])]) + } + + /// Whether this has any rows. + fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Asserts that there is a single column and returns the patterns in it. + fn single_column(self) -> Vec> { + self.0.into_iter().map(|w| w.single_pattern()).collect() + } + + /// Reverses specialization by the `Missing` constructor. This constructs a "wild" version of + /// `ctor`, that matches everything that can be built with it. For example, if `ctor` is a + /// `Constructor::Variant` for `Option::Some`, we get the pattern `Some(_)`. We push repeated + /// copies of that pattern to all rows. + fn push_wild_ctor(&mut self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: Constructor<'tcx>) { + let pat = DeconstructedPat::wild_from_ctor(pcx, ctor); + for witness in self.0.iter_mut() { + witness.push_pattern(pat.clone()) + } + } - self + /// Reverses specialization by `ctor`. + fn apply_constructor( + &mut self, + pcx: &PatCtxt<'_, 'p, 'tcx>, + matrix: &Matrix<'p, 'tcx>, // used to compute missing ctors + ctor: &Constructor<'tcx>, + ) { + if self.is_empty() { + return; + } + if !matches!(ctor, Constructor::Missing { .. }) { + for witness in self.0.iter_mut() { + witness.apply_constructor(pcx, ctor) + } + } else if pcx.is_non_exhaustive { + // Here we don't want the user to try to list all variants, we want them to add a + // wildcard, so we only suggest that. + self.push_wild_ctor(pcx, Constructor::Wildcard); + } else { + // We got the special `Missing` constructor, so each of the missing constructors gives a + // new pattern that is not caught by the match. We list those patterns and push them + // onto our current witnesses. + let old_witnesses = std::mem::replace(self, Self::new_empty()); + + let mut split_wildcard = SplitWildcard::new(pcx); + split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); + + // Skip any variants that are `doc(hidden)` or behind an unstable feature gate. + // We replace them with a wildcard. + let mut skipped_any_variant = false; + for missing_ctor in split_wildcard.iter_missing(pcx) { + if missing_ctor.is_doc_hidden_variant(pcx) || missing_ctor.is_unstable_variant(pcx) + { + skipped_any_variant = true; + } else { + let mut witnesses_with_missing_ctor = old_witnesses.clone(); + witnesses_with_missing_ctor.push_wild_ctor(pcx, missing_ctor.clone()); + self.extend(witnesses_with_missing_ctor) + } + } + if skipped_any_variant { + let mut witnesses_with_wildcard = old_witnesses; + witnesses_with_wildcard.push_wild_ctor(pcx, Constructor::Wildcard); + self.extend(witnesses_with_wildcard) + } + } + } + + /// Merges the rows of two witness matrices. Their column types must match. + fn extend(&mut self, other: Self) { + self.0.extend(other.0) } } @@ -1038,7 +1075,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( let v = PatStack::from_pattern(wild_pattern); let usefulness = is_useful(cx, &matrix, &v, FakeExtraWildcard, lint_root, false, true); let non_exhaustiveness_witnesses: Vec<_> = match usefulness { - WithWitnesses(pats) => pats.into_iter().map(|w| w.single_pattern()).collect(), + WithWitnesses(pats) => pats.single_column(), NoWitnesses { .. } => bug!(), }; From 49b158141d1b75b8471f1ee89f628ad5748d4a4d Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 15 May 2023 21:23:09 +0200 Subject: [PATCH 05/40] Add some or-pat tests --- tests/ui/or-patterns/exhaustiveness-pass.rs | 8 +++ .../exhaustiveness-unreachable-pattern.rs | 10 +++ .../exhaustiveness-unreachable-pattern.stderr | 72 ++++++++++++------- 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/tests/ui/or-patterns/exhaustiveness-pass.rs b/tests/ui/or-patterns/exhaustiveness-pass.rs index e8c8a0e7ba52a..1b8793101841c 100644 --- a/tests/ui/or-patterns/exhaustiveness-pass.rs +++ b/tests/ui/or-patterns/exhaustiveness-pass.rs @@ -35,4 +35,12 @@ fn main() { ((0, 0) | (1, 0),) => {} _ => {} } + match ((0, 0),) { + ((x, y) | (y, x),) if x == 0 => {} + _ => {} + } + match 0 { + 0 | 0 if 0 == 0 => {} + _ => {} + } } diff --git a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs index 8429799cabf15..20a8d7549961f 100644 --- a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs +++ b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.rs @@ -1,6 +1,7 @@ #![deny(unreachable_patterns)] // We wrap patterns in a tuple because top-level or-patterns were special-cased. +#[rustfmt::skip] fn main() { match (0u8,) { (1 | 2,) => {} @@ -73,6 +74,11 @@ fn main() { | 0] => {} //~ ERROR unreachable _ => {} } + match (true, 0) { + (true, 0 | 0) => {} //~ ERROR unreachable + (_, 0 | 0) => {} //~ ERROR unreachable + _ => {} + } match &[][..] { [0] => {} [0, _] => {} @@ -149,4 +155,8 @@ fn main() { | true, //~ ERROR unreachable false | true) => {} } + match (true, true) { + (x, y) + | (y, x) => {} //~ ERROR unreachable + } } diff --git a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr index 3f7d47dcb8ceb..3616dda99812f 100644 --- a/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr +++ b/tests/ui/or-patterns/exhaustiveness-unreachable-pattern.stderr @@ -1,5 +1,5 @@ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:7:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:8:9 | LL | (1,) => {} | ^^^^ @@ -11,128 +11,140 @@ LL | #![deny(unreachable_patterns)] | ^^^^^^^^^^^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:12:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:13:9 | LL | (2,) => {} | ^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:18:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:19:9 | LL | (1 | 2,) => {} | ^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:23:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:24:9 | LL | (1, 3) => {} | ^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:24:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:25:9 | LL | (1, 4) => {} | ^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:25:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:26:9 | LL | (2, 4) => {} | ^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:26:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:27:9 | LL | (2 | 1, 4) => {} | ^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:28:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:29:9 | LL | (1, 4 | 5) => {} | ^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:36:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:37:9 | LL | (Some(1),) => {} | ^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:37:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:38:9 | LL | (None,) => {} | ^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:42:9 + --> $DIR/exhaustiveness-unreachable-pattern.rs:43:9 | LL | ((1..=4,),) => {} | ^^^^^^^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:47:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:48:14 | LL | (1 | 1,) => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:51:19 + --> $DIR/exhaustiveness-unreachable-pattern.rs:52:19 | LL | (0 | 1) | 1 => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:57:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:58:14 | LL | 0 | (0 | 0) => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:57:18 + --> $DIR/exhaustiveness-unreachable-pattern.rs:58:18 | LL | 0 | (0 | 0) => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:65:13 + --> $DIR/exhaustiveness-unreachable-pattern.rs:66:13 | LL | / Some( LL | | 0 | 0) => {} | |______________________^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:71:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:72:15 | LL | | 0 | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:73:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:74:15 | LL | | 0] => {} | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:81:10 + --> $DIR/exhaustiveness-unreachable-pattern.rs:78:20 + | +LL | (true, 0 | 0) => {} + | ^ + +error: unreachable pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:79:17 + | +LL | (_, 0 | 0) => {} + | ^ + +error: unreachable pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:87:10 | LL | [1 | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:93:10 + --> $DIR/exhaustiveness-unreachable-pattern.rs:99:10 | LL | [true | ^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:100:36 + --> $DIR/exhaustiveness-unreachable-pattern.rs:106:36 | LL | (true | false, None | Some(true | ^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:105:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:111:14 | LL | (true | ^^^^ @@ -143,28 +155,34 @@ LL | (true | false, None | Some(t_or_f!())) => {} = note: this error originates in the macro `t_or_f` (in Nightly builds, run with -Z macro-backtrace for more info) error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:116:14 + --> $DIR/exhaustiveness-unreachable-pattern.rs:122:14 | LL | Some(0 | ^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:135:19 + --> $DIR/exhaustiveness-unreachable-pattern.rs:141:19 | LL | | false) => {} | ^^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:143:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:149:15 | LL | | true) => {} | ^^^^ error: unreachable pattern - --> $DIR/exhaustiveness-unreachable-pattern.rs:149:15 + --> $DIR/exhaustiveness-unreachable-pattern.rs:155:15 | LL | | true, | ^^^^ -error: aborting due to 26 previous errors +error: unreachable pattern + --> $DIR/exhaustiveness-unreachable-pattern.rs:160:15 + | +LL | | (y, x) => {} + | ^^^^^^ + +error: aborting due to 29 previous errors From 366a4442074d3c65b8a7f183221d1b935e84cd4f Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 16 May 2023 23:16:05 +0200 Subject: [PATCH 06/40] Separate arm reachability from match exhaustiveness --- .../src/thir/pattern/deconstruct_pat.rs | 6 +- .../src/thir/pattern/usefulness.rs | 221 ++++++++---------- 2 files changed, 97 insertions(+), 130 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 04115f72c1fdb..6bf2fb6f60431 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -767,7 +767,7 @@ impl<'tcx> Constructor<'tcx> { Wildcard => { let mut split_wildcard = SplitWildcard::new(pcx); split_wildcard.split(pcx, ctors); - split_wildcard.into_ctors(pcx) + split_wildcard.to_ctors(pcx) } // Fast-track if the range is trivial. In particular, we don't do the overlapping // ranges check. @@ -1064,7 +1064,7 @@ impl<'tcx> SplitWildcard<'tcx> { /// Return the set of constructors resulting from splitting the wildcard. As explained at the /// top of the file, if any constructors are missing we can ignore the present ones. - fn into_ctors(self, pcx: &PatCtxt<'_, '_, 'tcx>) -> SmallVec<[Constructor<'tcx>; 1]> { + pub(super) fn to_ctors(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> SmallVec<[Constructor<'tcx>; 1]> { if self.any_missing(pcx) { // Some constructors are missing, thus we can specialize with the special `Missing` // constructor, which stands for those constructors that are not seen in the matrix, @@ -1103,7 +1103,7 @@ impl<'tcx> SplitWildcard<'tcx> { } // All the constructors are present in the matrix, so we just go through them all. - self.all_ctors + self.all_ctors.clone() } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 750116a53135c..a8c5836f9f96b 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -305,8 +305,6 @@ //! stay as a full constant and become an `Opaque` pattern. These `Opaque` patterns do not participate //! in exhaustiveness, specialization or overlap checking. -use self::ArmType::*; -use self::Usefulness::*; use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard}; use crate::errors::{NonExhaustiveOmittedPattern, Uncovered}; @@ -550,82 +548,6 @@ impl<'p, 'tcx> fmt::Debug for Matrix<'p, 'tcx> { } } -/// This carries the results of computing usefulness, as described at the top of the file. When -/// checking usefulness of a match branch, we use the `NoWitnesses` variant, which also keeps track -/// of potential unreachable sub-patterns (in the presence of or-patterns). When checking -/// exhaustiveness of a whole match, we use the `WithWitnesses` variant, which carries a list of -/// witnesses of non-exhaustiveness when there are any. -/// Which variant to use is dictated by `ArmType`. -#[derive(Debug)] -enum Usefulness<'p, 'tcx> { - /// If we don't care about witnesses, simply remember if the pattern was useful. - NoWitnesses { useful: bool }, - /// Carries a list of witnesses of non-exhaustiveness. If empty, indicates that the whole - /// pattern is unreachable. - WithWitnesses(WitnessMatrix<'p, 'tcx>), -} - -impl<'p, 'tcx> Usefulness<'p, 'tcx> { - fn new_useful(preference: ArmType) -> Self { - match preference { - // A single (empty) witness of reachability. - FakeExtraWildcard => WithWitnesses(WitnessMatrix::new_unit()), - RealArm => NoWitnesses { useful: true }, - } - } - - fn new_not_useful(preference: ArmType) -> Self { - match preference { - FakeExtraWildcard => WithWitnesses(WitnessMatrix::new_empty()), - RealArm => NoWitnesses { useful: false }, - } - } - - fn is_useful(&self) -> bool { - match self { - Usefulness::NoWitnesses { useful } => *useful, - Usefulness::WithWitnesses(witnesses) => !witnesses.is_empty(), - } - } - - /// Combine usefulnesses from two branches. This is an associative operation. - fn extend(&mut self, other: Self) { - match (&mut *self, other) { - (WithWitnesses(_), WithWitnesses(o)) if o.is_empty() => {} - (WithWitnesses(s), WithWitnesses(o)) if s.is_empty() => *self = WithWitnesses(o), - (WithWitnesses(s), WithWitnesses(o)) => s.extend(o), - (NoWitnesses { useful: s_useful }, NoWitnesses { useful: o_useful }) => { - *s_useful = *s_useful || o_useful - } - _ => unreachable!(), - } - } - - /// After calculating usefulness after a specialization, call this to reconstruct a usefulness - /// that makes sense for the matrix pre-specialization. This new usefulness can then be merged - /// with the results of specializing with the other constructors. - fn apply_constructor( - self, - pcx: &PatCtxt<'_, 'p, 'tcx>, - matrix: &Matrix<'p, 'tcx>, // used to compute missing ctors - ctor: &Constructor<'tcx>, - ) -> Self { - match self { - NoWitnesses { .. } => self, - WithWitnesses(mut witnesses) => { - witnesses.apply_constructor(pcx, matrix, ctor); - WithWitnesses(witnesses) - } - } - } -} - -#[derive(Copy, Clone, Debug)] -enum ArmType { - FakeExtraWildcard, - RealArm, -} - /// A partially-constructed witness of non-exhaustiveness for error reporting, represented as a list /// of patterns (in reverse order of construction). This is very similar to `PatStack`, with two /// differences: the patterns are stored in the opposite order for historical reasons, and where the @@ -739,7 +661,7 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { fn apply_constructor( &mut self, pcx: &PatCtxt<'_, 'p, 'tcx>, - matrix: &Matrix<'p, 'tcx>, // used to compute missing ctors + split_wildcard: &SplitWildcard<'tcx>, ctor: &Constructor<'tcx>, ) { if self.is_empty() { @@ -757,13 +679,10 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { // We got the special `Missing` constructor, so each of the missing constructors gives a // new pattern that is not caught by the match. We list those patterns and push them // onto our current witnesses. - let old_witnesses = std::mem::replace(self, Self::new_empty()); - - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); // Skip any variants that are `doc(hidden)` or behind an unstable feature gate. // We replace them with a wildcard. + let old_witnesses = std::mem::replace(self, Self::new_empty()); let mut skipped_any_variant = false; for missing_ctor in split_wildcard.iter_missing(pcx) { if missing_ctor.is_doc_hidden_variant(pcx) || missing_ctor.is_unstable_variant(pcx) @@ -811,49 +730,35 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { /// `is_under_guard` is used to inform if the pattern has a guard. If it /// has one it must not be inserted into the matrix. This shouldn't be /// relied on for soundness. -#[instrument(level = "debug", skip(cx, matrix, lint_root), ret)] -fn is_useful<'p, 'tcx>( +#[instrument(level = "debug", skip(cx, lint_root, is_top_level), ret)] +fn is_arm_useful<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, matrix: &Matrix<'p, 'tcx>, v: &PatStack<'p, 'tcx>, - witness_preference: ArmType, lint_root: HirId, is_under_guard: bool, is_top_level: bool, -) -> Usefulness<'p, 'tcx> { - debug!(?matrix, ?v); +) -> bool { let Matrix { patterns: rows, .. } = matrix; + debug_assert!(rows.iter().all(|r| r.len() == v.len())); - // The base case. We are pattern-matching on () and the return value is - // based on whether our matrix has a row or not. - // NOTE: This could potentially be optimized by checking rows.is_empty() - // first and then, if v is non-empty, the return value is based on whether - // the type of the tuple we're checking is inhabited or not. + // The base case. We are pattern-matching on () and the return value is based on whether our + // matrix has a row or not. if v.is_empty() { - let ret = if rows.is_empty() { - Usefulness::new_useful(witness_preference) - } else { - Usefulness::new_not_useful(witness_preference) - }; - debug!(?ret); - return ret; + return rows.is_empty(); } - debug_assert!(rows.iter().all(|r| r.len() == v.len())); - // If the first pattern is an or-pattern, expand it. - let mut ret = Usefulness::new_not_useful(witness_preference); + let mut ret = false; if v.head().is_or_pat() { debug!("expanding or-pattern"); // We try each or-pattern branch in turn. let mut matrix = matrix.clone(); for v in v.expand_or_pat() { - debug!(?v); let usefulness = ensure_sufficient_stack(|| { - is_useful(cx, &matrix, &v, witness_preference, lint_root, is_under_guard, false) + is_arm_useful(cx, &matrix, &v, lint_root, is_under_guard, false) }); - debug!(?usefulness); - ret.extend(usefulness); + ret = ret || usefulness; // If pattern has a guard don't add it to the matrix. if !is_under_guard { // We push the already-seen patterns into the matrix in order to detect redundant @@ -872,7 +777,6 @@ fn is_useful<'p, 'tcx>( } } let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); - debug!("v.head: {:?}, v.span: {:?}", v.head(), v.head().span()); let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive }; let v_ctor = v.head().ctor(); @@ -893,32 +797,98 @@ fn is_useful<'p, 'tcx>( let start_matrix = &matrix; for ctor in split_ctors { debug!("specialize({:?})", ctor); - // We cache the result of `Fields::wildcards` because it is used a lot. let spec_matrix = start_matrix.specialize_constructor(pcx, &ctor); let v = v.pop_head_constructor(pcx, &ctor); let usefulness = ensure_sufficient_stack(|| { - is_useful( - cx, - &spec_matrix, - &v, - witness_preference, - lint_root, - is_under_guard, - false, - ) + is_arm_useful(cx, &spec_matrix, &v, lint_root, is_under_guard, false) }); - let usefulness = usefulness.apply_constructor(pcx, start_matrix, &ctor); - ret.extend(usefulness); + ret = ret || usefulness; } } - if ret.is_useful() { + if ret { v.head().set_reachable(); } ret } +/// Algorithm from . +/// The algorithm from the paper has been modified to correctly handle empty +/// types. The changes are: +/// (0) We don't exit early if the pattern matrix has zero rows. We just +/// continue to recurse over columns. +/// (1) all_constructors will only return constructors that are statically +/// possible. E.g., it will only return `Ok` for `Result`. +/// +/// This finds whether a (row) vector `v` of patterns is 'useful' in relation +/// to a set of such vectors `m` - this is defined as there being a set of +/// inputs that will match `v` but not any of the sets in `m`. +/// +/// All the patterns at each column of the `matrix ++ v` matrix must have the same type. +/// +/// This is used both for reachability checking (if a pattern isn't useful in +/// relation to preceding patterns, it is not reachable) and exhaustiveness +/// checking (if a wildcard pattern is useful in relation to a matrix, the +/// matrix isn't exhaustive). +/// +/// `is_under_guard` is used to inform if the pattern has a guard. If it +/// has one it must not be inserted into the matrix. This shouldn't be +/// relied on for soundness. +#[instrument(level = "debug", skip(cx, is_top_level), ret)] +fn compute_nonexhaustiveness_witnesses<'p, 'tcx>( + cx: &MatchCheckCtxt<'p, 'tcx>, + matrix: &Matrix<'p, 'tcx>, + v: &PatStack<'p, 'tcx>, + is_top_level: bool, +) -> WitnessMatrix<'p, 'tcx> { + let Matrix { patterns: rows, .. } = matrix; + debug_assert!(rows.iter().all(|r| r.len() == v.len())); + + // The base case. We are pattern-matching on () and the return value is + // based on whether our matrix has a row or not. + // NOTE: This could potentially be optimized by checking rows.is_empty() + // first and then, if v is non-empty, the return value is based on whether + // the type of the tuple we're checking is inhabited or not. + if v.is_empty() { + if rows.is_empty() { + return WitnessMatrix::new_unit(); + } else { + return WitnessMatrix::new_empty(); + } + } + + let mut ty = v.head().ty(); + // Opaque types can't get destructured/split, but the patterns can + // actually hint at hidden types, so we use the patterns' types instead. + if let ty::Alias(ty::Opaque, ..) = ty.kind() { + if let Some(row) = rows.first() { + ty = row.head().ty(); + } + } + debug!("ty: {ty:?}"); + let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); + let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; + + let mut ret = WitnessMatrix::new_empty(); + let mut split_wildcard = SplitWildcard::new(pcx); + split_wildcard.split(pcx, matrix.heads().map(|p| p.ctor())); + // For each constructor, we compute whether there's a value that starts with it that would + // witness the usefulness of `v`. + for ctor in split_wildcard.to_ctors(pcx) { + debug!("specialize({:?})", ctor); + let spec_matrix = matrix.specialize_constructor(pcx, &ctor); + let v = v.pop_head_constructor(pcx, &ctor); + let mut witnesses = ensure_sufficient_stack(|| { + compute_nonexhaustiveness_witnesses(cx, &spec_matrix, &v, false) + }); + witnesses.apply_constructor(pcx, &split_wildcard, &ctor); + ret.extend(witnesses); + } + + ret +} + /// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned /// in a given column. This traverses patterns column-by-column, where a column is the intuitive /// notion of "subpatterns that inspect the same subvalue". @@ -1058,7 +1028,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( .map(|arm| { debug!(?arm); let v = PatStack::from_pattern(arm.pat); - is_useful(cx, &matrix, &v, RealArm, lint_root, arm.has_guard, true); + is_arm_useful(cx, &matrix, &v, lint_root, arm.has_guard, true); if !arm.has_guard { matrix.push(v); } @@ -1073,11 +1043,8 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); let v = PatStack::from_pattern(wild_pattern); - let usefulness = is_useful(cx, &matrix, &v, FakeExtraWildcard, lint_root, false, true); - let non_exhaustiveness_witnesses: Vec<_> = match usefulness { - WithWitnesses(pats) => pats.single_column(), - NoWitnesses { .. } => bug!(), - }; + let non_exhaustiveness_witnesses = compute_nonexhaustiveness_witnesses(cx, &matrix, &v, true); + let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); // Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hittin // `if let`s. Only run if the match is exhaustive otherwise the error is redundant. From 8b958d115709860862c661f2a59033e3dcac6299 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 17 May 2023 12:49:40 +0200 Subject: [PATCH 07/40] Start push in matrix early --- .../src/thir/pattern/usefulness.rs | 177 ++++++++++-------- 1 file changed, 98 insertions(+), 79 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index a8c5836f9f96b..85849f7b1f4a2 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -378,17 +378,30 @@ impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> { /// A row of a matrix. Rows of len 1 are very common, which is why `SmallVec[_; 2]` /// works well. #[derive(Clone)] -pub(crate) struct PatStack<'p, 'tcx> { - pub(crate) pats: SmallVec<[&'p DeconstructedPat<'p, 'tcx>; 2]>, +struct PatStack<'p, 'tcx> { + pats: SmallVec<[&'p DeconstructedPat<'p, 'tcx>; 2]>, + /// The row (in the matrix) of the `PatStack` from which this one is derived. When there is + /// none, this is the id of the arm. + parent_row: usize, + is_under_guard: bool, + is_useful: bool, } impl<'p, 'tcx> PatStack<'p, 'tcx> { - fn from_pattern(pat: &'p DeconstructedPat<'p, 'tcx>) -> Self { - Self::from_vec(smallvec![pat]) + fn from_pattern( + pat: &'p DeconstructedPat<'p, 'tcx>, + parent_row: usize, + is_under_guard: bool, + ) -> Self { + Self::from_vec(smallvec![pat], parent_row, is_under_guard) } - fn from_vec(vec: SmallVec<[&'p DeconstructedPat<'p, 'tcx>; 2]>) -> Self { - PatStack { pats: vec } + fn from_vec( + vec: SmallVec<[&'p DeconstructedPat<'p, 'tcx>; 2]>, + parent_row: usize, + is_under_guard: bool, + ) -> Self { + PatStack { pats: vec, parent_row, is_under_guard, is_useful: false } } fn is_empty(&self) -> bool { @@ -407,31 +420,17 @@ impl<'p, 'tcx> PatStack<'p, 'tcx> { self.pats.iter().copied() } - // Recursively expand the first pattern into its subpatterns. Only useful if the pattern is an + // Expand the first pattern into its subpatterns. Only useful if the pattern is an // or-pattern. Panics if `self` is empty. fn expand_or_pat<'a>(&'a self) -> impl Iterator> + Captures<'a> { self.head().iter_fields().map(move |pat| { - let mut new_patstack = PatStack::from_pattern(pat); + let mut new_patstack = + PatStack::from_pattern(pat, self.parent_row, self.is_under_guard); new_patstack.pats.extend_from_slice(&self.pats[1..]); new_patstack }) } - // Recursively expand all patterns into their subpatterns and push each `PatStack` to matrix. - fn expand_and_extend<'a>(&'a self, matrix: &mut Matrix<'p, 'tcx>) { - if !self.is_empty() && self.head().is_or_pat() { - for pat in self.head().iter_fields() { - let mut new_patstack = PatStack::from_pattern(pat); - new_patstack.pats.extend_from_slice(&self.pats[1..]); - if !new_patstack.is_empty() && new_patstack.head().is_or_pat() { - new_patstack.expand_and_extend(matrix); - } else if !new_patstack.is_empty() { - matrix.push(new_patstack); - } - } - } - } - /// This computes `S(self.head().ctor(), self)`. See top of the file for explanations. /// /// Structure patterns with a partial wild pattern (Foo { a: 42, .. }) have their missing @@ -442,12 +441,13 @@ impl<'p, 'tcx> PatStack<'p, 'tcx> { &self, pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>, + parent_row: usize, ) -> PatStack<'p, 'tcx> { // We pop the head pattern and push the new fields extracted from the arguments of // `self.head()`. let mut new_fields: SmallVec<[_; 2]> = self.head().specialize(pcx, ctor); new_fields.extend_from_slice(&self.pats[1..]); - PatStack::from_vec(new_fields) + PatStack::from_vec(new_fields, parent_row, self.is_under_guard) } } @@ -464,35 +464,52 @@ impl<'p, 'tcx> fmt::Debug for PatStack<'p, 'tcx> { /// A 2D matrix. #[derive(Clone)] -pub(super) struct Matrix<'p, 'tcx> { - pub patterns: Vec>, +struct Matrix<'p, 'tcx> { + rows: Vec>, } impl<'p, 'tcx> Matrix<'p, 'tcx> { fn empty() -> Self { - Matrix { patterns: vec![] } + Matrix { rows: vec![] } } /// Number of columns of this matrix. `None` is the matrix is empty. pub(super) fn column_count(&self) -> Option { - self.patterns.get(0).map(|r| r.len()) + self.rows().next().map(|r| r.len()) } /// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively /// expands it. - fn push(&mut self, row: PatStack<'p, 'tcx>) { + fn push(&mut self, row: PatStack<'p, 'tcx>) -> usize { if !row.is_empty() && row.head().is_or_pat() { - row.expand_and_extend(self); + let mut count = 0; + for new_row in row.expand_or_pat() { + count += self.push(new_row); + } + count } else { - self.patterns.push(row); + self.rows.push(row); + 1 } } + /// Iterate over the first component of each row + fn rows<'a>( + &'a self, + ) -> impl Iterator> + Clone + DoubleEndedIterator { + self.rows.iter() + } + /// Iterate over the first component of each row + fn rows_mut<'a>( + &'a mut self, + ) -> impl Iterator> + DoubleEndedIterator { + self.rows.iter_mut() + } /// Iterate over the first component of each row fn heads<'a>( &'a self, ) -> impl Iterator> + Clone + Captures<'a> { - self.patterns.iter().map(|r| r.head()) + self.rows().map(|r| r.head()) } /// This computes `S(constructor, self)`. See top of the file for explanations. @@ -502,9 +519,9 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> { ctor: &Constructor<'tcx>, ) -> Matrix<'p, 'tcx> { let mut matrix = Matrix::empty(); - for row in &self.patterns { + for (i, row) in self.rows().enumerate() { if ctor.is_covered_by(pcx, row.head().ctor()) { - let new_row = row.pop_head_constructor(pcx, ctor); + let new_row = row.pop_head_constructor(pcx, ctor, i); matrix.push(new_row); } } @@ -525,7 +542,7 @@ impl<'p, 'tcx> fmt::Debug for Matrix<'p, 'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "\n")?; - let Matrix { patterns: m, .. } = self; + let Matrix { rows: m, .. } = self; let pretty_printed_matrix: Vec> = m.iter().map(|row| row.iter().map(|pat| format!("{:?}", pat)).collect()).collect(); @@ -733,38 +750,36 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { #[instrument(level = "debug", skip(cx, lint_root, is_top_level), ret)] fn is_arm_useful<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, - matrix: &Matrix<'p, 'tcx>, - v: &PatStack<'p, 'tcx>, + matrix: &mut Matrix<'p, 'tcx>, + mut v: PatStack<'p, 'tcx>, lint_root: HirId, - is_under_guard: bool, is_top_level: bool, -) -> bool { - let Matrix { patterns: rows, .. } = matrix; - debug_assert!(rows.iter().all(|r| r.len() == v.len())); +) { + debug_assert!(matrix.rows().all(|r| r.len() == v.len())); // The base case. We are pattern-matching on () and the return value is based on whether our // matrix has a row or not. if v.is_empty() { - return rows.is_empty(); + v.is_useful = matrix.rows().all(|v| v.is_under_guard); + matrix.push(v); + return; } // If the first pattern is an or-pattern, expand it. - let mut ret = false; + v.is_useful = false; if v.head().is_or_pat() { debug!("expanding or-pattern"); // We try each or-pattern branch in turn. - let mut matrix = matrix.clone(); - for v in v.expand_or_pat() { - let usefulness = ensure_sufficient_stack(|| { - is_arm_useful(cx, &matrix, &v, lint_root, is_under_guard, false) - }); - ret = ret || usefulness; - // If pattern has a guard don't add it to the matrix. - if !is_under_guard { - // We push the already-seen patterns into the matrix in order to detect redundant - // branches like `Some(_) | Some(0)`. - matrix.push(v); - } + let mut count = 0; + for sub_v in v.expand_or_pat() { + count += 1; + ensure_sufficient_stack(|| is_arm_useful(cx, matrix, sub_v, lint_root, false)); + } + for sub_v in matrix.rows().rev().take(count) { + v.is_useful = v.is_useful || sub_v.is_useful; + } + if v.is_useful { + v.head().set_reachable(); } } else { let mut ty = v.head().ty(); @@ -772,7 +787,7 @@ fn is_arm_useful<'p, 'tcx>( // Opaque types can't get destructured/split, but the patterns can // actually hint at hidden types, so we use the patterns' types instead. if let ty::Alias(ty::Opaque, ..) = ty.kind() { - if let Some(row) = rows.first() { + if let Some(row) = matrix.rows().next() { ty = row.head().ty(); } } @@ -797,20 +812,26 @@ fn is_arm_useful<'p, 'tcx>( let start_matrix = &matrix; for ctor in split_ctors { debug!("specialize({:?})", ctor); - let spec_matrix = start_matrix.specialize_constructor(pcx, &ctor); - let v = v.pop_head_constructor(pcx, &ctor); - let usefulness = ensure_sufficient_stack(|| { - is_arm_useful(cx, &spec_matrix, &v, lint_root, is_under_guard, false) + let mut spec_matrix = start_matrix.specialize_constructor(pcx, &ctor); + let sub_v = v.pop_head_constructor(pcx, &ctor, start_matrix.rows.len()); + assert_eq!(sub_v.parent_row, start_matrix.rows.len()); + ensure_sufficient_stack(|| { + is_arm_useful(cx, &mut spec_matrix, sub_v, lint_root, false) }); - ret = ret || usefulness; + for sub_v in spec_matrix + .rows() + .rev() + .take_while(|sub_v| sub_v.parent_row == start_matrix.rows.len()) + { + v.is_useful = v.is_useful || sub_v.is_useful; + } + } + matrix.push(v); + let v = matrix.rows().last().unwrap(); + if v.is_useful { + v.head().set_reachable(); } } - - if ret { - v.head().set_reachable(); - } - - ret } /// Algorithm from . @@ -842,8 +863,7 @@ fn compute_nonexhaustiveness_witnesses<'p, 'tcx>( v: &PatStack<'p, 'tcx>, is_top_level: bool, ) -> WitnessMatrix<'p, 'tcx> { - let Matrix { patterns: rows, .. } = matrix; - debug_assert!(rows.iter().all(|r| r.len() == v.len())); + debug_assert!(matrix.rows().all(|r| r.len() == v.len())); // The base case. We are pattern-matching on () and the return value is // based on whether our matrix has a row or not. @@ -851,7 +871,7 @@ fn compute_nonexhaustiveness_witnesses<'p, 'tcx>( // first and then, if v is non-empty, the return value is based on whether // the type of the tuple we're checking is inhabited or not. if v.is_empty() { - if rows.is_empty() { + if matrix.rows().all(|v| v.is_under_guard) { return WitnessMatrix::new_unit(); } else { return WitnessMatrix::new_empty(); @@ -862,7 +882,7 @@ fn compute_nonexhaustiveness_witnesses<'p, 'tcx>( // Opaque types can't get destructured/split, but the patterns can // actually hint at hidden types, so we use the patterns' types instead. if let ty::Alias(ty::Opaque, ..) = ty.kind() { - if let Some(row) = rows.first() { + if let Some(row) = matrix.rows().next() { ty = row.head().ty(); } } @@ -878,7 +898,7 @@ fn compute_nonexhaustiveness_witnesses<'p, 'tcx>( for ctor in split_wildcard.to_ctors(pcx) { debug!("specialize({:?})", ctor); let spec_matrix = matrix.specialize_constructor(pcx, &ctor); - let v = v.pop_head_constructor(pcx, &ctor); + let v = v.pop_head_constructor(pcx, &ctor, usize::MAX); let mut witnesses = ensure_sufficient_stack(|| { compute_nonexhaustiveness_witnesses(cx, &spec_matrix, &v, false) }); @@ -1025,13 +1045,11 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( let arm_usefulness: Vec<_> = arms .iter() .copied() - .map(|arm| { + .enumerate() + .map(|(row_id, arm)| { debug!(?arm); - let v = PatStack::from_pattern(arm.pat); - is_arm_useful(cx, &matrix, &v, lint_root, arm.has_guard, true); - if !arm.has_guard { - matrix.push(v); - } + let v = PatStack::from_pattern(arm.pat, row_id, arm.has_guard); + is_arm_useful(cx, &mut matrix, v, lint_root, true); let reachability = if arm.pat.is_reachable() { Reachability::Reachable(arm.pat.unreachable_spans()) } else { @@ -1041,8 +1059,9 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( }) .collect(); + matrix.rows.retain(|v| !v.is_under_guard); let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); - let v = PatStack::from_pattern(wild_pattern); + let v = PatStack::from_pattern(wild_pattern, usize::MAX, false); let non_exhaustiveness_witnesses = compute_nonexhaustiveness_witnesses(cx, &matrix, &v, true); let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); From df7b1568035c3ca6b6bd4efe5d977fa15773106a Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 17 May 2023 14:42:09 +0200 Subject: [PATCH 08/40] Getting close --- .../src/thir/pattern/deconstruct_pat.rs | 11 +- .../src/thir/pattern/usefulness.rs | 154 ++++++++---------- 2 files changed, 80 insertions(+), 85 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 6bf2fb6f60431..bd8c03b080043 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -1590,7 +1590,16 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { self.reachable.set(true) } pub(super) fn is_reachable(&self) -> bool { - self.reachable.get() + if self.reachable.get() { + true + } else if self.is_or_pat() && self.iter_fields().any(|f| f.is_reachable()) { + // We always expand or patterns in the matrix, so the algo will never see the or-pattern + // itself, only the children. We recover this information here. + self.set_reachable(); + true + } else { + false + } } /// Report the spans of subpatterns that were not reachable, if any. diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 85849f7b1f4a2..53ccab9e4dd1b 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -473,9 +473,8 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> { Matrix { rows: vec![] } } - /// Number of columns of this matrix. `None` is the matrix is empty. - pub(super) fn column_count(&self) -> Option { - self.rows().next().map(|r| r.len()) + fn len(&self) -> usize { + self.rows.len() } /// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively @@ -493,19 +492,13 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> { } } - /// Iterate over the first component of each row fn rows<'a>( &'a self, - ) -> impl Iterator> + Clone + DoubleEndedIterator { + ) -> impl Iterator> + Clone + DoubleEndedIterator + ExactSizeIterator + { self.rows.iter() } /// Iterate over the first component of each row - fn rows_mut<'a>( - &'a mut self, - ) -> impl Iterator> + DoubleEndedIterator { - self.rows.iter_mut() - } - /// Iterate over the first component of each row fn heads<'a>( &'a self, ) -> impl Iterator> + Clone + Captures<'a> { @@ -748,89 +741,78 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { /// has one it must not be inserted into the matrix. This shouldn't be /// relied on for soundness. #[instrument(level = "debug", skip(cx, lint_root, is_top_level), ret)] -fn is_arm_useful<'p, 'tcx>( +fn is_nth_row_useful<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, matrix: &mut Matrix<'p, 'tcx>, - mut v: PatStack<'p, 'tcx>, + row_id: usize, lint_root: HirId, is_top_level: bool, ) { - debug_assert!(matrix.rows().all(|r| r.len() == v.len())); + debug_assert!(matrix.rows().all(|r| r.len() == matrix.rows[0].len())); // The base case. We are pattern-matching on () and the return value is based on whether our // matrix has a row or not. - if v.is_empty() { - v.is_useful = matrix.rows().all(|v| v.is_under_guard); - matrix.push(v); + if matrix.rows[0].is_empty() { + let useful = matrix.rows().take(row_id).all(|v| v.is_under_guard); + matrix.rows[row_id].is_useful = useful; return; } - // If the first pattern is an or-pattern, expand it. - v.is_useful = false; - if v.head().is_or_pat() { - debug!("expanding or-pattern"); - // We try each or-pattern branch in turn. - let mut count = 0; - for sub_v in v.expand_or_pat() { - count += 1; - ensure_sufficient_stack(|| is_arm_useful(cx, matrix, sub_v, lint_root, false)); - } - for sub_v in matrix.rows().rev().take(count) { - v.is_useful = v.is_useful || sub_v.is_useful; - } - if v.is_useful { - v.head().set_reachable(); - } - } else { - let mut ty = v.head().ty(); - - // Opaque types can't get destructured/split, but the patterns can - // actually hint at hidden types, so we use the patterns' types instead. - if let ty::Alias(ty::Opaque, ..) = ty.kind() { - if let Some(row) = matrix.rows().next() { - ty = row.head().ty(); - } - } - let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); - let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive }; - - let v_ctor = v.head().ctor(); - debug!(?v_ctor); - if let Constructor::IntRange(ctor_range) = &v_ctor { - // Lint on likely incorrect range patterns (#63987) - ctor_range.lint_overlapping_range_endpoints( - pcx, - matrix.heads(), - matrix.column_count().unwrap_or(0), - lint_root, - ) + let v = &matrix.rows[row_id]; + + let mut ty = v.head().ty(); + // Opaque types can't get destructured/split, but the patterns can + // actually hint at hidden types, so we use the patterns' types instead. + if let ty::Alias(ty::Opaque, ..) = ty.kind() { + if let Some(row) = matrix.rows().next() { + ty = row.head().ty(); } - // We split the head constructor of `v`. - let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - // For each constructor, we compute whether there's a value that starts with it that would - // witness the usefulness of `v`. - let start_matrix = &matrix; - for ctor in split_ctors { - debug!("specialize({:?})", ctor); - let mut spec_matrix = start_matrix.specialize_constructor(pcx, &ctor); - let sub_v = v.pop_head_constructor(pcx, &ctor, start_matrix.rows.len()); - assert_eq!(sub_v.parent_row, start_matrix.rows.len()); - ensure_sufficient_stack(|| { - is_arm_useful(cx, &mut spec_matrix, sub_v, lint_root, false) - }); - for sub_v in spec_matrix - .rows() - .rev() - .take_while(|sub_v| sub_v.parent_row == start_matrix.rows.len()) - { - v.is_useful = v.is_useful || sub_v.is_useful; + } + + let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); + let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive }; + + let v_ctor = v.head().ctor(); + debug!(?v_ctor); + + if let Constructor::IntRange(ctor_range) = &v_ctor { + // Lint on likely incorrect range patterns (#63987) + ctor_range.lint_overlapping_range_endpoints( + pcx, + matrix.heads().take(row_id), + v.len(), + lint_root, + ) + } + + matrix.rows[row_id].is_useful = false; + // We split the head constructor of `v`. + let split_ctors = v_ctor.split(pcx, matrix.heads().take(row_id).map(DeconstructedPat::ctor)); + // For each constructor, we compute whether there's a value that starts with it that would + // witness the usefulness of `v`. + let below = matrix.rows.split_off(row_id + 1); + let mut v = matrix.rows.pop().unwrap(); + let start_matrix = &matrix; + for ctor in split_ctors { + debug!("specialize({:?})", ctor); + let mut spec_matrix = start_matrix.specialize_constructor(pcx, &ctor); + let sub_v = v.pop_head_constructor(pcx, &ctor, row_id); + spec_matrix.push(sub_v); + + for i in 0..spec_matrix.len() { + if spec_matrix.rows[i].parent_row == row_id { + ensure_sufficient_stack(|| { + is_nth_row_useful(cx, &mut spec_matrix, i, lint_root, false) + }); + v.is_useful = v.is_useful || spec_matrix.rows[i].is_useful; } } - matrix.push(v); - let v = matrix.rows().last().unwrap(); - if v.is_useful { - v.head().set_reachable(); - } + } + matrix.push(v); + matrix.rows.extend(below); + let v = &matrix.rows[row_id]; + if v.is_useful { + v.head().set_reachable(); } } @@ -1042,14 +1024,18 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( scrut_span: Span, ) -> UsefulnessReport<'p, 'tcx> { let mut matrix = Matrix::empty(); + for (row_id, arm) in arms.iter().enumerate() { + let v = PatStack::from_pattern(arm.pat, row_id, arm.has_guard); + matrix.push(v); + } + for i in 0..matrix.len() { + is_nth_row_useful(cx, &mut matrix, i, lint_root, true); + } let arm_usefulness: Vec<_> = arms .iter() .copied() - .enumerate() - .map(|(row_id, arm)| { + .map(|arm| { debug!(?arm); - let v = PatStack::from_pattern(arm.pat, row_id, arm.has_guard); - is_arm_useful(cx, &mut matrix, v, lint_root, true); let reachability = if arm.pat.is_reachable() { Reachability::Reachable(arm.pat.unreachable_spans()) } else { From 90afa1dd88223154cd1b274db3348394e9050485 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 17 May 2023 15:30:23 +0200 Subject: [PATCH 09/40] fix constants! --- .../src/thir/pattern/deconstruct_pat.rs | 39 +++++++++++++------ .../src/thir/pattern/usefulness.rs | 11 ++---- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index bd8c03b080043..b048adf8ecc8a 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -613,6 +613,18 @@ impl SplitVarLenSlice { } } +/// A globally unique id to distinguish `Opaque` patterns. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) struct OpaqueId(u32); + +impl OpaqueId { + fn new() -> Self { + use std::sync::atomic::{AtomicU32, Ordering}; + static OPAQUE_ID: AtomicU32 = AtomicU32::new(0); + OpaqueId(OPAQUE_ID.fetch_add(1, Ordering::SeqCst)) + } +} + /// A value can be decomposed into a constructor applied to some fields. This struct represents /// the constructor. See also `Fields`. /// @@ -635,10 +647,11 @@ pub(super) enum Constructor<'tcx> { Str(mir::ConstantKind<'tcx>), /// Array and slice patterns. Slice(Slice), - /// Constants that must not be matched structurally. They are treated as black - /// boxes for the purposes of exhaustiveness: we must not inspect them, and they - /// don't count towards making a match exhaustive. - Opaque, + /// Constants that must not be matched structurally. They are treated as black boxes for the + /// purposes of exhaustiveness: we must not inspect them, and they don't count towards making a + /// match exhaustive. + /// Carries an id that must be globally unique. + Opaque(OpaqueId), /// Fake extra constructor for enums that aren't allowed to be matched exhaustively. Also used /// for those types for which we cannot list constructors explicitly, like `f64` and `str`. NonExhaustive, @@ -735,7 +748,7 @@ impl<'tcx> Constructor<'tcx> { | FloatRange(..) | IntRange(..) | NonExhaustive - | Opaque + | Opaque(..) | Missing { .. } | Wildcard => 0, Or => bug!("The `Or` constructor doesn't have a fixed arity"), @@ -828,8 +841,10 @@ impl<'tcx> Constructor<'tcx> { } (Slice(self_slice), Slice(other_slice)) => self_slice.is_covered_by(*other_slice), - // We are trying to inspect an opaque constant. Thus we skip the row. - (Opaque, _) | (_, Opaque) => false, + // Opaque constructors don't interact with anything unless they come from the + // structurally identical pattern. + (Opaque(self_id), Opaque(other_id)) => self_id == other_id, + (Opaque(..), _) | (_, Opaque(..)) => false, // Only a wildcard pattern can match the special extra constructor. (NonExhaustive, _) => false, @@ -869,7 +884,7 @@ impl<'tcx> Constructor<'tcx> { .any(|other| slice.is_covered_by(other)), // This constructor is never covered by anything else NonExhaustive => false, - Str(..) | FloatRange(..) | Opaque | Missing { .. } | Wildcard | Or => { + Str(..) | FloatRange(..) | Opaque(..) | Missing { .. } | Wildcard | Or => { span_bug!(pcx.span, "found unexpected ctor in all_ctors: {:?}", self) } } @@ -1220,7 +1235,7 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { | FloatRange(..) | IntRange(..) | NonExhaustive - | Opaque + | Opaque(..) | Missing { .. } | Wildcard => Fields::empty(), Or => { @@ -1378,7 +1393,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { // into the corresponding `Pat`s by `const_to_pat`. Constants that remain are // opaque. _ => { - ctor = Opaque; + ctor = Opaque(OpaqueId::new()); fields = Fields::empty(); } } @@ -1507,7 +1522,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { "trying to convert a `Missing` constructor into a `Pat`; this is probably a bug, `Missing` should have been processed in `apply_constructors`" ), - Opaque | Or => { + Opaque(..) | Or => { bug!("can't convert to pattern: {:?}", self) } }; @@ -1711,7 +1726,7 @@ impl<'p, 'tcx> fmt::Debug for DeconstructedPat<'p, 'tcx> { Ok(()) } Str(value) => write!(f, "{}", value), - Opaque => write!(f, ""), + Opaque(..) => write!(f, ""), } } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 53ccab9e4dd1b..e2d43e1f3cd42 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -791,24 +791,19 @@ fn is_nth_row_useful<'p, 'tcx>( // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. let below = matrix.rows.split_off(row_id + 1); - let mut v = matrix.rows.pop().unwrap(); - let start_matrix = &matrix; for ctor in split_ctors { debug!("specialize({:?})", ctor); - let mut spec_matrix = start_matrix.specialize_constructor(pcx, &ctor); - let sub_v = v.pop_head_constructor(pcx, &ctor, row_id); - spec_matrix.push(sub_v); - + let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); for i in 0..spec_matrix.len() { if spec_matrix.rows[i].parent_row == row_id { ensure_sufficient_stack(|| { is_nth_row_useful(cx, &mut spec_matrix, i, lint_root, false) }); - v.is_useful = v.is_useful || spec_matrix.rows[i].is_useful; + matrix.rows[row_id].is_useful = + matrix.rows[row_id].is_useful || spec_matrix.rows[i].is_useful; } } } - matrix.push(v); matrix.rows.extend(below); let v = &matrix.rows[row_id]; if v.is_useful { From a2310d6ecc30744095234fd4178fb3dbed27ed32 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 17 May 2023 15:33:46 +0200 Subject: [PATCH 10/40] kill perf now --- compiler/rustc_mir_build/src/thir/pattern/usefulness.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index e2d43e1f3cd42..a623c27f44ed1 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -787,10 +787,9 @@ fn is_nth_row_useful<'p, 'tcx>( matrix.rows[row_id].is_useful = false; // We split the head constructor of `v`. - let split_ctors = v_ctor.split(pcx, matrix.heads().take(row_id).map(DeconstructedPat::ctor)); + let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. - let below = matrix.rows.split_off(row_id + 1); for ctor in split_ctors { debug!("specialize({:?})", ctor); let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); @@ -804,7 +803,6 @@ fn is_nth_row_useful<'p, 'tcx>( } } } - matrix.rows.extend(below); let v = &matrix.rows[row_id]; if v.is_useful { v.head().set_reachable(); From 4c3fdbb2b97fdb9441f8adf82bd1ec7e8cbc5470 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 17 May 2023 17:55:17 +0200 Subject: [PATCH 11/40] ayyyy one pass --- .../src/thir/pattern/deconstruct_pat.rs | 191 +++++++++++++++++- .../src/thir/pattern/usefulness.rs | 93 ++++----- .../usefulness/integer-ranges/reachability.rs | 5 +- .../integer-ranges/reachability.stderr | 56 ++--- 4 files changed, 267 insertions(+), 78 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index b048adf8ecc8a..632a7000f0c16 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -891,6 +891,194 @@ impl<'tcx> Constructor<'tcx> { } } +/// Describes a set of constructors for a type. The constructors must be an exhaustive set, must +/// cover all constructors in the matrix, and must be split wrt the constructors of the matrix, as +/// explained at the top of the file. +pub(super) enum ConstructorSet<'tcx> { + Uninhabited, + Ranges(Vec), + Slices(Slice), + UnListable, + Other { base_ctors: Vec> }, +} + +impl<'tcx> ConstructorSet<'tcx> { + pub(super) fn new<'p>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { + debug!("ConstructorSet::new({:?})", pcx.ty); + //let cx = pcx.cx; + //let make_range = |start, end| { + // // `unwrap()` is ok because we know the type is an integer. + // IntRange::from_range(cx.tcx, start, end, pcx.ty, &RangeEnd::Included).unwrap() + //}; + //// This determines the set of all possible constructors for the type `pcx.ty`. For numbers, + //// arrays and slices we use ranges and variable-length slices when appropriate. + //// + //// If the `exhaustive_patterns` feature is enabled, we make sure to omit constructors that + //// are statically impossible. E.g., for `Option`, we do not include `Some(_)` in the + //// returned list of constructors. + //// Invariant: this is empty if and only if the type is uninhabited (as determined by + //// `cx.is_uninhabited()`). + //match pcx.ty.kind() { + // ty::Bool => Self::Ranges(vec![make_range(0, 1)]), + // ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => { + // let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize; + // if len != 0 && cx.is_uninhabited(*sub_ty) { + // Self::Uninhabited + // } else { + // Self::Slices(Slice::new(Some(len), VarLen(0, 0))) + // } + // } + // // Treat arrays of a constant but unknown length like slices. + // ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { + // let kind = if cx.is_uninhabited(*sub_ty) { FixedLen(0) } else { VarLen(0, 0) }; + // Self::Slices(Slice::new(None, kind)) + // } + // ty::Adt(def, substs) if def.is_enum() => { + // // If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an + // // additional "unknown" constructor. + // // There is no point in enumerating all possible variants, because the user can't + // // actually match against them all themselves. So we always return only the fictitious + // // constructor. + // // 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 only `_` as the + // // witness. + // let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(pcx.ty); + + // let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; + + // // If `exhaustive_patterns` is disabled and our scrutinee is an empty enum, we treat it + // // as though it had an "unknown" constructor to avoid exposing its emptiness. The + // // exception is if the pattern is at the top level, because we want empty matches to be + // // considered exhaustive. + // let is_secretly_empty = + // def.variants().is_empty() && !is_exhaustive_pat_feature && !pcx.is_top_level; + + // let mut ctors: Vec<_> = def + // .variants() + // .iter_enumerated() + // .filter(|(_, v)| { + // // If `exhaustive_patterns` is enabled, we exclude variants known to be + // // uninhabited. + // !is_exhaustive_pat_feature + // || v.inhabited_predicate(cx.tcx, *def).subst(cx.tcx, substs).apply( + // cx.tcx, + // cx.param_env, + // cx.module, + // ) + // }) + // .map(|(idx, _)| Variant(idx)) + // .collect(); + + // if is_secretly_empty || is_declared_nonexhaustive { + // ctors.push(NonExhaustive); + // } + // Self::Other { all_ctors: ctors } + // } + // ty::Char => { + // Self::Ranges(vec![ + // // The valid Unicode Scalar Value ranges. + // make_range('\u{0000}' as u128, '\u{D7FF}' as u128), + // make_range('\u{E000}' as u128, '\u{10FFFF}' as u128), + // ]) + // } + // ty::Int(_) | ty::Uint(_) + // if pcx.ty.is_ptr_sized_integral() + // && !cx.tcx.features().precise_pointer_size_matching => + // { + // // `usize`/`isize` are not allowed to be matched exhaustively unless the + // // `precise_pointer_size_matching` feature is enabled. So we treat those types like + // // `#[non_exhaustive]` enums by returning a special unmatchable constructor. + // Self::UnListable + // } + // &ty::Int(ity) => { + // let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; + // let min = 1u128 << (bits - 1); + // let max = min - 1; + // Self::Ranges(vec![make_range(min, max)]) + // } + // &ty::Uint(uty) => { + // let size = Integer::from_uint_ty(&cx.tcx, uty).size(); + // let max = size.truncate(u128::MAX); + // Self::Ranges(vec![make_range(0, max)]) + // } + // // If `exhaustive_patterns` is disabled and our scrutinee is the never type, we cannot + // // expose its emptiness. The exception is if the pattern is at the top level, because we + // // want empty matches to be considered exhaustive. + // ty::Never if !cx.tcx.features().exhaustive_patterns && !pcx.is_top_level => { + // Self::UnListable + // } + // ty::Never => Self::Uninhabited, + // _ if cx.is_uninhabited(pcx.ty) => Self::Uninhabited, + // ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => Self::Other { all_ctors: vec![Single] }, + // // This type is one for which we cannot list constructors, like `str` or `f64`. + // _ => Self::UnListable, + //} + Self::Other { base_ctors: SplitWildcard::new(pcx).all_ctors.to_vec() } + } + + /// Pass a set of constructors relative to which to split this one. + pub(super) fn split<'a>( + self, + pcx: &PatCtxt<'_, '_, 'tcx>, + ctors: impl Iterator> + Clone, + ) -> Vec> + where + 'tcx: 'a, + { + let mut split_ctors = Vec::new(); + let mut seen_ctors = Vec::new(); + let mut seen_wildcard = false; + let mut any_missing = false; + for ctor in ctors { + if ctor.is_wildcard() { + seen_wildcard = true; + } else if let Constructor::Opaque(..) = ctor { + split_ctors.push(ctor.clone()); + } else { + seen_ctors.push(ctor.clone()); + } + } + match self { + ConstructorSet::Uninhabited => todo!(), + ConstructorSet::Ranges(_) => todo!(), + ConstructorSet::Slices(_) => todo!(), + ConstructorSet::UnListable => todo!(), + ConstructorSet::Other { base_ctors } => { + if matches!(base_ctors.as_slice(), [Constructor::NonExhaustive]) { + // FIXME: self-splitting is only useful for isize/usize. Would be nice to reust + // IntRange there somehow. + split_ctors + .extend(seen_ctors.iter().flat_map(|c| c.split(pcx, seen_ctors.iter()))); + any_missing = true; + } else { + // FIXME: splitting and collecting present ctors should be done in one pass. + let split_base_ctors = + base_ctors.iter().flat_map(|ctor| ctor.split(pcx, seen_ctors.iter())); + for ctor in split_base_ctors { + if ctor.is_covered_by_any(pcx, seen_ctors.as_slice()) { + split_ctors.push(ctor.clone()); + } else { + any_missing = true; + } + } + } + } + } + if seen_wildcard && any_missing { + split_ctors.push(Constructor::Missing); + } + split_ctors + } +} + /// A wildcard constructor that we split relative to the constructors in the matrix, as explained /// at the top of the file. /// @@ -1069,7 +1257,8 @@ impl<'tcx> SplitWildcard<'tcx> { /// Iterate over the constructors for this type that are present in the matrix. This has the /// effect of deduplicating present constructors. - /// WARNING: this omits special constructors like `Wildcard` and `Opaque`. + /// WARNING: this omits special constructors like `Wildcard` and `Opaque` and doesn't work for + /// non exhaustive types. pub(super) fn iter_present<'a, 'p>( &'a self, pcx: &'a PatCtxt<'a, 'p, 'tcx>, diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index a623c27f44ed1..e3245f9badfaa 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -305,7 +305,9 @@ //! stay as a full constant and become an `Opaque` pattern. These `Opaque` patterns do not participate //! in exhaustiveness, specialization or overlap checking. -use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard}; +use super::deconstruct_pat::{ + Constructor, ConstructorSet, DeconstructedPat, Fields, SplitWildcard, +}; use crate::errors::{NonExhaustiveOmittedPattern, Uncovered}; use rustc_data_structures::captures::Captures; @@ -741,71 +743,64 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { /// has one it must not be inserted into the matrix. This shouldn't be /// relied on for soundness. #[instrument(level = "debug", skip(cx, lint_root, is_top_level), ret)] -fn is_nth_row_useful<'p, 'tcx>( +fn compute_row_usefulnesses<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, matrix: &mut Matrix<'p, 'tcx>, - row_id: usize, lint_root: HirId, is_top_level: bool, ) { + if matrix.rows.is_empty() { + return; + } debug_assert!(matrix.rows().all(|r| r.len() == matrix.rows[0].len())); - // The base case. We are pattern-matching on () and the return value is based on whether our // matrix has a row or not. - if matrix.rows[0].is_empty() { - let useful = matrix.rows().take(row_id).all(|v| v.is_under_guard); - matrix.rows[row_id].is_useful = useful; - return; - } - - let v = &matrix.rows[row_id]; - - let mut ty = v.head().ty(); - // Opaque types can't get destructured/split, but the patterns can - // actually hint at hidden types, so we use the patterns' types instead. - if let ty::Alias(ty::Opaque, ..) = ty.kind() { - if let Some(row) = matrix.rows().next() { - ty = row.head().ty(); + let first_row = &matrix.rows[0]; + if first_row.is_empty() { + for row_id in 0..matrix.len() { + let useful = matrix.rows().take(row_id).all(|v| v.is_under_guard); + matrix.rows[row_id].is_useful = useful; } + return; } + let ty = first_row.head().ty(); let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); - let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive }; - - let v_ctor = v.head().ctor(); - debug!(?v_ctor); - - if let Constructor::IntRange(ctor_range) = &v_ctor { - // Lint on likely incorrect range patterns (#63987) - ctor_range.lint_overlapping_range_endpoints( - pcx, - matrix.heads().take(row_id), - v.len(), - lint_root, - ) + for row_id in 0..matrix.len() { + matrix.rows[row_id].is_useful = false; + let v = &matrix.rows[row_id]; + if let Constructor::IntRange(ctor_range) = v.head().ctor() { + // Lint on likely incorrect range patterns (#63987) + let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive }; + ctor_range.lint_overlapping_range_endpoints( + pcx, + matrix.heads().take(row_id), + v.len(), + lint_root, + ) + } } - matrix.rows[row_id].is_useful = false; - // We split the head constructor of `v`. - let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - // For each constructor, we compute whether there's a value that starts with it that would - // witness the usefulness of `v`. - for ctor in split_ctors { + let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; + let mut set = ConstructorSet::new(pcx); + let split_ctors = set.split(pcx, matrix.heads().map(|p| p.ctor())); + for ctor in &split_ctors { debug!("specialize({:?})", ctor); let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); + ensure_sufficient_stack(|| { + compute_row_usefulnesses(cx, &mut spec_matrix, lint_root, false) + }); for i in 0..spec_matrix.len() { - if spec_matrix.rows[i].parent_row == row_id { - ensure_sufficient_stack(|| { - is_nth_row_useful(cx, &mut spec_matrix, i, lint_root, false) - }); - matrix.rows[row_id].is_useful = - matrix.rows[row_id].is_useful || spec_matrix.rows[i].is_useful; - } + matrix.rows[spec_matrix.rows[i].parent_row].is_useful = + matrix.rows[spec_matrix.rows[i].parent_row].is_useful + || spec_matrix.rows[i].is_useful; } } - let v = &matrix.rows[row_id]; - if v.is_useful { - v.head().set_reachable(); + + for row in matrix.rows() { + if row.is_useful { + row.head().set_reachable(); + } } } @@ -1021,9 +1016,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( let v = PatStack::from_pattern(arm.pat, row_id, arm.has_guard); matrix.push(v); } - for i in 0..matrix.len() { - is_nth_row_useful(cx, &mut matrix, i, lint_root, true); - } + compute_row_usefulnesses(cx, &mut matrix, lint_root, true); let arm_usefulness: Vec<_> = arms .iter() .copied() diff --git a/tests/ui/pattern/usefulness/integer-ranges/reachability.rs b/tests/ui/pattern/usefulness/integer-ranges/reachability.rs index fb4d59b05780e..247fdd91572cd 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/reachability.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/reachability.rs @@ -9,9 +9,10 @@ macro_rules! m { $t2 => {} _ => {} } - } + }; } +#[rustfmt::skip] fn main() { m!(0u8, 42, 41); m!(0u8, 42, 42); //~ ERROR unreachable pattern @@ -85,7 +86,7 @@ fn main() { match 'a' { '\u{0}'..='\u{D7FF}' => {}, '\u{E000}'..='\u{10_FFFF}' => {}, - '\u{D7FF}'..='\u{E000}' => {}, // FIXME should be unreachable + '\u{D7FF}'..='\u{E000}' => {}, //~ ERROR unreachable pattern } match (0u8, true) { diff --git a/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr b/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr index 0ffb0ffd82aa0..c5b028d2038c3 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr @@ -1,5 +1,5 @@ error: unreachable pattern - --> $DIR/reachability.rs:17:17 + --> $DIR/reachability.rs:18:17 | LL | m!(0u8, 42, 42); | ^^ @@ -11,127 +11,127 @@ LL | #![deny(unreachable_patterns)] | ^^^^^^^^^^^^^^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:21:22 + --> $DIR/reachability.rs:22:22 | LL | m!(0u8, 20..=30, 20); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:22:22 + --> $DIR/reachability.rs:23:22 | LL | m!(0u8, 20..=30, 21); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:23:22 + --> $DIR/reachability.rs:24:22 | LL | m!(0u8, 20..=30, 25); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:24:22 + --> $DIR/reachability.rs:25:22 | LL | m!(0u8, 20..=30, 29); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:25:22 + --> $DIR/reachability.rs:26:22 | LL | m!(0u8, 20..=30, 30); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:28:21 + --> $DIR/reachability.rs:29:21 | LL | m!(0u8, 20..30, 20); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:29:21 + --> $DIR/reachability.rs:30:21 | LL | m!(0u8, 20..30, 21); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:30:21 + --> $DIR/reachability.rs:31:21 | LL | m!(0u8, 20..30, 25); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:31:21 + --> $DIR/reachability.rs:32:21 | LL | m!(0u8, 20..30, 29); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:35:22 + --> $DIR/reachability.rs:36:22 | LL | m!(0u8, 20..=30, 20..=30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:36:22 + --> $DIR/reachability.rs:37:22 | LL | m!(0u8, 20.. 30, 20.. 30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:37:22 + --> $DIR/reachability.rs:38:22 | LL | m!(0u8, 20..=30, 20.. 30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:39:22 + --> $DIR/reachability.rs:40:22 | LL | m!(0u8, 20..=30, 21..=30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:40:22 + --> $DIR/reachability.rs:41:22 | LL | m!(0u8, 20..=30, 20..=29); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:42:24 + --> $DIR/reachability.rs:43:24 | LL | m!('a', 'A'..='z', 'a'..='z'); | ^^^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:49:9 + --> $DIR/reachability.rs:50:9 | LL | 5..=8 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:55:9 + --> $DIR/reachability.rs:56:9 | LL | 5..15 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:62:9 + --> $DIR/reachability.rs:63:9 | LL | 5..25 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:70:9 + --> $DIR/reachability.rs:71:9 | LL | 5..25 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:76:9 + --> $DIR/reachability.rs:77:9 | LL | 5..15 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:83:9 + --> $DIR/reachability.rs:84:9 | LL | _ => {}, | - matches any value @@ -139,16 +139,22 @@ LL | '\u{D7FF}'..='\u{E000}' => {}, | ^^^^^^^^^^^^^^^^^^^^^^^ unreachable pattern error: unreachable pattern - --> $DIR/reachability.rs:104:9 + --> $DIR/reachability.rs:89:9 + | +LL | '\u{D7FF}'..='\u{E000}' => {}, + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: unreachable pattern + --> $DIR/reachability.rs:105:9 | LL | &FOO => {} | ^^^^ error: unreachable pattern - --> $DIR/reachability.rs:105:9 + --> $DIR/reachability.rs:106:9 | LL | BAR => {} | ^^^ -error: aborting due to 24 previous errors +error: aborting due to 25 previous errors From 690e6008e2a1d521e757384c313bc5ce24ab8079 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 17 May 2023 23:12:27 +0200 Subject: [PATCH 12/40] use set to emulate splitwildcard --- .../src/thir/pattern/deconstruct_pat.rs | 73 ++++++++++++++++--- .../src/thir/pattern/usefulness.rs | 33 +++++---- tests/ui/pattern/usefulness/issue-3601.stderr | 6 +- .../usefulness/match-non-exhaustive.stderr | 10 +-- 4 files changed, 89 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 632a7000f0c16..0641d340202e1 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -895,10 +895,10 @@ impl<'tcx> Constructor<'tcx> { /// cover all constructors in the matrix, and must be split wrt the constructors of the matrix, as /// explained at the top of the file. pub(super) enum ConstructorSet<'tcx> { - Uninhabited, - Ranges(Vec), - Slices(Slice), - UnListable, + // Uninhabited, + // Ranges(Vec), + // Slices(Slice), + // UnListable, Other { base_ctors: Vec> }, } @@ -1037,20 +1037,19 @@ impl<'tcx> ConstructorSet<'tcx> { let mut seen_ctors = Vec::new(); let mut seen_wildcard = false; let mut any_missing = false; + let mut seen_any_non_wildcard = false; for ctor in ctors { if ctor.is_wildcard() { seen_wildcard = true; } else if let Constructor::Opaque(..) = ctor { split_ctors.push(ctor.clone()); + seen_any_non_wildcard = true; } else { seen_ctors.push(ctor.clone()); + seen_any_non_wildcard = true; } } match self { - ConstructorSet::Uninhabited => todo!(), - ConstructorSet::Ranges(_) => todo!(), - ConstructorSet::Slices(_) => todo!(), - ConstructorSet::UnListable => todo!(), ConstructorSet::Other { base_ctors } => { if matches!(base_ctors.as_slice(), [Constructor::NonExhaustive]) { // FIXME: self-splitting is only useful for isize/usize. Would be nice to reust @@ -1073,10 +1072,66 @@ impl<'tcx> ConstructorSet<'tcx> { } } if seen_wildcard && any_missing { - split_ctors.push(Constructor::Missing); + let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); + if seen_any_non_wildcard || report_when_all_missing { + split_ctors.push(Missing); + } else { + split_ctors.push(Wildcard); + } } split_ctors } + + /// Pass a set of constructors relative to which to split this one. + pub(super) fn split_for_wildcard<'a>( + self, + pcx: &PatCtxt<'_, '_, 'tcx>, + ctors: impl Iterator> + Clone, + ) -> (Vec>, Vec>) + where + 'tcx: 'a, + { + let split_base_ctors: Vec<_>; + let mut missing_ctors = Vec::new(); + let mut seen_ctors = Vec::new(); + let mut seen_any_non_wildcard = false; + // let mut seen_wildcard = false; + for ctor in ctors { + if ctor.is_wildcard() { + // seen_wildcard = true; + } else if let Constructor::Opaque(..) = ctor { + // split_ctors.push(ctor.clone()); + seen_any_non_wildcard = true; + } else { + seen_ctors.push(ctor.clone()); + seen_any_non_wildcard = true; + } + } + match self { + ConstructorSet::Other { base_ctors } => { + split_base_ctors = + base_ctors.iter().flat_map(|ctor| ctor.split(pcx, seen_ctors.iter())).collect(); + for ctor in split_base_ctors.iter() { + if ctor.is_covered_by_any(pcx, seen_ctors.as_slice()) { + // split_ctors.push(ctor.clone()); + } else { + missing_ctors.push(ctor.clone()); + } + } + } + } + let ctors = if !missing_ctors.is_empty() { + let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); + if seen_any_non_wildcard || report_when_all_missing { + vec![Missing] + } else { + vec![Wildcard] + } + } else { + split_base_ctors + }; + (ctors, missing_ctors) + } } /// A wildcard constructor that we split relative to the constructors in the matrix, as explained diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index e3245f9badfaa..7d7a24b1ca515 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -673,7 +673,7 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { fn apply_constructor( &mut self, pcx: &PatCtxt<'_, 'p, 'tcx>, - split_wildcard: &SplitWildcard<'tcx>, + missing_ctors: &[Constructor<'tcx>], ctor: &Constructor<'tcx>, ) { if self.is_empty() { @@ -696,7 +696,7 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { // We replace them with a wildcard. let old_witnesses = std::mem::replace(self, Self::new_empty()); let mut skipped_any_variant = false; - for missing_ctor in split_wildcard.iter_missing(pcx) { + for missing_ctor in missing_ctors { if missing_ctor.is_doc_hidden_variant(pcx) || missing_ctor.is_unstable_variant(pcx) { skipped_any_variant = true; @@ -782,7 +782,7 @@ fn compute_row_usefulnesses<'p, 'tcx>( } let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; - let mut set = ConstructorSet::new(pcx); + let set = ConstructorSet::new(pcx); let split_ctors = set.split(pcx, matrix.heads().map(|p| p.ctor())); for ctor in &split_ctors { debug!("specialize({:?})", ctor); @@ -829,7 +829,7 @@ fn compute_row_usefulnesses<'p, 'tcx>( #[instrument(level = "debug", skip(cx, is_top_level), ret)] fn compute_nonexhaustiveness_witnesses<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, - matrix: &Matrix<'p, 'tcx>, + matrix: &mut Matrix<'p, 'tcx>, v: &PatStack<'p, 'tcx>, is_top_level: bool, ) -> WitnessMatrix<'p, 'tcx> { @@ -861,18 +861,19 @@ fn compute_nonexhaustiveness_witnesses<'p, 'tcx>( let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; let mut ret = WitnessMatrix::new_empty(); - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, matrix.heads().map(|p| p.ctor())); + let set = ConstructorSet::new(pcx); + let (split_ctors, missing_ctors) = + set.split_for_wildcard(pcx, matrix.heads().map(|p| p.ctor())); // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. - for ctor in split_wildcard.to_ctors(pcx) { + for ctor in split_ctors { debug!("specialize({:?})", ctor); - let spec_matrix = matrix.specialize_constructor(pcx, &ctor); + let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); let v = v.pop_head_constructor(pcx, &ctor, usize::MAX); let mut witnesses = ensure_sufficient_stack(|| { - compute_nonexhaustiveness_witnesses(cx, &spec_matrix, &v, false) + compute_nonexhaustiveness_witnesses(cx, &mut spec_matrix, &v, false) }); - witnesses.apply_constructor(pcx, &split_wildcard, &ctor); + witnesses.apply_constructor(pcx, &missing_ctors, &ctor); ret.extend(witnesses); } @@ -1016,7 +1017,13 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( let v = PatStack::from_pattern(arm.pat, row_id, arm.has_guard); matrix.push(v); } + + let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); + let v = PatStack::from_pattern(wild_pattern, usize::MAX, false); compute_row_usefulnesses(cx, &mut matrix, lint_root, true); + let non_exhaustiveness_witnesses = + compute_nonexhaustiveness_witnesses(cx, &mut matrix, &v, true); + let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); let arm_usefulness: Vec<_> = arms .iter() .copied() @@ -1031,12 +1038,6 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( }) .collect(); - matrix.rows.retain(|v| !v.is_under_guard); - let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); - let v = PatStack::from_pattern(wild_pattern, usize::MAX, false); - let non_exhaustiveness_witnesses = compute_nonexhaustiveness_witnesses(cx, &matrix, &v, true); - let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); - // Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hittin // `if let`s. Only run if the match is exhaustive otherwise the error is redundant. if cx.refutable diff --git a/tests/ui/pattern/usefulness/issue-3601.stderr b/tests/ui/pattern/usefulness/issue-3601.stderr index 2f6b167d4f8bd..4a25473f3f070 100644 --- a/tests/ui/pattern/usefulness/issue-3601.stderr +++ b/tests/ui/pattern/usefulness/issue-3601.stderr @@ -1,8 +1,8 @@ -error[E0004]: non-exhaustive patterns: `box _` not covered +error[E0004]: non-exhaustive patterns: `box ElementKind::HTMLImageElement(_)` not covered --> $DIR/issue-3601.rs:30:44 | LL | box NodeKind::Element(ed) => match ed.kind { - | ^^^^^^^ pattern `box _` not covered + | ^^^^^^^ pattern `box ElementKind::HTMLImageElement(_)` not covered | note: `Box` defined here --> $SRC_DIR/alloc/src/boxed.rs:LL:COL @@ -10,7 +10,7 @@ note: `Box` defined here help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | LL ~ box ElementKind::HTMLImageElement(ref d) if d.image.is_some() => { true }, -LL + box _ => todo!() +LL + box ElementKind::HTMLImageElement(_) => todo!() | error: aborting due to previous error diff --git a/tests/ui/pattern/usefulness/match-non-exhaustive.stderr b/tests/ui/pattern/usefulness/match-non-exhaustive.stderr index 08dde523a15fb..f226bdf80646e 100644 --- a/tests/ui/pattern/usefulness/match-non-exhaustive.stderr +++ b/tests/ui/pattern/usefulness/match-non-exhaustive.stderr @@ -10,17 +10,17 @@ help: ensure that all possible cases are being handled by adding a match arm wit LL | match 0 { 1 => (), i32::MIN..=0_i32 | 2_i32..=i32::MAX => todo!() } | ++++++++++++++++++++++++++++++++++++++++++++++++ -error[E0004]: non-exhaustive patterns: `_` not covered +error[E0004]: non-exhaustive patterns: `i32::MIN..=-1_i32` and `1_i32..=i32::MAX` not covered --> $DIR/match-non-exhaustive.rs:3:11 | LL | match 0 { 0 if false => () } - | ^ pattern `_` not covered + | ^ patterns `i32::MIN..=-1_i32` and `1_i32..=i32::MAX` not covered | = note: the matched value is of type `i32` -help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown +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 0 { 0 if false => (), _ => todo!() } - | ++++++++++++++ +LL | match 0 { 0 if false => (), i32::MIN..=-1_i32 | 1_i32..=i32::MAX => todo!() } + | +++++++++++++++++++++++++++++++++++++++++++++++++ error: aborting due to 2 previous errors From d96a78ad2bca48b8fd06e154cefa3f1a6e1bb168 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 May 2023 00:00:43 +0200 Subject: [PATCH 13/40] true one pass! --- .../src/thir/pattern/deconstruct_pat.rs | 73 ++------- .../src/thir/pattern/usefulness.rs | 147 ++++++------------ 2 files changed, 58 insertions(+), 162 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 0641d340202e1..f733bf92f6503 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -1029,18 +1029,16 @@ impl<'tcx> ConstructorSet<'tcx> { self, pcx: &PatCtxt<'_, '_, 'tcx>, ctors: impl Iterator> + Clone, - ) -> Vec> + ) -> (Vec>, Vec>) where 'tcx: 'a, { + let mut missing_ctors = Vec::new(); let mut split_ctors = Vec::new(); let mut seen_ctors = Vec::new(); - let mut seen_wildcard = false; - let mut any_missing = false; let mut seen_any_non_wildcard = false; for ctor in ctors { if ctor.is_wildcard() { - seen_wildcard = true; } else if let Constructor::Opaque(..) = ctor { split_ctors.push(ctor.clone()); seen_any_non_wildcard = true; @@ -1052,26 +1050,28 @@ impl<'tcx> ConstructorSet<'tcx> { match self { ConstructorSet::Other { base_ctors } => { if matches!(base_ctors.as_slice(), [Constructor::NonExhaustive]) { - // FIXME: self-splitting is only useful for isize/usize. Would be nice to reust + // Since `base_ctors` isn't listing our constructors, we need to take the ones + // in the matrix. + // FIXME: self-splitting is only useful for isize/usize. Would be nice to reuse // IntRange there somehow. split_ctors .extend(seen_ctors.iter().flat_map(|c| c.split(pcx, seen_ctors.iter()))); - any_missing = true; + missing_ctors = base_ctors; } else { // FIXME: splitting and collecting present ctors should be done in one pass. let split_base_ctors = - base_ctors.iter().flat_map(|ctor| ctor.split(pcx, seen_ctors.iter())); + base_ctors.into_iter().flat_map(|ctor| ctor.split(pcx, seen_ctors.iter())); for ctor in split_base_ctors { if ctor.is_covered_by_any(pcx, seen_ctors.as_slice()) { - split_ctors.push(ctor.clone()); + split_ctors.push(ctor); } else { - any_missing = true; + missing_ctors.push(ctor); } } } } } - if seen_wildcard && any_missing { + if !missing_ctors.is_empty() { let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); if seen_any_non_wildcard || report_when_all_missing { split_ctors.push(Missing); @@ -1079,58 +1079,7 @@ impl<'tcx> ConstructorSet<'tcx> { split_ctors.push(Wildcard); } } - split_ctors - } - - /// Pass a set of constructors relative to which to split this one. - pub(super) fn split_for_wildcard<'a>( - self, - pcx: &PatCtxt<'_, '_, 'tcx>, - ctors: impl Iterator> + Clone, - ) -> (Vec>, Vec>) - where - 'tcx: 'a, - { - let split_base_ctors: Vec<_>; - let mut missing_ctors = Vec::new(); - let mut seen_ctors = Vec::new(); - let mut seen_any_non_wildcard = false; - // let mut seen_wildcard = false; - for ctor in ctors { - if ctor.is_wildcard() { - // seen_wildcard = true; - } else if let Constructor::Opaque(..) = ctor { - // split_ctors.push(ctor.clone()); - seen_any_non_wildcard = true; - } else { - seen_ctors.push(ctor.clone()); - seen_any_non_wildcard = true; - } - } - match self { - ConstructorSet::Other { base_ctors } => { - split_base_ctors = - base_ctors.iter().flat_map(|ctor| ctor.split(pcx, seen_ctors.iter())).collect(); - for ctor in split_base_ctors.iter() { - if ctor.is_covered_by_any(pcx, seen_ctors.as_slice()) { - // split_ctors.push(ctor.clone()); - } else { - missing_ctors.push(ctor.clone()); - } - } - } - } - let ctors = if !missing_ctors.is_empty() { - let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); - if seen_any_non_wildcard || report_when_all_missing { - vec![Missing] - } else { - vec![Wildcard] - } - } else { - split_base_ctors - }; - (ctors, missing_ctors) + (split_ctors, missing_ctors) } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 7d7a24b1ca515..754b1adde60bf 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -720,90 +720,6 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { } } -/// Algorithm from . -/// The algorithm from the paper has been modified to correctly handle empty -/// types. The changes are: -/// (0) We don't exit early if the pattern matrix has zero rows. We just -/// continue to recurse over columns. -/// (1) all_constructors will only return constructors that are statically -/// possible. E.g., it will only return `Ok` for `Result`. -/// -/// This finds whether a (row) vector `v` of patterns is 'useful' in relation -/// to a set of such vectors `m` - this is defined as there being a set of -/// inputs that will match `v` but not any of the sets in `m`. -/// -/// All the patterns at each column of the `matrix ++ v` matrix must have the same type. -/// -/// This is used both for reachability checking (if a pattern isn't useful in -/// relation to preceding patterns, it is not reachable) and exhaustiveness -/// checking (if a wildcard pattern is useful in relation to a matrix, the -/// matrix isn't exhaustive). -/// -/// `is_under_guard` is used to inform if the pattern has a guard. If it -/// has one it must not be inserted into the matrix. This shouldn't be -/// relied on for soundness. -#[instrument(level = "debug", skip(cx, lint_root, is_top_level), ret)] -fn compute_row_usefulnesses<'p, 'tcx>( - cx: &MatchCheckCtxt<'p, 'tcx>, - matrix: &mut Matrix<'p, 'tcx>, - lint_root: HirId, - is_top_level: bool, -) { - if matrix.rows.is_empty() { - return; - } - debug_assert!(matrix.rows().all(|r| r.len() == matrix.rows[0].len())); - // The base case. We are pattern-matching on () and the return value is based on whether our - // matrix has a row or not. - let first_row = &matrix.rows[0]; - if first_row.is_empty() { - for row_id in 0..matrix.len() { - let useful = matrix.rows().take(row_id).all(|v| v.is_under_guard); - matrix.rows[row_id].is_useful = useful; - } - return; - } - - let ty = first_row.head().ty(); - let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); - for row_id in 0..matrix.len() { - matrix.rows[row_id].is_useful = false; - let v = &matrix.rows[row_id]; - if let Constructor::IntRange(ctor_range) = v.head().ctor() { - // Lint on likely incorrect range patterns (#63987) - let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive }; - ctor_range.lint_overlapping_range_endpoints( - pcx, - matrix.heads().take(row_id), - v.len(), - lint_root, - ) - } - } - - let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; - let set = ConstructorSet::new(pcx); - let split_ctors = set.split(pcx, matrix.heads().map(|p| p.ctor())); - for ctor in &split_ctors { - debug!("specialize({:?})", ctor); - let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); - ensure_sufficient_stack(|| { - compute_row_usefulnesses(cx, &mut spec_matrix, lint_root, false) - }); - for i in 0..spec_matrix.len() { - matrix.rows[spec_matrix.rows[i].parent_row].is_useful = - matrix.rows[spec_matrix.rows[i].parent_row].is_useful - || spec_matrix.rows[i].is_useful; - } - } - - for row in matrix.rows() { - if row.is_useful { - row.head().set_reachable(); - } - } -} - /// Algorithm from . /// The algorithm from the paper has been modified to correctly handle empty /// types. The changes are: @@ -827,21 +743,27 @@ fn compute_row_usefulnesses<'p, 'tcx>( /// has one it must not be inserted into the matrix. This shouldn't be /// relied on for soundness. #[instrument(level = "debug", skip(cx, is_top_level), ret)] -fn compute_nonexhaustiveness_witnesses<'p, 'tcx>( +fn compute_usefulness<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, matrix: &mut Matrix<'p, 'tcx>, v: &PatStack<'p, 'tcx>, + lint_root: HirId, is_top_level: bool, ) -> WitnessMatrix<'p, 'tcx> { debug_assert!(matrix.rows().all(|r| r.len() == v.len())); - // The base case. We are pattern-matching on () and the return value is - // based on whether our matrix has a row or not. - // NOTE: This could potentially be optimized by checking rows.is_empty() - // first and then, if v is non-empty, the return value is based on whether - // the type of the tuple we're checking is inhabited or not. + // The base case. We are pattern-matching on () and the return value is based on whether our + // matrix has a row or not. if v.is_empty() { - if matrix.rows().all(|v| v.is_under_guard) { + let mut useful = true; + for row in matrix.rows.iter_mut() { + row.is_useful = useful; + useful = useful && row.is_under_guard; + if !useful { + break; + } + } + if useful { return WitnessMatrix::new_unit(); } else { return WitnessMatrix::new_empty(); @@ -860,21 +782,48 @@ fn compute_nonexhaustiveness_witnesses<'p, 'tcx>( let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; - let mut ret = WitnessMatrix::new_empty(); + for row_id in 0..matrix.len() { + let v = &matrix.rows[row_id]; + if let Constructor::IntRange(ctor_range) = v.head().ctor() { + // Lint on likely incorrect range patterns (#63987) + let pcx = &PatCtxt { span: v.head().span(), ..*pcx }; + let compare_against = + matrix.rows().take(row_id).filter(|row| !row.is_under_guard).map(|row| row.head()); + ctor_range.lint_overlapping_range_endpoints(pcx, compare_against, v.len(), lint_root) + } + } + let set = ConstructorSet::new(pcx); - let (split_ctors, missing_ctors) = - set.split_for_wildcard(pcx, matrix.heads().map(|p| p.ctor())); + let (split_ctors, missing_ctors) = set.split(pcx, matrix.heads().map(|p| p.ctor())); // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. + let mut ret = WitnessMatrix::new_empty(); for ctor in split_ctors { debug!("specialize({:?})", ctor); let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); let v = v.pop_head_constructor(pcx, &ctor, usize::MAX); let mut witnesses = ensure_sufficient_stack(|| { - compute_nonexhaustiveness_witnesses(cx, &mut spec_matrix, &v, false) + compute_usefulness(cx, &mut spec_matrix, &v, lint_root, false) }); - witnesses.apply_constructor(pcx, &missing_ctors, &ctor); - ret.extend(witnesses); + if missing_ctors.is_empty() || matches!(ctor, Constructor::Wildcard | Constructor::Missing) + { + // If some ctors are missing we only report those. Mostly for historical reasons, + // nothing stops us from reporting all. + // FIXME: propagate this choice deeper to save work. + witnesses.apply_constructor(pcx, &missing_ctors, &ctor); + ret.extend(witnesses); + } + + for child_row in spec_matrix.rows() { + let parent_row = &mut matrix.rows[child_row.parent_row]; + parent_row.is_useful = parent_row.is_useful || child_row.is_useful; + } + } + + for row in matrix.rows() { + if row.is_useful { + row.head().set_reachable(); + } } ret @@ -1020,9 +969,7 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); let v = PatStack::from_pattern(wild_pattern, usize::MAX, false); - compute_row_usefulnesses(cx, &mut matrix, lint_root, true); - let non_exhaustiveness_witnesses = - compute_nonexhaustiveness_witnesses(cx, &mut matrix, &v, true); + let non_exhaustiveness_witnesses = compute_usefulness(cx, &mut matrix, &v, lint_root, true); let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); let arm_usefulness: Vec<_> = arms .iter() From 51fed5ea30a6ce4363a66899c8ae8115f6451cab Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 May 2023 00:21:25 +0200 Subject: [PATCH 14/40] don't need SplitWildcard anymore --- .../src/thir/pattern/deconstruct_pat.rs | 87 +------------------ .../src/thir/pattern/usefulness.rs | 15 ++-- 2 files changed, 7 insertions(+), 95 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index f733bf92f6503..00e3be8abb79d 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -778,9 +778,7 @@ impl<'tcx> Constructor<'tcx> { { match self { Wildcard => { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, ctors); - split_wildcard.to_ctors(pcx) + bug!("should not be splitting a wildcard"); } // Fast-track if the range is trivial. In particular, we don't do the overlapping // ranges check. @@ -1230,89 +1228,6 @@ impl<'tcx> SplitWildcard<'tcx> { SplitWildcard { matrix_ctors: Vec::new(), all_ctors } } - - /// Pass a set of constructors relative to which to split this one. Don't call twice, it won't - /// do what you want. - pub(super) fn split<'a>( - &mut self, - pcx: &PatCtxt<'_, '_, 'tcx>, - ctors: impl Iterator> + Clone, - ) where - 'tcx: 'a, - { - // Since `all_ctors` never contains wildcards, this won't recurse further. - self.all_ctors = - self.all_ctors.iter().flat_map(|ctor| ctor.split(pcx, ctors.clone())).collect(); - self.matrix_ctors = ctors.filter(|c| !matches!(c, Wildcard | Opaque)).cloned().collect(); - } - - /// Whether there are any value constructors for this type that are not present in the matrix. - fn any_missing(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool { - self.iter_missing(pcx).next().is_some() - } - - /// Iterate over the constructors for this type that are not present in the matrix. - pub(super) fn iter_missing<'a, 'p>( - &'a self, - pcx: &'a PatCtxt<'a, 'p, 'tcx>, - ) -> impl Iterator> + Captures<'p> { - self.all_ctors.iter().filter(move |ctor| !ctor.is_covered_by_any(pcx, &self.matrix_ctors)) - } - - /// Iterate over the constructors for this type that are present in the matrix. This has the - /// effect of deduplicating present constructors. - /// WARNING: this omits special constructors like `Wildcard` and `Opaque` and doesn't work for - /// non exhaustive types. - pub(super) fn iter_present<'a, 'p>( - &'a self, - pcx: &'a PatCtxt<'a, 'p, 'tcx>, - ) -> impl Iterator> + Captures<'p> { - self.all_ctors.iter().filter(move |ctor| ctor.is_covered_by_any(pcx, &self.matrix_ctors)) - } - - /// Return the set of constructors resulting from splitting the wildcard. As explained at the - /// top of the file, if any constructors are missing we can ignore the present ones. - pub(super) fn to_ctors(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> SmallVec<[Constructor<'tcx>; 1]> { - if self.any_missing(pcx) { - // Some constructors are missing, thus we can specialize with the special `Missing` - // constructor, which stands for those constructors that are not seen in the matrix, - // and matches the same rows as any of them (namely the wildcard rows). See the top of - // the file for details. - // However, when all constructors are missing we can also specialize with the full - // `Wildcard` constructor. The difference will depend on what we want in diagnostics. - - // If some constructors are missing, we typically want to report those constructors, - // e.g.: - // ``` - // enum Direction { N, S, E, W } - // let Direction::N = ...; - // ``` - // we can report 3 witnesses: `S`, `E`, and `W`. - // - // However, 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 `(, , - // true)` - we are satisfied with `(_, _, true)`. So if all constructors are missing we - // prefer to report just a wildcard `_`. - // - // The exception is: if we are at the top-level, for example in an empty match, we - // sometimes prefer reporting the list of constructors instead of just `_`. - let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); - let ctor = if !self.matrix_ctors.is_empty() || report_when_all_missing { - Missing - } else { - Wildcard - }; - return smallvec![ctor]; - } - - // All the constructors are present in the matrix, so we just go through them all. - self.all_ctors.clone() - } } /// A value can be decomposed into a constructor applied to some fields. This struct represents diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 754b1adde60bf..5465a1d06f681 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -305,9 +305,7 @@ //! stay as a full constant and become an `Opaque` pattern. These `Opaque` patterns do not participate //! in exhaustiveness, specialization or overlap checking. -use super::deconstruct_pat::{ - Constructor, ConstructorSet, DeconstructedPat, Fields, SplitWildcard, -}; +use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, Fields}; use crate::errors::{NonExhaustiveOmittedPattern, Uncovered}; use rustc_data_structures::captures::Captures; @@ -851,23 +849,22 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>( let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); let pcx = &PatCtxt { cx, ty, span, is_top_level: false, is_non_exhaustive }; - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, column.iter().map(|p| p.ctor())); + let set = ConstructorSet::new(pcx); + let (split_ctors, missing_ctors) = set.split(pcx, column.iter().map(|p| p.ctor())); if is_non_exhaustive { witnesses.extend( - split_wildcard - .iter_missing(pcx) + missing_ctors + .into_iter() // Filter out the `NonExhaustive` ctor because we want to list only real variants. // Also remove any unstable feature gated variants. .filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))) - .cloned() .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)), ) } // Recurse into the fields. - for ctor in split_wildcard.iter_present(pcx) { + for ctor in split_ctors { let arity = ctor.arity(pcx); if arity == 0 { continue; From 1e1db2e6137b27fdeed799a55cda5cf53c3a4494 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 May 2023 02:14:47 +0200 Subject: [PATCH 15/40] Specialize `ConstructorSet` per type. Got rid of `ctor.split()` --- .../src/thir/pattern/deconstruct_pat.rs | 574 +++++++----------- 1 file changed, 232 insertions(+), 342 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 00e3be8abb79d..0f94aa6cd2445 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -755,57 +755,13 @@ impl<'tcx> Constructor<'tcx> { } } - /// Some constructors (namely `Wildcard`, `IntRange` and `Slice`) actually stand for a set of actual - /// constructors (like variants, integers or fixed-sized slices). When specializing for these - /// constructors, we want to be specialising for the actual underlying constructors. - /// Naively, we would simply return the list of constructors they correspond to. We instead are - /// more clever: if there are constructors that we know will behave the same wrt the current - /// matrix, we keep them grouped. For example, all slices of a sufficiently large length - /// will either be all useful or all non-useful with a given matrix. - /// - /// See the branches for details on how the splitting is done. - /// - /// This function may discard some irrelevant constructors if this preserves behavior and - /// diagnostics. Eg. for the `_` case, we ignore the constructors already present in the - /// matrix, unless all of them are. - pub(super) fn split<'a>( - &self, - pcx: &PatCtxt<'_, '_, 'tcx>, - ctors: impl Iterator> + Clone, - ) -> SmallVec<[Self; 1]> - where - 'tcx: 'a, - { - match self { - Wildcard => { - bug!("should not be splitting a wildcard"); - } - // Fast-track if the range is trivial. In particular, we don't do the overlapping - // ranges check. - IntRange(ctor_range) if !ctor_range.is_singleton() => { - let mut split_range = SplitIntRange::new(ctor_range.clone()); - let int_ranges = ctors.filter_map(|ctor| ctor.as_int_range()); - split_range.split(int_ranges.cloned()); - split_range.iter().map(IntRange).collect() - } - &Slice(Slice { kind: VarLen(self_prefix, self_suffix), array_len }) => { - let mut split_self = SplitVarLenSlice::new(self_prefix, self_suffix, array_len); - let slices = ctors.filter_map(|c| c.as_slice()).map(|s| s.kind); - split_self.split(slices); - split_self.iter().map(Slice).collect() - } - // Any other constructor can be used unchanged. - _ => smallvec![self.clone()], - } - } - /// Returns whether `self` is covered by `other`, i.e. whether `self` is a subset of `other`. /// For the simple cases, this is simply checking for equality. For the "grouped" constructors, /// this checks for inclusion. // We inline because this has a single call site in `Matrix::specialize_constructor`. #[inline] pub(super) fn is_covered_by<'p>(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, other: &Self) -> bool { - // This must be kept in sync with `is_covered_by_any`. + // This must be compatible with `ConstructorSet::split`. match (self, other) { // Wildcards cover anything (_, Wildcard) => true, @@ -854,265 +810,36 @@ impl<'tcx> Constructor<'tcx> { ), } } - - /// Faster version of `is_covered_by` when applied to many constructors. `used_ctors` is - /// assumed to be built from `matrix.head_ctors()` with wildcards and opaques filtered out, - /// and `self` is assumed to have been split from a wildcard. - fn is_covered_by_any<'p>( - &self, - pcx: &PatCtxt<'_, 'p, 'tcx>, - used_ctors: &[Constructor<'tcx>], - ) -> bool { - if used_ctors.is_empty() { - return false; - } - - // This must be kept in sync with `is_covered_by`. - match self { - // If `self` is `Single`, `used_ctors` cannot contain anything else than `Single`s. - Single => !used_ctors.is_empty(), - Variant(vid) => used_ctors.iter().any(|c| matches!(c, Variant(i) if i == vid)), - IntRange(range) => used_ctors - .iter() - .filter_map(|c| c.as_int_range()) - .any(|other| range.is_covered_by(other)), - Slice(slice) => used_ctors - .iter() - .filter_map(|c| c.as_slice()) - .any(|other| slice.is_covered_by(other)), - // This constructor is never covered by anything else - NonExhaustive => false, - Str(..) | FloatRange(..) | Opaque(..) | Missing { .. } | Wildcard | Or => { - span_bug!(pcx.span, "found unexpected ctor in all_ctors: {:?}", self) - } - } - } } -/// Describes a set of constructors for a type. The constructors must be an exhaustive set, must -/// cover all constructors in the matrix, and must be split wrt the constructors of the matrix, as -/// explained at the top of the file. -pub(super) enum ConstructorSet<'tcx> { - // Uninhabited, - // Ranges(Vec), - // Slices(Slice), - // UnListable, - Other { base_ctors: Vec> }, +/// Describes the set of all constructors for a type. +pub(super) enum ConstructorSet { + /// The type has a single constructor, e.g. `&T` or a newtype. + Single, + /// This type has the following list of constructors. + Variants { variants: Vec, non_exhaustive: bool }, + /// The type is spanned by a range of integer values, e.g. all number types. + Range(IntRange), + /// The type is spanned by two ranges of integer values. This is special for chars. + Ranges([IntRange; 2]), + /// The type is spanned by a range of integer values of unknown boundaries. + UnlistableRange, + /// The type is matched by slices. + Slice(Slice), + /// The constructors cannot be listed, and the type cannot be matched exhaustively. E.g. `str`, + /// floats. + Unlistable, + /// The type has no inhabitants. + Uninhabited, } -impl<'tcx> ConstructorSet<'tcx> { - pub(super) fn new<'p>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { +impl ConstructorSet { + pub(super) fn new<'p, 'tcx>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { debug!("ConstructorSet::new({:?})", pcx.ty); - //let cx = pcx.cx; - //let make_range = |start, end| { - // // `unwrap()` is ok because we know the type is an integer. - // IntRange::from_range(cx.tcx, start, end, pcx.ty, &RangeEnd::Included).unwrap() - //}; - //// This determines the set of all possible constructors for the type `pcx.ty`. For numbers, - //// arrays and slices we use ranges and variable-length slices when appropriate. - //// - //// If the `exhaustive_patterns` feature is enabled, we make sure to omit constructors that - //// are statically impossible. E.g., for `Option`, we do not include `Some(_)` in the - //// returned list of constructors. - //// Invariant: this is empty if and only if the type is uninhabited (as determined by - //// `cx.is_uninhabited()`). - //match pcx.ty.kind() { - // ty::Bool => Self::Ranges(vec![make_range(0, 1)]), - // ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => { - // let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize; - // if len != 0 && cx.is_uninhabited(*sub_ty) { - // Self::Uninhabited - // } else { - // Self::Slices(Slice::new(Some(len), VarLen(0, 0))) - // } - // } - // // Treat arrays of a constant but unknown length like slices. - // ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { - // let kind = if cx.is_uninhabited(*sub_ty) { FixedLen(0) } else { VarLen(0, 0) }; - // Self::Slices(Slice::new(None, kind)) - // } - // ty::Adt(def, substs) if def.is_enum() => { - // // If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an - // // additional "unknown" constructor. - // // There is no point in enumerating all possible variants, because the user can't - // // actually match against them all themselves. So we always return only the fictitious - // // constructor. - // // 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 only `_` as the - // // witness. - // let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(pcx.ty); - - // let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; - - // // If `exhaustive_patterns` is disabled and our scrutinee is an empty enum, we treat it - // // as though it had an "unknown" constructor to avoid exposing its emptiness. The - // // exception is if the pattern is at the top level, because we want empty matches to be - // // considered exhaustive. - // let is_secretly_empty = - // def.variants().is_empty() && !is_exhaustive_pat_feature && !pcx.is_top_level; - - // let mut ctors: Vec<_> = def - // .variants() - // .iter_enumerated() - // .filter(|(_, v)| { - // // If `exhaustive_patterns` is enabled, we exclude variants known to be - // // uninhabited. - // !is_exhaustive_pat_feature - // || v.inhabited_predicate(cx.tcx, *def).subst(cx.tcx, substs).apply( - // cx.tcx, - // cx.param_env, - // cx.module, - // ) - // }) - // .map(|(idx, _)| Variant(idx)) - // .collect(); - - // if is_secretly_empty || is_declared_nonexhaustive { - // ctors.push(NonExhaustive); - // } - // Self::Other { all_ctors: ctors } - // } - // ty::Char => { - // Self::Ranges(vec![ - // // The valid Unicode Scalar Value ranges. - // make_range('\u{0000}' as u128, '\u{D7FF}' as u128), - // make_range('\u{E000}' as u128, '\u{10FFFF}' as u128), - // ]) - // } - // ty::Int(_) | ty::Uint(_) - // if pcx.ty.is_ptr_sized_integral() - // && !cx.tcx.features().precise_pointer_size_matching => - // { - // // `usize`/`isize` are not allowed to be matched exhaustively unless the - // // `precise_pointer_size_matching` feature is enabled. So we treat those types like - // // `#[non_exhaustive]` enums by returning a special unmatchable constructor. - // Self::UnListable - // } - // &ty::Int(ity) => { - // let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; - // let min = 1u128 << (bits - 1); - // let max = min - 1; - // Self::Ranges(vec![make_range(min, max)]) - // } - // &ty::Uint(uty) => { - // let size = Integer::from_uint_ty(&cx.tcx, uty).size(); - // let max = size.truncate(u128::MAX); - // Self::Ranges(vec![make_range(0, max)]) - // } - // // If `exhaustive_patterns` is disabled and our scrutinee is the never type, we cannot - // // expose its emptiness. The exception is if the pattern is at the top level, because we - // // want empty matches to be considered exhaustive. - // ty::Never if !cx.tcx.features().exhaustive_patterns && !pcx.is_top_level => { - // Self::UnListable - // } - // ty::Never => Self::Uninhabited, - // _ if cx.is_uninhabited(pcx.ty) => Self::Uninhabited, - // ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => Self::Other { all_ctors: vec![Single] }, - // // This type is one for which we cannot list constructors, like `str` or `f64`. - // _ => Self::UnListable, - //} - Self::Other { base_ctors: SplitWildcard::new(pcx).all_ctors.to_vec() } - } - - /// Pass a set of constructors relative to which to split this one. - pub(super) fn split<'a>( - self, - pcx: &PatCtxt<'_, '_, 'tcx>, - ctors: impl Iterator> + Clone, - ) -> (Vec>, Vec>) - where - 'tcx: 'a, - { - let mut missing_ctors = Vec::new(); - let mut split_ctors = Vec::new(); - let mut seen_ctors = Vec::new(); - let mut seen_any_non_wildcard = false; - for ctor in ctors { - if ctor.is_wildcard() { - } else if let Constructor::Opaque(..) = ctor { - split_ctors.push(ctor.clone()); - seen_any_non_wildcard = true; - } else { - seen_ctors.push(ctor.clone()); - seen_any_non_wildcard = true; - } - } - match self { - ConstructorSet::Other { base_ctors } => { - if matches!(base_ctors.as_slice(), [Constructor::NonExhaustive]) { - // Since `base_ctors` isn't listing our constructors, we need to take the ones - // in the matrix. - // FIXME: self-splitting is only useful for isize/usize. Would be nice to reuse - // IntRange there somehow. - split_ctors - .extend(seen_ctors.iter().flat_map(|c| c.split(pcx, seen_ctors.iter()))); - missing_ctors = base_ctors; - } else { - // FIXME: splitting and collecting present ctors should be done in one pass. - let split_base_ctors = - base_ctors.into_iter().flat_map(|ctor| ctor.split(pcx, seen_ctors.iter())); - for ctor in split_base_ctors { - if ctor.is_covered_by_any(pcx, seen_ctors.as_slice()) { - split_ctors.push(ctor); - } else { - missing_ctors.push(ctor); - } - } - } - } - } - if !missing_ctors.is_empty() { - let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); - if seen_any_non_wildcard || report_when_all_missing { - split_ctors.push(Missing); - } else { - split_ctors.push(Wildcard); - } - } - (split_ctors, missing_ctors) - } -} - -/// A wildcard constructor that we split relative to the constructors in the matrix, as explained -/// at the top of the file. -/// -/// A constructor that is not present in the matrix rows will only be covered by the rows that have -/// wildcards. Thus we can group all of those constructors together; we call them "missing -/// constructors". Splitting a wildcard would therefore list all present constructors individually -/// (or grouped if they are integers or slices), and then all missing constructors together as a -/// group. -/// -/// However we can go further: since any constructor will match the wildcard rows, and having more -/// rows can only reduce the amount of usefulness witnesses, we can skip the present constructors -/// and only try the missing ones. -/// This will not preserve the whole list of witnesses, but will preserve whether the list is empty -/// or not. In fact this is quite natural from the point of view of diagnostics too. This is done -/// in `to_ctors`: in some cases we only return `Missing`. -#[derive(Debug)] -pub(super) struct SplitWildcard<'tcx> { - /// Constructors (other than wildcards and opaques) seen in the matrix. - matrix_ctors: Vec>, - /// All the constructors for this type - all_ctors: SmallVec<[Constructor<'tcx>; 1]>, -} - -impl<'tcx> SplitWildcard<'tcx> { - pub(super) fn new<'p>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { - debug!("SplitWildcard::new({:?})", pcx.ty); let cx = pcx.cx; let make_range = |start, end| { - IntRange( - // `unwrap()` is ok because we know the type is an integer. - IntRange::from_range(cx.tcx, start, end, pcx.ty, &RangeEnd::Included).unwrap(), - ) + // `unwrap()` is ok because we know the type is an integer. + IntRange::from_range(cx.tcx, start, end, pcx.ty, &RangeEnd::Included).unwrap() }; // This determines the set of all possible constructors for the type `pcx.ty`. For numbers, // arrays and slices we use ranges and variable-length slices when appropriate. @@ -1120,22 +847,48 @@ impl<'tcx> SplitWildcard<'tcx> { // If the `exhaustive_patterns` feature is enabled, we make sure to omit constructors that // are statically impossible. E.g., for `Option`, we do not include `Some(_)` in the // returned list of constructors. - // Invariant: this is empty if and only if the type is uninhabited (as determined by + // Invariant: this is `Uninhabited` if and only if the type is uninhabited (as determined by // `cx.is_uninhabited()`). - let all_ctors = match pcx.ty.kind() { - ty::Bool => smallvec![make_range(0, 1)], + match pcx.ty.kind() { + ty::Bool => Self::Range(make_range(0, 1)), + ty::Char => { + Self::Ranges([ + // The valid Unicode Scalar Value ranges. + make_range('\u{0000}' as u128, '\u{D7FF}' as u128), + make_range('\u{E000}' as u128, '\u{10FFFF}' as u128), + ]) + } + ty::Int(_) | ty::Uint(_) + if pcx.ty.is_ptr_sized_integral() + && !cx.tcx.features().precise_pointer_size_matching => + { + // `usize`/`isize` are not allowed to be matched exhaustively unless the + // `precise_pointer_size_matching` feature is enabled. + Self::UnlistableRange + } + &ty::Int(ity) => { + let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; + let min = 1u128 << (bits - 1); + let max = min - 1; + Self::Range(make_range(min, max)) + } + &ty::Uint(uty) => { + let size = Integer::from_uint_ty(&cx.tcx, uty).size(); + let max = size.truncate(u128::MAX); + Self::Range(make_range(0, max)) + } ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => { let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize; if len != 0 && cx.is_uninhabited(*sub_ty) { - smallvec![] + Self::Uninhabited } else { - smallvec![Slice(Slice::new(Some(len), VarLen(0, 0)))] + Self::Slice(Slice::new(Some(len), VarLen(0, 0))) } } // Treat arrays of a constant but unknown length like slices. ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { let kind = if cx.is_uninhabited(*sub_ty) { FixedLen(0) } else { VarLen(0, 0) }; - smallvec![Slice(Slice::new(None, kind))] + Self::Slice(Slice::new(None, kind)) } ty::Adt(def, substs) if def.is_enum() => { // If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an @@ -1165,7 +918,7 @@ impl<'tcx> SplitWildcard<'tcx> { let is_secretly_empty = def.variants().is_empty() && !is_exhaustive_pat_feature && !pcx.is_top_level; - let mut ctors: SmallVec<[_; 1]> = def + let variants: Vec<_> = def .variants() .iter_enumerated() .filter(|(_, v)| { @@ -1178,55 +931,192 @@ impl<'tcx> SplitWildcard<'tcx> { cx.module, ) }) - .map(|(idx, _)| Variant(idx)) + .map(|(idx, _)| idx) .collect(); - if is_secretly_empty || is_declared_nonexhaustive { - ctors.push(NonExhaustive); + Self::Variants { + variants, + non_exhaustive: is_secretly_empty || is_declared_nonexhaustive, } - ctors - } - ty::Char => { - smallvec![ - // The valid Unicode Scalar Value ranges. - make_range('\u{0000}' as u128, '\u{D7FF}' as u128), - make_range('\u{E000}' as u128, '\u{10FFFF}' as u128), - ] - } - ty::Int(_) | ty::Uint(_) - if pcx.ty.is_ptr_sized_integral() - && !cx.tcx.features().precise_pointer_size_matching => - { - // `usize`/`isize` are not allowed to be matched exhaustively unless the - // `precise_pointer_size_matching` feature is enabled. So we treat those types like - // `#[non_exhaustive]` enums by returning a special unmatchable constructor. - smallvec![NonExhaustive] - } - &ty::Int(ity) => { - let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; - let min = 1u128 << (bits - 1); - let max = min - 1; - smallvec![make_range(min, max)] - } - &ty::Uint(uty) => { - let size = Integer::from_uint_ty(&cx.tcx, uty).size(); - let max = size.truncate(u128::MAX); - smallvec![make_range(0, max)] } // If `exhaustive_patterns` is disabled and our scrutinee is the never type, we cannot // expose its emptiness. The exception is if the pattern is at the top level, because we // want empty matches to be considered exhaustive. ty::Never if !cx.tcx.features().exhaustive_patterns && !pcx.is_top_level => { - smallvec![NonExhaustive] + Self::Unlistable } - ty::Never => smallvec![], - _ if cx.is_uninhabited(pcx.ty) => smallvec![], - ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => smallvec![Single], + ty::Never => Self::Uninhabited, + _ if cx.is_uninhabited(pcx.ty) => Self::Uninhabited, + ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => Self::Single, // This type is one for which we cannot list constructors, like `str` or `f64`. - _ => smallvec![NonExhaustive], - }; + _ => Self::Unlistable, + } + } - SplitWildcard { matrix_ctors: Vec::new(), all_ctors } + /// Split the set into two vecs `split` and `missing`, respecting the following: + /// - `split` covers the whole type + /// - constructors in `split` and `missing` are split for the `ctors` list + /// - all non-wildcards in `ctors` cover something in `split` (including `Opaques`) + /// - no non-wildcard in `ctors` covers anything in `missing` + /// - if we replace any `Wildcard`/`Missing` in `split` with all of `missing`, this also + /// covers the whole type + pub(super) fn split<'a, 'tcx>( + self, + pcx: &PatCtxt<'_, '_, 'tcx>, + ctors: impl Iterator> + Clone, + ) -> (Vec>, Vec>) + where + 'tcx: 'a, + { + let mut missing = Vec::new(); + let mut split = Vec::new(); + // Constructors in `ctors`, except wildcards and opaques. + let mut seen = Vec::new(); + for ctor in ctors.cloned() { + match ctor { + Wildcard => {} + Constructor::Opaque(..) => { + split.push(ctor); + } + _ => { + seen.push(ctor); + } + } + } + let seen_any_non_wildcard = !(split.is_empty() && seen.is_empty()); + match self { + ConstructorSet::Single => { + if seen.is_empty() { + missing.push(Single); + } else { + split.push(Single); + } + } + ConstructorSet::Variants { variants, non_exhaustive } if seen.is_empty() => { + missing.extend(variants.into_iter().map(Variant)); + if non_exhaustive { + missing.push(NonExhaustive); + } + } + ConstructorSet::Variants { variants, non_exhaustive } => { + // FIXME: can do better than quadratic. + for variant in variants { + let was_seen = seen.iter().any(|c| matches!(c, Variant(i) if *i == variant)); + let ctor = Variant(variant); + if was_seen { + split.push(ctor); + } else { + missing.push(ctor); + } + } + if non_exhaustive { + missing.push(NonExhaustive); + } + } + ConstructorSet::Range(base_range) if seen.is_empty() => { + missing.push(IntRange(base_range)); + } + ConstructorSet::Range(base_range) => { + // FIXME: splitting and collecting should be done in one pass. + let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); + let mut split_base_range = SplitIntRange::new(base_range); + split_base_range.split(seen_ranges.clone().cloned()); + for splitted_range in split_base_range.iter() { + let is_covered_by_any = + seen_ranges.clone().any(|other| splitted_range.is_covered_by(other)); + let ctor = IntRange(splitted_range); + if is_covered_by_any { + split.push(ctor); + } else { + missing.push(ctor); + } + } + } + ConstructorSet::Ranges(base_ranges) if seen.is_empty() => { + missing.extend(base_ranges.into_iter().map(IntRange)); + } + ConstructorSet::Ranges(base_ranges) => { + // FIXME: splitting and collecting should be done in one pass. + let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); + for base_range in base_ranges { + let mut split_base_range = SplitIntRange::new(base_range); + split_base_range.split(seen_ranges.clone().cloned()); + for splitted_range in split_base_range.iter() { + let is_covered_by_any = + seen_ranges.clone().any(|other| splitted_range.is_covered_by(other)); + let ctor = IntRange(splitted_range); + if is_covered_by_any { + split.push(ctor); + } else { + missing.push(ctor); + } + } + } + } + ConstructorSet::UnlistableRange if seen.is_empty() => { + missing.push(NonExhaustive); + } + ConstructorSet::UnlistableRange => { + // Since we can't list constructors, we need to take the ones in the matrix. + // FIXME: would be nice to reuse the `Range` case somehow instead of this quadratic + // splitting. + let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()).cloned(); + for seen_range in seen_ranges.clone() { + if seen_range.is_singleton() { + split.push(IntRange(seen_range)) + } else { + let mut split_range = SplitIntRange::new(seen_range); + split_range.split(seen_ranges.clone()); + split.extend(split_range.iter().map(IntRange)); + } + } + missing.push(NonExhaustive); + } + ConstructorSet::Slice(base_slice) if seen.is_empty() => { + missing.push(Slice(base_slice)); + } + ConstructorSet::Slice(base_slice) => { + match base_slice.kind { + FixedLen(..) => { + split.push(Slice(base_slice)); + } + VarLen(self_prefix, self_suffix) => { + // FIXME: splitting and collecting should be done in one pass. + let mut split_base_slice = + SplitVarLenSlice::new(self_prefix, self_suffix, base_slice.array_len); + let seen_slices = seen.iter().filter_map(|c| c.as_slice()); + split_base_slice.split(seen_slices.clone().map(|s| s.kind)); + for splitted_slice in split_base_slice.iter() { + let is_covered_by_any = seen_slices + .clone() + .any(|other| splitted_slice.is_covered_by(other)); + let ctor = Slice(splitted_slice); + if is_covered_by_any { + split.push(ctor); + } else { + missing.push(ctor); + } + } + } + } + } + ConstructorSet::Unlistable => { + // Since we can't list constructors, we take the ones in the matrix. This might list + // some constructors several times but there's not much we can do. + split.extend(seen); + missing.push(NonExhaustive); + } + ConstructorSet::Uninhabited => {} + } + if !missing.is_empty() { + let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); + if seen_any_non_wildcard || report_when_all_missing { + split.push(Missing); + } else { + split.push(Wildcard); + } + } + (split, missing) } } From 9c33ea79a1f2680a4dfbb06c4bf6f7474d3fc087 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 May 2023 05:20:17 +0200 Subject: [PATCH 16/40] track whether we're collecting witnesses --- .../src/thir/pattern/usefulness.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 5465a1d06f681..a51cd09c24e7a 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -745,6 +745,7 @@ fn compute_usefulness<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, matrix: &mut Matrix<'p, 'tcx>, v: &PatStack<'p, 'tcx>, + collect_witnesses: bool, lint_root: HirId, is_top_level: bool, ) -> WitnessMatrix<'p, 'tcx> { @@ -761,7 +762,7 @@ fn compute_usefulness<'p, 'tcx>( break; } } - if useful { + if useful && collect_witnesses { return WitnessMatrix::new_unit(); } else { return WitnessMatrix::new_empty(); @@ -797,17 +798,18 @@ fn compute_usefulness<'p, 'tcx>( // witness the usefulness of `v`. let mut ret = WitnessMatrix::new_empty(); for ctor in split_ctors { + // If some ctors are missing we only report those. Could report all if that's useful for + // some applications. + let collect_witnesses = collect_witnesses + && (missing_ctors.is_empty() + || matches!(ctor, Constructor::Wildcard | Constructor::Missing)); debug!("specialize({:?})", ctor); let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); let v = v.pop_head_constructor(pcx, &ctor, usize::MAX); let mut witnesses = ensure_sufficient_stack(|| { - compute_usefulness(cx, &mut spec_matrix, &v, lint_root, false) + compute_usefulness(cx, &mut spec_matrix, &v, collect_witnesses, lint_root, false) }); - if missing_ctors.is_empty() || matches!(ctor, Constructor::Wildcard | Constructor::Missing) - { - // If some ctors are missing we only report those. Mostly for historical reasons, - // nothing stops us from reporting all. - // FIXME: propagate this choice deeper to save work. + if collect_witnesses { witnesses.apply_constructor(pcx, &missing_ctors, &ctor); ret.extend(witnesses); } @@ -966,7 +968,8 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); let v = PatStack::from_pattern(wild_pattern, usize::MAX, false); - let non_exhaustiveness_witnesses = compute_usefulness(cx, &mut matrix, &v, lint_root, true); + let non_exhaustiveness_witnesses = + compute_usefulness(cx, &mut matrix, &v, true, lint_root, true); let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); let arm_usefulness: Vec<_> = arms .iter() From ea36f62ac997833551da624992b8d8251dbf6f8c Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 May 2023 13:45:51 +0200 Subject: [PATCH 17/40] try a lil perf improvement --- .../src/thir/pattern/deconstruct_pat.rs | 17 +++++++----- .../src/thir/pattern/usefulness.rs | 26 +++++++++++++------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 0f94aa6cd2445..940743f18b48f 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -51,6 +51,7 @@ use std::ops::RangeInclusive; use smallvec::{smallvec, SmallVec}; use rustc_data_structures::captures::Captures; +use rustc_data_structures::fx::FxHashSet; use rustc_hir::{HirId, RangeEnd}; use rustc_index::Idx; use rustc_middle::mir; @@ -107,7 +108,7 @@ pub(crate) struct IntRange { impl IntRange { #[inline] - fn is_integral(ty: Ty<'_>) -> bool { + pub(super) fn is_integral(ty: Ty<'_>) -> bool { matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_) | ty::Bool) } @@ -673,13 +674,18 @@ impl<'tcx> Constructor<'tcx> { matches!(self, NonExhaustive) } + fn as_variant(&self) -> Option { + match self { + Variant(i) => Some(*i), + _ => None, + } + } fn as_int_range(&self) -> Option<&IntRange> { match self { IntRange(range) => Some(range), _ => None, } } - fn as_slice(&self) -> Option { match self { Slice(slice) => Some(*slice), @@ -999,11 +1005,10 @@ impl ConstructorSet { } } ConstructorSet::Variants { variants, non_exhaustive } => { - // FIXME: can do better than quadratic. + let seen_set: FxHashSet<_> = seen.iter().map(|c| c.as_variant().unwrap()).collect(); for variant in variants { - let was_seen = seen.iter().any(|c| matches!(c, Variant(i) if *i == variant)); let ctor = Variant(variant); - if was_seen { + if seen_set.contains(&variant) { split.push(ctor); } else { missing.push(ctor); @@ -1084,7 +1089,7 @@ impl ConstructorSet { // FIXME: splitting and collecting should be done in one pass. let mut split_base_slice = SplitVarLenSlice::new(self_prefix, self_suffix, base_slice.array_len); - let seen_slices = seen.iter().filter_map(|c| c.as_slice()); + let seen_slices = seen.iter().map(|c| c.as_slice().unwrap()); split_base_slice.split(seen_slices.clone().map(|s| s.kind)); for splitted_slice in split_base_slice.iter() { let is_covered_by_any = seen_slices diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index a51cd09c24e7a..12dfaa8ed9785 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -781,14 +781,24 @@ fn compute_usefulness<'p, 'tcx>( let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; - for row_id in 0..matrix.len() { - let v = &matrix.rows[row_id]; - if let Constructor::IntRange(ctor_range) = v.head().ctor() { - // Lint on likely incorrect range patterns (#63987) - let pcx = &PatCtxt { span: v.head().span(), ..*pcx }; - let compare_against = - matrix.rows().take(row_id).filter(|row| !row.is_under_guard).map(|row| row.head()); - ctor_range.lint_overlapping_range_endpoints(pcx, compare_against, v.len(), lint_root) + if super::deconstruct_pat::IntRange::is_integral(ty) { + for row_id in 0..matrix.len() { + let v = &matrix.rows[row_id]; + if let Constructor::IntRange(ctor_range) = v.head().ctor() { + // Lint on likely incorrect range patterns (#63987) + let pcx = &PatCtxt { span: v.head().span(), ..*pcx }; + let compare_against = matrix + .rows() + .take(row_id) + .filter(|row| !row.is_under_guard) + .map(|row| row.head()); + ctor_range.lint_overlapping_range_endpoints( + pcx, + compare_against, + v.len(), + lint_root, + ) + } } } From d8e3c8a6219690b9749d7d09bd4532ac1a2a6b32 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 18 May 2023 18:48:35 +0200 Subject: [PATCH 18/40] fix silly doc errors --- .../rustc_mir_build/src/thir/pattern/deconstruct_pat.rs | 6 +++--- compiler/rustc_mir_build/src/thir/pattern/usefulness.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 940743f18b48f..2995add90130b 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -37,9 +37,9 @@ //! skipping some constructors as long as it doesn't change whether the resulting list of witnesses //! is empty of not. We use this in the wildcard `_` case. //! -//! Splitting is implemented in the [`Constructor::split`] function. We don't do splitting for +//! Splitting is implemented in the `Constructor::split` function. We don't do splitting for //! or-patterns; instead we just try the alternatives one-by-one. For details on splitting -//! wildcards, see [`SplitWildcard`]; for integer ranges, see [`SplitIntRange`]; for slices, see +//! wildcards, see `SplitWildcard`; for integer ranges, see [`SplitIntRange`]; for slices, see //! [`SplitVarLenSlice`]. use std::cell::Cell; @@ -657,7 +657,7 @@ pub(super) enum Constructor<'tcx> { /// for those types for which we cannot list constructors explicitly, like `f64` and `str`. NonExhaustive, /// Stands for constructors that are not seen in the matrix, as explained in the documentation - /// for [`SplitWildcard`]. + /// for `SplitWildcard`. Missing, /// Wildcard pattern. Wildcard, diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 12dfaa8ed9785..56fb779dfcbfd 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -272,7 +272,7 @@ //! //==>> we have tried all the constructors. The output is the single witness `[Some(false)]`. //! ``` //! -//! This computation is done in [`is_useful`]. In practice we don't care about the list of +//! This computation is done in `is_useful`. In practice we don't care about the list of //! witnesses when computing reachability; we only need to know whether any exist. We do keep the //! witnesses when computing exhaustiveness to report them to the user. //! @@ -287,7 +287,7 @@ //! group together constructors that behave the same. //! //! The details are not necessary to understand this file, so we explain them in -//! [`super::deconstruct_pat`]. Splitting is done by the [`Constructor::split`] function. +//! [`super::deconstruct_pat`]. Splitting is done by the `Constructor::split` function. //! //! # Constants in patterns //! From 08086290cfbf64a8712f5cc16eba8b3de39e7210 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 19 May 2023 06:40:15 +0200 Subject: [PATCH 19/40] tiny speedup --- compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs | 2 +- compiler/rustc_mir_build/src/thir/pattern/usefulness.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 2995add90130b..bfa6b12eb8130 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -112,7 +112,7 @@ impl IntRange { matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_) | ty::Bool) } - fn is_singleton(&self) -> bool { + pub(super) fn is_singleton(&self) -> bool { self.range.start() == self.range.end() } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 56fb779dfcbfd..1cc2056035be1 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -785,6 +785,9 @@ fn compute_usefulness<'p, 'tcx>( for row_id in 0..matrix.len() { let v = &matrix.rows[row_id]; if let Constructor::IntRange(ctor_range) = v.head().ctor() { + if ctor_range.is_singleton() { + continue; + } // Lint on likely incorrect range patterns (#63987) let pcx = &PatCtxt { span: v.head().span(), ..*pcx }; let compare_against = matrix From 0a67808c306f931fbc34c3de4b7d1e7def82b23d Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 19 May 2023 07:12:52 +0200 Subject: [PATCH 20/40] Test usize + half-open ranges --- .../pointer-sized-int.allow.stderr | 2 +- .../pointer-sized-int.deny.stderr | 100 ++++++++++++++++-- .../integer-ranges/pointer-sized-int.rs | 29 +++-- 3 files changed, 114 insertions(+), 17 deletions(-) diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr index 9f277fa1e1800..4ee9eb275a491 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.allow.stderr @@ -1,5 +1,5 @@ error[E0004]: non-exhaustive patterns: type `usize` is non-empty - --> $DIR/pointer-sized-int.rs:48:11 + --> $DIR/pointer-sized-int.rs:65:11 | LL | match 7usize {} | ^^^^^^ diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr index 0e0f0c3e11ee8..28f793b0cdcd2 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.deny.stderr @@ -9,7 +9,7 @@ LL | match 0usize { = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | -LL ~ 0 ..= usize::MAX => {}, +LL ~ 0..=usize::MAX => {}, LL + _ => todo!() | @@ -24,7 +24,7 @@ LL | match 0isize { = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `isize` matching help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | -LL ~ isize::MIN ..= isize::MAX => {}, +LL ~ isize::MIN..=isize::MAX => {}, LL + _ => todo!() | @@ -85,6 +85,60 @@ LL | match $s { $($t)+ => {}, (_, _) => todo!() } error[E0004]: non-exhaustive patterns: `_` not covered --> $DIR/pointer-sized-int.rs:31:8 | +LL | m!(0usize, 0..); + | ^^^^^^ pattern `_` not covered + | + = note: the matched value is of type `usize` + = note: `usize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively + = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:33:8 + | +LL | m!(0usize, 0..5 | 5..); + | ^^^^^^ pattern `_` not covered + | + = note: the matched value is of type `usize` + = note: `usize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively + = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:35:8 + | +LL | m!(0usize, ..5 | 5..); + | ^^^^^^ pattern `_` not covered + | + = note: the matched value is of type `usize` + = note: `usize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively + = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `usize` matching +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `(_, _)` not covered + --> $DIR/pointer-sized-int.rs:37:8 + | +LL | m!((0usize, true), (0..5, true) | (5.., true) | (0.., false)); + | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered + | + = note: the matched value is of type `(usize, bool)` +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, (_, _) => todo!() } + | +++++++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:40:8 + | LL | m!(0isize, isize::MIN..=isize::MAX); | ^^^^^^ pattern `_` not covered | @@ -97,7 +151,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:33:8 + --> $DIR/pointer-sized-int.rs:42:8 | LL | m!(0isize, isize::MIN..5 | 5..=isize::MAX); | ^^^^^^ pattern `_` not covered @@ -111,7 +165,7 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:35:8 + --> $DIR/pointer-sized-int.rs:44:8 | LL | m!(0isize, isize::MIN..isize::MAX | isize::MAX); | ^^^^^^ pattern `_` not covered @@ -125,9 +179,35 @@ LL | match $s { $($t)+ => {}, _ => todo!() } | ++++++++++++++ error[E0004]: non-exhaustive patterns: `(_, _)` not covered - --> $DIR/pointer-sized-int.rs:37:8 + --> $DIR/pointer-sized-int.rs:47:9 + | +LL | (0isize, true), + | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered + | + = note: the matched value is of type `(isize, bool)` +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, (_, _) => todo!() } + | +++++++++++++++++++ + +error[E0004]: non-exhaustive patterns: `_` not covered + --> $DIR/pointer-sized-int.rs:52:8 + | +LL | m!(0isize, ..5 | 5..); + | ^^^^^^ pattern `_` not covered + | + = note: the matched value is of type `isize` + = note: `isize` does not have a fixed maximum value, so a wildcard `_` is necessary to match exhaustively + = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `isize` matching +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | match $s { $($t)+ => {}, _ => todo!() } + | ++++++++++++++ + +error[E0004]: non-exhaustive patterns: `(_, _)` not covered + --> $DIR/pointer-sized-int.rs:54:8 | -LL | m!((0isize, true), (isize::MIN..5, true) +LL | m!((0isize, true), (..5, true) | ^^^^^^^^^^^^^^ pattern `(_, _)` not covered | = note: the matched value is of type `(isize, bool)` @@ -137,7 +217,7 @@ LL | match $s { $($t)+ => {}, (_, _) => todo!() } | +++++++++++++++++++ error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/pointer-sized-int.rs:41:11 + --> $DIR/pointer-sized-int.rs:58:11 | LL | match 0isize { | ^^^^^^ pattern `_` not covered @@ -147,12 +227,12 @@ LL | match 0isize { = help: add `#![feature(precise_pointer_size_matching)]` to the crate attributes to enable precise `isize` matching help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | -LL ~ 1 ..= isize::MAX => {}, +LL ~ 1..=isize::MAX => {}, LL + _ => todo!() | error[E0004]: non-exhaustive patterns: type `usize` is non-empty - --> $DIR/pointer-sized-int.rs:48:11 + --> $DIR/pointer-sized-int.rs:65:11 | LL | match 7usize {} | ^^^^^^ @@ -165,6 +245,6 @@ LL + _ => todo!(), LL + } | -error: aborting due to 12 previous errors +error: aborting due to 18 previous errors For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs index 1ed18c2676358..8487734514af8 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/pointer-sized-int.rs @@ -11,12 +11,12 @@ macro_rules! m { fn main() { match 0usize { //[deny]~^ ERROR non-exhaustive patterns - 0 ..= usize::MAX => {} + 0..=usize::MAX => {} } match 0isize { //[deny]~^ ERROR non-exhaustive patterns - isize::MIN ..= isize::MAX => {} + isize::MIN..=isize::MAX => {} } m!(0usize, 0..=usize::MAX); @@ -28,21 +28,38 @@ fn main() { m!((0usize, true), (0..5, true) | (5..=usize::MAX, true) | (0..=usize::MAX, false)); //[deny]~^ ERROR non-exhaustive patterns + m!(0usize, 0..); + //[deny]~^ ERROR non-exhaustive patterns + m!(0usize, 0..5 | 5..); + //[deny]~^ ERROR non-exhaustive patterns + m!(0usize, ..5 | 5..); + //[deny]~^ ERROR non-exhaustive patterns + m!((0usize, true), (0..5, true) | (5.., true) | (0.., false)); + //[deny]~^ ERROR non-exhaustive patterns + m!(0isize, isize::MIN..=isize::MAX); //[deny]~^ ERROR non-exhaustive patterns m!(0isize, isize::MIN..5 | 5..=isize::MAX); //[deny]~^ ERROR non-exhaustive patterns m!(0isize, isize::MIN..isize::MAX | isize::MAX); //[deny]~^ ERROR non-exhaustive patterns - m!((0isize, true), (isize::MIN..5, true) - | (5..=isize::MAX, true) | (isize::MIN..=isize::MAX, false)); + m!( + (0isize, true), + (isize::MIN..5, true) | (5..=isize::MAX, true) | (isize::MIN..=isize::MAX, false) + ); + //[deny]~^^^ ERROR non-exhaustive patterns + + m!(0isize, ..5 | 5..); + //[deny]~^ ERROR non-exhaustive patterns + m!((0isize, true), (..5, true) + | (5.., true) | (..0 | 0.., false)); //[deny]~^^ ERROR non-exhaustive patterns match 0isize { //[deny]~^ ERROR non-exhaustive patterns - isize::MIN ..= -1 => {} + isize::MIN..=-1 => {} 0 => {} - 1 ..= isize::MAX => {} + 1..=isize::MAX => {} } match 7usize {} From 3dd0d1c3579f035dd81ec447a2bd7eea1a66cf22 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 19 May 2023 07:41:56 +0200 Subject: [PATCH 21/40] Avoid quadratic behavior in usize/isize matches --- .../src/thir/pattern/deconstruct_pat.rs | 60 ++++++++----------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index bfa6b12eb8130..d76ee73662571 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -825,11 +825,11 @@ pub(super) enum ConstructorSet { /// This type has the following list of constructors. Variants { variants: Vec, non_exhaustive: bool }, /// The type is spanned by a range of integer values, e.g. all number types. - Range(IntRange), + /// `non_exhaustive` is used when the range is not allowed to be matched exhaustively (that's + /// for usize/isize). + Range { range: IntRange, non_exhaustive: bool }, /// The type is spanned by two ranges of integer values. This is special for chars. Ranges([IntRange; 2]), - /// The type is spanned by a range of integer values of unknown boundaries. - UnlistableRange, /// The type is matched by slices. Slice(Slice), /// The constructors cannot be listed, and the type cannot be matched exhaustively. E.g. `str`, @@ -856,7 +856,7 @@ impl ConstructorSet { // Invariant: this is `Uninhabited` if and only if the type is uninhabited (as determined by // `cx.is_uninhabited()`). match pcx.ty.kind() { - ty::Bool => Self::Range(make_range(0, 1)), + ty::Bool => Self::Range { range: make_range(0, 1), non_exhaustive: false }, ty::Char => { Self::Ranges([ // The valid Unicode Scalar Value ranges. @@ -864,24 +864,24 @@ impl ConstructorSet { make_range('\u{E000}' as u128, '\u{10FFFF}' as u128), ]) } - ty::Int(_) | ty::Uint(_) - if pcx.ty.is_ptr_sized_integral() - && !cx.tcx.features().precise_pointer_size_matching => - { + &ty::Int(ity) => { // `usize`/`isize` are not allowed to be matched exhaustively unless the // `precise_pointer_size_matching` feature is enabled. - Self::UnlistableRange - } - &ty::Int(ity) => { + let non_exhaustive = pcx.ty.is_ptr_sized_integral() + && !cx.tcx.features().precise_pointer_size_matching; let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; let min = 1u128 << (bits - 1); let max = min - 1; - Self::Range(make_range(min, max)) + Self::Range { range: make_range(min, max), non_exhaustive } } &ty::Uint(uty) => { + // `usize`/`isize` are not allowed to be matched exhaustively unless the + // `precise_pointer_size_matching` feature is enabled. + let non_exhaustive = pcx.ty.is_ptr_sized_integral() + && !cx.tcx.features().precise_pointer_size_matching; let size = Integer::from_uint_ty(&cx.tcx, uty).size(); let max = size.truncate(u128::MAX); - Self::Range(make_range(0, max)) + Self::Range { range: make_range(0, max), non_exhaustive } } ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => { let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize; @@ -1018,10 +1018,14 @@ impl ConstructorSet { missing.push(NonExhaustive); } } - ConstructorSet::Range(base_range) if seen.is_empty() => { - missing.push(IntRange(base_range)); + ConstructorSet::Range { range: base_range, non_exhaustive } if seen.is_empty() => { + if non_exhaustive { + missing.push(NonExhaustive); + } else { + missing.push(IntRange(base_range)); + } } - ConstructorSet::Range(base_range) => { + ConstructorSet::Range { range: base_range, non_exhaustive } => { // FIXME: splitting and collecting should be done in one pass. let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); let mut split_base_range = SplitIntRange::new(base_range); @@ -1032,10 +1036,13 @@ impl ConstructorSet { let ctor = IntRange(splitted_range); if is_covered_by_any { split.push(ctor); - } else { + } else if !non_exhaustive { missing.push(ctor); } } + if non_exhaustive { + missing.push(NonExhaustive); + } } ConstructorSet::Ranges(base_ranges) if seen.is_empty() => { missing.extend(base_ranges.into_iter().map(IntRange)); @@ -1058,25 +1065,6 @@ impl ConstructorSet { } } } - ConstructorSet::UnlistableRange if seen.is_empty() => { - missing.push(NonExhaustive); - } - ConstructorSet::UnlistableRange => { - // Since we can't list constructors, we need to take the ones in the matrix. - // FIXME: would be nice to reuse the `Range` case somehow instead of this quadratic - // splitting. - let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()).cloned(); - for seen_range in seen_ranges.clone() { - if seen_range.is_singleton() { - split.push(IntRange(seen_range)) - } else { - let mut split_range = SplitIntRange::new(seen_range); - split_range.split(seen_ranges.clone()); - split.extend(split_range.iter().map(IntRange)); - } - } - missing.push(NonExhaustive); - } ConstructorSet::Slice(base_slice) if seen.is_empty() => { missing.push(Slice(base_slice)); } From 33ec800514c2dc080faf8c9d5ba56068de0c40b4 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 19 May 2023 07:57:51 +0200 Subject: [PATCH 22/40] Clean up SplitIntRange a bit --- .../src/thir/pattern/deconstruct_pat.rs | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index d76ee73662571..ff1597c244db3 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -332,7 +332,7 @@ enum IntBorder { AfterMax, } -/// A range of integers that is partitioned into disjoint subranges. This does constructor +/// A range of integers that is to be partitioned into disjoint subranges. This does constructor /// splitting for integer ranges as explained at the top of the file. /// /// This is fed multiple ranges, and returns an output that covers the input, but is split so that @@ -353,14 +353,11 @@ enum IntBorder { struct SplitIntRange { /// The range we are splitting range: IntRange, - /// The borders of ranges we have seen. They are all contained within `range`. This is kept - /// sorted. - borders: Vec, } impl SplitIntRange { fn new(range: IntRange) -> Self { - SplitIntRange { range, borders: Vec::new() } + SplitIntRange { range } } /// Internal use @@ -375,30 +372,26 @@ impl SplitIntRange { [lo, hi] } - /// Add ranges relative to which we split. - fn split(&mut self, ranges: impl Iterator) { - let this_range = &self.range; - let included_ranges = ranges.filter_map(|r| this_range.intersection(&r)); - let included_borders = included_ranges.flat_map(|r| { - let borders = Self::to_borders(r); - once(borders[0]).chain(once(borders[1])) - }); - self.borders.extend(included_borders); - self.borders.sort_unstable(); - } - - /// Iterate over the contained ranges. - fn iter(&self) -> impl Iterator + Captures<'_> { + /// Iterate over the split ranges. + fn split(self, ranges: impl Iterator) -> impl Iterator { use IntBorder::*; - let self_range = Self::to_borders(self.range.clone()); + // The borders of ranges we have seen. They are all contained within `range`. This is kept + // sorted. + let mut borders: Vec<_> = ranges + .filter_map(|r| self.range.intersection(&r)) + .flat_map(|r| Self::to_borders(r)) + .collect(); + borders.sort_unstable(); + + let self_bias = self.range.bias; + let [self_lo, self_hi] = Self::to_borders(self.range); // Start with the start of the range. - let mut prev_border = self_range[0]; - self.borders - .iter() - .copied() + let mut prev_border = self_lo; + borders + .into_iter() // End with the end of the range. - .chain(once(self_range[1])) + .chain(once(self_hi)) // List pairs of adjacent borders. .map(move |border| { let ret = (prev_border, border); @@ -414,7 +407,7 @@ impl SplitIntRange { (JustBefore(n), AfterMax) => n..=u128::MAX, _ => unreachable!(), // Ruled out by the sorting and filtering we did }; - IntRange { range, bias: self.range.bias } + IntRange { range, bias: self_bias } }) } } @@ -1028,9 +1021,9 @@ impl ConstructorSet { ConstructorSet::Range { range: base_range, non_exhaustive } => { // FIXME: splitting and collecting should be done in one pass. let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); - let mut split_base_range = SplitIntRange::new(base_range); - split_base_range.split(seen_ranges.clone().cloned()); - for splitted_range in split_base_range.iter() { + let splitted_ranges = + SplitIntRange::new(base_range).split(seen_ranges.clone().cloned()); + for splitted_range in splitted_ranges { let is_covered_by_any = seen_ranges.clone().any(|other| splitted_range.is_covered_by(other)); let ctor = IntRange(splitted_range); @@ -1051,9 +1044,9 @@ impl ConstructorSet { // FIXME: splitting and collecting should be done in one pass. let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); for base_range in base_ranges { - let mut split_base_range = SplitIntRange::new(base_range); - split_base_range.split(seen_ranges.clone().cloned()); - for splitted_range in split_base_range.iter() { + let splitted_ranges = + SplitIntRange::new(base_range).split(seen_ranges.clone().cloned()); + for splitted_range in splitted_ranges { let is_covered_by_any = seen_ranges.clone().any(|other| splitted_range.is_covered_by(other)); let ctor = IntRange(splitted_range); From 449b28417565bb585557482b0b86286d821c84cc Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 19 May 2023 12:24:49 +0200 Subject: [PATCH 23/40] better range analysis: step 1 --- .../src/thir/pattern/deconstruct_pat.rs | 109 ++++++++++-------- 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index ff1597c244db3..2948f958633bf 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -324,14 +324,21 @@ impl fmt::Debug for IntRange { } } -/// Represents a border between 2 integers. Because the intervals spanning borders must be able to -/// cover every integer, we need to be able to represent 2^128 + 1 such borders. +/// Represents a boundary between 2 integers. Because the intervals spanning bdys must be able to +/// cover every integer, we need to be able to represent 2^128 + 1 such bdys. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -enum IntBorder { +enum IntBoundary { JustBefore(u128), AfterMax, } +/// Whether we have seen a constructor in the matrix or not. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum Presence { + Unseen, + Seen, +} + /// A range of integers that is to be partitioned into disjoint subranges. This does constructor /// splitting for integer ranges as explained at the top of the file. /// @@ -361,8 +368,8 @@ impl SplitIntRange { } /// Internal use - fn to_borders(r: IntRange) -> [IntBorder; 2] { - use IntBorder::*; + fn to_boundaries(r: &IntRange) -> [IntBoundary; 2] { + use IntBoundary::*; let (lo, hi) = r.boundaries(); let lo = JustBefore(lo); let hi = match hi.checked_add(1) { @@ -373,41 +380,56 @@ impl SplitIntRange { } /// Iterate over the split ranges. - fn split(self, ranges: impl Iterator) -> impl Iterator { - use IntBorder::*; - - // The borders of ranges we have seen. They are all contained within `range`. This is kept - // sorted. - let mut borders: Vec<_> = ranges - .filter_map(|r| self.range.intersection(&r)) - .flat_map(|r| Self::to_borders(r)) - .collect(); - borders.sort_unstable(); + fn split( + self, + ranges: impl Iterator, + ) -> impl Iterator { + use IntBoundary::*; + use Presence::*; let self_bias = self.range.bias; - let [self_lo, self_hi] = Self::to_borders(self.range); + let [self_lo, self_hi] = Self::to_boundaries(&self.range); + + let ranges: Vec<[IntBoundary; 2]> = ranges + .map(|r| Self::to_boundaries(&r)) + .filter_map(|&[other_lo, other_hi]| { + // Intersect with `self` + if self_lo <= other_hi && other_lo <= self_hi { + Some([max(self_lo, other_lo), min(self_hi, other_hi)]) + } else { + None + } + }) + .collect(); + let mut bdys: Vec = ranges.iter().flat_map(|[lo, hi]| [lo, hi]).collect(); + bdys.sort_unstable(); + // Start with the start of the range. - let mut prev_border = self_lo; - borders - .into_iter() + let mut prev_bdy = self_lo; + bdys.into_iter() // End with the end of the range. .chain(once(self_hi)) - // List pairs of adjacent borders. - .map(move |border| { - let ret = (prev_border, border); - prev_border = border; + // List pairs of adjacent bdys. + .map(move |bdy| { + let ret = (prev_bdy, bdy); + prev_bdy = bdy; ret }) - // Skip duplicates. - .filter(|(prev_border, border)| prev_border != border) + // Skip duplicate boundaries. + .filter(|(prev_bdy, bdy)| prev_bdy != bdy) // Finally, convert to ranges. - .map(move |(prev_border, border)| { - let range = match (prev_border, border) { - (JustBefore(n), JustBefore(m)) if n < m => n..=(m - 1), + .map(move |(prev_bdy, bdy)| { + let is_covered_by_any = ranges + .iter() + .any(|&[other_lo, other_hi]| other_lo <= prev_bdy && bdy <= other_hi); + let presence = if is_covered_by_any { Seen } else { Unseen }; + let range = match (prev_bdy, bdy) { + (JustBefore(n), JustBefore(m)) if n < m => n..=m - 1, (JustBefore(n), AfterMax) => n..=u128::MAX, _ => unreachable!(), // Ruled out by the sorting and filtering we did }; - IntRange { range, bias: self_bias } + let range = IntRange { range, bias: self_bias }; + (presence, range) }) } } @@ -1019,18 +1041,15 @@ impl ConstructorSet { } } ConstructorSet::Range { range: base_range, non_exhaustive } => { - // FIXME: splitting and collecting should be done in one pass. let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); - let splitted_ranges = - SplitIntRange::new(base_range).split(seen_ranges.clone().cloned()); - for splitted_range in splitted_ranges { - let is_covered_by_any = - seen_ranges.clone().any(|other| splitted_range.is_covered_by(other)); + let splitted_ranges = SplitIntRange::new(base_range).split(seen_ranges.cloned()); + for (seen, splitted_range) in splitted_ranges { let ctor = IntRange(splitted_range); - if is_covered_by_any { - split.push(ctor); - } else if !non_exhaustive { - missing.push(ctor); + match seen { + // We don't report missing constructors in this case + Presence::Unseen if non_exhaustive => {} + Presence::Unseen => missing.push(ctor), + Presence::Seen => split.push(ctor), } } if non_exhaustive { @@ -1041,19 +1060,15 @@ impl ConstructorSet { missing.extend(base_ranges.into_iter().map(IntRange)); } ConstructorSet::Ranges(base_ranges) => { - // FIXME: splitting and collecting should be done in one pass. let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); for base_range in base_ranges { let splitted_ranges = SplitIntRange::new(base_range).split(seen_ranges.clone().cloned()); - for splitted_range in splitted_ranges { - let is_covered_by_any = - seen_ranges.clone().any(|other| splitted_range.is_covered_by(other)); + for (seen, splitted_range) in splitted_ranges { let ctor = IntRange(splitted_range); - if is_covered_by_any { - split.push(ctor); - } else { - missing.push(ctor); + match seen { + Presence::Unseen => missing.push(ctor), + Presence::Seen => split.push(ctor), } } } From e762149070a2ca7630e30b5691f36ec8ed724caa Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 19 May 2023 17:33:02 +0200 Subject: [PATCH 24/40] better range analysis: step 2 --- .../src/thir/pattern/deconstruct_pat.rs | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 2948f958633bf..904655f2ca099 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -390,9 +390,10 @@ impl SplitIntRange { let self_bias = self.range.bias; let [self_lo, self_hi] = Self::to_boundaries(&self.range); - let ranges: Vec<[IntBoundary; 2]> = ranges + // Boundary counts as +1 if it starts a range and -1 if it ends it. + let mut bdys: Vec<(IntBoundary, isize)> = ranges .map(|r| Self::to_boundaries(&r)) - .filter_map(|&[other_lo, other_hi]| { + .filter_map(|[other_lo, other_hi]| { // Intersect with `self` if self_lo <= other_hi && other_lo <= self_hi { Some([max(self_lo, other_lo), min(self_hi, other_hi)]) @@ -400,15 +401,22 @@ impl SplitIntRange { None } }) + .flat_map(|[lo, hi]| [(lo, 1), (hi, -1)]) .collect(); - let mut bdys: Vec = ranges.iter().flat_map(|[lo, hi]| [lo, hi]).collect(); bdys.sort_unstable(); // Start with the start of the range. - let mut prev_bdy = self_lo; + let mut prev_bdy = (self_lo, 0); + let mut count = 0isize; bdys.into_iter() // End with the end of the range. - .chain(once(self_hi)) + .chain(once((self_hi, 0))) + // Accumulate deltas. This does the equivalent of parenthesis matching: if the count is + // > 0 between two boundaries, we are within one of the seen ranges. + .map(move |(bdy, delta)| { + count += delta; + (bdy, count) + }) // List pairs of adjacent bdys. .map(move |bdy| { let ret = (prev_bdy, bdy); @@ -416,13 +424,10 @@ impl SplitIntRange { ret }) // Skip duplicate boundaries. - .filter(|(prev_bdy, bdy)| prev_bdy != bdy) + .filter(|((prev_bdy, _), (bdy, _))| prev_bdy != bdy) // Finally, convert to ranges. - .map(move |(prev_bdy, bdy)| { - let is_covered_by_any = ranges - .iter() - .any(|&[other_lo, other_hi]| other_lo <= prev_bdy && bdy <= other_hi); - let presence = if is_covered_by_any { Seen } else { Unseen }; + .map(move |((prev_bdy, prev_count), (bdy, _))| { + let presence = if prev_count > 0 { Seen } else { Unseen }; let range = match (prev_bdy, bdy) { (JustBefore(n), JustBefore(m)) if n < m => n..=m - 1, (JustBefore(n), AfterMax) => n..=u128::MAX, From 8458c9f8eebaa3410422b3953383f0f9cf4d67bd Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Fri, 19 May 2023 18:19:49 +0200 Subject: [PATCH 25/40] better range analysis: step 3 --- .../src/thir/pattern/deconstruct_pat.rs | 155 ++++++++++-------- 1 file changed, 83 insertions(+), 72 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 904655f2ca099..a5510065ae894 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -357,18 +357,15 @@ enum Presence { /// ||---|--||-|---|---|---|--| /// ``` #[derive(Debug, Clone)] -struct SplitIntRange { - /// The range we are splitting - range: IntRange, +enum SplitIntRange { + Single(IntRange), + // For chars + Double([IntRange; 2]), } impl SplitIntRange { - fn new(range: IntRange) -> Self { - SplitIntRange { range } - } - /// Internal use - fn to_boundaries(r: &IntRange) -> [IntBoundary; 2] { + fn into_boundaries(r: IntRange) -> [IntBoundary; 2] { use IntBoundary::*; let (lo, hi) = r.boundaries(); let lo = JustBefore(lo); @@ -380,6 +377,15 @@ impl SplitIntRange { } /// Iterate over the split ranges. + /// We have in `self` one or two base ranges of valid values. + /// We have in `ranges` a set of ranges that count as "seen". + /// We partition the `self` ranges into subranges such that: + /// - each output subrange is either included or disjoint with each output subrange + /// - we track whether a given output subrange is included in some input range or not + /// + /// We do this with a parenthesis matching algorithm. We go through all the range boundaries and + /// keep track in parallel of 1/ whether we are within some base range and 2/ whether we are + /// within some seen range. fn split( self, ranges: impl Iterator, @@ -387,47 +393,59 @@ impl SplitIntRange { use IntBoundary::*; use Presence::*; - let self_bias = self.range.bias; - let [self_lo, self_hi] = Self::to_boundaries(&self.range); - - // Boundary counts as +1 if it starts a range and -1 if it ends it. - let mut bdys: Vec<(IntBoundary, isize)> = ranges - .map(|r| Self::to_boundaries(&r)) - .filter_map(|[other_lo, other_hi]| { - // Intersect with `self` - if self_lo <= other_hi && other_lo <= self_hi { - Some([max(self_lo, other_lo), min(self_hi, other_hi)]) - } else { - None - } - }) - .flat_map(|[lo, hi]| [(lo, 1), (hi, -1)]) + // We do a double parenthesis matching. We have two counters. The first is for `self` + // ranges, the second for `ranges` ranges. + // A boundary counts as +1 if it starts a range and -1 if it ends it. When the count is > 0 + // between two boundaries, we are within a range. + let mut bdys: Vec<(IntBoundary, isize, isize)> = ranges + .map(|r| Self::into_boundaries(r)) + .flat_map(|[lo, hi]| [(lo, 0, 1), (hi, 0, -1)]) .collect(); + let self_bias; + match self { + Self::Single(self_range) => { + self_bias = self_range.bias; + + let [lo, hi] = Self::into_boundaries(self_range); + bdys.push((lo, 1, 0)); + bdys.push((hi, -1, 0)); + } + Self::Double([range_1, range_2]) => { + self_bias = range_1.bias; + + let [lo, hi] = Self::into_boundaries(range_1); + bdys.push((lo, 1, 0)); + bdys.push((hi, -1, 0)); + let [lo, hi] = Self::into_boundaries(range_2); + bdys.push((lo, 1, 0)); + bdys.push((hi, -1, 0)); + } + } bdys.sort_unstable(); - // Start with the start of the range. - let mut prev_bdy = (self_lo, 0); - let mut count = 0isize; - bdys.into_iter() - // End with the end of the range. - .chain(once((self_hi, 0))) - // Accumulate deltas. This does the equivalent of parenthesis matching: if the count is - // > 0 between two boundaries, we are within one of the seen ranges. - .map(move |(bdy, delta)| { - count += delta; - (bdy, count) - }) + let mut base_counter = 0isize; + let mut seen_counter = 0isize; + let mut iter = bdys + .into_iter() + // Accumulate deltas. + .map(move |(bdy, base_delta, seen_delta)| { + base_counter += base_delta; + seen_counter += seen_delta; + (bdy, base_counter, seen_counter) + }); + let mut prev_bdy = iter.next().unwrap(); + iter // List pairs of adjacent bdys. .map(move |bdy| { let ret = (prev_bdy, bdy); prev_bdy = bdy; ret }) - // Skip duplicate boundaries. - .filter(|((prev_bdy, _), (bdy, _))| prev_bdy != bdy) + // Skip duplicate boundaries and boundaries not within base ranges. + .filter(|&((prev_bdy, base_count, _), (bdy, ..))| prev_bdy != bdy && base_count > 0) // Finally, convert to ranges. - .map(move |((prev_bdy, prev_count), (bdy, _))| { - let presence = if prev_count > 0 { Seen } else { Unseen }; + .map(move |((prev_bdy, _, seen_count), (bdy, ..))| { + let presence = if seen_count > 0 { Seen } else { Unseen }; let range = match (prev_bdy, bdy) { (JustBefore(n), JustBefore(m)) if n < m => n..=m - 1, (JustBefore(n), AfterMax) => n..=u128::MAX, @@ -844,12 +862,12 @@ pub(super) enum ConstructorSet { Single, /// This type has the following list of constructors. Variants { variants: Vec, non_exhaustive: bool }, - /// The type is spanned by a range of integer values, e.g. all number types. + /// The type is spanned by integer values. The range or ranges give the set of allowed values. + /// The second range is only useful for `char`. + /// This is reused for bool. FIXME: don't. /// `non_exhaustive` is used when the range is not allowed to be matched exhaustively (that's /// for usize/isize). - Range { range: IntRange, non_exhaustive: bool }, - /// The type is spanned by two ranges of integer values. This is special for chars. - Ranges([IntRange; 2]), + Integers { range_1: IntRange, range_2: Option, non_exhaustive: bool }, /// The type is matched by slices. Slice(Slice), /// The constructors cannot be listed, and the type cannot be matched exhaustively. E.g. `str`, @@ -876,13 +894,16 @@ impl ConstructorSet { // Invariant: this is `Uninhabited` if and only if the type is uninhabited (as determined by // `cx.is_uninhabited()`). match pcx.ty.kind() { - ty::Bool => Self::Range { range: make_range(0, 1), non_exhaustive: false }, + ty::Bool => { + Self::Integers { range_1: make_range(0, 1), range_2: None, non_exhaustive: false } + } ty::Char => { - Self::Ranges([ - // The valid Unicode Scalar Value ranges. - make_range('\u{0000}' as u128, '\u{D7FF}' as u128), - make_range('\u{E000}' as u128, '\u{10FFFF}' as u128), - ]) + // The valid Unicode Scalar Value ranges. + Self::Integers { + range_1: make_range('\u{0000}' as u128, '\u{D7FF}' as u128), + range_2: Some(make_range('\u{E000}' as u128, '\u{10FFFF}' as u128)), + non_exhaustive: false, + } } &ty::Int(ity) => { // `usize`/`isize` are not allowed to be matched exhaustively unless the @@ -892,7 +913,7 @@ impl ConstructorSet { let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; let min = 1u128 << (bits - 1); let max = min - 1; - Self::Range { range: make_range(min, max), non_exhaustive } + Self::Integers { range_1: make_range(min, max), non_exhaustive, range_2: None } } &ty::Uint(uty) => { // `usize`/`isize` are not allowed to be matched exhaustively unless the @@ -901,7 +922,7 @@ impl ConstructorSet { && !cx.tcx.features().precise_pointer_size_matching; let size = Integer::from_uint_ty(&cx.tcx, uty).size(); let max = size.truncate(u128::MAX); - Self::Range { range: make_range(0, max), non_exhaustive } + Self::Integers { range_1: make_range(0, max), non_exhaustive, range_2: None } } ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => { let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize; @@ -1038,16 +1059,23 @@ impl ConstructorSet { missing.push(NonExhaustive); } } - ConstructorSet::Range { range: base_range, non_exhaustive } if seen.is_empty() => { + ConstructorSet::Integers { range_1, range_2, non_exhaustive } if seen.is_empty() => { if non_exhaustive { missing.push(NonExhaustive); } else { - missing.push(IntRange(base_range)); + missing.push(IntRange(range_1)); + if let Some(range_2) = range_2 { + missing.push(IntRange(range_2)); + } } } - ConstructorSet::Range { range: base_range, non_exhaustive } => { + ConstructorSet::Integers { range_1, range_2, non_exhaustive } => { + let range = match range_2 { + None => SplitIntRange::Single(range_1), + Some(range_2) => SplitIntRange::Double([range_1, range_2]), + }; let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); - let splitted_ranges = SplitIntRange::new(base_range).split(seen_ranges.cloned()); + let splitted_ranges = range.split(seen_ranges.cloned()); for (seen, splitted_range) in splitted_ranges { let ctor = IntRange(splitted_range); match seen { @@ -1061,23 +1089,6 @@ impl ConstructorSet { missing.push(NonExhaustive); } } - ConstructorSet::Ranges(base_ranges) if seen.is_empty() => { - missing.extend(base_ranges.into_iter().map(IntRange)); - } - ConstructorSet::Ranges(base_ranges) => { - let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); - for base_range in base_ranges { - let splitted_ranges = - SplitIntRange::new(base_range).split(seen_ranges.clone().cloned()); - for (seen, splitted_range) in splitted_ranges { - let ctor = IntRange(splitted_range); - match seen { - Presence::Unseen => missing.push(ctor), - Presence::Seen => split.push(ctor), - } - } - } - } ConstructorSet::Slice(base_slice) if seen.is_empty() => { missing.push(Slice(base_slice)); } From a3c91dac7ac5ff4eb78e399c61e13b63017dbbbe Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sat, 20 May 2023 00:38:58 +0200 Subject: [PATCH 26/40] better range analysis: do overlap check after splitting --- .../src/thir/pattern/deconstruct_pat.rs | 104 +++++++++--------- .../src/thir/pattern/usefulness.rs | 49 ++++----- .../overlapping_range_endpoints.rs | 32 +++++- .../overlapping_range_endpoints.stderr | 46 ++++++-- 4 files changed, 139 insertions(+), 92 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index a5510065ae894..8cb6b0fa40bef 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -212,23 +212,6 @@ impl IntRange { } } - fn suspicious_intersection(&self, other: &Self) -> bool { - // `false` in the following cases: - // 1 ---- // 1 ---------- // 1 ---- // 1 ---- - // 2 ---------- // 2 ---- // 2 ---- // 2 ---- - // - // The following are currently `false`, but could be `true` in the future (#64007): - // 1 --------- // 1 --------- - // 2 ---------- // 2 ---------- - // - // `true` in the following cases: - // 1 ------- // 1 ------- - // 2 -------- // 2 ------- - let (lo, hi) = self.boundaries(); - let (other_lo, other_hi) = other.boundaries(); - (lo == other_hi || hi == other_lo) && !self.is_singleton() && !other.is_singleton() - } - /// Only used for displaying the range properly. fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> { let (lo, hi) = self.boundaries(); @@ -257,44 +240,67 @@ impl IntRange { pub(super) fn lint_overlapping_range_endpoints<'a, 'p: 'a, 'tcx: 'a>( &self, pcx: &PatCtxt<'_, 'p, 'tcx>, - pats: impl Iterator>, + pats: impl Iterator, bool)>, column_count: usize, lint_root: HirId, ) { - if self.is_singleton() { + // FIXME: for now, only check for overlapping ranges on non-nested range patterns. Otherwise + // with the current logic the following is detected as overlapping: + // ``` + // match (0u8, true) { + // (0 ..= 125, false) => {} + // (125 ..= 255, true) => {} + // _ => {} + // } + // ``` + if !self.is_singleton() || column_count != 1 { return; } - if column_count != 1 { - // FIXME: for now, only check for overlapping ranges on simple range - // patterns. Otherwise with the current logic the following is detected - // as overlapping: - // ``` - // match (0u8, true) { - // (0 ..= 125, false) => {} - // (125 ..= 255, true) => {} - // _ => {} - // } - // ``` - return; - } - - let overlap: Vec<_> = pats - .filter_map(|pat| Some((pat.ctor().as_int_range()?, pat.span()))) - .filter(|(range, _)| self.suspicious_intersection(range)) - .map(|(range, span)| Overlap { - range: self.intersection(&range).unwrap().to_pat(pcx.cx.tcx, pcx.ty), - span, - }) - .collect(); - - if !overlap.is_empty() { - pcx.cx.tcx.emit_spanned_lint( - lint::builtin::OVERLAPPING_RANGE_ENDPOINTS, - lint_root, - pcx.span, - OverlappingRangeEndpoints { overlap, range: pcx.span }, - ); + let overlap: u128 = self.boundaries().0; + // Spans of ranges that start or end with the overlap. + let mut prefixes: SmallVec<[_; 1]> = Default::default(); + let mut suffixes: SmallVec<[_; 1]> = Default::default(); + // Iterate on rows that contained `overlap`. + for (range, this_span, is_under_guard) in pats.filter_map(|(pat, under_guard)| { + Some((pat.ctor().as_int_range()?, pat.span(), under_guard)) + }) { + if range.is_singleton() { + continue; + } + let mut overlaps: SmallVec<[_; 1]> = Default::default(); + if *range.range.start() == overlap { + if !prefixes.is_empty() { + overlaps = prefixes.clone(); + } + if !is_under_guard { + suffixes.push(this_span) + } + } else if *range.range.end() == overlap { + if !suffixes.is_empty() { + overlaps = suffixes.clone(); + } + if !is_under_guard { + prefixes.push(this_span) + } + } + if !overlaps.is_empty() { + let overlap_as_pat = (super::deconstruct_pat::IntRange { + range: overlap..=overlap, + bias: self.bias, + }) + .to_pat(pcx.cx.tcx, pcx.ty); + let overlaps: Vec<_> = overlaps + .into_iter() + .map(|span| Overlap { range: overlap_as_pat.clone(), span }) + .collect(); + pcx.cx.tcx.emit_spanned_lint( + lint::builtin::OVERLAPPING_RANGE_ENDPOINTS, + lint_root, + this_span, + OverlappingRangeEndpoints { overlap: overlaps, range: this_span }, + ); + } } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 1cc2056035be1..7249daaf1bba6 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -308,9 +308,8 @@ use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, Fields}; use crate::errors::{NonExhaustiveOmittedPattern, Uncovered}; -use rustc_data_structures::captures::Captures; - use rustc_arena::TypedArena; +use rustc_data_structures::captures::Captures; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::def_id::DefId; use rustc_hir::HirId; @@ -473,10 +472,6 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> { Matrix { rows: vec![] } } - fn len(&self) -> usize { - self.rows.len() - } - /// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively /// expands it. fn push(&mut self, row: PatStack<'p, 'tcx>) -> usize { @@ -781,35 +776,12 @@ fn compute_usefulness<'p, 'tcx>( let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; - if super::deconstruct_pat::IntRange::is_integral(ty) { - for row_id in 0..matrix.len() { - let v = &matrix.rows[row_id]; - if let Constructor::IntRange(ctor_range) = v.head().ctor() { - if ctor_range.is_singleton() { - continue; - } - // Lint on likely incorrect range patterns (#63987) - let pcx = &PatCtxt { span: v.head().span(), ..*pcx }; - let compare_against = matrix - .rows() - .take(row_id) - .filter(|row| !row.is_under_guard) - .map(|row| row.head()); - ctor_range.lint_overlapping_range_endpoints( - pcx, - compare_against, - v.len(), - lint_root, - ) - } - } - } - let set = ConstructorSet::new(pcx); let (split_ctors, missing_ctors) = set.split(pcx, matrix.heads().map(|p| p.ctor())); // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. let mut ret = WitnessMatrix::new_empty(); + let orig_column_count = v.len(); for ctor in split_ctors { // If some ctors are missing we only report those. Could report all if that's useful for // some applications. @@ -827,6 +799,23 @@ fn compute_usefulness<'p, 'tcx>( ret.extend(witnesses); } + // Lint on likely incorrect range patterns (#63987) + if let Constructor::IntRange(overlap_range) = ctor { + // If two ranges overlap on their boundaries, that boundary will be found as a singleton + // range after splitting. + if overlap_range.is_singleton() && orig_column_count == 1 { + overlap_range.lint_overlapping_range_endpoints( + pcx, + spec_matrix + .rows() + .map(|child_row| &matrix.rows[child_row.parent_row]) + .map(|parent_row| (parent_row.head(), parent_row.is_under_guard)), + orig_column_count, + lint_root, + ); + } + } + for child_row in spec_matrix.rows() { let parent_row = &mut matrix.rows[child_row.parent_row]; parent_row.is_useful = parent_row.is_useful || child_row.is_useful; diff --git a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs index 5ea92b07081af..74e293b4ffdad 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs @@ -8,7 +8,7 @@ macro_rules! m { $t2 => {} _ => {} } - } + }; } fn main() { @@ -16,9 +16,9 @@ fn main() { m!(0u8, 30..=40, 20..=30); //~ ERROR multiple patterns overlap on their endpoints m!(0u8, 20..=30, 31..=40); m!(0u8, 20..=30, 29..=40); - m!(0u8, 20.. 30, 29..=40); //~ ERROR multiple patterns overlap on their endpoints - m!(0u8, 20.. 30, 28..=40); - m!(0u8, 20.. 30, 30..=40); + m!(0u8, 20..30, 29..=40); //~ ERROR multiple patterns overlap on their endpoints + m!(0u8, 20..30, 28..=40); + m!(0u8, 20..30, 30..=40); m!(0u8, 20..=30, 30..=30); m!(0u8, 20..=30, 30..=31); //~ ERROR multiple patterns overlap on their endpoints m!(0u8, 20..=30, 29..=30); @@ -28,15 +28,28 @@ fn main() { m!(0u8, 20..=30, 20); m!(0u8, 20..=30, 25); m!(0u8, 20..=30, 30); - m!(0u8, 20.. 30, 29); + m!(0u8, 20..30, 29); m!(0u8, 20, 20..=30); m!(0u8, 25, 20..=30); m!(0u8, 30, 20..=30); match 0u8 { + 1..=10 => {} 0..=10 => {} 20..=30 => {} - 10..=20 => {} //~ ERROR multiple patterns overlap on their endpoints + 10..=20 => {} + //~^ ERROR multiple patterns overlap on their endpoints + //~| ERROR multiple patterns overlap on their endpoints + _ => {} + } + match 0u8 { + 0..=10 => {} + 10..=20 if true => {} //~ ERROR multiple patterns overlap on their endpoints + _ => {} + } + match 0u8 { + 0..=10 if true => {} + 10..=20 => {} _ => {} } match (0u8, true) { @@ -51,6 +64,13 @@ fn main() { (false, 10..20) => {} _ => {} } + // this probably shouldn't lint + match (true, 0u8) { + (true, 0..=10) => {} + (false, _) => {} + (_, 10..20) => {} //~ ERROR multiple patterns overlap on their endpoints + _ => {} + } match Some(0u8) { Some(0..=10) => {} Some(10..20) => {} //~ ERROR multiple patterns overlap on their endpoints diff --git a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr index ea0e8f6e49e07..be52abc4147fb 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr @@ -24,10 +24,10 @@ LL | m!(0u8, 30..=40, 20..=30); = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:19:22 + --> $DIR/overlapping_range_endpoints.rs:19:21 | -LL | m!(0u8, 20.. 30, 29..=40); - | ------- ^^^^^^^ ... with this range +LL | m!(0u8, 20..30, 29..=40); + | ------ ^^^^^^^ ... with this range | | | this range overlaps on `29_u8`... | @@ -54,10 +54,21 @@ LL | m!(0u8, 20..=30, 19..=20); = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:39:9 + --> $DIR/overlapping_range_endpoints.rs:40:9 | +LL | 1..=10 => {} + | ------ this range overlaps on `10_u8`... LL | 0..=10 => {} | ------ this range overlaps on `10_u8`... +LL | 20..=30 => {} +LL | 10..=20 => {} + | ^^^^^^^ ... with this range + | + = note: you likely meant to write mutually exclusive ranges + +error: multiple patterns overlap on their endpoints + --> $DIR/overlapping_range_endpoints.rs:40:9 + | LL | 20..=30 => {} | ------- this range overlaps on `20_u8`... LL | 10..=20 => {} @@ -66,7 +77,17 @@ LL | 10..=20 => {} = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:50:16 + --> $DIR/overlapping_range_endpoints.rs:47:9 + | +LL | 0..=10 => {} + | ------ this range overlaps on `10_u8`... +LL | 10..=20 if true => {} + | ^^^^^^^ ... with this range + | + = note: you likely meant to write mutually exclusive ranges + +error: multiple patterns overlap on their endpoints + --> $DIR/overlapping_range_endpoints.rs:63:16 | LL | (true, 0..=10) => {} | ------ this range overlaps on `10_u8`... @@ -76,7 +97,18 @@ LL | (true, 10..20) => {} = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:56:14 + --> $DIR/overlapping_range_endpoints.rs:71:13 + | +LL | (true, 0..=10) => {} + | ------ this range overlaps on `10_u8`... +LL | (false, _) => {} +LL | (_, 10..20) => {} + | ^^^^^^ ... with this range + | + = note: you likely meant to write mutually exclusive ranges + +error: multiple patterns overlap on their endpoints + --> $DIR/overlapping_range_endpoints.rs:76:14 | LL | Some(0..=10) => {} | ------ this range overlaps on `10_u8`... @@ -85,5 +117,5 @@ LL | Some(10..20) => {} | = note: you likely meant to write mutually exclusive ranges -error: aborting due to 8 previous errors +error: aborting due to 11 previous errors From 1bf40c257122aaae35e6583b6b2a22e00145b83c Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 21 May 2023 22:45:36 +0200 Subject: [PATCH 27/40] maybe tiny perf gain? --- .../src/thir/pattern/usefulness.rs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 7249daaf1bba6..6c8ff9c29f3ba 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -800,19 +800,22 @@ fn compute_usefulness<'p, 'tcx>( } // Lint on likely incorrect range patterns (#63987) - if let Constructor::IntRange(overlap_range) = ctor { - // If two ranges overlap on their boundaries, that boundary will be found as a singleton - // range after splitting. - if overlap_range.is_singleton() && orig_column_count == 1 { - overlap_range.lint_overlapping_range_endpoints( - pcx, - spec_matrix - .rows() - .map(|child_row| &matrix.rows[child_row.parent_row]) - .map(|parent_row| (parent_row.head(), parent_row.is_under_guard)), - orig_column_count, - lint_root, - ); + if spec_matrix.rows.len() >= 2 { + if let Constructor::IntRange(overlap_range) = ctor { + // If two ranges overlap on their boundaries, that boundary will be found as a singleton + // range after splitting. + // We limit to a single column for now, see `lint_overlapping_range_endpoints`. + if overlap_range.is_singleton() && orig_column_count == 1 { + overlap_range.lint_overlapping_range_endpoints( + pcx, + spec_matrix + .rows() + .map(|child_row| &matrix.rows[child_row.parent_row]) + .map(|parent_row| (parent_row.head(), parent_row.is_under_guard)), + orig_column_count, + lint_root, + ); + } } } From 5450fd58649d518abb85272aa1b5206e77ad7f2c Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 23 May 2023 12:12:10 +0200 Subject: [PATCH 28/40] Store wildcard row in the matrix --- .../src/thir/pattern/usefulness.rs | 72 +++++++++++-------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 6c8ff9c29f3ba..637c5c2f87746 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -464,35 +464,58 @@ impl<'p, 'tcx> fmt::Debug for PatStack<'p, 'tcx> { /// A 2D matrix. #[derive(Clone)] struct Matrix<'p, 'tcx> { + /// The rows of this matrix. + /// Invariant: `row.head()` is never an or-pattern. rows: Vec>, + /// Fake row that stores wildcard patterns that match the other rows. This is used only to track + /// the type and number of columns of the matrix. + wildcard_row: PatStack<'p, 'tcx>, } impl<'p, 'tcx> Matrix<'p, 'tcx> { - fn empty() -> Self { - Matrix { rows: vec![] } + fn empty(wildcard_row: PatStack<'p, 'tcx>) -> Self { + Matrix { rows: vec![], wildcard_row } } /// Pushes a new row to the matrix. If the row starts with an or-pattern, this recursively /// expands it. - fn push(&mut self, row: PatStack<'p, 'tcx>) -> usize { + fn push(&mut self, row: PatStack<'p, 'tcx>) { if !row.is_empty() && row.head().is_or_pat() { - let mut count = 0; for new_row in row.expand_or_pat() { - count += self.push(new_row); + self.push(new_row); } - count } else { self.rows.push(row); - 1 } } + fn column_count(&self) -> usize { + self.wildcard_row.len() + } + fn head_ty(&self) -> Ty<'tcx> { + let mut ty = self.wildcard_row.head().ty(); + // Opaque types can't get destructured/split, but the patterns can + // actually hint at hidden types, so we use the patterns' types instead. + if let ty::Alias(ty::Opaque, ..) = ty.kind() { + if let Some(row) = self.rows().next() { + ty = row.head().ty(); + } + } + ty + } + fn rows<'a>( &'a self, ) -> impl Iterator> + Clone + DoubleEndedIterator + ExactSizeIterator { self.rows.iter() } + fn rows_mut<'a>( + &'a mut self, + ) -> impl Iterator> + DoubleEndedIterator + ExactSizeIterator + { + self.rows.iter_mut() + } /// Iterate over the first component of each row fn heads<'a>( &'a self, @@ -506,7 +529,8 @@ impl<'p, 'tcx> Matrix<'p, 'tcx> { pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>, ) -> Matrix<'p, 'tcx> { - let mut matrix = Matrix::empty(); + let mut matrix = + Matrix::empty(self.wildcard_row.pop_head_constructor(pcx, ctor, usize::MAX)); for (i, row) in self.rows().enumerate() { if ctor.is_covered_by(pcx, row.head().ctor()) { let new_row = row.pop_head_constructor(pcx, ctor, i); @@ -739,18 +763,17 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { fn compute_usefulness<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, matrix: &mut Matrix<'p, 'tcx>, - v: &PatStack<'p, 'tcx>, collect_witnesses: bool, lint_root: HirId, is_top_level: bool, ) -> WitnessMatrix<'p, 'tcx> { - debug_assert!(matrix.rows().all(|r| r.len() == v.len())); + debug_assert!(matrix.rows().all(|r| r.len() == matrix.column_count())); // The base case. We are pattern-matching on () and the return value is based on whether our // matrix has a row or not. - if v.is_empty() { + if matrix.column_count() == 0 { let mut useful = true; - for row in matrix.rows.iter_mut() { + for row in matrix.rows_mut() { row.is_useful = useful; useful = useful && row.is_under_guard; if !useful { @@ -764,14 +787,7 @@ fn compute_usefulness<'p, 'tcx>( } } - let mut ty = v.head().ty(); - // Opaque types can't get destructured/split, but the patterns can - // actually hint at hidden types, so we use the patterns' types instead. - if let ty::Alias(ty::Opaque, ..) = ty.kind() { - if let Some(row) = matrix.rows().next() { - ty = row.head().ty(); - } - } + let ty = matrix.head_ty(); debug!("ty: {ty:?}"); let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; @@ -781,7 +797,7 @@ fn compute_usefulness<'p, 'tcx>( // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. let mut ret = WitnessMatrix::new_empty(); - let orig_column_count = v.len(); + let orig_column_count = matrix.column_count(); for ctor in split_ctors { // If some ctors are missing we only report those. Could report all if that's useful for // some applications. @@ -790,9 +806,8 @@ fn compute_usefulness<'p, 'tcx>( || matches!(ctor, Constructor::Wildcard | Constructor::Missing)); debug!("specialize({:?})", ctor); let mut spec_matrix = matrix.specialize_constructor(pcx, &ctor); - let v = v.pop_head_constructor(pcx, &ctor, usize::MAX); let mut witnesses = ensure_sufficient_stack(|| { - compute_usefulness(cx, &mut spec_matrix, &v, collect_witnesses, lint_root, false) + compute_usefulness(cx, &mut spec_matrix, collect_witnesses, lint_root, false) }); if collect_witnesses { witnesses.apply_constructor(pcx, &missing_ctors, &ctor); @@ -800,7 +815,7 @@ fn compute_usefulness<'p, 'tcx>( } // Lint on likely incorrect range patterns (#63987) - if spec_matrix.rows.len() >= 2 { + if spec_matrix.rows().len() >= 2 { if let Constructor::IntRange(overlap_range) = ctor { // If two ranges overlap on their boundaries, that boundary will be found as a singleton // range after splitting. @@ -965,16 +980,15 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( scrut_ty: Ty<'tcx>, scrut_span: Span, ) -> UsefulnessReport<'p, 'tcx> { - let mut matrix = Matrix::empty(); + let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); + let wildcard_row = PatStack::from_pattern(wild_pattern, usize::MAX, false); + let mut matrix = Matrix::empty(wildcard_row); for (row_id, arm) in arms.iter().enumerate() { let v = PatStack::from_pattern(arm.pat, row_id, arm.has_guard); matrix.push(v); } - let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); - let v = PatStack::from_pattern(wild_pattern, usize::MAX, false); - let non_exhaustiveness_witnesses = - compute_usefulness(cx, &mut matrix, &v, true, lint_root, true); + let non_exhaustiveness_witnesses = compute_usefulness(cx, &mut matrix, true, lint_root, true); let non_exhaustiveness_witnesses: Vec<_> = non_exhaustiveness_witnesses.single_column(); let arm_usefulness: Vec<_> = arms .iter() From 144ea112882aadb718b63f6fe20798a514c4b363 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 00:55:53 +0200 Subject: [PATCH 29/40] cleanup --- .../rustc_mir_build/src/thir/pattern/usefulness.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 637c5c2f87746..33c75fbda297b 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -364,8 +364,6 @@ pub(super) struct PatCtxt<'a, 'p, 'tcx> { /// Whether the current pattern is the whole pattern as found in a match arm, or if it's a /// subpattern. pub(super) is_top_level: bool, - /// Whether the current pattern is from a `non_exhaustive` enum. - pub(super) is_non_exhaustive: bool, } impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> { @@ -700,10 +698,10 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { for witness in self.0.iter_mut() { witness.apply_constructor(pcx, ctor) } - } else if pcx.is_non_exhaustive { + } else if missing_ctors.iter().any(|c| c.is_non_exhaustive()) { // Here we don't want the user to try to list all variants, we want them to add a // wildcard, so we only suggest that. - self.push_wild_ctor(pcx, Constructor::Wildcard); + self.push_wild_ctor(pcx, Constructor::NonExhaustive); } else { // We got the special `Missing` constructor, so each of the missing constructors gives a // new pattern that is not caught by the match. We list those patterns and push them @@ -789,8 +787,7 @@ fn compute_usefulness<'p, 'tcx>( let ty = matrix.head_ty(); debug!("ty: {ty:?}"); - let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); - let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level, is_non_exhaustive }; + let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level }; let set = ConstructorSet::new(pcx); let (split_ctors, missing_ctors) = set.split(pcx, matrix.heads().map(|p| p.ctor())); @@ -868,13 +865,12 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>( let ty = column[0].ty(); let span = column[0].span(); - let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); - let pcx = &PatCtxt { cx, ty, span, is_top_level: false, is_non_exhaustive }; + let pcx = &PatCtxt { cx, ty, span, is_top_level: false }; let set = ConstructorSet::new(pcx); let (split_ctors, missing_ctors) = set.split(pcx, column.iter().map(|p| p.ctor())); - if is_non_exhaustive { + if cx.is_foreign_non_exhaustive_enum(ty) { witnesses.extend( missing_ctors .into_iter() From 23dc7c9741622c9a7650805b90c541eda7c490f8 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 01:42:28 +0200 Subject: [PATCH 30/40] Clarify handling empty types --- .../src/thir/pattern/deconstruct_pat.rs | 66 +++++++++---------- .../src/thir/pattern/usefulness.rs | 12 ++-- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 8cb6b0fa40bef..b087fc5399610 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -962,42 +962,27 @@ impl ConstructorSet { // witness. let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(pcx.ty); - let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; - - // If `exhaustive_patterns` is disabled and our scrutinee is an empty enum, we treat it - // as though it had an "unknown" constructor to avoid exposing its emptiness. The - // exception is if the pattern is at the top level, because we want empty matches to be - // considered exhaustive. - let is_secretly_empty = - def.variants().is_empty() && !is_exhaustive_pat_feature && !pcx.is_top_level; - - let variants: Vec<_> = def - .variants() - .iter_enumerated() - .filter(|(_, v)| { - // If `exhaustive_patterns` is enabled, we exclude variants known to be - // uninhabited. - !is_exhaustive_pat_feature - || v.inhabited_predicate(cx.tcx, *def).subst(cx.tcx, substs).apply( - cx.tcx, - cx.param_env, - cx.module, - ) - }) - .map(|(idx, _)| idx) - .collect(); - - Self::Variants { - variants, - non_exhaustive: is_secretly_empty || is_declared_nonexhaustive, + if def.variants().is_empty() && !is_declared_nonexhaustive { + Self::Uninhabited + } else { + let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; + let variants: Vec<_> = + def.variants() + .iter_enumerated() + .filter(|(_, v)| { + // If `exhaustive_patterns` is enabled, we exclude variants known to be + // uninhabited. + !is_exhaustive_pat_feature + || v.inhabited_predicate(cx.tcx, *def) + .subst(cx.tcx, substs) + .apply(cx.tcx, cx.param_env, cx.module) + }) + .map(|(idx, _)| idx) + .collect(); + + Self::Variants { variants, non_exhaustive: is_declared_nonexhaustive } } } - // If `exhaustive_patterns` is disabled and our scrutinee is the never type, we cannot - // expose its emptiness. The exception is if the pattern is at the top level, because we - // want empty matches to be considered exhaustive. - ty::Never if !cx.tcx.features().exhaustive_patterns && !pcx.is_top_level => { - Self::Unlistable - } ty::Never => Self::Uninhabited, _ if cx.is_uninhabited(pcx.ty) => Self::Uninhabited, ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => Self::Single, @@ -1017,6 +1002,9 @@ impl ConstructorSet { self, pcx: &PatCtxt<'_, '_, 'tcx>, ctors: impl Iterator> + Clone, + // Whether the current pattern is the whole pattern as found in a match arm, or if it's a + // subpattern. + is_top_level: bool, ) -> (Vec>, Vec>) where 'tcx: 'a, @@ -1129,10 +1117,18 @@ impl ConstructorSet { split.extend(seen); missing.push(NonExhaustive); } + // If `exhaustive_patterns` is disabled and our scrutinee is an empty type, we cannot + // expose its emptiness. The exception is if the pattern is at the top level, because we + // want empty matches to be considered exhaustive. + ConstructorSet::Uninhabited + if !pcx.cx.tcx.features().exhaustive_patterns && !is_top_level => + { + missing.push(NonExhaustive); + } ConstructorSet::Uninhabited => {} } if !missing.is_empty() { - let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); + let report_when_all_missing = is_top_level && !IntRange::is_integral(pcx.ty); if seen_any_non_wildcard || report_when_all_missing { split.push(Missing); } else { diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 33c75fbda297b..00362dfbd1992 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -361,9 +361,6 @@ pub(super) struct PatCtxt<'a, 'p, 'tcx> { pub(super) ty: Ty<'tcx>, /// Span of the current pattern under investigation. pub(super) span: Span, - /// Whether the current pattern is the whole pattern as found in a match arm, or if it's a - /// subpattern. - pub(super) is_top_level: bool, } impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> { @@ -787,10 +784,11 @@ fn compute_usefulness<'p, 'tcx>( let ty = matrix.head_ty(); debug!("ty: {ty:?}"); - let pcx = &PatCtxt { cx, ty, span: DUMMY_SP, is_top_level }; + let pcx = &PatCtxt { cx, ty, span: DUMMY_SP }; let set = ConstructorSet::new(pcx); - let (split_ctors, missing_ctors) = set.split(pcx, matrix.heads().map(|p| p.ctor())); + let (split_ctors, missing_ctors) = + set.split(pcx, matrix.heads().map(|p| p.ctor()), is_top_level); // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. let mut ret = WitnessMatrix::new_empty(); @@ -865,10 +863,10 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>( let ty = column[0].ty(); let span = column[0].span(); - let pcx = &PatCtxt { cx, ty, span, is_top_level: false }; + let pcx = &PatCtxt { cx, ty, span }; let set = ConstructorSet::new(pcx); - let (split_ctors, missing_ctors) = set.split(pcx, column.iter().map(|p| p.ctor())); + let (split_ctors, missing_ctors) = set.split(pcx, column.iter().map(|p| p.ctor()), false); if cx.is_foreign_non_exhaustive_enum(ty) { witnesses.extend( From 77ba32dee15c7d9e02fdcb3f1a19d44e983316b3 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 01:51:07 +0200 Subject: [PATCH 31/40] Filter hidden variants earlier --- .../src/thir/pattern/deconstruct_pat.rs | 8 ++++++ .../src/thir/pattern/usefulness.rs | 25 ++++--------------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index b087fc5399610..990a82edde580 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -1041,14 +1041,22 @@ impl ConstructorSet { } ConstructorSet::Variants { variants, non_exhaustive } => { let seen_set: FxHashSet<_> = seen.iter().map(|c| c.as_variant().unwrap()).collect(); + let mut skipped_any_missing_variant = false; for variant in variants { let ctor = Variant(variant); if seen_set.contains(&variant) { split.push(ctor); + } else if ctor.is_doc_hidden_variant(pcx) || ctor.is_unstable_variant(pcx) { + // We don't want to mention any variants that are `doc(hidden)` or + // behind an unstable feature gate. + skipped_any_missing_variant = true; } else { missing.push(ctor); } } + if skipped_any_missing_variant { + missing.push(Wildcard); + } if non_exhaustive { missing.push(NonExhaustive); } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 00362dfbd1992..a08d2c9c0f500 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -703,25 +703,11 @@ impl<'p, 'tcx> WitnessMatrix<'p, 'tcx> { // We got the special `Missing` constructor, so each of the missing constructors gives a // new pattern that is not caught by the match. We list those patterns and push them // onto our current witnesses. - - // Skip any variants that are `doc(hidden)` or behind an unstable feature gate. - // We replace them with a wildcard. let old_witnesses = std::mem::replace(self, Self::new_empty()); - let mut skipped_any_variant = false; for missing_ctor in missing_ctors { - if missing_ctor.is_doc_hidden_variant(pcx) || missing_ctor.is_unstable_variant(pcx) - { - skipped_any_variant = true; - } else { - let mut witnesses_with_missing_ctor = old_witnesses.clone(); - witnesses_with_missing_ctor.push_wild_ctor(pcx, missing_ctor.clone()); - self.extend(witnesses_with_missing_ctor) - } - } - if skipped_any_variant { - let mut witnesses_with_wildcard = old_witnesses; - witnesses_with_wildcard.push_wild_ctor(pcx, Constructor::Wildcard); - self.extend(witnesses_with_wildcard) + let mut witnesses_with_missing_ctor = old_witnesses.clone(); + witnesses_with_missing_ctor.push_wild_ctor(pcx, missing_ctor.clone()); + self.extend(witnesses_with_missing_ctor) } } } @@ -872,9 +858,8 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>( witnesses.extend( missing_ctors .into_iter() - // Filter out the `NonExhaustive` ctor because we want to list only real variants. - // Also remove any unstable feature gated variants. - .filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))) + // We want to list only real variants. + .filter(|c| !(c.is_non_exhaustive() || c.is_wildcard())) .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)), ) } From 23645221f54dcd363701fadd58ffd4a398c53d22 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 02:04:54 +0200 Subject: [PATCH 32/40] tweak --- .../src/thir/pattern/deconstruct_pat.rs | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 990a82edde580..76c6bdb974d54 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -999,7 +999,7 @@ impl ConstructorSet { /// - if we replace any `Wildcard`/`Missing` in `split` with all of `missing`, this also /// covers the whole type pub(super) fn split<'a, 'tcx>( - self, + &self, pcx: &PatCtxt<'_, '_, 'tcx>, ctors: impl Iterator> + Clone, // Whether the current pattern is the whole pattern as found in a match arm, or if it's a @@ -1034,8 +1034,8 @@ impl ConstructorSet { } } ConstructorSet::Variants { variants, non_exhaustive } if seen.is_empty() => { - missing.extend(variants.into_iter().map(Variant)); - if non_exhaustive { + missing.extend(variants.iter().copied().map(Variant)); + if *non_exhaustive { missing.push(NonExhaustive); } } @@ -1043,7 +1043,7 @@ impl ConstructorSet { let seen_set: FxHashSet<_> = seen.iter().map(|c| c.as_variant().unwrap()).collect(); let mut skipped_any_missing_variant = false; for variant in variants { - let ctor = Variant(variant); + let ctor = Variant(*variant); if seen_set.contains(&variant) { split.push(ctor); } else if ctor.is_doc_hidden_variant(pcx) || ctor.is_unstable_variant(pcx) { @@ -1057,44 +1057,41 @@ impl ConstructorSet { if skipped_any_missing_variant { missing.push(Wildcard); } - if non_exhaustive { + if *non_exhaustive { missing.push(NonExhaustive); } } ConstructorSet::Integers { range_1, range_2, non_exhaustive } if seen.is_empty() => { - if non_exhaustive { + if *non_exhaustive { missing.push(NonExhaustive); } else { - missing.push(IntRange(range_1)); + missing.push(IntRange(range_1.clone())); if let Some(range_2) = range_2 { - missing.push(IntRange(range_2)); + missing.push(IntRange(range_2.clone())); } } } ConstructorSet::Integers { range_1, range_2, non_exhaustive } => { let range = match range_2 { - None => SplitIntRange::Single(range_1), - Some(range_2) => SplitIntRange::Double([range_1, range_2]), + None => SplitIntRange::Single(range_1.clone()), + Some(range_2) => SplitIntRange::Double([range_1.clone(), range_2.clone()]), }; let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()); - let splitted_ranges = range.split(seen_ranges.cloned()); - for (seen, splitted_range) in splitted_ranges { + for (seen, splitted_range) in range.split(seen_ranges.cloned()) { let ctor = IntRange(splitted_range); match seen { - // We don't report missing constructors in this case - Presence::Unseen if non_exhaustive => {} Presence::Unseen => missing.push(ctor), Presence::Seen => split.push(ctor), } } - if non_exhaustive { + if *non_exhaustive { missing.push(NonExhaustive); } } - ConstructorSet::Slice(base_slice) if seen.is_empty() => { + &ConstructorSet::Slice(base_slice) if seen.is_empty() => { missing.push(Slice(base_slice)); } - ConstructorSet::Slice(base_slice) => { + &ConstructorSet::Slice(base_slice) => { match base_slice.kind { FixedLen(..) => { split.push(Slice(base_slice)); From c6976d882b9da7ee11ac70d79a1acfe5030b4fff Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 02:05:01 +0200 Subject: [PATCH 33/40] tweak --- compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 76c6bdb974d54..de1fa9749b71b 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -1133,7 +1133,7 @@ impl ConstructorSet { ConstructorSet::Uninhabited => {} } if !missing.is_empty() { - let report_when_all_missing = is_top_level && !IntRange::is_integral(pcx.ty); + let report_when_all_missing = is_top_level && !matches!(self, Self::Integers { .. }); if seen_any_non_wildcard || report_when_all_missing { split.push(Missing); } else { From 500e0bd553e543f592d9517231e4bf8141690c80 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 02:07:27 +0200 Subject: [PATCH 34/40] tweak --- .../src/thir/pattern/deconstruct_pat.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index de1fa9749b71b..1cc5832fdf4a6 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -887,11 +887,12 @@ impl ConstructorSet { pub(super) fn new<'p, 'tcx>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { debug!("ConstructorSet::new({:?})", pcx.ty); let cx = pcx.cx; + let ty = pcx.ty; let make_range = |start, end| { // `unwrap()` is ok because we know the type is an integer. - IntRange::from_range(cx.tcx, start, end, pcx.ty, &RangeEnd::Included).unwrap() + IntRange::from_range(cx.tcx, start, end, ty, &RangeEnd::Included).unwrap() }; - // This determines the set of all possible constructors for the type `pcx.ty`. For numbers, + // This determines the set of all possible constructors for the type `ty`. For numbers, // arrays and slices we use ranges and variable-length slices when appropriate. // // If the `exhaustive_patterns` feature is enabled, we make sure to omit constructors that @@ -899,7 +900,7 @@ impl ConstructorSet { // returned list of constructors. // Invariant: this is `Uninhabited` if and only if the type is uninhabited (as determined by // `cx.is_uninhabited()`). - match pcx.ty.kind() { + match ty.kind() { ty::Bool => { Self::Integers { range_1: make_range(0, 1), range_2: None, non_exhaustive: false } } @@ -914,8 +915,8 @@ impl ConstructorSet { &ty::Int(ity) => { // `usize`/`isize` are not allowed to be matched exhaustively unless the // `precise_pointer_size_matching` feature is enabled. - let non_exhaustive = pcx.ty.is_ptr_sized_integral() - && !cx.tcx.features().precise_pointer_size_matching; + let non_exhaustive = + ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching; let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; let min = 1u128 << (bits - 1); let max = min - 1; @@ -924,8 +925,8 @@ impl ConstructorSet { &ty::Uint(uty) => { // `usize`/`isize` are not allowed to be matched exhaustively unless the // `precise_pointer_size_matching` feature is enabled. - let non_exhaustive = pcx.ty.is_ptr_sized_integral() - && !cx.tcx.features().precise_pointer_size_matching; + let non_exhaustive = + ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching; let size = Integer::from_uint_ty(&cx.tcx, uty).size(); let max = size.truncate(u128::MAX); Self::Integers { range_1: make_range(0, max), non_exhaustive, range_2: None } @@ -960,7 +961,7 @@ impl ConstructorSet { // // we don't want to show every possible IO error, but instead have only `_` as the // witness. - let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(pcx.ty); + let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(ty); if def.variants().is_empty() && !is_declared_nonexhaustive { Self::Uninhabited @@ -984,7 +985,7 @@ impl ConstructorSet { } } ty::Never => Self::Uninhabited, - _ if cx.is_uninhabited(pcx.ty) => Self::Uninhabited, + _ if cx.is_uninhabited(ty) => Self::Uninhabited, ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => Self::Single, // This type is one for which we cannot list constructors, like `str` or `f64`. _ => Self::Unlistable, From 5072a1ec07649c754047b087f99a386037f33ba6 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 02:15:48 +0200 Subject: [PATCH 35/40] `pcx.span` was no longer meaningful --- .../src/thir/pattern/deconstruct_pat.rs | 24 +++++++------------ .../src/thir/pattern/usefulness.rs | 9 +++---- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 1cc5832fdf4a6..e9caf1d8605b4 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -852,12 +852,7 @@ impl<'tcx> Constructor<'tcx> { // Only a wildcard pattern can match the special extra constructor. (NonExhaustive, _) => false, - _ => span_bug!( - pcx.span, - "trying to compare incompatible constructors {:?} and {:?}", - self, - other - ), + _ => bug!("trying to compare incompatible constructors {:?} and {:?}", self, other), } } } @@ -1193,9 +1188,8 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { fn wildcards_from_tys( cx: &MatchCheckCtxt<'p, 'tcx>, tys: impl IntoIterator>, - span: Span, ) -> Self { - Fields::from_iter(cx, tys.into_iter().map(|ty| DeconstructedPat::wildcard(ty, span))) + Fields::from_iter(cx, tys.into_iter().map(|ty| DeconstructedPat::wildcard(ty, DUMMY_SP))) } // In the cases of either a `#[non_exhaustive]` field list or a non-public field, we hide @@ -1231,18 +1225,18 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { pub(super) fn wildcards(pcx: &PatCtxt<'_, 'p, 'tcx>, constructor: &Constructor<'tcx>) -> Self { let ret = match constructor { Single | Variant(_) => match pcx.ty.kind() { - ty::Tuple(fs) => Fields::wildcards_from_tys(pcx.cx, fs.iter(), pcx.span), - ty::Ref(_, rty, _) => Fields::wildcards_from_tys(pcx.cx, once(*rty), pcx.span), + ty::Tuple(fs) => Fields::wildcards_from_tys(pcx.cx, fs.iter()), + ty::Ref(_, rty, _) => Fields::wildcards_from_tys(pcx.cx, once(*rty)), ty::Adt(adt, substs) => { if adt.is_box() { // The only legal patterns of type `Box` (outside `std`) are `_` and box // patterns. If we're here we can assume this is a box pattern. - Fields::wildcards_from_tys(pcx.cx, once(substs.type_at(0)), pcx.span) + Fields::wildcards_from_tys(pcx.cx, once(substs.type_at(0))) } else { let variant = &adt.variant(constructor.variant_index_for_adt(*adt)); let tys = Fields::list_variant_nonhidden_fields(pcx.cx, pcx.ty, variant) .map(|(_, ty)| ty); - Fields::wildcards_from_tys(pcx.cx, tys, pcx.span) + Fields::wildcards_from_tys(pcx.cx, tys) } } _ => bug!("Unexpected type for `Single` constructor: {:?}", pcx), @@ -1250,7 +1244,7 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { Slice(slice) => match *pcx.ty.kind() { ty::Slice(ty) | ty::Array(ty, _) => { let arity = slice.arity(); - Fields::wildcards_from_tys(pcx.cx, (0..arity).map(|_| ty), pcx.span) + Fields::wildcards_from_tys(pcx.cx, (0..arity).map(|_| ty)) } _ => bug!("bad slice pattern {:?} {:?}", constructor, pcx), }, @@ -1310,7 +1304,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { /// `Some(_)`. pub(super) fn wild_from_ctor(pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: Constructor<'tcx>) -> Self { let fields = Fields::wildcards(pcx, &ctor); - DeconstructedPat::new(ctor, fields, pcx.ty, pcx.span) + DeconstructedPat::new(ctor, fields, pcx.ty, DUMMY_SP) } pub(crate) fn from_pat(cx: &MatchCheckCtxt<'p, 'tcx>, pat: &Pat<'tcx>) -> Self { @@ -1611,7 +1605,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { let wildcard: &_ = pcx .cx .pattern_arena - .alloc(DeconstructedPat::wildcard(inner_ty, pcx.span)); + .alloc(DeconstructedPat::wildcard(inner_ty, DUMMY_SP)); let extra_wildcards = other_slice.arity() - self_slice.arity(); let extra_wildcards = (0..extra_wildcards).map(|_| wildcard); prefix.iter().chain(extra_wildcards).chain(suffix).collect() diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index a08d2c9c0f500..db4dcd624b92a 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -359,8 +359,6 @@ pub(super) struct PatCtxt<'a, 'p, 'tcx> { pub(super) cx: &'a MatchCheckCtxt<'p, 'tcx>, /// Type of the current column under investigation. pub(super) ty: Ty<'tcx>, - /// Span of the current pattern under investigation. - pub(super) span: Span, } impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> { @@ -631,7 +629,7 @@ impl<'p, 'tcx> WitnessStack<'p, 'tcx> { let pats = self.0.drain((len - arity)..).rev(); Fields::from_iter(pcx.cx, pats) }; - let pat = DeconstructedPat::new(ctor.clone(), fields, pcx.ty, pcx.span); + let pat = DeconstructedPat::new(ctor.clone(), fields, pcx.ty, DUMMY_SP); self.0.push(pat); } @@ -770,7 +768,7 @@ fn compute_usefulness<'p, 'tcx>( let ty = matrix.head_ty(); debug!("ty: {ty:?}"); - let pcx = &PatCtxt { cx, ty, span: DUMMY_SP }; + let pcx = &PatCtxt { cx, ty }; let set = ConstructorSet::new(pcx); let (split_ctors, missing_ctors) = @@ -848,8 +846,7 @@ fn collect_nonexhaustive_missing_variants<'p, 'tcx>( } let ty = column[0].ty(); - let span = column[0].span(); - let pcx = &PatCtxt { cx, ty, span }; + let pcx = &PatCtxt { cx, ty }; let set = ConstructorSet::new(pcx); let (split_ctors, missing_ctors) = set.split(pcx, column.iter().map(|p| p.ctor()), false); From f030769965512c59991ffe61d3854a57494837cf Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 14:27:54 +0200 Subject: [PATCH 36/40] Cleanup slice splitting --- .../src/thir/pattern/deconstruct_pat.rs | 148 +++++++++--------- 1 file changed, 71 insertions(+), 77 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index e9caf1d8605b4..0bd30eda9b0e5 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -588,73 +588,68 @@ impl Slice { /// /// `max_slice` below will be made to have arity `L`. #[derive(Debug)] -struct SplitVarLenSlice { - /// If the type is an array, this is its size. - array_len: Option, - /// The arity of the input slice. - arity: usize, - /// The smallest slice bigger than any slice seen. `max_slice.arity()` is the length `L` - /// described above. - max_slice: SliceKind, -} +struct SplitVarLenSlice; impl SplitVarLenSlice { - fn new(prefix: usize, suffix: usize, array_len: Option) -> Self { - SplitVarLenSlice { array_len, arity: prefix + suffix, max_slice: VarLen(prefix, suffix) } - } - - /// Pass a set of slices relative to which to split this one. - fn split(&mut self, slices: impl Iterator) { - let VarLen(max_prefix_len, max_suffix_len) = &mut self.max_slice else { - // No need to split - return; - }; - // We grow `self.max_slice` to be larger than all slices encountered, as described above. - // For diagnostics, we keep the prefix and suffix lengths separate, but grow them so that - // `L = max_prefix_len + max_suffix_len`. + /// Split the base `[..]` slice relative to `slices`. + fn split( + array_len: Option, + slices: impl Iterator + Clone, + ) -> impl Iterator { + let mut max_prefix_len = 0; + let mut max_suffix_len = 0; let mut max_fixed_len = 0; - for slice in slices { + let mut min_var_len = usize::MAX; + let mut seen_fixed_lens = FxHashSet::default(); + for slice in slices.clone() { match slice { FixedLen(len) => { max_fixed_len = cmp::max(max_fixed_len, len); + seen_fixed_lens.insert(len); } VarLen(prefix, suffix) => { - *max_prefix_len = cmp::max(*max_prefix_len, prefix); - *max_suffix_len = cmp::max(*max_suffix_len, suffix); + max_prefix_len = cmp::max(max_prefix_len, prefix); + max_suffix_len = cmp::max(max_suffix_len, suffix); + min_var_len = cmp::min(min_var_len, prefix + suffix); } } } - // We want `L = max(L, max_fixed_len + 1)`, modulo the fact that we keep prefix and - // suffix separate. - if max_fixed_len + 1 >= *max_prefix_len + *max_suffix_len { - // The subtraction can't overflow thanks to the above check. + // We want to compute `L` that is larger than all slices encountered, as described above. We + // take `L = max_prefix_len + max_suffix_len`, then ensure `L > max_fixed_len`. + // The subtlety with prefix vs suffix is for diagnostics purposes. + if max_fixed_len + 1 >= max_prefix_len + max_suffix_len { + // The subtraction can't underflow thanks to the above check. // The new `max_prefix_len` is larger than its previous value. - *max_prefix_len = max_fixed_len + 1 - *max_suffix_len; + max_prefix_len = max_fixed_len + 1 - max_suffix_len; } + // The smallest slice bigger than any slice seen. `max_slice.arity()` is the length `L` + // described above. + let mut max_slice = VarLen(max_prefix_len, max_suffix_len); // We cap the arity of `max_slice` at the array size. - match self.array_len { - Some(len) if self.max_slice.arity() >= len => self.max_slice = FixedLen(len), + match array_len { + Some(len) if max_slice.arity() >= len => max_slice = FixedLen(len), _ => {} } - } - - /// Iterate over the partition of this slice. - fn iter(&self) -> impl Iterator + Captures<'_> { - let smaller_lengths = match self.array_len { - // The only admissible fixed-length slice is one of the array size. Whether `max_slice` - // is fixed-length or variable-length, it will be the only relevant slice to output - // here. + let smaller_lengths = match array_len { + // If the array has known length, the only admissible patterns are fixed-length of the + // array size or variable-length of a smaller size. This is exactly what `max_slice` + // captures, so we only output that. Some(_) => 0..0, // empty range - // We cover all arities in the range `(self.arity..infinity)`. We split that range into - // two: lengths smaller than `max_slice.arity()` are treated independently as - // fixed-lengths slices, and lengths above are captured by `max_slice`. - None => self.arity..self.max_slice.arity(), + // We cover all arities in the range `(0..infinity)`. We split that range into two: + // lengths smaller than `max_slice.arity()` are treated independently as fixed-lengths + // slices, and lengths above are captured by `max_slice`. + None => 0..max_slice.arity(), }; - smaller_lengths - .map(FixedLen) - .chain(once(self.max_slice)) - .map(move |kind| Slice::new(self.array_len, kind)) + smaller_lengths.map(FixedLen).chain(once(max_slice)).map(move |slice| { + let arity = slice.arity(); + let seen = if min_var_len <= arity || seen_fixed_lens.contains(&arity) { + Presence::Seen + } else { + Presence::Unseen + }; + (seen, Slice::new(array_len, slice)) + }) } } @@ -869,8 +864,10 @@ pub(super) enum ConstructorSet { /// `non_exhaustive` is used when the range is not allowed to be matched exhaustively (that's /// for usize/isize). Integers { range_1: IntRange, range_2: Option, non_exhaustive: bool }, - /// The type is matched by slices. - Slice(Slice), + /// The type is matched by slices. The usize is the compile-time length of the array, if known. + Slice(Option), + /// The type is matched by slices whose elements are uninhabited. + SliceOfEmpty, /// The constructors cannot be listed, and the type cannot be matched exhaustively. E.g. `str`, /// floats. Unlistable, @@ -931,13 +928,16 @@ impl ConstructorSet { if len != 0 && cx.is_uninhabited(*sub_ty) { Self::Uninhabited } else { - Self::Slice(Slice::new(Some(len), VarLen(0, 0))) + Self::Slice(Some(len)) } } // Treat arrays of a constant but unknown length like slices. ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { - let kind = if cx.is_uninhabited(*sub_ty) { FixedLen(0) } else { VarLen(0, 0) }; - Self::Slice(Slice::new(None, kind)) + if cx.is_uninhabited(*sub_ty) { + Self::SliceOfEmpty + } else { + Self::Slice(None) + } } ty::Adt(def, substs) if def.is_enum() => { // If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an @@ -1084,34 +1084,28 @@ impl ConstructorSet { missing.push(NonExhaustive); } } - &ConstructorSet::Slice(base_slice) if seen.is_empty() => { - missing.push(Slice(base_slice)); + &ConstructorSet::Slice(array_len) if seen.is_empty() => { + missing.push(Slice(Slice::new(array_len, VarLen(0, 0)))); } - &ConstructorSet::Slice(base_slice) => { - match base_slice.kind { - FixedLen(..) => { - split.push(Slice(base_slice)); - } - VarLen(self_prefix, self_suffix) => { - // FIXME: splitting and collecting should be done in one pass. - let mut split_base_slice = - SplitVarLenSlice::new(self_prefix, self_suffix, base_slice.array_len); - let seen_slices = seen.iter().map(|c| c.as_slice().unwrap()); - split_base_slice.split(seen_slices.clone().map(|s| s.kind)); - for splitted_slice in split_base_slice.iter() { - let is_covered_by_any = seen_slices - .clone() - .any(|other| splitted_slice.is_covered_by(other)); - let ctor = Slice(splitted_slice); - if is_covered_by_any { - split.push(ctor); - } else { - missing.push(ctor); - } - } + &ConstructorSet::Slice(array_len) => { + let seen_slices = seen.iter().map(|c| c.as_slice().unwrap()).map(|s| s.kind); + for (seen, splitted_slice) in SplitVarLenSlice::split(array_len, seen_slices) { + let ctor = Slice(splitted_slice); + match seen { + Presence::Unseen => missing.push(ctor), + Presence::Seen => split.push(ctor), } } } + ConstructorSet::SliceOfEmpty => { + // Behaves essentially like `Single`. + let slice = Slice(Slice::new(None, FixedLen(0))); + if seen.is_empty() { + missing.push(slice); + } else { + split.push(slice); + } + } ConstructorSet::Unlistable => { // Since we can't list constructors, we take the ones in the matrix. This might list // some constructors several times but there's not much we can do. From 5cfb953327922f5e96775a75db7af3a8e92dfa2e Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 18:32:20 +0200 Subject: [PATCH 37/40] Splitting ensures subrange comparison is all we need --- .../src/thir/pattern/deconstruct_pat.rs | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 0bd30eda9b0e5..42473778f1a08 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -43,7 +43,7 @@ //! [`SplitVarLenSlice`]. use std::cell::Cell; -use std::cmp::{self, max, min, Ordering}; +use std::cmp::{self, Ordering}; use std::fmt; use std::iter::once; use std::ops::RangeInclusive; @@ -202,16 +202,6 @@ impl IntRange { other.range.start() <= self.range.start() && self.range.end() <= other.range.end() } - fn intersection(&self, other: &Self) -> Option { - let (lo, hi) = self.boundaries(); - let (other_lo, other_hi) = other.boundaries(); - if lo <= other_hi && other_lo <= hi { - Some(IntRange { range: max(lo, other_lo)..=min(hi, other_hi), bias: self.bias }) - } else { - None - } - } - /// Only used for displaying the range properly. fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> { let (lo, hi) = self.boundaries(); @@ -303,18 +293,6 @@ impl IntRange { } } } - - /// See `Constructor::is_covered_by` - fn is_covered_by(&self, other: &Self) -> bool { - if self.intersection(other).is_some() { - // Constructor splitting should ensure that all intersections we encounter are actually - // inclusions. - assert!(self.is_subrange(other)); - true - } else { - false - } - } } /// Note: this is often not what we want: e.g. `false` is converted into the range `0..=0` and @@ -816,7 +794,7 @@ impl<'tcx> Constructor<'tcx> { (Single, Single) => true, (Variant(self_id), Variant(other_id)) => self_id == other_id, - (IntRange(self_range), IntRange(other_range)) => self_range.is_covered_by(other_range), + (IntRange(self_range), IntRange(other_range)) => self_range.is_subrange(other_range), ( FloatRange(self_from, self_to, self_end), FloatRange(other_from, other_to, other_end), From 8670ba9bda8c0136ef5b4b5cabc7efcb0ac6fb07 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 22 May 2023 18:32:37 +0200 Subject: [PATCH 38/40] Don't need to carry the `bias` around in `IntRange` --- .../src/thir/pattern/deconstruct_pat.rs | 57 +++++++------------ 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 42473778f1a08..9ee393057d81e 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -100,10 +100,6 @@ fn expand_or_pat<'p, 'tcx>(pat: &'p Pat<'tcx>) -> Vec<&'p Pat<'tcx>> { #[derive(Clone, PartialEq, Eq)] pub(crate) struct IntRange { range: RangeInclusive, - /// Keeps the bias used for encoding the range. It depends on the type of the range and - /// possibly the pointer size of the current architecture. The algorithm ensures we never - /// compare `IntRange`s with different types/architectures. - bias: u128, } impl IntRange { @@ -141,28 +137,25 @@ impl IntRange { value: mir::ConstantKind<'tcx>, ) -> Option { let ty = value.ty(); - if let Some((target_size, bias)) = Self::integral_size_and_signed_bias(tcx, ty) { - let val = if let mir::ConstantKind::Val(ConstValue::Scalar(scalar), _) = value { - // For this specific pattern we can skip a lot of effort and go - // straight to the result, after doing a bit of checking. (We - // could remove this branch and just fall through, which - // is more general but much slower.) - scalar.to_bits_or_ptr_internal(target_size).unwrap().left()? - } else { - if let mir::ConstantKind::Ty(c) = value + let (target_size, bias) = Self::integral_size_and_signed_bias(tcx, ty)?; + let val = if let mir::ConstantKind::Val(ConstValue::Scalar(scalar), _) = value { + // For this specific pattern we can skip a lot of effort and go + // straight to the result, after doing a bit of checking. (We + // could remove this branch and just fall through, which + // is more general but much slower.) + scalar.to_bits_or_ptr_internal(target_size).unwrap().left()? + } else { + if let mir::ConstantKind::Ty(c) = value && let ty::ConstKind::Value(_) = c.kind() { bug!("encountered ConstValue in mir::ConstantKind::Ty, whereas this is expected to be in ConstantKind::Val"); } - // This is a more general form of the previous case. - value.try_eval_bits(tcx, param_env, ty)? - }; - let val = val ^ bias; - Some(IntRange { range: val..=val, bias }) - } else { - None - } + // This is a more general form of the previous case. + value.try_eval_bits(tcx, param_env, ty)? + }; + let val = val ^ bias; + Some(IntRange { range: val..=val }) } #[inline] @@ -183,7 +176,7 @@ impl IntRange { // This should have been caught earlier by E0030. bug!("malformed range pattern: {}..={}", lo, (hi - offset)); } - IntRange { range: lo..=(hi - offset), bias } + IntRange { range: lo..=(hi - offset) } }) } @@ -206,7 +199,7 @@ impl IntRange { fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> { let (lo, hi) = self.boundaries(); - let bias = self.bias; + let bias = IntRange::signed_bias(tcx, ty); let (lo, hi) = (lo ^ bias, hi ^ bias); let env = ty::ParamEnv::empty().and(ty); @@ -275,11 +268,8 @@ impl IntRange { } } if !overlaps.is_empty() { - let overlap_as_pat = (super::deconstruct_pat::IntRange { - range: overlap..=overlap, - bias: self.bias, - }) - .to_pat(pcx.cx.tcx, pcx.ty); + let overlap_as_pat = + (IntRange { range: overlap..=overlap }).to_pat(pcx.cx.tcx, pcx.ty); let overlaps: Vec<_> = overlaps .into_iter() .map(|span| Overlap { range: overlap_as_pat.clone(), span }) @@ -296,12 +286,10 @@ impl IntRange { } /// Note: this is often not what we want: e.g. `false` is converted into the range `0..=0` and -/// would be displayed as such. To render properly, convert to a pattern first. +/// `-128..=127i8` is encoded as `0..=255`. To render properly, convert to a pattern first. impl fmt::Debug for IntRange { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (lo, hi) = self.boundaries(); - let bias = self.bias; - let (lo, hi) = (lo ^ bias, hi ^ bias); write!(f, "{}", lo)?; write!(f, "{}", RangeEnd::Included)?; write!(f, "{}", hi) @@ -385,18 +373,13 @@ impl SplitIntRange { .map(|r| Self::into_boundaries(r)) .flat_map(|[lo, hi]| [(lo, 0, 1), (hi, 0, -1)]) .collect(); - let self_bias; match self { Self::Single(self_range) => { - self_bias = self_range.bias; - let [lo, hi] = Self::into_boundaries(self_range); bdys.push((lo, 1, 0)); bdys.push((hi, -1, 0)); } Self::Double([range_1, range_2]) => { - self_bias = range_1.bias; - let [lo, hi] = Self::into_boundaries(range_1); bdys.push((lo, 1, 0)); bdys.push((hi, -1, 0)); @@ -435,7 +418,7 @@ impl SplitIntRange { (JustBefore(n), AfterMax) => n..=u128::MAX, _ => unreachable!(), // Ruled out by the sorting and filtering we did }; - let range = IntRange { range, bias: self_bias }; + let range = IntRange { range }; (presence, range) }) } From 839b0320b9da2f5f5c07b59dff1d427c75dbbe88 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 23 May 2023 16:49:23 +0200 Subject: [PATCH 39/40] Revert one-pass-omitted-patterns-lint --- .../src/thir/pattern/deconstruct_pat.rs | 38 ++-- .../src/thir/pattern/usefulness.rs | 171 +++++----------- ...te-non_exhaustive_omitted_patterns_lint.rs | 4 - ...on_exhaustive_omitted_patterns_lint.stderr | 66 ++---- .../omitted-patterns.rs | 79 +++++--- .../omitted-patterns.stderr | 190 +++++++++++++----- .../stable-omitted-patterns.rs | 8 +- .../stable-omitted-patterns.stderr | 12 +- 8 files changed, 280 insertions(+), 288 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 9ee393057d81e..8f0626ae29873 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -48,7 +48,7 @@ use std::fmt; use std::iter::once; use std::ops::RangeInclusive; -use smallvec::{smallvec, SmallVec}; +use smallvec::SmallVec; use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxHashSet; @@ -991,7 +991,9 @@ impl ConstructorSet { } } ConstructorSet::Variants { variants, non_exhaustive } if seen.is_empty() => { - missing.extend(variants.iter().copied().map(Variant)); + missing.extend(variants.iter().copied().map(Variant).filter(|ctor| { + !(ctor.is_doc_hidden_variant(pcx) || ctor.is_unstable_variant(pcx)) + })); if *non_exhaustive { missing.push(NonExhaustive); } @@ -1143,8 +1145,9 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { fn wildcards_from_tys( cx: &MatchCheckCtxt<'p, 'tcx>, tys: impl IntoIterator>, + span: Span, ) -> Self { - Fields::from_iter(cx, tys.into_iter().map(|ty| DeconstructedPat::wildcard(ty, DUMMY_SP))) + Fields::from_iter(cx, tys.into_iter().map(|ty| DeconstructedPat::wildcard(ty, span))) } // In the cases of either a `#[non_exhaustive]` field list or a non-public field, we hide @@ -1177,21 +1180,25 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { /// Creates a new list of wildcard fields for a given constructor. The result must have a /// length of `constructor.arity()`. #[instrument(level = "trace")] - pub(super) fn wildcards(pcx: &PatCtxt<'_, 'p, 'tcx>, constructor: &Constructor<'tcx>) -> Self { + pub(super) fn wildcards( + pcx: &PatCtxt<'_, 'p, 'tcx>, + constructor: &Constructor<'tcx>, + span: Span, + ) -> Self { let ret = match constructor { Single | Variant(_) => match pcx.ty.kind() { - ty::Tuple(fs) => Fields::wildcards_from_tys(pcx.cx, fs.iter()), - ty::Ref(_, rty, _) => Fields::wildcards_from_tys(pcx.cx, once(*rty)), + ty::Tuple(fs) => Fields::wildcards_from_tys(pcx.cx, fs.iter(), span), + ty::Ref(_, rty, _) => Fields::wildcards_from_tys(pcx.cx, once(*rty), span), ty::Adt(adt, substs) => { if adt.is_box() { // The only legal patterns of type `Box` (outside `std`) are `_` and box // patterns. If we're here we can assume this is a box pattern. - Fields::wildcards_from_tys(pcx.cx, once(substs.type_at(0))) + Fields::wildcards_from_tys(pcx.cx, once(substs.type_at(0)), span) } else { let variant = &adt.variant(constructor.variant_index_for_adt(*adt)); let tys = Fields::list_variant_nonhidden_fields(pcx.cx, pcx.ty, variant) .map(|(_, ty)| ty); - Fields::wildcards_from_tys(pcx.cx, tys) + Fields::wildcards_from_tys(pcx.cx, tys, span) } } _ => bug!("Unexpected type for `Single` constructor: {:?}", pcx), @@ -1199,7 +1206,7 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { Slice(slice) => match *pcx.ty.kind() { ty::Slice(ty) | ty::Array(ty, _) => { let arity = slice.arity(); - Fields::wildcards_from_tys(pcx.cx, (0..arity).map(|_| ty)) + Fields::wildcards_from_tys(pcx.cx, (0..arity).map(|_| ty), span) } _ => bug!("bad slice pattern {:?} {:?}", constructor, pcx), }, @@ -1258,7 +1265,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { /// For example, if `ctor` is a `Constructor::Variant` for `Option::Some`, we get the pattern /// `Some(_)`. pub(super) fn wild_from_ctor(pcx: &PatCtxt<'_, 'p, 'tcx>, ctor: Constructor<'tcx>) -> Self { - let fields = Fields::wildcards(pcx, &ctor); + let fields = Fields::wildcards(pcx, &ctor, DUMMY_SP); DeconstructedPat::new(ctor, fields, pcx.ty, DUMMY_SP) } @@ -1505,13 +1512,6 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { pub(super) fn is_or_pat(&self) -> bool { matches!(self.ctor, Or) } - pub(super) fn flatten_or_pat(&'p self) -> SmallVec<[&'p Self; 1]> { - if self.is_or_pat() { - self.iter_fields().flat_map(|p| p.flatten_or_pat()).collect() - } else { - smallvec![self] - } - } pub(super) fn ctor(&self) -> &Constructor<'tcx> { &self.ctor @@ -1539,7 +1539,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { match (&self.ctor, other_ctor) { (Wildcard, _) => { // We return a wildcard for each field of `other_ctor`. - Fields::wildcards(pcx, other_ctor).iter_patterns().collect() + Fields::wildcards(pcx, other_ctor, self.span()).iter_patterns().collect() } (Slice(self_slice), Slice(other_slice)) if self_slice.arity() != other_slice.arity() => @@ -1560,7 +1560,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { let wildcard: &_ = pcx .cx .pattern_arena - .alloc(DeconstructedPat::wildcard(inner_ty, DUMMY_SP)); + .alloc(DeconstructedPat::wildcard(inner_ty, self.span())); let extra_wildcards = other_slice.arity() - self_slice.arity(); let extra_wildcards = (0..extra_wildcards).map(|_| wildcard); prefix.iter().chain(extra_wildcards).chain(suffix).collect() diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index db4dcd624b92a..3cb4ea0861738 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -376,6 +376,7 @@ struct PatStack<'p, 'tcx> { /// none, this is the id of the arm. parent_row: usize, is_under_guard: bool, + arm_hir_id: HirId, is_useful: bool, } @@ -384,16 +385,18 @@ impl<'p, 'tcx> PatStack<'p, 'tcx> { pat: &'p DeconstructedPat<'p, 'tcx>, parent_row: usize, is_under_guard: bool, + arm_hir_id: HirId, ) -> Self { - Self::from_vec(smallvec![pat], parent_row, is_under_guard) + Self::from_vec(smallvec![pat], parent_row, is_under_guard, arm_hir_id) } fn from_vec( vec: SmallVec<[&'p DeconstructedPat<'p, 'tcx>; 2]>, parent_row: usize, is_under_guard: bool, + arm_hir_id: HirId, ) -> Self { - PatStack { pats: vec, parent_row, is_under_guard, is_useful: false } + PatStack { pats: vec, parent_row, is_under_guard, arm_hir_id, is_useful: false } } fn is_empty(&self) -> bool { @@ -417,7 +420,7 @@ impl<'p, 'tcx> PatStack<'p, 'tcx> { fn expand_or_pat<'a>(&'a self) -> impl Iterator> + Captures<'a> { self.head().iter_fields().map(move |pat| { let mut new_patstack = - PatStack::from_pattern(pat, self.parent_row, self.is_under_guard); + PatStack::from_pattern(pat, self.parent_row, self.is_under_guard, self.arm_hir_id); new_patstack.pats.extend_from_slice(&self.pats[1..]); new_patstack }) @@ -439,7 +442,7 @@ impl<'p, 'tcx> PatStack<'p, 'tcx> { // `self.head()`. let mut new_fields: SmallVec<[_; 2]> = self.head().specialize(pcx, ctor); new_fields.extend_from_slice(&self.pats[1..]); - PatStack::from_vec(new_fields, parent_row, self.is_under_guard) + PatStack::from_vec(new_fields, parent_row, self.is_under_guard, self.arm_hir_id) } } @@ -770,9 +773,10 @@ fn compute_usefulness<'p, 'tcx>( debug!("ty: {ty:?}"); let pcx = &PatCtxt { cx, ty }; - let set = ConstructorSet::new(pcx); + let ctor_set = ConstructorSet::new(pcx); let (split_ctors, missing_ctors) = - set.split(pcx, matrix.heads().map(|p| p.ctor()), is_top_level); + ctor_set.split(pcx, matrix.heads().map(|p| p.ctor()), is_top_level); + // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. let mut ret = WitnessMatrix::new_empty(); @@ -795,7 +799,7 @@ fn compute_usefulness<'p, 'tcx>( // Lint on likely incorrect range patterns (#63987) if spec_matrix.rows().len() >= 2 { - if let Constructor::IntRange(overlap_range) = ctor { + if let Constructor::IntRange(overlap_range) = &ctor { // If two ranges overlap on their boundaries, that boundary will be found as a singleton // range after splitting. // We limit to a single column for now, see `lint_overlapping_range_endpoints`. @@ -813,6 +817,40 @@ fn compute_usefulness<'p, 'tcx>( } } + // When all the conditions are met we have a match with a `non_exhaustive` enum + // that has the potential to trigger the `non_exhaustive_omitted_patterns` lint. + if cx.refutable + && matches!(&ctor, Constructor::Missing) + && matches!(&ctor_set, ConstructorSet::Variants { non_exhaustive: true, .. }) + && spec_matrix.rows().len() != 0 + { + let patterns = missing_ctors + .iter() + // We want to list only real variants. + .filter(|c| !(c.is_non_exhaustive() || c.is_wildcard())) + .cloned() + .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)) + .collect::>(); + + if !patterns.is_empty() { + let first_spec_row = spec_matrix.rows().next().unwrap(); + let first_wildcard_row = &matrix.rows[first_spec_row.parent_row]; + let wildcard_span = first_wildcard_row.head().span(); + // Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns` + // is not exhaustive enough. + // NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`. + cx.tcx.emit_spanned_lint( + NON_EXHAUSTIVE_OMITTED_PATTERNS, + first_wildcard_row.arm_hir_id, + wildcard_span, + NonExhaustiveOmittedPattern { + scrut_ty: pcx.ty, + uncovered: Uncovered::new(wildcard_span, pcx.cx, patterns), + }, + ); + } + } + for child_row in spec_matrix.rows() { let parent_row = &mut matrix.rows[child_row.parent_row]; parent_row.is_useful = parent_row.is_useful || child_row.is_useful; @@ -828,92 +866,6 @@ fn compute_usefulness<'p, 'tcx>( ret } -/// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned -/// in a given column. This traverses patterns column-by-column, where a column is the intuitive -/// notion of "subpatterns that inspect the same subvalue". -/// Despite similarities with `is_useful`, this traversal is different. Notably this is linear in the -/// depth of patterns, whereas `is_useful` is worst-case exponential (exhaustiveness is NP-complete). -fn collect_nonexhaustive_missing_variants<'p, 'tcx>( - cx: &MatchCheckCtxt<'p, 'tcx>, - column: &[&DeconstructedPat<'p, 'tcx>], -) -> Vec> { - let mut witnesses = Vec::new(); - // If the column is all wildcards, we don't want to collect anything and we can't recurse - // further. - let is_all_wildcards = column.iter().all(|p| p.ctor().is_wildcard()); - if column.is_empty() || is_all_wildcards { - return witnesses; - } - - let ty = column[0].ty(); - let pcx = &PatCtxt { cx, ty }; - - let set = ConstructorSet::new(pcx); - let (split_ctors, missing_ctors) = set.split(pcx, column.iter().map(|p| p.ctor()), false); - - if cx.is_foreign_non_exhaustive_enum(ty) { - witnesses.extend( - missing_ctors - .into_iter() - // We want to list only real variants. - .filter(|c| !(c.is_non_exhaustive() || c.is_wildcard())) - .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)), - ) - } - - // Recurse into the fields. - for ctor in split_ctors { - let arity = ctor.arity(pcx); - if arity == 0 { - continue; - } - - // We specialize the column by `ctor`. This gives us `arity`-many columns of patterns. These - // columns may not have the same length in the presence of or-patterns (this is why we can't - // reuse what `Matrix` does). - let mut specialized_columns: Vec> = (0..arity).map(|_| Vec::new()).collect(); - let relevant_patterns = column.iter().filter(|pat| ctor.is_covered_by(pcx, pat.ctor())); - for pat in relevant_patterns { - let specialized = pat.specialize(pcx, &ctor); - for (subpat, sub_column) in specialized.iter().zip(&mut specialized_columns) { - if subpat.is_or_pat() { - sub_column.extend(subpat.iter_fields()) - } else { - sub_column.push(subpat) - } - } - } - assert!( - !specialized_columns[0].is_empty(), - "ctor {ctor:?} was listed as present but isn't" - ); - - for (i, col_i) in specialized_columns.iter().enumerate() { - // Compute witnesses for each column. - let wits_for_col_i = collect_nonexhaustive_missing_variants(cx, col_i.as_slice()); - if wits_for_col_i.is_empty() { - continue; - } - // For each witness, we build a new pattern in the shape of `ctor(_, _, wit, _, _)`, - // adding enough wildcards to match `arity`. - let fields = Fields::wildcards(pcx, &ctor); - for wit in wits_for_col_i { - let fields_with_wit = Fields::from_iter( - cx, - fields - .iter_patterns() - .enumerate() - .map(|(j, wild)| if i == j { &wit } else { wild }) - .cloned(), - ); - let pat = DeconstructedPat::new(ctor.clone(), fields_with_wit, ty, DUMMY_SP); - witnesses.push(pat); - } - } - } - witnesses -} - /// The arm of a match expression. #[derive(Clone, Copy, Debug)] pub(crate) struct MatchArm<'p, 'tcx> { @@ -957,10 +909,10 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( scrut_span: Span, ) -> UsefulnessReport<'p, 'tcx> { let wild_pattern = cx.pattern_arena.alloc(DeconstructedPat::wildcard(scrut_ty, DUMMY_SP)); - let wildcard_row = PatStack::from_pattern(wild_pattern, usize::MAX, false); + let wildcard_row = PatStack::from_pattern(wild_pattern, usize::MAX, false, lint_root); let mut matrix = Matrix::empty(wildcard_row); for (row_id, arm) in arms.iter().enumerate() { - let v = PatStack::from_pattern(arm.pat, row_id, arm.has_guard); + let v = PatStack::from_pattern(arm.pat, row_id, arm.has_guard, arm.hir_id); matrix.push(v); } @@ -980,34 +932,5 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>( }) .collect(); - // Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hittin - // `if let`s. Only run if the match is exhaustive otherwise the error is redundant. - if cx.refutable - && non_exhaustiveness_witnesses.is_empty() - && !matches!( - cx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, lint_root).0, - rustc_session::lint::Level::Allow - ) - { - let pat_column = arms.iter().flat_map(|arm| arm.pat.flatten_or_pat()).collect::>(); - let witnesses = collect_nonexhaustive_missing_variants(cx, &pat_column); - - if !witnesses.is_empty() { - // Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns` - // is not exhaustive enough. - // - // NB: The partner lint for structs lives in `compiler/rustc_hir_analysis/src/check/pat.rs`. - cx.tcx.emit_spanned_lint( - NON_EXHAUSTIVE_OMITTED_PATTERNS, - lint_root, - scrut_span, - NonExhaustiveOmittedPattern { - scrut_ty, - uncovered: Uncovered::new(scrut_span, cx, witnesses), - }, - ); - } - } - UsefulnessReport { arm_usefulness, non_exhaustiveness_witnesses } } diff --git a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs index c14fa88507746..47f4a964d0822 100644 --- a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs +++ b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.rs @@ -3,11 +3,9 @@ #![deny(non_exhaustive_omitted_patterns)] //~^ WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` -//~| WARNING unknown lint: `non_exhaustive_omitted_patterns` #![allow(non_exhaustive_omitted_patterns)] //~^ WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` -//~| WARNING unknown lint: `non_exhaustive_omitted_patterns` fn main() { enum Foo { @@ -21,7 +19,6 @@ fn main() { //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` - //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` match Foo::A { _ => {} } @@ -31,7 +28,6 @@ fn main() { //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` - //~| WARNING unknown lint: `non_exhaustive_omitted_patterns` match Foo::A { _ => {} } diff --git a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr index b17e9a1a25d6e..07c05110c712c 100644 --- a/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr +++ b/tests/ui/feature-gates/feature-gate-non_exhaustive_omitted_patterns_lint.stderr @@ -10,7 +10,7 @@ LL | #![deny(non_exhaustive_omitted_patterns)] = note: `#[warn(unknown_lints)]` on by default warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:7:1 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:6:1 | LL | #![allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,7 +20,7 @@ LL | #![allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:17:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:17:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:26:5 | LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -50,7 +50,7 @@ LL | #[warn(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:26:5 | LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -70,7 +70,7 @@ LL | #![deny(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:7:1 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:6:1 | LL | #![allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -80,7 +80,7 @@ LL | #![allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:17:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -90,7 +90,7 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:17:5 | LL | #[allow(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -100,7 +100,7 @@ LL | #[allow(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:26:5 | LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -110,47 +110,7 @@ LL | #[warn(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 - | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the `non_exhaustive_omitted_patterns` lint is unstable - = note: see issue #89554 for more information - = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable - -warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:3:1 - | -LL | #![deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the `non_exhaustive_omitted_patterns` lint is unstable - = note: see issue #89554 for more information - = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable - -warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:7:1 - | -LL | #![allow(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the `non_exhaustive_omitted_patterns` lint is unstable - = note: see issue #89554 for more information - = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable - -warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:19:5 - | -LL | #[allow(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the `non_exhaustive_omitted_patterns` lint is unstable - = note: see issue #89554 for more information - = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable - -warning: unknown lint: `non_exhaustive_omitted_patterns` - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:29:5 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:26:5 | LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -160,13 +120,13 @@ LL | #[warn(non_exhaustive_omitted_patterns)] = help: add `#![feature(non_exhaustive_omitted_patterns_lint)]` to the crate attributes to enable error[E0004]: non-exhaustive patterns: `Foo::A`, `Foo::B` and `Foo::C` not covered - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:39:11 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:35:11 | LL | match Foo::A {} | ^^^^^^ patterns `Foo::A`, `Foo::B` and `Foo::C` not covered | note: `Foo` defined here - --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:14:9 + --> $DIR/feature-gate-non_exhaustive_omitted_patterns_lint.rs:12:9 | LL | enum Foo { | --- @@ -184,6 +144,6 @@ LL + Foo::A | Foo::B | Foo::C => todo!(), LL + } | -error: aborting due to previous error; 16 warnings emitted +error: aborting due to previous error; 12 warnings emitted For more information about this error, try `rustc --explain E0004`. diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs index 17e1b3363c567..3a4fa24eb4284 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.rs @@ -1,7 +1,6 @@ // Test that the `non_exhaustive_omitted_patterns` lint is triggered correctly. #![feature(non_exhaustive_omitted_patterns_lint, unstable_test_feature)] -#![deny(unreachable_patterns)] // aux-build:enums.rs extern crate enums; @@ -32,17 +31,6 @@ pub enum Bar { C, } -fn no_lint() { - let non_enum = NonExhaustiveEnum::Unit; - // Ok: without the attribute - match non_enum { - NonExhaustiveEnum::Unit => {} - NonExhaustiveEnum::Tuple(_) => {} - _ => {} - } -} - -#[warn(non_exhaustive_omitted_patterns)] fn main() { let enumeration = Bar::A; @@ -50,97 +38,126 @@ fn main() { match enumeration { Bar::A => {} Bar::B => {} + #[deny(non_exhaustive_omitted_patterns)] _ => {} } let non_enum = NonExhaustiveEnum::Unit; + // Ok: without the attribute + match non_enum { + NonExhaustiveEnum::Unit => {} + NonExhaustiveEnum::Tuple(_) => {} + _ => {} + } + match non_enum { - //~^ some variants are not matched explicitly NonExhaustiveEnum::Unit => {} NonExhaustiveEnum::Tuple(_) => {} + #[deny(non_exhaustive_omitted_patterns)] _ => {} } + //~^^ some variants are not matched explicitly match non_enum { - //~^ some variants are not matched explicitly NonExhaustiveEnum::Unit | NonExhaustiveEnum::Struct { .. } => {} + #[deny(non_exhaustive_omitted_patterns)] _ => {} } + //~^^ some variants are not matched explicitly + // Ok: guards don't change anything let x = 5; match non_enum { NonExhaustiveEnum::Unit if x > 10 => {} NonExhaustiveEnum::Tuple(_) => {} NonExhaustiveEnum::Struct { .. } => {} + #[deny(non_exhaustive_omitted_patterns)] _ => {} } // Ok: all covered and not `unreachable-patterns` + #[deny(unreachable_patterns)] match non_enum { NonExhaustiveEnum::Unit => {} NonExhaustiveEnum::Tuple(_) => {} NonExhaustiveEnum::Struct { .. } => {} + #[deny(non_exhaustive_omitted_patterns)] _ => {} } + #[deny(non_exhaustive_omitted_patterns)] match NestedNonExhaustive::B { - //~^ some variants are not matched explicitly NestedNonExhaustive::A(NonExhaustiveEnum::Unit) => {} NestedNonExhaustive::A(_) => {} NestedNonExhaustive::B => {} _ => {} } + //~^^ some variants are not matched explicitly + //~^^^^^ some variants are not matched explicitly + #[warn(non_exhaustive_omitted_patterns)] match VariantNonExhaustive::Baz(1, 2) { VariantNonExhaustive::Baz(_, _) => {} VariantNonExhaustive::Bar { x, .. } => {} } //~^^ some fields are not explicitly listed + #[warn(non_exhaustive_omitted_patterns)] let FunctionalRecord { first_field, second_field, .. } = FunctionalRecord::default(); //~^ some fields are not explicitly listed // Ok: this is local + #[warn(non_exhaustive_omitted_patterns)] let Foo { a, b, .. } = Foo::default(); + #[warn(non_exhaustive_omitted_patterns)] let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = NestedStruct::default(); //~^ some fields are not explicitly listed //~^^ some fields are not explicitly listed // Ok: this tests https://github.com/rust-lang/rust/issues/89382 + #[warn(non_exhaustive_omitted_patterns)] let MixedVisFields { a, b, .. } = MixedVisFields::default(); // Ok: because this only has 1 variant + #[deny(non_exhaustive_omitted_patterns)] match NonExhaustiveSingleVariant::A(true) { NonExhaustiveSingleVariant::A(true) => {} _ => {} } - // Don't lint if no variant is mentioned, because we can't do it consistently. + // No variants are mentioned + #[deny(non_exhaustive_omitted_patterns)] match NonExhaustiveSingleVariant::A(true) { _ => {} } + //~^^ some variants are not matched explicitly + #[deny(non_exhaustive_omitted_patterns)] match Some(NonExhaustiveSingleVariant::A(true)) { Some(_) => {} None => {} } + #[deny(non_exhaustive_omitted_patterns)] match Some(&NonExhaustiveSingleVariant::A(true)) { Some(_) => {} None => {} } // Ok: we don't lint on `if let` expressions + #[deny(non_exhaustive_omitted_patterns)] if let NonExhaustiveEnum::Tuple(_) = non_enum {} match UnstableEnum::Stable { - //~^ some variants are not matched explicitly UnstableEnum::Stable => {} UnstableEnum::Stable2 => {} + #[deny(non_exhaustive_omitted_patterns)] _ => {} } + //~^^ some variants are not matched explicitly // Ok: the feature is on and all variants are matched + #[deny(non_exhaustive_omitted_patterns)] match UnstableEnum::Stable { UnstableEnum::Stable => {} UnstableEnum::Stable2 => {} @@ -149,66 +166,74 @@ fn main() { } // Ok: the feature is on and both variants are matched + #[deny(non_exhaustive_omitted_patterns)] match OnlyUnstableEnum::Unstable { OnlyUnstableEnum::Unstable => {} OnlyUnstableEnum::Unstable2 => {} _ => {} } + #[deny(non_exhaustive_omitted_patterns)] match OnlyUnstableEnum::Unstable { - //~^ some variants are not matched explicitly OnlyUnstableEnum::Unstable => {} _ => {} } + //~^^ some variants are not matched explicitly + #[warn(non_exhaustive_omitted_patterns)] let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); //~^ some fields are not explicitly listed // OK: both unstable fields are matched with feature on + #[warn(non_exhaustive_omitted_patterns)] let OnlyUnstableStruct { unstable, unstable2, .. } = OnlyUnstableStruct::new(); + #[warn(non_exhaustive_omitted_patterns)] let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); //~^ some fields are not explicitly listed // OK: both unstable and stable fields are matched with feature on + #[warn(non_exhaustive_omitted_patterns)] let UnstableStruct { stable, stable2, unstable, .. } = UnstableStruct::default(); // Ok: local bindings are allowed + #[deny(non_exhaustive_omitted_patterns)] let local = NonExhaustiveEnum::Unit; // Ok: missing patterns will be blocked by the pattern being refutable + #[deny(non_exhaustive_omitted_patterns)] let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit; //~^ refutable pattern in local binding - // Check that matching on a reference results in a correct diagnostic + // Check that matching on a reference results in a correctly spanned diagnostic + #[deny(non_exhaustive_omitted_patterns)] match &non_enum { - //~^ some variants are not matched explicitly - //~| pattern `&NonExhaustiveEnum::Struct { .. }` not covered NonExhaustiveEnum::Unit => {} NonExhaustiveEnum::Tuple(_) => {} _ => {} } + //~^^ some variants are not matched explicitly + #[deny(non_exhaustive_omitted_patterns)] match (true, &non_enum) { - //~^ some variants are not matched explicitly - //~| patterns `(_, &NonExhaustiveEnum::Tuple(_))` and `(_, &NonExhaustiveEnum::Struct { .. })` not covered (true, NonExhaustiveEnum::Unit) => {} _ => {} } + //~^^ some variants are not matched explicitly + #[deny(non_exhaustive_omitted_patterns)] match (&non_enum, true) { - //~^ some variants are not matched explicitly - //~| patterns `(&NonExhaustiveEnum::Tuple(_), _)` and `(&NonExhaustiveEnum::Struct { .. }, _)` not covered (NonExhaustiveEnum::Unit, true) => {} _ => {} } + //~^^ some variants are not matched explicitly + #[deny(non_exhaustive_omitted_patterns)] match Some(&non_enum) { - //~^ some variants are not matched explicitly - //~| pattern `Some(&NonExhaustiveEnum::Struct { .. })` not covered Some(NonExhaustiveEnum::Unit | NonExhaustiveEnum::Tuple(_)) => {} _ => {} } + //~^^ some variants are not matched explicitly } #[deny(non_exhaustive_omitted_patterns)] diff --git a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr index 3bdad4a360620..580aa2fea73a8 100644 --- a/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr +++ b/tests/ui/rfc-2008-non-exhaustive/omitted-patterns.stderr @@ -1,5 +1,5 @@ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:97:9 + --> $DIR/omitted-patterns.rs:102:9 | LL | VariantNonExhaustive::Bar { x, .. } => {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `y` not listed @@ -7,31 +7,41 @@ LL | VariantNonExhaustive::Bar { x, .. } => {} = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `VariantNonExhaustive` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/omitted-patterns.rs:45:8 + --> $DIR/omitted-patterns.rs:99:12 | -LL | #[warn(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:101:9 + --> $DIR/omitted-patterns.rs:107:9 | LL | let FunctionalRecord { first_field, second_field, .. } = FunctionalRecord::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `third_field` not listed | = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `FunctionalRecord` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:106:12 + | +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:107:29 + --> $DIR/omitted-patterns.rs:115:29 | LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = NestedStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `second_field` not listed | = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `NormalStruct` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:114:12 + | +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:107:9 + --> $DIR/omitted-patterns.rs:115:9 | LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = NestedStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `foo` not listed @@ -40,70 +50,128 @@ LL | let NestedStruct { bar: NormalStruct { first_field, .. }, .. } = Nested = note: the pattern is of type `NestedStruct` and the `non_exhaustive_omitted_patterns` attribute was found warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:164:9 + --> $DIR/omitted-patterns.rs:184:9 | LL | let OnlyUnstableStruct { unstable, .. } = OnlyUnstableStruct::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable2` not listed | = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `OnlyUnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:183:12 + | +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ warning: some fields are not explicitly listed - --> $DIR/omitted-patterns.rs:170:9 + --> $DIR/omitted-patterns.rs:192:9 | LL | let UnstableStruct { stable, stable2, .. } = UnstableStruct::default(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `unstable` not listed | = help: ensure that all fields are mentioned explicitly by adding the suggested fields = note: the pattern is of type `UnstableStruct` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:191:12 + | +LL | #[warn(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:58:9 + | +LL | _ => {} + | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered + | + = help: ensure that all variants are matched explicitly by adding the suggested match arms + = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:57:16 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:58:11 +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:65:9 | -LL | match non_enum { - | ^^^^^^^^ pattern `NonExhaustiveEnum::Struct { .. }` not covered +LL | _ => {} + | ^ pattern `NonExhaustiveEnum::Tuple(_)` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:64:16 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:65:11 +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:92:32 | -LL | match non_enum { - | ^^^^^^^^ pattern `NonExhaustiveEnum::Tuple(_)` not covered +LL | NestedNonExhaustive::A(_) => {} + | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:89:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:87:11 +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:94:9 | -LL | match NestedNonExhaustive::B { - | ^^^^^^^^^^^^^^^^^^^^^^ patterns `NestedNonExhaustive::C`, `NestedNonExhaustive::A(NonExhaustiveEnum::Tuple(_))` and `NestedNonExhaustive::A(NonExhaustiveEnum::Struct { .. })` not covered +LL | _ => {} + | ^ pattern `NestedNonExhaustive::C` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `NestedNonExhaustive` and the `non_exhaustive_omitted_patterns` attribute was found -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:136:11 +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:133:9 | -LL | match UnstableEnum::Stable { - | ^^^^^^^^^^^^^^^^^^^^ pattern `UnstableEnum::Unstable` not covered +LL | _ => {} + | ^ pattern `NonExhaustiveSingleVariant::A(_)` not covered + | + = help: ensure that all variants are matched explicitly by adding the suggested match arms + = note: the matched value is of type `NonExhaustiveSingleVariant` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:131:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:155:9 + | +LL | _ => {} + | ^ pattern `UnstableEnum::Unstable` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `UnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:154:16 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:158:11 +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:179:9 | -LL | match OnlyUnstableEnum::Unstable { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ pattern `OnlyUnstableEnum::Unstable2` not covered +LL | _ => {} + | ^ pattern `OnlyUnstableEnum::Unstable2` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `OnlyUnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:176:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0005]: refutable pattern in local binding - --> $DIR/omitted-patterns.rs:180:9 + --> $DIR/omitted-patterns.rs:205:9 | LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit; | ^^^^^^^^^^^^^^^ pattern `_` not covered @@ -116,42 +184,62 @@ help: you might want to use `let else` to handle the variant that isn't matched LL | let local_refutable @ NonExhaustiveEnum::Unit = NonExhaustiveEnum::Unit else { todo!() }; | ++++++++++++++++ -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:184:11 +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:213:9 | -LL | match &non_enum { - | ^^^^^^^^^ pattern `&NonExhaustiveEnum::Struct { .. }` not covered +LL | _ => {} + | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `&NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found + = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:209:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:192:11 +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:220:9 | -LL | match (true, &non_enum) { - | ^^^^^^^^^^^^^^^^^ patterns `(_, &NonExhaustiveEnum::Tuple(_))` and `(_, &NonExhaustiveEnum::Struct { .. })` not covered +LL | _ => {} + | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `(bool, &NonExhaustiveEnum)` and the `non_exhaustive_omitted_patterns` attribute was found + = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:217:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:199:11 +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:227:9 | -LL | match (&non_enum, true) { - | ^^^^^^^^^^^^^^^^^ patterns `(&NonExhaustiveEnum::Tuple(_), _)` and `(&NonExhaustiveEnum::Struct { .. }, _)` not covered +LL | _ => {} + | ^ patterns `NonExhaustiveEnum::Tuple(_)` and `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `(&NonExhaustiveEnum, bool)` and the `non_exhaustive_omitted_patterns` attribute was found + = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:224:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -warning: some variants are not matched explicitly - --> $DIR/omitted-patterns.rs:206:11 +error: some variants are not matched explicitly + --> $DIR/omitted-patterns.rs:234:9 | -LL | match Some(&non_enum) { - | ^^^^^^^^^^^^^^^ pattern `Some(&NonExhaustiveEnum::Struct { .. })` not covered +LL | _ => {} + | ^ pattern `NonExhaustiveEnum::Struct { .. }` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms - = note: the matched value is of type `Option<&NonExhaustiveEnum>` and the `non_exhaustive_omitted_patterns` attribute was found + = note: the matched value is of type `NonExhaustiveEnum` and the `non_exhaustive_omitted_patterns` attribute was found +note: the lint level is defined here + --> $DIR/omitted-patterns.rs:231:12 + | +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error; 15 warnings emitted +error: aborting due to 12 previous errors; 6 warnings emitted For more information about this error, try `rustc --explain E0005`. diff --git a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs index 1828fdef90139..82ee68687ed00 100644 --- a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs +++ b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.rs @@ -6,23 +6,23 @@ // aux-build:unstable.rs extern crate unstable; -use unstable::{OnlyUnstableEnum, OnlyUnstableStruct, UnstableEnum, UnstableStruct}; +use unstable::{UnstableEnum, OnlyUnstableEnum, UnstableStruct, OnlyUnstableStruct}; fn main() { // OK: this matches all the stable variants - #[deny(non_exhaustive_omitted_patterns)] match UnstableEnum::Stable { UnstableEnum::Stable => {} UnstableEnum::Stable2 => {} + #[deny(non_exhaustive_omitted_patterns)] _ => {} } - #[deny(non_exhaustive_omitted_patterns)] match UnstableEnum::Stable { - //~^ some variants are not matched explicitly UnstableEnum::Stable => {} + #[deny(non_exhaustive_omitted_patterns)] _ => {} } + //~^^ some variants are not matched explicitly // Ok: although this is a bit odd, we don't have anything to report // since there is no stable variants and the feature is off diff --git a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr index 27939176f75ed..f38368590fb1c 100644 --- a/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr +++ b/tests/ui/rfc-2008-non-exhaustive/stable-omitted-patterns.stderr @@ -13,18 +13,18 @@ LL | #[warn(non_exhaustive_omitted_patterns)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: some variants are not matched explicitly - --> $DIR/stable-omitted-patterns.rs:21:11 + --> $DIR/stable-omitted-patterns.rs:23:9 | -LL | match UnstableEnum::Stable { - | ^^^^^^^^^^^^^^^^^^^^ pattern `UnstableEnum::Stable2` not covered +LL | _ => {} + | ^ pattern `UnstableEnum::Stable2` not covered | = help: ensure that all variants are matched explicitly by adding the suggested match arms = note: the matched value is of type `UnstableEnum` and the `non_exhaustive_omitted_patterns` attribute was found note: the lint level is defined here - --> $DIR/stable-omitted-patterns.rs:20:12 + --> $DIR/stable-omitted-patterns.rs:22:16 | -LL | #[deny(non_exhaustive_omitted_patterns)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[deny(non_exhaustive_omitted_patterns)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to previous error; 1 warning emitted From 16095a88f7b26dde42dfcd102b30c5e6baefa683 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 23 May 2023 16:55:55 +0200 Subject: [PATCH 40/40] Check overlapping_range lint level per arm --- .../src/thir/pattern/deconstruct_pat.rs | 13 ++++++------ .../src/thir/pattern/usefulness.rs | 14 ++++++++----- .../overlapping_range_endpoints.rs | 9 ++++++++ .../overlapping_range_endpoints.stderr | 21 ++++++++++++++----- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 8f0626ae29873..0cd3ee1d0bfd9 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -223,9 +223,8 @@ impl IntRange { pub(super) fn lint_overlapping_range_endpoints<'a, 'p: 'a, 'tcx: 'a>( &self, pcx: &PatCtxt<'_, 'p, 'tcx>, - pats: impl Iterator, bool)>, + pats: impl Iterator, bool, HirId)>, column_count: usize, - lint_root: HirId, ) { // FIXME: for now, only check for overlapping ranges on non-nested range patterns. Otherwise // with the current logic the following is detected as overlapping: @@ -245,9 +244,11 @@ impl IntRange { let mut prefixes: SmallVec<[_; 1]> = Default::default(); let mut suffixes: SmallVec<[_; 1]> = Default::default(); // Iterate on rows that contained `overlap`. - for (range, this_span, is_under_guard) in pats.filter_map(|(pat, under_guard)| { - Some((pat.ctor().as_int_range()?, pat.span(), under_guard)) - }) { + for (range, this_span, is_under_guard, arm_hir_id) in + pats.filter_map(|(pat, under_guard, arm_hir_id)| { + Some((pat.ctor().as_int_range()?, pat.span(), under_guard, arm_hir_id)) + }) + { if range.is_singleton() { continue; } @@ -276,7 +277,7 @@ impl IntRange { .collect(); pcx.cx.tcx.emit_spanned_lint( lint::builtin::OVERLAPPING_RANGE_ENDPOINTS, - lint_root, + arm_hir_id, this_span, OverlappingRangeEndpoints { overlap: overlaps, range: this_span }, ); diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 3cb4ea0861738..6d46f22d77d75 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -806,12 +806,16 @@ fn compute_usefulness<'p, 'tcx>( if overlap_range.is_singleton() && orig_column_count == 1 { overlap_range.lint_overlapping_range_endpoints( pcx, - spec_matrix - .rows() - .map(|child_row| &matrix.rows[child_row.parent_row]) - .map(|parent_row| (parent_row.head(), parent_row.is_under_guard)), + spec_matrix.rows().map(|child_row| &matrix.rows[child_row.parent_row]).map( + |parent_row| { + ( + parent_row.head(), + parent_row.is_under_guard, + parent_row.arm_hir_id, + ) + }, + ), orig_column_count, - lint_root, ); } } diff --git a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs index 74e293b4ffdad..e5855edce735d 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.rs @@ -42,6 +42,15 @@ fn main() { //~| ERROR multiple patterns overlap on their endpoints _ => {} } + // Selectively allow lint + match 0u8 { + 0..=10 => {} + #[allow(overlapping_range_endpoints)] + 10..=20 => {} + 10..=20 => {} + //~^ ERROR multiple patterns overlap on their endpoints + _ => {} + } match 0u8 { 0..=10 => {} 10..=20 if true => {} //~ ERROR multiple patterns overlap on their endpoints diff --git a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr index be52abc4147fb..9a61c849d0af8 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/overlapping_range_endpoints.stderr @@ -77,7 +77,18 @@ LL | 10..=20 => {} = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:47:9 + --> $DIR/overlapping_range_endpoints.rs:50:9 + | +LL | 0..=10 => {} + | ------ this range overlaps on `10_u8`... +... +LL | 10..=20 => {} + | ^^^^^^^ ... with this range + | + = note: you likely meant to write mutually exclusive ranges + +error: multiple patterns overlap on their endpoints + --> $DIR/overlapping_range_endpoints.rs:56:9 | LL | 0..=10 => {} | ------ this range overlaps on `10_u8`... @@ -87,7 +98,7 @@ LL | 10..=20 if true => {} = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:63:16 + --> $DIR/overlapping_range_endpoints.rs:72:16 | LL | (true, 0..=10) => {} | ------ this range overlaps on `10_u8`... @@ -97,7 +108,7 @@ LL | (true, 10..20) => {} = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:71:13 + --> $DIR/overlapping_range_endpoints.rs:80:13 | LL | (true, 0..=10) => {} | ------ this range overlaps on `10_u8`... @@ -108,7 +119,7 @@ LL | (_, 10..20) => {} = note: you likely meant to write mutually exclusive ranges error: multiple patterns overlap on their endpoints - --> $DIR/overlapping_range_endpoints.rs:76:14 + --> $DIR/overlapping_range_endpoints.rs:85:14 | LL | Some(0..=10) => {} | ------ this range overlaps on `10_u8`... @@ -117,5 +128,5 @@ LL | Some(10..20) => {} | = note: you likely meant to write mutually exclusive ranges -error: aborting due to 11 previous errors +error: aborting due to 12 previous errors