From 8536979cdb67143bc09398150cf7d2f023992488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Thu, 15 Jan 2026 16:50:20 +0100 Subject: [PATCH 1/8] Move some match edge case tests While these test cases were inspired by issue 137467, they aren't directly related, and better fit into match-edge-cases_2.rs --- .../match/match-edge-cases_2.rs | 78 +++++++++++++++++++ tests/ui/closures/or-patterns-issue-137467.rs | 64 --------------- 2 files changed, 78 insertions(+), 64 deletions(-) diff --git a/tests/ui/closures/2229_closure_analysis/match/match-edge-cases_2.rs b/tests/ui/closures/2229_closure_analysis/match/match-edge-cases_2.rs index a3b19708899a0..81013e5b2cd16 100644 --- a/tests/ui/closures/2229_closure_analysis/match/match-edge-cases_2.rs +++ b/tests/ui/closures/2229_closure_analysis/match/match-edge-cases_2.rs @@ -34,4 +34,82 @@ fn edge_case_if() { _b(); } +struct Unit; + +enum TSingle { + A(u32, u32), +} + +enum SSingle { + A { a: u32, b: u32 }, +} + +struct TStruct(u32, u32); +struct SStruct { a: u32, b: u32 } + + +// Destructuring a unit struct should not capture it +fn match_unit_struct(mut x: (Unit, u32)) { + let r = &mut x.0; + let _ = || { + let (Unit, a) = x; + a + }; + + let _ = *r; +} + +// The same is true for an equivalent enum +fn match_unit_enum(mut x: (SingleVariant, u32)) { + let r = &mut x.0; + let _ = || { + let (SingleVariant::A, a) = x; + a + }; + + let _ = *r; +} + +// More generally, destructuring a struct should only capture the fields being touched +fn match_struct(mut x: SStruct) { + let r = &mut x.a; + let _ = || { + let SStruct { b, .. } = x; + b + }; + + let _ = *r; +} + +fn match_tuple_struct(mut x: TStruct) { + let r = &mut x.0; + let _ = || { + let TStruct(_, a) = x; + a + }; + + let _ = *r; +} + +// The same is true for an equivalent enum as well +fn match_singleton(mut x: SSingle) { + let SSingle::A { a: ref mut r, .. } = x; + let _ = || { + let SSingle::A { b, .. } = x; + b + }; + + let _ = *r; +} + +fn match_tuple_singleton(mut x: TSingle) { + let TSingle::A(ref mut r, _) = x; + let _ = || { + let TSingle::A(_, a) = x; + a + }; + + let _ = *r; +} + fn main() {} diff --git a/tests/ui/closures/or-patterns-issue-137467.rs b/tests/ui/closures/or-patterns-issue-137467.rs index 5a1e84e1c9a0d..de2a4beeaf9d1 100644 --- a/tests/ui/closures/or-patterns-issue-137467.rs +++ b/tests/ui/closures/or-patterns-issue-137467.rs @@ -40,30 +40,6 @@ fn match_unit_variant(x: (Choice, u32, u32)) { }; } -struct Unit; - -fn match_unit_struct(mut x: (Unit, u32)) { - let r = &mut x.0; - let _ = || { - let (Unit, a) = x; - a - }; - - let _ = *r; -} - -enum Also { Unit } - -fn match_unit_enum(mut x: (Also, u32)) { - let r = &mut x.0; - let _ = || { - let (Also::Unit, a) = x; - a - }; - - let _ = *r; -} - enum TEnum { A(u32), B(u32), @@ -99,46 +75,6 @@ enum SSingle { struct TStruct(u32, u32); struct SStruct { a: u32, b: u32 } -fn match_struct(mut x: SStruct) { - let r = &mut x.a; - let _ = || { - let SStruct { b, .. } = x; - b - }; - - let _ = *r; -} - -fn match_tuple_struct(mut x: TStruct) { - let r = &mut x.0; - let _ = || { - let TStruct(_, a) = x; - a - }; - - let _ = *r; -} - -fn match_singleton(mut x: SSingle) { - let SSingle::A { a: ref mut r, .. } = x; - let _ = || { - let SSingle::A { b, .. } = x; - b - }; - - let _ = *r; -} - -fn match_tuple_singleton(mut x: TSingle) { - let TSingle::A(ref mut r, _) = x; - let _ = || { - let TSingle::A(_, a) = x; - a - }; - - let _ = *r; -} - fn match_slice(x: (&[u32], u32, u32)) { let _ = || { let (([], a, _) | ([_, ..], _, a)) = x; From 4e090078b46833b2b71179b7e4382ddbddafc4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Thu, 15 Jan 2026 17:24:27 +0100 Subject: [PATCH 2/8] capture-enums.rs: get rid of feature gate noise --- .../2229_closure_analysis/capture-enums.rs | 7 +-- .../capture-enums.stderr | 51 ++++++------------- 2 files changed, 16 insertions(+), 42 deletions(-) diff --git a/tests/ui/closures/2229_closure_analysis/capture-enums.rs b/tests/ui/closures/2229_closure_analysis/capture-enums.rs index 4c600ccdaa438..36b98351854bf 100644 --- a/tests/ui/closures/2229_closure_analysis/capture-enums.rs +++ b/tests/ui/closures/2229_closure_analysis/capture-enums.rs @@ -1,6 +1,7 @@ //@ edition:2021 #![feature(rustc_attrs)] +#![feature(stmt_expr_attributes)] enum Info { Point(i32, i32, String), @@ -14,9 +15,6 @@ fn multi_variant_enum() { let meta = Info::Meta("meta".into(), vec); let c = #[rustc_capture_analysis] - //~^ ERROR: attributes on expressions are experimental - //~| NOTE: see issue #15701 - //~| NOTE: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date || { //~^ ERROR First Pass analysis includes: //~| ERROR Min Capture analysis includes: @@ -48,9 +46,6 @@ fn single_variant_enum() { let point = SingleVariant::Point(10, -10, "1".into()); let c = #[rustc_capture_analysis] - //~^ ERROR: attributes on expressions are experimental - //~| NOTE: see issue #15701 - //~| NOTE: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date || { //~^ ERROR First Pass analysis includes: //~| ERROR Min Capture analysis includes: diff --git a/tests/ui/closures/2229_closure_analysis/capture-enums.stderr b/tests/ui/closures/2229_closure_analysis/capture-enums.stderr index b62384ffe12e0..2f49c8668f85c 100644 --- a/tests/ui/closures/2229_closure_analysis/capture-enums.stderr +++ b/tests/ui/closures/2229_closure_analysis/capture-enums.stderr @@ -1,25 +1,5 @@ -error[E0658]: attributes on expressions are experimental - --> $DIR/capture-enums.rs:16:13 - | -LL | let c = #[rustc_capture_analysis] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #15701 for more information - = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error[E0658]: attributes on expressions are experimental - --> $DIR/capture-enums.rs:50:13 - | -LL | let c = #[rustc_capture_analysis] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: see issue #15701 for more information - = help: add `#![feature(stmt_expr_attributes)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - error: First Pass analysis includes: - --> $DIR/capture-enums.rs:20:5 + --> $DIR/capture-enums.rs:18:5 | LL | / || { LL | | @@ -30,38 +10,38 @@ LL | | }; | |_____^ | note: Capturing point[] -> Immutable - --> $DIR/capture-enums.rs:23:41 + --> $DIR/capture-enums.rs:21:41 | LL | if let Info::Point(_, _, str) = point { | ^^^^^ note: Capturing point[] -> Immutable - --> $DIR/capture-enums.rs:23:41 + --> $DIR/capture-enums.rs:21:41 | LL | if let Info::Point(_, _, str) = point { | ^^^^^ note: Capturing point[(2, 0)] -> ByValue - --> $DIR/capture-enums.rs:23:41 + --> $DIR/capture-enums.rs:21:41 | LL | if let Info::Point(_, _, str) = point { | ^^^^^ note: Capturing meta[] -> Immutable - --> $DIR/capture-enums.rs:31:35 + --> $DIR/capture-enums.rs:29:35 | LL | if let Info::Meta(_, v) = meta { | ^^^^ note: Capturing meta[] -> Immutable - --> $DIR/capture-enums.rs:31:35 + --> $DIR/capture-enums.rs:29:35 | LL | if let Info::Meta(_, v) = meta { | ^^^^ note: Capturing meta[(1, 1)] -> ByValue - --> $DIR/capture-enums.rs:31:35 + --> $DIR/capture-enums.rs:29:35 | LL | if let Info::Meta(_, v) = meta { | ^^^^ error: Min Capture analysis includes: - --> $DIR/capture-enums.rs:20:5 + --> $DIR/capture-enums.rs:18:5 | LL | / || { LL | | @@ -72,18 +52,18 @@ LL | | }; | |_____^ | note: Min Capture point[] -> ByValue - --> $DIR/capture-enums.rs:23:41 + --> $DIR/capture-enums.rs:21:41 | LL | if let Info::Point(_, _, str) = point { | ^^^^^ note: Min Capture meta[] -> ByValue - --> $DIR/capture-enums.rs:31:35 + --> $DIR/capture-enums.rs:29:35 | LL | if let Info::Meta(_, v) = meta { | ^^^^ error: First Pass analysis includes: - --> $DIR/capture-enums.rs:54:5 + --> $DIR/capture-enums.rs:49:5 | LL | / || { LL | | @@ -95,13 +75,13 @@ LL | | }; | |_____^ | note: Capturing point[(2, 0)] -> ByValue - --> $DIR/capture-enums.rs:57:47 + --> $DIR/capture-enums.rs:52:47 | LL | let SingleVariant::Point(_, _, str) = point; | ^^^^^ error: Min Capture analysis includes: - --> $DIR/capture-enums.rs:54:5 + --> $DIR/capture-enums.rs:49:5 | LL | / || { LL | | @@ -113,11 +93,10 @@ LL | | }; | |_____^ | note: Min Capture point[(2, 0)] -> ByValue - --> $DIR/capture-enums.rs:57:47 + --> $DIR/capture-enums.rs:52:47 | LL | let SingleVariant::Point(_, _, str) = point; | ^^^^^ -error: aborting due to 6 previous errors +error: aborting due to 4 previous errors -For more information about this error, try `rustc --explain E0658`. From 940a48966fd1c4cf8809abcac8d501cf47e4576c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Thu, 15 Jan 2026 17:30:51 +0100 Subject: [PATCH 3/8] non-exhaustive-match.rs: actually test what the comments say --- .../2229_closure_analysis/match/non-exhaustive-match.rs | 2 ++ .../match/non-exhaustive-match.stderr | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs index 3225558271812..5b7259c6c2cc6 100644 --- a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs +++ b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs @@ -10,6 +10,8 @@ // Ignore non_exhaustive in the same crate #[non_exhaustive] enum L1 { A, B } + +#[non_exhaustive] enum L2 { C } extern crate match_non_exhaustive_lib; diff --git a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr index 85426dd9a5ea6..99d33b05429e8 100644 --- a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr +++ b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr @@ -1,5 +1,5 @@ error[E0004]: non-exhaustive patterns: `L1::B` not covered - --> $DIR/non-exhaustive-match.rs:26:25 + --> $DIR/non-exhaustive-match.rs:28:25 | LL | let _b = || { match l1 { L1::A => () } }; | ^^ pattern `L1::B` not covered @@ -16,7 +16,7 @@ LL | let _b = || { match l1 { L1::A => (), L1::B => todo!() } }; | ++++++++++++++++++ error[E0004]: non-exhaustive patterns: type `E1` is non-empty - --> $DIR/non-exhaustive-match.rs:37:25 + --> $DIR/non-exhaustive-match.rs:39:25 | LL | let _d = || { match e1 {} }; | ^^ @@ -35,7 +35,7 @@ LL ~ } }; | error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/non-exhaustive-match.rs:39:25 + --> $DIR/non-exhaustive-match.rs:41:25 | LL | let _e = || { match e2 { E2::A => (), E2::B => () } }; | ^^ pattern `_` not covered @@ -53,7 +53,7 @@ LL | let _e = || { match e2 { E2::A => (), E2::B => (), _ => todo!() } }; | ++++++++++++++ error[E0505]: cannot move out of `e3` because it is borrowed - --> $DIR/non-exhaustive-match.rs:46:22 + --> $DIR/non-exhaustive-match.rs:48:22 | LL | let _g = || { match e3 { E3::C => (), _ => () } }; | -- -- borrow occurs due to use in closure From a28b279357283aacb9cfb0ecb8321a798738df46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Wed, 14 Jan 2026 23:47:33 +0100 Subject: [PATCH 4/8] Make some mir-opt tests more resilient --- tests/mir-opt/unreachable.rs | 7 ++++--- tests/mir-opt/unreachable_enum_branching.rs | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/mir-opt/unreachable.rs b/tests/mir-opt/unreachable.rs index f7f4815ae7ce1..afab1291fc3df 100644 --- a/tests/mir-opt/unreachable.rs +++ b/tests/mir-opt/unreachable.rs @@ -47,10 +47,11 @@ fn as_match() { // CHECK: bb1: { // CHECK: [[eq:_.*]] = Ne({{.*}}, const 1_isize); // CHECK-NEXT: assume(move [[eq]]); - // CHECK-NEXT: goto -> bb2; - // CHECK: bb2: { + // CHECK-NEXT: goto -> [[return:bb.*]]; + // CHECK: [[return]]: { + // CHECK-NOT: {{bb.*}}: { // CHECK: return; - // CHECK: bb3: { + // CHECK: {{bb.*}}: { // CHECK-NEXT: unreachable; match empty() { Some(_x) => match _x {}, diff --git a/tests/mir-opt/unreachable_enum_branching.rs b/tests/mir-opt/unreachable_enum_branching.rs index 7647f9bf0779a..0f6656f4168ec 100644 --- a/tests/mir-opt/unreachable_enum_branching.rs +++ b/tests/mir-opt/unreachable_enum_branching.rs @@ -49,7 +49,7 @@ struct Plop { fn simple() { // CHECK-LABEL: fn simple( // CHECK: [[discr:_.*]] = discriminant( - // CHECK: switchInt(move [[discr]]) -> [0: [[unreachable:bb.*]], 1: [[unreachable]], 2: bb1, otherwise: [[unreachable]]]; + // CHECK: switchInt(move [[discr]]) -> [0: [[unreachable:bb.*]], 1: [[unreachable]], 2: {{bb.*}}, otherwise: [[unreachable]]]; // CHECK: [[unreachable]]: { // CHECK-NEXT: unreachable; match Test1::C { @@ -63,7 +63,7 @@ fn simple() { fn custom_discriminant() { // CHECK-LABEL: fn custom_discriminant( // CHECK: [[discr:_.*]] = discriminant( - // CHECK: switchInt(move [[discr]]) -> [4: bb3, 5: bb2, otherwise: [[unreachable:bb.*]]]; + // CHECK: switchInt(move [[discr]]) -> [4: {{bb.*}}, 5: {{bb.*}}, otherwise: [[unreachable:bb.*]]]; // CHECK: [[unreachable]]: { // CHECK-NEXT: unreachable; match Test2::D { @@ -76,7 +76,7 @@ fn custom_discriminant() { fn otherwise_t1() { // CHECK-LABEL: fn otherwise_t1( // CHECK: [[discr:_.*]] = discriminant( - // CHECK: switchInt(move [[discr]]) -> [0: bb5, 1: bb5, 2: bb1, otherwise: [[unreachable:bb.*]]]; + // CHECK: switchInt(move [[discr]]) -> [0: {{bb.*}}, 1: {{bb.*}}, 2: {{bb.*}}, otherwise: [[unreachable:bb.*]]]; // CHECK: [[unreachable]]: { // CHECK-NEXT: unreachable; match Test1::C { @@ -90,7 +90,7 @@ fn otherwise_t1() { fn otherwise_t2() { // CHECK-LABEL: fn otherwise_t2( // CHECK: [[discr:_.*]] = discriminant( - // CHECK: switchInt(move [[discr]]) -> [4: bb2, 5: bb1, otherwise: [[unreachable:bb.*]]]; + // CHECK: switchInt(move [[discr]]) -> [4: {{bb.*}}, 5: {{bb.*}}, otherwise: [[unreachable:bb.*]]]; // CHECK: [[unreachable]]: { // CHECK-NEXT: unreachable; match Test2::D { From 628bacac3312357af71816b0875174062b310a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Sun, 4 Jan 2026 04:22:55 +0100 Subject: [PATCH 5/8] add tests for emergent behavior of partial captures --- .../auxiliary/partial_move_drop_order_lib.rs | 11 ++ .../match/auxiliary/partial_move_lib.rs | 4 + .../match/partial-move-drop-order.rs | 83 ++++++++++++ .../match/partial-move-drop-order.run.stdout | 23 ++++ .../match/partial-move.rs | 118 +++++++++++++++++ .../match/partial-move.stderr | 123 ++++++++++++++++++ 6 files changed, 362 insertions(+) create mode 100644 tests/ui/closures/2229_closure_analysis/match/auxiliary/partial_move_drop_order_lib.rs create mode 100644 tests/ui/closures/2229_closure_analysis/match/auxiliary/partial_move_lib.rs create mode 100644 tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.rs create mode 100644 tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.run.stdout create mode 100644 tests/ui/closures/2229_closure_analysis/match/partial-move.rs create mode 100644 tests/ui/closures/2229_closure_analysis/match/partial-move.stderr diff --git a/tests/ui/closures/2229_closure_analysis/match/auxiliary/partial_move_drop_order_lib.rs b/tests/ui/closures/2229_closure_analysis/match/auxiliary/partial_move_drop_order_lib.rs new file mode 100644 index 0000000000000..8cf25c6ba7b25 --- /dev/null +++ b/tests/ui/closures/2229_closure_analysis/match/auxiliary/partial_move_drop_order_lib.rs @@ -0,0 +1,11 @@ +pub struct LoudDrop(pub &'static str); +impl Drop for LoudDrop { + fn drop(&mut self) { + println!("dropping {}", self.0); + } +} + +#[non_exhaustive] +pub enum ExtNonExhaustive { + One(i32, LoudDrop), +} diff --git a/tests/ui/closures/2229_closure_analysis/match/auxiliary/partial_move_lib.rs b/tests/ui/closures/2229_closure_analysis/match/auxiliary/partial_move_lib.rs new file mode 100644 index 0000000000000..b0646f1d0032c --- /dev/null +++ b/tests/ui/closures/2229_closure_analysis/match/auxiliary/partial_move_lib.rs @@ -0,0 +1,4 @@ +#[non_exhaustive] +pub enum ExtNonExhaustive { + A(u32, String), +} diff --git a/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.rs b/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.rs new file mode 100644 index 0000000000000..1c178bd3f684b --- /dev/null +++ b/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.rs @@ -0,0 +1,83 @@ +//@ edition:2021 +//@ run-pass +//@ check-run-results +//@ aux-build:partial_move_drop_order_lib.rs + +extern crate partial_move_drop_order_lib; +use partial_move_drop_order_lib::{LoudDrop, ExtNonExhaustive}; + +pub enum OneVariant { + One(i32, LoudDrop), +} + +pub enum TwoVariants { + One(i32, LoudDrop), + Two, +} + +#[non_exhaustive] +pub enum NonExhaustive { + One(i32, LoudDrop), +} + +#[allow(unused)] +fn one_variant() { + println!("one variant:"); + let mut thing = OneVariant::One(0, LoudDrop("a")); + let closure = move || match thing { + OneVariant::One(x, _) => {} + _ => unreachable!(), + }; + println!("before assign"); + thing = OneVariant::One(1, LoudDrop("b")); + println!("after assign"); +} + +#[allow(unused)] +fn two_variants() { + println!("two variants:"); + let mut thing = TwoVariants::One(0, LoudDrop("a")); + let closure = move || match thing { + TwoVariants::One(x, _) => {} + _ => unreachable!(), + }; + println!("before assign"); + thing = TwoVariants::One(1, LoudDrop("b")); + println!("after assign"); +} + +#[allow(unused)] +fn non_exhaustive() { + println!("non exhaustive:"); + let mut thing = NonExhaustive::One(0, LoudDrop("a")); + let closure = move || match thing { + NonExhaustive::One(x, _) => {} + _ => unreachable!(), + }; + println!("before assign"); + thing = NonExhaustive::One(1, LoudDrop("b")); + println!("after assign"); +} + +#[allow(unused)] +fn ext_non_exhaustive() { + println!("external non exhaustive:"); + let mut thing = ExtNonExhaustive::One(0, LoudDrop("a")); + let closure = move || match thing { + ExtNonExhaustive::One(x, _) => {} + _ => unreachable!(), + }; + println!("before assign"); + thing = ExtNonExhaustive::One(1, LoudDrop("b")); + println!("after assign"); +} + +fn main() { + one_variant(); + println!(); + two_variants(); + println!(); + non_exhaustive(); + println!(); + ext_non_exhaustive(); +} diff --git a/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.run.stdout b/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.run.stdout new file mode 100644 index 0000000000000..e0d83dd995492 --- /dev/null +++ b/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.run.stdout @@ -0,0 +1,23 @@ +one variant: +before assign +dropping a +after assign +dropping b + +two variants: +before assign +after assign +dropping a +dropping b + +non exhaustive: +before assign +dropping a +after assign +dropping b + +external non exhaustive: +before assign +after assign +dropping a +dropping b diff --git a/tests/ui/closures/2229_closure_analysis/match/partial-move.rs b/tests/ui/closures/2229_closure_analysis/match/partial-move.rs new file mode 100644 index 0000000000000..62d1b362cb83b --- /dev/null +++ b/tests/ui/closures/2229_closure_analysis/match/partial-move.rs @@ -0,0 +1,118 @@ +// This test measures the effect of matching-induced partial captures on the borrow checker. +// In particular, in each of the cases below, the closure either captures the entire enum/struct, +// or each field separately. +// +// If the entire ADT gets captured, it'll happen by move, and the closure will live for 'static. +// On the other hand, if each field gets captured separately, the u32 field, being Copy, will only +// get captured by an immutable borrow, resulting in a borrow checker error. +// +// See rust-lang/rust#147722 +// +//@ edition:2021 +//@ aux-build:partial_move_lib.rs +pub struct Struct(u32, String); + +pub enum Enum { + A(u32, String), +} + +pub enum TwoVariants { + A(u32, String), + B, +} + +#[non_exhaustive] +pub enum NonExhaustive { + A(u32, String), +} + +extern crate partial_move_lib; +use partial_move_lib::ExtNonExhaustive; + +// First, let's assert that the additional wildcard arm is not a source of any behavior +// differences: +pub fn test_enum1(x: Enum) -> impl FnOnce() { + || { + //~^ ERROR: closure may outlive the current function, but it borrows `x.0` + match x { + Enum::A(a, b) => { + drop((a, b)); + } + _ => unreachable!(), + } + } +} + +pub fn test_enum2(x: Enum) -> impl FnOnce() { + || { + //~^ ERROR: closure may outlive the current function, but it borrows `x.0` + match x { + Enum::A(a, b) => { + drop((a, b)); + } + } + } +} + +// The behavior for single-variant enums matches what happens for a struct +pub fn test_struct(x: Struct) -> impl FnOnce() { + || { + //~^ ERROR: closure may outlive the current function, but it borrows `x.0` + match x { + Struct(a, b) => { + drop((a, b)); + } + } + } +} + +// If we have two variants, the entire enum gets moved into the closure +pub fn test_two_variants(x: TwoVariants) -> impl FnOnce() { + || { + match x { + TwoVariants::A(a, b) => { + drop((a, b)); + } + _ => unreachable!(), + } + } +} + +// ...and single-variant, non-exhaustive enums *should* behave as if they had multiple variants +pub fn test_non_exhaustive1(x: NonExhaustive) -> impl FnOnce() { + || { + //~^ ERROR: closure may outlive the current function, but it borrows `x.0` + match x { + NonExhaustive::A(a, b) => { + drop((a, b)); + } + _ => unreachable!(), + } + } +} + +// (again, wildcard branch or not) +pub fn test_non_exhaustive2(x: NonExhaustive) -> impl FnOnce() { + || { + //~^ ERROR: closure may outlive the current function, but it borrows `x.0` + match x { + NonExhaustive::A(a, b) => { + drop((a, b)); + } + } + } +} + +// ...regardless of whether the enum is defined in the current, or in another crate +pub fn test_ext(x: ExtNonExhaustive) -> impl FnOnce() { + || { + match x { + ExtNonExhaustive::A(a, b) => { + drop((a, b)); + } + _ => unreachable!(), + } + } +} + +fn main() {} diff --git a/tests/ui/closures/2229_closure_analysis/match/partial-move.stderr b/tests/ui/closures/2229_closure_analysis/match/partial-move.stderr new file mode 100644 index 0000000000000..36adcc6717edd --- /dev/null +++ b/tests/ui/closures/2229_closure_analysis/match/partial-move.stderr @@ -0,0 +1,123 @@ +error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function + --> $DIR/partial-move.rs:35:5 + | +LL | || { + | ^^ may outlive borrowed value `x.0` +LL | +LL | match x { + | - `x.0` is borrowed here + | +note: closure is returned here + --> $DIR/partial-move.rs:35:5 + | +LL | / || { +LL | | +LL | | match x { +LL | | Enum::A(a, b) => { +... | +LL | | } + | |_____^ +help: to force the closure to take ownership of `x.0` (and any other referenced variables), use the `move` keyword + | +LL | move || { + | ++++ + +error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function + --> $DIR/partial-move.rs:47:5 + | +LL | || { + | ^^ may outlive borrowed value `x.0` +LL | +LL | match x { + | - `x.0` is borrowed here + | +note: closure is returned here + --> $DIR/partial-move.rs:47:5 + | +LL | / || { +LL | | +LL | | match x { +LL | | Enum::A(a, b) => { +... | +LL | | } + | |_____^ +help: to force the closure to take ownership of `x.0` (and any other referenced variables), use the `move` keyword + | +LL | move || { + | ++++ + +error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function + --> $DIR/partial-move.rs:59:5 + | +LL | || { + | ^^ may outlive borrowed value `x.0` +LL | +LL | match x { + | - `x.0` is borrowed here + | +note: closure is returned here + --> $DIR/partial-move.rs:59:5 + | +LL | / || { +LL | | +LL | | match x { +LL | | Struct(a, b) => { +... | +LL | | } + | |_____^ +help: to force the closure to take ownership of `x.0` (and any other referenced variables), use the `move` keyword + | +LL | move || { + | ++++ + +error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function + --> $DIR/partial-move.rs:83:5 + | +LL | || { + | ^^ may outlive borrowed value `x.0` +LL | +LL | match x { + | - `x.0` is borrowed here + | +note: closure is returned here + --> $DIR/partial-move.rs:83:5 + | +LL | / || { +LL | | +LL | | match x { +LL | | NonExhaustive::A(a, b) => { +... | +LL | | } + | |_____^ +help: to force the closure to take ownership of `x.0` (and any other referenced variables), use the `move` keyword + | +LL | move || { + | ++++ + +error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function + --> $DIR/partial-move.rs:96:5 + | +LL | || { + | ^^ may outlive borrowed value `x.0` +LL | +LL | match x { + | - `x.0` is borrowed here + | +note: closure is returned here + --> $DIR/partial-move.rs:96:5 + | +LL | / || { +LL | | +LL | | match x { +LL | | NonExhaustive::A(a, b) => { +... | +LL | | } + | |_____^ +help: to force the closure to take ownership of `x.0` (and any other referenced variables), use the `move` keyword + | +LL | move || { + | ++++ + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0373`. From 3c175080dca2a60eb42a28d46ecc416d3a6f8879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Sun, 4 Jan 2026 19:34:37 +0100 Subject: [PATCH 6/8] add tests for discriminant read borrowck --- tests/ui/match/borrowck-uninhabited.rs | 53 +++++++++++++++++++ tests/ui/match/borrowck-uninhabited.stderr | 14 +++++ tests/ui/match/uninhabited-granular-moves.rs | 46 ++++++++++++++++ .../match/uninhabited-granular-moves.stderr | 18 +++++++ 4 files changed, 131 insertions(+) create mode 100644 tests/ui/match/borrowck-uninhabited.rs create mode 100644 tests/ui/match/borrowck-uninhabited.stderr create mode 100644 tests/ui/match/uninhabited-granular-moves.rs create mode 100644 tests/ui/match/uninhabited-granular-moves.stderr diff --git a/tests/ui/match/borrowck-uninhabited.rs b/tests/ui/match/borrowck-uninhabited.rs new file mode 100644 index 0000000000000..93838ede27938 --- /dev/null +++ b/tests/ui/match/borrowck-uninhabited.rs @@ -0,0 +1,53 @@ +// See: rust-lang/rust#146590 + +enum Never {} + +// baseline +fn both_inhabited(x: &mut Result) { + match x { + &mut Ok(ref mut y) => match x { + //~^ ERROR: cannot use `*x` because it was mutably borrowed + &mut Err(ref mut z) => { + let _y = y; + let _z = z; + } + _ => {} + }, + _ => {} + }; +} + +fn ref_uninhabited(x: &mut Result) { + match x { + &mut Ok(ref mut y) => match x { + &mut Err(ref mut z) => { + let _y = y; + let _z = z; + } + _ => {} + }, + _ => {} + }; +} + +enum Single { + V(String, String), +} + +// arguably this should be rejected as well, but currently it is still accepted +fn single_variant(x: &mut Single) { + match x { + &mut Single::V(ref mut y, _) => { + match x { + &mut Single::V(_, ref mut z) => { + let _y = y; + let _z = z; + } + _ => {} + } + }, + _ => {} + }; +} + +fn main() {} diff --git a/tests/ui/match/borrowck-uninhabited.stderr b/tests/ui/match/borrowck-uninhabited.stderr new file mode 100644 index 0000000000000..ee3b814da9f4a --- /dev/null +++ b/tests/ui/match/borrowck-uninhabited.stderr @@ -0,0 +1,14 @@ +error[E0503]: cannot use `*x` because it was mutably borrowed + --> $DIR/borrowck-uninhabited.rs:8:37 + | +LL | &mut Ok(ref mut y) => match x { + | --------- ^ use of borrowed `x.0` + | | + | `x.0` is borrowed here +... +LL | let _y = y; + | - borrow later used here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0503`. diff --git a/tests/ui/match/uninhabited-granular-moves.rs b/tests/ui/match/uninhabited-granular-moves.rs new file mode 100644 index 0000000000000..9d04fcb3259e2 --- /dev/null +++ b/tests/ui/match/uninhabited-granular-moves.rs @@ -0,0 +1,46 @@ +// See rust-lang/rust#146590, as well as Zulip discussion: +// +// https://rust-lang.zulipchat.com/#narrow/channel/513289-t-patterns/topic/Question.20about.20patterns.20and.20moves/with/558638455 +// +// Whether pattern matching performs a discriminant read shouldn't depend on whether +// you explicitly write down an uninhabited branch, or leave it implicit. + +enum Emp { } + +enum Foo { + Bar(A), + Qux(Emp), +} + +fn test1(thefoo: Foo<(Box, Box)>) { + match thefoo { + Foo::Bar((a, _)) => { } + } + + match thefoo { + Foo::Bar((_, a)) => { } + } +} + +fn test2(thefoo: Foo<(Box, Box)>) { + match thefoo { + Foo::Bar((a, _)) => { } + Foo::Qux(_) => { } + } + match thefoo { //~ ERROR: use of partially moved value: `thefoo` + Foo::Bar((_, a)) => { } + Foo::Qux(_) => { } + } +} + +fn test3(thefoo: Foo<(Box, Box)>) { + match thefoo { + Foo::Bar((a, _)) => { } + Foo::Qux(_) => { } + } + match thefoo { + Foo::Bar((_, a)) => { } + } +} + +fn main() {} diff --git a/tests/ui/match/uninhabited-granular-moves.stderr b/tests/ui/match/uninhabited-granular-moves.stderr new file mode 100644 index 0000000000000..d32f935d3d555 --- /dev/null +++ b/tests/ui/match/uninhabited-granular-moves.stderr @@ -0,0 +1,18 @@ +error[E0382]: use of partially moved value: `thefoo` + --> $DIR/uninhabited-granular-moves.rs:30:11 + | +LL | Foo::Bar((a, _)) => { } + | - value partially moved here +... +LL | match thefoo { + | ^^^^^^ value used here after partial move + | + = note: partial move occurs because value has type `Box`, which does not implement the `Copy` trait +help: borrow this binding in the pattern to avoid moving the value + | +LL | Foo::Bar((ref a, _)) => { } + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`. From ee1a6f4e88bb71d964864de1d7c935d53a3d0cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Sun, 4 Jan 2026 04:22:55 +0100 Subject: [PATCH 7/8] match in closure: capture non_exhaustive even if defined in current crate --- .../rustc_hir_typeck/src/expr_use_visitor.rs | 29 +++-------- .../match/non-exhaustive-match.rs | 16 +++--- .../match/non-exhaustive-match.stderr | 19 +++++-- .../match/partial-move-drop-order.rs | 5 ++ .../match/partial-move-drop-order.run.stdout | 2 +- .../match/partial-move.rs | 4 +- .../match/partial-move.stderr | 50 +------------------ 7 files changed, 39 insertions(+), 86 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index ad34994526ed9..9e9b511b9b233 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -818,14 +818,12 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx /// The core driver for walking a pattern /// /// This should mirror how pattern-matching gets lowered to MIR, as - /// otherwise lowering will ICE when trying to resolve the upvars. + /// otherwise said lowering will ICE when trying to resolve the upvars. /// /// However, it is okay to approximate it here by doing *more* accesses than /// the actual MIR builder will, which is useful when some checks are too - /// cumbersome to perform here. For example, if after typeck it becomes - /// clear that only one variant of an enum is inhabited, and therefore a - /// read of the discriminant is not necessary, `walk_pat` will have - /// over-approximated the necessary upvar capture granularity. + /// cumbersome to perform here, because e.g. they require more typeck results + /// than available. /// /// Do note that discrepancies like these do still create obscure corners /// in the semantics of the language, and should be avoided if possible. @@ -1852,26 +1850,13 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx } /// Checks whether a type has multiple variants, and therefore, whether a - /// read of the discriminant might be necessary. Note that the actual MIR - /// builder code does a more specific check, filtering out variants that - /// happen to be uninhabited. - /// - /// Here, it is not practical to perform such a check, because inhabitedness - /// queries require typeck results, and typeck requires closure capture analysis. - /// - /// Moreover, the language is moving towards uninhabited variants still semantically - /// causing a discriminant read, so we *shouldn't* perform any such check. - /// - /// FIXME(never_patterns): update this comment once the aforementioned MIR builder - /// code is changed to be insensitive to inhhabitedness. + /// read of the discriminant might be necessary. #[instrument(skip(self, span), level = "debug")] fn is_multivariant_adt(&self, ty: Ty<'tcx>, span: Span) -> bool { if let ty::Adt(def, _) = self.cx.structurally_resolve_type(span, ty).kind() { - // Note that if a non-exhaustive SingleVariant is defined in another crate, we need - // to assume that more cases will be added to the variant in the future. This mean - // that we should handle non-exhaustive SingleVariant the same way we would handle - // a MultiVariant. - def.variants().len() > 1 || def.variant_list_has_applicable_non_exhaustive() + // We treat non-exhaustive enums the same independent of the crate they are + // defined in, to avoid differences in the operational semantics between crates. + def.variants().len() > 1 || def.is_variant_list_non_exhaustive() } else { false } diff --git a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs index 5b7259c6c2cc6..f47d70b52f200 100644 --- a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs +++ b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs @@ -28,12 +28,6 @@ fn main() { let _b = || { match l1 { L1::A => () } }; //~^ ERROR: non-exhaustive patterns: `L1::B` not covered [E0004] - // l2 should not be captured as it is a non-exhaustive SingleVariant - // defined in this crate - let _c = || { match l2 { L2::C => (), _ => () } }; - let mut mut_l2 = l2; - _c(); - // E1 is not visibly uninhabited from here let (e1, e2, e3, e4) = bar(); let _d = || { match e1 {} }; @@ -42,8 +36,14 @@ fn main() { //~^ ERROR: non-exhaustive patterns: `_` not covered [E0004] let _f = || { match e2 { E2::A => (), E2::B => (), _ => () } }; - // e3 should be captured as it is a non-exhaustive SingleVariant - // defined in another crate + // non-exhaustive enums should always be captured, regardless if they + // are defined in the current crate: + let _c = || { match l2 { L2::C => (), _ => () } }; + let mut mut_l2 = l2; + //~^ ERROR: cannot move out of `l2` because it is borrowed + _c(); + + // ...or in another crate: let _g = || { match e3 { E3::C => (), _ => () } }; let mut mut_e3 = e3; //~^ ERROR: cannot move out of `e3` because it is borrowed diff --git a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr index 99d33b05429e8..e34d1889803a9 100644 --- a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr +++ b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr @@ -16,7 +16,7 @@ LL | let _b = || { match l1 { L1::A => (), L1::B => todo!() } }; | ++++++++++++++++++ error[E0004]: non-exhaustive patterns: type `E1` is non-empty - --> $DIR/non-exhaustive-match.rs:39:25 + --> $DIR/non-exhaustive-match.rs:33:25 | LL | let _d = || { match e1 {} }; | ^^ @@ -35,7 +35,7 @@ LL ~ } }; | error[E0004]: non-exhaustive patterns: `_` not covered - --> $DIR/non-exhaustive-match.rs:41:25 + --> $DIR/non-exhaustive-match.rs:35:25 | LL | let _e = || { match e2 { E2::A => (), E2::B => () } }; | ^^ pattern `_` not covered @@ -52,6 +52,19 @@ help: ensure that all possible cases are being handled by adding a match arm wit LL | let _e = || { match e2 { E2::A => (), E2::B => (), _ => todo!() } }; | ++++++++++++++ +error[E0505]: cannot move out of `l2` because it is borrowed + --> $DIR/non-exhaustive-match.rs:42:22 + | +LL | let _c = || { match l2 { L2::C => (), _ => () } }; + | -- -- borrow occurs due to use in closure + | | + | borrow of `l2` occurs here +LL | let mut mut_l2 = l2; + | ^^ move out of `l2` occurs here +LL | +LL | _c(); + | -- borrow later used here + error[E0505]: cannot move out of `e3` because it is borrowed --> $DIR/non-exhaustive-match.rs:48:22 | @@ -65,7 +78,7 @@ LL | LL | _g(); | -- borrow later used here -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors Some errors have detailed explanations: E0004, E0505. For more information about an error, try `rustc --explain E0004`. diff --git a/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.rs b/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.rs index 1c178bd3f684b..348d558dd9803 100644 --- a/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.rs +++ b/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.rs @@ -1,3 +1,8 @@ +// Make sure that #[non_exhaustive] cannot cause drop order to depend on which +// crate the code is in. +// +// See rust-lang/rust#147722 +// //@ edition:2021 //@ run-pass //@ check-run-results diff --git a/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.run.stdout b/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.run.stdout index e0d83dd995492..3fa346d87b072 100644 --- a/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.run.stdout +++ b/tests/ui/closures/2229_closure_analysis/match/partial-move-drop-order.run.stdout @@ -12,8 +12,8 @@ dropping b non exhaustive: before assign -dropping a after assign +dropping a dropping b external non exhaustive: diff --git a/tests/ui/closures/2229_closure_analysis/match/partial-move.rs b/tests/ui/closures/2229_closure_analysis/match/partial-move.rs index 62d1b362cb83b..7bd435bd1856f 100644 --- a/tests/ui/closures/2229_closure_analysis/match/partial-move.rs +++ b/tests/ui/closures/2229_closure_analysis/match/partial-move.rs @@ -78,10 +78,9 @@ pub fn test_two_variants(x: TwoVariants) -> impl FnOnce() { } } -// ...and single-variant, non-exhaustive enums *should* behave as if they had multiple variants +// ...and single-variant, non-exhaustive enums behave as if they had multiple variants pub fn test_non_exhaustive1(x: NonExhaustive) -> impl FnOnce() { || { - //~^ ERROR: closure may outlive the current function, but it borrows `x.0` match x { NonExhaustive::A(a, b) => { drop((a, b)); @@ -94,7 +93,6 @@ pub fn test_non_exhaustive1(x: NonExhaustive) -> impl FnOnce() { // (again, wildcard branch or not) pub fn test_non_exhaustive2(x: NonExhaustive) -> impl FnOnce() { || { - //~^ ERROR: closure may outlive the current function, but it borrows `x.0` match x { NonExhaustive::A(a, b) => { drop((a, b)); diff --git a/tests/ui/closures/2229_closure_analysis/match/partial-move.stderr b/tests/ui/closures/2229_closure_analysis/match/partial-move.stderr index 36adcc6717edd..09f9adf95d5bb 100644 --- a/tests/ui/closures/2229_closure_analysis/match/partial-move.stderr +++ b/tests/ui/closures/2229_closure_analysis/match/partial-move.stderr @@ -70,54 +70,6 @@ help: to force the closure to take ownership of `x.0` (and any other referenced LL | move || { | ++++ -error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function - --> $DIR/partial-move.rs:83:5 - | -LL | || { - | ^^ may outlive borrowed value `x.0` -LL | -LL | match x { - | - `x.0` is borrowed here - | -note: closure is returned here - --> $DIR/partial-move.rs:83:5 - | -LL | / || { -LL | | -LL | | match x { -LL | | NonExhaustive::A(a, b) => { -... | -LL | | } - | |_____^ -help: to force the closure to take ownership of `x.0` (and any other referenced variables), use the `move` keyword - | -LL | move || { - | ++++ - -error[E0373]: closure may outlive the current function, but it borrows `x.0`, which is owned by the current function - --> $DIR/partial-move.rs:96:5 - | -LL | || { - | ^^ may outlive borrowed value `x.0` -LL | -LL | match x { - | - `x.0` is borrowed here - | -note: closure is returned here - --> $DIR/partial-move.rs:96:5 - | -LL | / || { -LL | | -LL | | match x { -LL | | NonExhaustive::A(a, b) => { -... | -LL | | } - | |_____^ -help: to force the closure to take ownership of `x.0` (and any other referenced variables), use the `move` keyword - | -LL | move || { - | ++++ - -error: aborting due to 5 previous errors +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0373`. From af302a67fdc508cfd08ee22facb96bcf0e5bf831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Sun, 4 Jan 2026 19:34:37 +0100 Subject: [PATCH 8/8] discriminant reads: make semantics independent of module/crate --- .../src/builder/matches/match_pair.rs | 20 ++++---- .../fail/match/all_variants_uninhabited.rs | 25 ++++++++++ .../match/all_variants_uninhabited.stderr | 13 +++++ .../{ => match}/closures/deref-in-pattern.rs | 0 .../closures/deref-in-pattern.stderr | 2 +- .../{ => match}/closures/partial-pattern.rs | 0 .../closures/partial-pattern.stderr | 2 +- .../closures/uninhabited-variant1.rs} | 0 .../closures/uninhabited-variant1.stderr} | 6 +-- .../match/closures/uninhabited-variant2.rs | 32 +++++++++++++ .../closures/uninhabited-variant2.stderr | 18 +++++++ .../fail/match/only_inhabited_variant.rs | 21 ++++++++ .../fail/match/only_inhabited_variant.stderr | 13 +++++ .../miri/tests/fail/match/single_variant.rs | 31 ++++++++++++ .../tests/fail/match/single_variant.stderr | 13 +++++ .../tests/fail/match/single_variant_uninit.rs | 36 ++++++++++++++ .../fail/match/single_variant_uninit.stderr | 18 +++++++ .../enum/enum-transparent-extract.rs | 4 ++ ...atterns.opt1.SimplifyCfg-initial.after.mir | 10 ++-- ...atterns.opt2.SimplifyCfg-initial.after.mir | 15 ++++++ ...atterns.opt3.SimplifyCfg-initial.after.mir | 17 ++++--- ...ng.identity.JumpThreading.panic-abort.diff | 19 ++++++-- ...g.identity.JumpThreading.panic-unwind.diff | 19 ++++++-- ...map_via_question_mark.PreCodegen.after.mir | 48 +++++++++++-------- ...e_const_switch.identity.JumpThreading.diff | 15 ++++-- ...ch.UnreachablePropagation.panic-abort.diff | 12 +++-- ...h.UnreachablePropagation.panic-unwind.diff | 12 +++-- tests/mir-opt/unreachable.rs | 2 +- ....UnreachableEnumBranching.panic-abort.diff | 24 +++++----- ...UnreachableEnumBranching.panic-unwind.diff | 24 +++++----- tests/ui/match/borrowck-uninhabited.rs | 2 + tests/ui/match/borrowck-uninhabited.stderr | 13 ++++- tests/ui/match/uninhabited-granular-moves.rs | 4 +- .../match/uninhabited-granular-moves.stderr | 32 ++++++++++++- .../borrowck-exhaustive.rs | 12 ----- .../borrowck-non-exhaustive.rs | 12 +++++ .../borrowck-non-exhaustive.stderr | 15 +++++- 37 files changed, 450 insertions(+), 111 deletions(-) create mode 100644 src/tools/miri/tests/fail/match/all_variants_uninhabited.rs create mode 100644 src/tools/miri/tests/fail/match/all_variants_uninhabited.stderr rename src/tools/miri/tests/fail/{ => match}/closures/deref-in-pattern.rs (100%) rename src/tools/miri/tests/fail/{ => match}/closures/deref-in-pattern.stderr (91%) rename src/tools/miri/tests/fail/{ => match}/closures/partial-pattern.rs (100%) rename src/tools/miri/tests/fail/{ => match}/closures/partial-pattern.stderr (92%) rename src/tools/miri/tests/fail/{closures/uninhabited-variant.rs => match/closures/uninhabited-variant1.rs} (100%) rename src/tools/miri/tests/fail/{closures/uninhabited-variant.stderr => match/closures/uninhabited-variant1.stderr} (74%) create mode 100644 src/tools/miri/tests/fail/match/closures/uninhabited-variant2.rs create mode 100644 src/tools/miri/tests/fail/match/closures/uninhabited-variant2.stderr create mode 100644 src/tools/miri/tests/fail/match/only_inhabited_variant.rs create mode 100644 src/tools/miri/tests/fail/match/only_inhabited_variant.stderr create mode 100644 src/tools/miri/tests/fail/match/single_variant.rs create mode 100644 src/tools/miri/tests/fail/match/single_variant.stderr create mode 100644 src/tools/miri/tests/fail/match/single_variant_uninit.rs create mode 100644 src/tools/miri/tests/fail/match/single_variant_uninit.stderr diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs index 8cee3ff27e8f9..07af66127ef90 100644 --- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs +++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs @@ -295,22 +295,18 @@ impl<'tcx> MatchPairTree<'tcx> { } } - PatKind::Variant { adt_def, variant_index, args, ref subpatterns } => { + PatKind::Variant { adt_def, variant_index, args: _, ref subpatterns } => { let downcast_place = place_builder.downcast(adt_def, variant_index); // `(x as Variant)` cx.field_match_pairs(&mut subpairs, extra_data, downcast_place, subpatterns); - let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| { - i == variant_index - || !v.inhabited_predicate(cx.tcx, adt_def).instantiate(cx.tcx, args).apply( - cx.tcx, - cx.infcx.typing_env(cx.param_env), - cx.def_id.into(), - ) - }) && !adt_def.variant_list_has_applicable_non_exhaustive(); - if irrefutable { - None - } else { + // We treat non-exhaustive enums the same independent of the crate they are + // defined in, to avoid differences in the operational semantics between crates. + let refutable = + adt_def.variants().len() > 1 || adt_def.is_variant_list_non_exhaustive(); + if refutable { Some(TestableCase::Variant { adt_def, variant_index }) + } else { + None } } diff --git a/src/tools/miri/tests/fail/match/all_variants_uninhabited.rs b/src/tools/miri/tests/fail/match/all_variants_uninhabited.rs new file mode 100644 index 0000000000000..e7ca45579d84d --- /dev/null +++ b/src/tools/miri/tests/fail/match/all_variants_uninhabited.rs @@ -0,0 +1,25 @@ +#![allow(deref_nullptr)] + +enum Never {} + +fn main() { + unsafe { + match *std::ptr::null::>() { + //~^ ERROR: read discriminant of an uninhabited enum variant + Ok(_) => { + lol(); + } + Err(_) => { + wut(); + } + } + } +} + +fn lol() { + println!("lol"); +} + +fn wut() { + println!("wut"); +} diff --git a/src/tools/miri/tests/fail/match/all_variants_uninhabited.stderr b/src/tools/miri/tests/fail/match/all_variants_uninhabited.stderr new file mode 100644 index 0000000000000..90809219340b4 --- /dev/null +++ b/src/tools/miri/tests/fail/match/all_variants_uninhabited.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: read discriminant of an uninhabited enum variant + --> tests/fail/match/all_variants_uninhabited.rs:LL:CC + | +LL | match *std::ptr::null::>() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/closures/deref-in-pattern.rs b/src/tools/miri/tests/fail/match/closures/deref-in-pattern.rs similarity index 100% rename from src/tools/miri/tests/fail/closures/deref-in-pattern.rs rename to src/tools/miri/tests/fail/match/closures/deref-in-pattern.rs diff --git a/src/tools/miri/tests/fail/closures/deref-in-pattern.stderr b/src/tools/miri/tests/fail/match/closures/deref-in-pattern.stderr similarity index 91% rename from src/tools/miri/tests/fail/closures/deref-in-pattern.stderr rename to src/tools/miri/tests/fail/match/closures/deref-in-pattern.stderr index fae2d286c4876..cbb74df5de773 100644 --- a/src/tools/miri/tests/fail/closures/deref-in-pattern.stderr +++ b/src/tools/miri/tests/fail/match/closures/deref-in-pattern.stderr @@ -1,5 +1,5 @@ error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free) - --> tests/fail/closures/deref-in-pattern.rs:LL:CC + --> tests/fail/match/closures/deref-in-pattern.rs:LL:CC | LL | let _ = || { | _____________^ diff --git a/src/tools/miri/tests/fail/closures/partial-pattern.rs b/src/tools/miri/tests/fail/match/closures/partial-pattern.rs similarity index 100% rename from src/tools/miri/tests/fail/closures/partial-pattern.rs rename to src/tools/miri/tests/fail/match/closures/partial-pattern.rs diff --git a/src/tools/miri/tests/fail/closures/partial-pattern.stderr b/src/tools/miri/tests/fail/match/closures/partial-pattern.stderr similarity index 92% rename from src/tools/miri/tests/fail/closures/partial-pattern.stderr rename to src/tools/miri/tests/fail/match/closures/partial-pattern.stderr index 8dea4d4d8c64e..b8ca04559e23d 100644 --- a/src/tools/miri/tests/fail/closures/partial-pattern.stderr +++ b/src/tools/miri/tests/fail/match/closures/partial-pattern.stderr @@ -1,5 +1,5 @@ error: Undefined Behavior: constructing invalid value: encountered a dangling reference (use-after-free) - --> tests/fail/closures/partial-pattern.rs:LL:CC + --> tests/fail/match/closures/partial-pattern.rs:LL:CC | LL | let _ = || { | _____________^ diff --git a/src/tools/miri/tests/fail/closures/uninhabited-variant.rs b/src/tools/miri/tests/fail/match/closures/uninhabited-variant1.rs similarity index 100% rename from src/tools/miri/tests/fail/closures/uninhabited-variant.rs rename to src/tools/miri/tests/fail/match/closures/uninhabited-variant1.rs diff --git a/src/tools/miri/tests/fail/closures/uninhabited-variant.stderr b/src/tools/miri/tests/fail/match/closures/uninhabited-variant1.stderr similarity index 74% rename from src/tools/miri/tests/fail/closures/uninhabited-variant.stderr rename to src/tools/miri/tests/fail/match/closures/uninhabited-variant1.stderr index aea878660115f..1a10a5b24d256 100644 --- a/src/tools/miri/tests/fail/closures/uninhabited-variant.stderr +++ b/src/tools/miri/tests/fail/match/closures/uninhabited-variant1.stderr @@ -1,5 +1,5 @@ error: Undefined Behavior: read discriminant of an uninhabited enum variant - --> tests/fail/closures/uninhabited-variant.rs:LL:CC + --> tests/fail/match/closures/uninhabited-variant1.rs:LL:CC | LL | match r { | ^ Undefined Behavior occurred here @@ -8,9 +8,9 @@ LL | match r { = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: stack backtrace: 0: main::{closure#0} - at tests/fail/closures/uninhabited-variant.rs:LL:CC + at tests/fail/match/closures/uninhabited-variant1.rs:LL:CC 1: main - at tests/fail/closures/uninhabited-variant.rs:LL:CC + at tests/fail/match/closures/uninhabited-variant1.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/match/closures/uninhabited-variant2.rs b/src/tools/miri/tests/fail/match/closures/uninhabited-variant2.rs new file mode 100644 index 0000000000000..ed68e357fbd59 --- /dev/null +++ b/src/tools/miri/tests/fail/match/closures/uninhabited-variant2.rs @@ -0,0 +1,32 @@ +// Motivated by rust-lang/rust#138961, this shows how invalid discriminants interact with +// closure captures. +// +// Test case with only one inhabited variant, for which rustc used to not emit +// a discriminant read in the first place. See: rust-lang/miri#4778 +#![feature(never_type)] + +#[repr(C)] +#[allow(dead_code)] +enum E { + V0, // discriminant: 0 + V1(!), // 1 +} + +fn main() { + assert_eq!(std::mem::size_of::(), 4); + + let val = 1u32; + let ptr = (&raw const val).cast::(); + let r = unsafe { &*ptr }; + let f = || { + // After rust-lang/rust#138961, constructing the closure performs a reborrow of r. + // Nevertheless, the discriminant is only actually inspected when the closure + // is called. + match r { //~ ERROR: read discriminant of an uninhabited enum variant + E::V0 => {} + E::V1(_) => {} + } + }; + + f(); +} diff --git a/src/tools/miri/tests/fail/match/closures/uninhabited-variant2.stderr b/src/tools/miri/tests/fail/match/closures/uninhabited-variant2.stderr new file mode 100644 index 0000000000000..611e0435ef0e9 --- /dev/null +++ b/src/tools/miri/tests/fail/match/closures/uninhabited-variant2.stderr @@ -0,0 +1,18 @@ +error: Undefined Behavior: read discriminant of an uninhabited enum variant + --> tests/fail/match/closures/uninhabited-variant2.rs:LL:CC + | +LL | match r { + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: stack backtrace: + 0: main::{closure#0} + at tests/fail/match/closures/uninhabited-variant2.rs:LL:CC + 1: main + at tests/fail/match/closures/uninhabited-variant2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/match/only_inhabited_variant.rs b/src/tools/miri/tests/fail/match/only_inhabited_variant.rs new file mode 100644 index 0000000000000..30a7350d2b667 --- /dev/null +++ b/src/tools/miri/tests/fail/match/only_inhabited_variant.rs @@ -0,0 +1,21 @@ +// rust-lang/miri#4778 +#![feature(never_type)] + +#[repr(C)] +#[allow(dead_code)] +enum E { + V0, // discriminant: 0 + V1(!), // 1 +} + +fn main() { + assert_eq!(std::mem::size_of::(), 4); + + let val = 1u32; + let ptr = (&raw const val).cast::(); + let r = unsafe { &*ptr }; + match r { //~ ERROR: read discriminant of an uninhabited enum variant + E::V0 => {} + E::V1(_) => {} + } +} diff --git a/src/tools/miri/tests/fail/match/only_inhabited_variant.stderr b/src/tools/miri/tests/fail/match/only_inhabited_variant.stderr new file mode 100644 index 0000000000000..8ca362f902d39 --- /dev/null +++ b/src/tools/miri/tests/fail/match/only_inhabited_variant.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: read discriminant of an uninhabited enum variant + --> tests/fail/match/only_inhabited_variant.rs:LL:CC + | +LL | match r { + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/match/single_variant.rs b/src/tools/miri/tests/fail/match/single_variant.rs new file mode 100644 index 0000000000000..dcef6d461a2c3 --- /dev/null +++ b/src/tools/miri/tests/fail/match/single_variant.rs @@ -0,0 +1,31 @@ +// Ideally, this would be UB regardless of #[non_exhaustive]. For now, +// at least the semantics don't depend on the crate you're in. +// +// See: rust-lang/rust#147722 +#![allow(dead_code)] + +#[repr(u8)] +enum Exhaustive { + A(u8) = 42, +} + +#[repr(u8)] +#[non_exhaustive] +enum NonExhaustive { + A(u8) = 42, +} + +fn main() { + unsafe { + let x: &[u8; 2] = &[21, 37]; + let y: &Exhaustive = std::mem::transmute(x); + match y { + Exhaustive::A(_) => {}, + } + + let y: &NonExhaustive = std::mem::transmute(x); + match y { //~ ERROR: enum value has invalid tag + NonExhaustive::A(_) => {}, + } + } +} diff --git a/src/tools/miri/tests/fail/match/single_variant.stderr b/src/tools/miri/tests/fail/match/single_variant.stderr new file mode 100644 index 0000000000000..7467e27ff7915 --- /dev/null +++ b/src/tools/miri/tests/fail/match/single_variant.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: enum value has invalid tag: 0x15 + --> tests/fail/match/single_variant.rs:LL:CC + | +LL | match y { + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/match/single_variant_uninit.rs b/src/tools/miri/tests/fail/match/single_variant_uninit.rs new file mode 100644 index 0000000000000..51e8bc57c837a --- /dev/null +++ b/src/tools/miri/tests/fail/match/single_variant_uninit.rs @@ -0,0 +1,36 @@ +// Ideally, this would be UB regardless of #[non_exhaustive]. For now, +// at least the semantics don't depend on the crate you're in. +// +// See: rust-lang/rust#147722 +#![allow(dead_code)] +#![allow(unreachable_patterns)] + +#[repr(u8)] +enum Exhaustive { + A(u8) = 0, +} + +#[repr(u8)] +#[non_exhaustive] +enum NonExhaustive { + A(u8) = 0, +} + +use std::mem::MaybeUninit; + +fn main() { + let buffer: [MaybeUninit; 2] = [MaybeUninit::uninit(), MaybeUninit::new(0u8)]; + let exh: *const Exhaustive = (&raw const buffer).cast(); + let nexh: *const NonExhaustive = (&raw const buffer).cast(); + unsafe { + match *exh { + Exhaustive::A(ref _val) => {} + _ => {} + } + + match *nexh { //~ ERROR: memory is uninitialized + NonExhaustive::A(ref _val) => {} + _ => {} + } + } +} diff --git a/src/tools/miri/tests/fail/match/single_variant_uninit.stderr b/src/tools/miri/tests/fail/match/single_variant_uninit.stderr new file mode 100644 index 0000000000000..fa8e9babae602 --- /dev/null +++ b/src/tools/miri/tests/fail/match/single_variant_uninit.stderr @@ -0,0 +1,18 @@ +error: Undefined Behavior: reading memory at ALLOC[0x0..0x1], but memory is uninitialized at [0x0..0x1], and this operation requires initialized memory + --> tests/fail/match/single_variant_uninit.rs:LL:CC + | +LL | match *nexh { + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +Uninitialized memory occurred at ALLOC[0x0..0x1], in this allocation: +ALLOC (stack variable, size: 2, align: 1) { + __ 00 │ ░. +} + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/tests/codegen-llvm/enum/enum-transparent-extract.rs b/tests/codegen-llvm/enum/enum-transparent-extract.rs index 1435e6ec8022a..1a05b236abfbb 100644 --- a/tests/codegen-llvm/enum/enum-transparent-extract.rs +++ b/tests/codegen-llvm/enum/enum-transparent-extract.rs @@ -11,6 +11,8 @@ pub enum Never {} pub fn make_unmake_result_never(x: i32) -> i32 { // CHECK-LABEL: define i32 @make_unmake_result_never(i32{{( signext)?}} %x) // CHECK: start: + // CHECK-NEXT: br label %[[next:bb.*]] + // CHECK: [[next]]: // CHECK-NEXT: ret i32 %x let y: Result = Ok(x); @@ -22,6 +24,8 @@ pub fn make_unmake_result_never(x: i32) -> i32 { pub fn extract_control_flow_never(x: ControlFlow<&str, Never>) -> &str { // CHECK-LABEL: define { ptr, i64 } @extract_control_flow_never(ptr align 1 %x.0, i64 %x.1) // CHECK: start: + // CHECK-NEXT: br label %[[next:bb.*]] + // CHECK: [[next]]: // CHECK-NEXT: %[[P0:.+]] = insertvalue { ptr, i64 } poison, ptr %x.0, 0 // CHECK-NEXT: %[[P1:.+]] = insertvalue { ptr, i64 } %[[P0]], i64 %x.1, 1 // CHECK-NEXT: ret { ptr, i64 } %[[P1]] diff --git a/tests/mir-opt/building/match/never_patterns.opt1.SimplifyCfg-initial.after.mir b/tests/mir-opt/building/match/never_patterns.opt1.SimplifyCfg-initial.after.mir index bba4d9c0149a1..78356a90743a6 100644 --- a/tests/mir-opt/building/match/never_patterns.opt1.SimplifyCfg-initial.after.mir +++ b/tests/mir-opt/building/match/never_patterns.opt1.SimplifyCfg-initial.after.mir @@ -13,17 +13,17 @@ fn opt1(_1: &Result) -> &u32 { bb0: { PlaceMention(_1); - falseEdge -> [real: bb4, imaginary: bb1]; + _2 = discriminant((*_1)); + switchInt(move _2) -> [0: bb2, 1: bb3, otherwise: bb1]; } bb1: { - _2 = discriminant((*_1)); - switchInt(move _2) -> [1: bb3, otherwise: bb2]; + FakeRead(ForMatchedPlace(None), _1); + unreachable; } bb2: { - FakeRead(ForMatchedPlace(None), _1); - unreachable; + falseEdge -> [real: bb4, imaginary: bb3]; } bb3: { diff --git a/tests/mir-opt/building/match/never_patterns.opt2.SimplifyCfg-initial.after.mir b/tests/mir-opt/building/match/never_patterns.opt2.SimplifyCfg-initial.after.mir index fc0769d6f7dcc..979fbb2860dcb 100644 --- a/tests/mir-opt/building/match/never_patterns.opt2.SimplifyCfg-initial.after.mir +++ b/tests/mir-opt/building/match/never_patterns.opt2.SimplifyCfg-initial.after.mir @@ -11,10 +11,25 @@ fn opt2(_1: &Result) -> &u32 { bb0: { PlaceMention(_1); + _2 = discriminant((*_1)); + switchInt(move _2) -> [0: bb2, 1: bb3, otherwise: bb1]; + } + + bb1: { + FakeRead(ForMatchedPlace(None), _1); + unreachable; + } + + bb2: { StorageLive(_3); _3 = &(((*_1) as Ok).0: u32); _0 = &(*_3); StorageDead(_3); return; } + + bb3: { + FakeRead(ForMatchedPlace(None), (((*_1) as Err).0: Void)); + unreachable; + } } diff --git a/tests/mir-opt/building/match/never_patterns.opt3.SimplifyCfg-initial.after.mir b/tests/mir-opt/building/match/never_patterns.opt3.SimplifyCfg-initial.after.mir index 86347db4d92eb..93ebe600b3ff7 100644 --- a/tests/mir-opt/building/match/never_patterns.opt3.SimplifyCfg-initial.after.mir +++ b/tests/mir-opt/building/match/never_patterns.opt3.SimplifyCfg-initial.after.mir @@ -12,19 +12,24 @@ fn opt3(_1: &Result) -> &u32 { bb0: { PlaceMention(_1); _2 = discriminant((*_1)); - switchInt(move _2) -> [1: bb2, otherwise: bb1]; + switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1]; } bb1: { - StorageLive(_3); - _3 = &(((*_1) as Ok).0: u32); - _0 = &(*_3); - StorageDead(_3); - return; + FakeRead(ForMatchedPlace(None), _1); + unreachable; } bb2: { FakeRead(ForMatchedPlace(None), (((*_1) as Err).0: Void)); unreachable; } + + bb3: { + StorageLive(_3); + _3 = &(((*_1) as Ok).0: u32); + _0 = &(*_3); + StorageDead(_3); + return; + } } diff --git a/tests/mir-opt/jump_threading.identity.JumpThreading.panic-abort.diff b/tests/mir-opt/jump_threading.identity.JumpThreading.panic-abort.diff index 97b8d484194f5..9630f4001494a 100644 --- a/tests/mir-opt/jump_threading.identity.JumpThreading.panic-abort.diff +++ b/tests/mir-opt/jump_threading.identity.JumpThreading.panic-abort.diff @@ -16,8 +16,10 @@ debug residual => _6; scope 2 { scope 8 (inlined #[track_caller] as FromResidual>>::from_residual) { - let _14: i32; - let mut _15: i32; + let mut _14: isize; + let _15: i32; + let mut _16: i32; + let mut _17: bool; scope 9 { scope 10 (inlined >::from) { } @@ -74,10 +76,17 @@ StorageLive(_8); _8 = copy _6; StorageLive(_14); - _14 = move ((_8 as Err).0: i32); StorageLive(_15); - _15 = move _14; - _0 = Result::::Err(move _15); + StorageLive(_17); + _14 = discriminant(_8); + _17 = Eq(copy _14, const 1_isize); + assume(move _17); + _15 = move ((_8 as Err).0: i32); + StorageLive(_16); + _16 = move _15; + _0 = Result::::Err(move _16); + StorageDead(_16); + StorageDead(_17); StorageDead(_15); StorageDead(_14); StorageDead(_8); diff --git a/tests/mir-opt/jump_threading.identity.JumpThreading.panic-unwind.diff b/tests/mir-opt/jump_threading.identity.JumpThreading.panic-unwind.diff index 97b8d484194f5..9630f4001494a 100644 --- a/tests/mir-opt/jump_threading.identity.JumpThreading.panic-unwind.diff +++ b/tests/mir-opt/jump_threading.identity.JumpThreading.panic-unwind.diff @@ -16,8 +16,10 @@ debug residual => _6; scope 2 { scope 8 (inlined #[track_caller] as FromResidual>>::from_residual) { - let _14: i32; - let mut _15: i32; + let mut _14: isize; + let _15: i32; + let mut _16: i32; + let mut _17: bool; scope 9 { scope 10 (inlined >::from) { } @@ -74,10 +76,17 @@ StorageLive(_8); _8 = copy _6; StorageLive(_14); - _14 = move ((_8 as Err).0: i32); StorageLive(_15); - _15 = move _14; - _0 = Result::::Err(move _15); + StorageLive(_17); + _14 = discriminant(_8); + _17 = Eq(copy _14, const 1_isize); + assume(move _17); + _15 = move ((_8 as Err).0: i32); + StorageLive(_16); + _16 = move _15; + _0 = Result::::Err(move _16); + StorageDead(_16); + StorageDead(_17); StorageDead(_15); StorageDead(_14); StorageDead(_8); diff --git a/tests/mir-opt/pre-codegen/simple_option_map.map_via_question_mark.PreCodegen.after.mir b/tests/mir-opt/pre-codegen/simple_option_map.map_via_question_mark.PreCodegen.after.mir index b921b96966b29..ef7ccfa5bddf6 100644 --- a/tests/mir-opt/pre-codegen/simple_option_map.map_via_question_mark.PreCodegen.after.mir +++ b/tests/mir-opt/pre-codegen/simple_option_map.map_via_question_mark.PreCodegen.after.mir @@ -3,56 +3,66 @@ fn map_via_question_mark(_1: Option) -> Option { debug x => _1; let mut _0: std::option::Option; - let mut _4: std::ops::ControlFlow, i32>; - let _5: i32; - let mut _6: i32; + let mut _4: std::option::Option; + let mut _7: std::ops::ControlFlow, i32>; + let _8: i32; + let mut _9: i32; scope 1 { debug residual => const Option::::None; scope 2 { scope 7 (inlined as FromResidual>>::from_residual) { + let mut _3: isize; + let mut _5: bool; } } } scope 3 { - debug val => _5; + debug val => _8; scope 4 { } } scope 5 (inlined as Try>::branch) { let mut _2: isize; - let _3: i32; + let _6: i32; scope 6 { } } bb0: { - StorageLive(_6); - StorageLive(_4); + StorageLive(_9); + StorageLive(_7); StorageLive(_2); - StorageLive(_3); + StorageLive(_6); _2 = discriminant(_1); switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb4]; } bb1: { - StorageDead(_3); + StorageDead(_6); StorageDead(_2); + StorageLive(_3); + StorageLive(_5); + _3 = discriminant(_4); + _5 = Eq(copy _3, const 0_isize); + assume(move _5); _0 = const Option::::None; - StorageDead(_6); - StorageDead(_4); + StorageDead(_5); + StorageDead(_3); + StorageDead(_9); + StorageDead(_7); goto -> bb3; } bb2: { - _3 = copy ((_1 as Some).0: i32); - _4 = ControlFlow::, i32>::Continue(copy _3); - StorageDead(_3); - StorageDead(_2); - _5 = copy ((_4 as Continue).0: i32); - _6 = Add(copy _5, const 1_i32); - _0 = Option::::Some(move _6); + _6 = copy ((_1 as Some).0: i32); + _7 = ControlFlow::, i32>::Continue(copy _6); StorageDead(_6); - StorageDead(_4); + StorageDead(_2); + _8 = copy ((_7 as Continue).0: i32); + _9 = Add(copy _8, const 1_i32); + _0 = Option::::Some(move _9); + StorageDead(_9); + StorageDead(_7); goto -> bb3; } diff --git a/tests/mir-opt/separate_const_switch.identity.JumpThreading.diff b/tests/mir-opt/separate_const_switch.identity.JumpThreading.diff index 34f451fc698c7..10ad4ec754141 100644 --- a/tests/mir-opt/separate_const_switch.identity.JumpThreading.diff +++ b/tests/mir-opt/separate_const_switch.identity.JumpThreading.diff @@ -12,7 +12,9 @@ debug residual => _4; scope 2 { scope 8 (inlined #[track_caller] as FromResidual>>::from_residual) { - let _10: i32; + let mut _10: isize; + let _11: i32; + let mut _12: bool; scope 9 { scope 10 (inlined >::from) { } @@ -58,8 +60,15 @@ bb3: { _4 = copy ((_2 as Break).0: std::result::Result); - _10 = copy ((_4 as Err).0: i32); - _0 = Result::::Err(copy _10); + StorageLive(_10); + StorageLive(_12); + _10 = discriminant(_4); + _12 = Eq(copy _10, const 1_isize); + assume(move _12); + _11 = copy ((_4 as Err).0: i32); + _0 = Result::::Err(copy _11); + StorageDead(_12); + StorageDead(_10); StorageDead(_2); return; } diff --git a/tests/mir-opt/unreachable.as_match.UnreachablePropagation.panic-abort.diff b/tests/mir-opt/unreachable.as_match.UnreachablePropagation.panic-abort.diff index 17ddce0cdf8d4..8c3b8ad9b66a4 100644 --- a/tests/mir-opt/unreachable.as_match.UnreachablePropagation.panic-abort.diff +++ b/tests/mir-opt/unreachable.as_match.UnreachablePropagation.panic-abort.diff @@ -19,19 +19,23 @@ bb1: { _2 = discriminant(_1); -- switchInt(move _2) -> [1: bb3, otherwise: bb2]; -+ _5 = Ne(copy _2, const 1_isize); +- switchInt(move _2) -> [0: bb3, 1: bb4, otherwise: bb2]; ++ _5 = Eq(copy _2, const 0_isize); + assume(move _5); -+ goto -> bb2; ++ goto -> bb3; } bb2: { + unreachable; + } + + bb3: { _0 = const (); StorageDead(_1); return; } - bb3: { + bb4: { - StorageLive(_3); - _3 = move ((_1 as Some).0: Empty); - StorageLive(_4); diff --git a/tests/mir-opt/unreachable.as_match.UnreachablePropagation.panic-unwind.diff b/tests/mir-opt/unreachable.as_match.UnreachablePropagation.panic-unwind.diff index 2f78092f5bd24..98f2a0a692ffa 100644 --- a/tests/mir-opt/unreachable.as_match.UnreachablePropagation.panic-unwind.diff +++ b/tests/mir-opt/unreachable.as_match.UnreachablePropagation.panic-unwind.diff @@ -19,19 +19,23 @@ bb1: { _2 = discriminant(_1); -- switchInt(move _2) -> [1: bb3, otherwise: bb2]; -+ _5 = Ne(copy _2, const 1_isize); +- switchInt(move _2) -> [0: bb3, 1: bb4, otherwise: bb2]; ++ _5 = Eq(copy _2, const 0_isize); + assume(move _5); -+ goto -> bb2; ++ goto -> bb3; } bb2: { + unreachable; + } + + bb3: { _0 = const (); StorageDead(_1); return; } - bb3: { + bb4: { - StorageLive(_3); - _3 = move ((_1 as Some).0: Empty); - StorageLive(_4); diff --git a/tests/mir-opt/unreachable.rs b/tests/mir-opt/unreachable.rs index afab1291fc3df..97cd15b107c14 100644 --- a/tests/mir-opt/unreachable.rs +++ b/tests/mir-opt/unreachable.rs @@ -45,7 +45,7 @@ fn as_match() { // CHECK: bb0: { // CHECK: {{_.*}} = empty() // CHECK: bb1: { - // CHECK: [[eq:_.*]] = Ne({{.*}}, const 1_isize); + // CHECK: [[eq:_.*]] = Eq({{.*}}, const 0_isize); // CHECK-NEXT: assume(move [[eq]]); // CHECK-NEXT: goto -> [[return:bb.*]]; // CHECK: [[return]]: { diff --git a/tests/mir-opt/unreachable_enum_branching.simple.UnreachableEnumBranching.panic-abort.diff b/tests/mir-opt/unreachable_enum_branching.simple.UnreachableEnumBranching.panic-abort.diff index c24bd7e7446d2..befee026d6cb4 100644 --- a/tests/mir-opt/unreachable_enum_branching.simple.UnreachableEnumBranching.panic-abort.diff +++ b/tests/mir-opt/unreachable_enum_branching.simple.UnreachableEnumBranching.panic-abort.diff @@ -14,40 +14,40 @@ StorageLive(_2); _2 = Test1::C; _3 = discriminant(_2); -- switchInt(move _3) -> [0: bb3, 1: bb2, otherwise: bb1]; -+ switchInt(move _3) -> [0: bb5, 1: bb5, 2: bb1, otherwise: bb5]; +- switchInt(move _3) -> [0: bb4, 1: bb3, 2: bb2, otherwise: bb1]; ++ switchInt(move _3) -> [0: bb1, 1: bb1, 2: bb2, otherwise: bb1]; } bb1: { + unreachable; + } + + bb2: { StorageLive(_5); _5 = const "C"; _1 = &(*_5); StorageDead(_5); - goto -> bb4; + goto -> bb5; } - bb2: { + bb3: { StorageLive(_4); _4 = const "B(Empty)"; _1 = &(*_4); StorageDead(_4); - goto -> bb4; + goto -> bb5; } - bb3: { + bb4: { _1 = const "A(Empty)"; - goto -> bb4; + goto -> bb5; } - bb4: { + bb5: { StorageDead(_2); StorageDead(_1); _0 = const (); return; -+ } -+ -+ bb5: { -+ unreachable; } } diff --git a/tests/mir-opt/unreachable_enum_branching.simple.UnreachableEnumBranching.panic-unwind.diff b/tests/mir-opt/unreachable_enum_branching.simple.UnreachableEnumBranching.panic-unwind.diff index c24bd7e7446d2..befee026d6cb4 100644 --- a/tests/mir-opt/unreachable_enum_branching.simple.UnreachableEnumBranching.panic-unwind.diff +++ b/tests/mir-opt/unreachable_enum_branching.simple.UnreachableEnumBranching.panic-unwind.diff @@ -14,40 +14,40 @@ StorageLive(_2); _2 = Test1::C; _3 = discriminant(_2); -- switchInt(move _3) -> [0: bb3, 1: bb2, otherwise: bb1]; -+ switchInt(move _3) -> [0: bb5, 1: bb5, 2: bb1, otherwise: bb5]; +- switchInt(move _3) -> [0: bb4, 1: bb3, 2: bb2, otherwise: bb1]; ++ switchInt(move _3) -> [0: bb1, 1: bb1, 2: bb2, otherwise: bb1]; } bb1: { + unreachable; + } + + bb2: { StorageLive(_5); _5 = const "C"; _1 = &(*_5); StorageDead(_5); - goto -> bb4; + goto -> bb5; } - bb2: { + bb3: { StorageLive(_4); _4 = const "B(Empty)"; _1 = &(*_4); StorageDead(_4); - goto -> bb4; + goto -> bb5; } - bb3: { + bb4: { _1 = const "A(Empty)"; - goto -> bb4; + goto -> bb5; } - bb4: { + bb5: { StorageDead(_2); StorageDead(_1); _0 = const (); return; -+ } -+ -+ bb5: { -+ unreachable; } } diff --git a/tests/ui/match/borrowck-uninhabited.rs b/tests/ui/match/borrowck-uninhabited.rs index 93838ede27938..34f5e323a91e9 100644 --- a/tests/ui/match/borrowck-uninhabited.rs +++ b/tests/ui/match/borrowck-uninhabited.rs @@ -17,9 +17,11 @@ fn both_inhabited(x: &mut Result) { }; } +// this used to be accepted, even though it shouldn't fn ref_uninhabited(x: &mut Result) { match x { &mut Ok(ref mut y) => match x { + //~^ ERROR: cannot use `*x` because it was mutably borrowed &mut Err(ref mut z) => { let _y = y; let _z = z; diff --git a/tests/ui/match/borrowck-uninhabited.stderr b/tests/ui/match/borrowck-uninhabited.stderr index ee3b814da9f4a..4bbe6ecea16b9 100644 --- a/tests/ui/match/borrowck-uninhabited.stderr +++ b/tests/ui/match/borrowck-uninhabited.stderr @@ -9,6 +9,17 @@ LL | &mut Ok(ref mut y) => match x { LL | let _y = y; | - borrow later used here -error: aborting due to 1 previous error +error[E0503]: cannot use `*x` because it was mutably borrowed + --> $DIR/borrowck-uninhabited.rs:23:37 + | +LL | &mut Ok(ref mut y) => match x { + | --------- ^ use of borrowed `x.0` + | | + | `x.0` is borrowed here +... +LL | let _y = y; + | - borrow later used here + +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0503`. diff --git a/tests/ui/match/uninhabited-granular-moves.rs b/tests/ui/match/uninhabited-granular-moves.rs index 9d04fcb3259e2..f836aedbebc74 100644 --- a/tests/ui/match/uninhabited-granular-moves.rs +++ b/tests/ui/match/uninhabited-granular-moves.rs @@ -17,7 +17,7 @@ fn test1(thefoo: Foo<(Box, Box)>) { Foo::Bar((a, _)) => { } } - match thefoo { + match thefoo { //~ ERROR: use of partially moved value: `thefoo` Foo::Bar((_, a)) => { } } } @@ -38,7 +38,7 @@ fn test3(thefoo: Foo<(Box, Box)>) { Foo::Bar((a, _)) => { } Foo::Qux(_) => { } } - match thefoo { + match thefoo { //~ ERROR: use of partially moved value: `thefoo` Foo::Bar((_, a)) => { } } } diff --git a/tests/ui/match/uninhabited-granular-moves.stderr b/tests/ui/match/uninhabited-granular-moves.stderr index d32f935d3d555..e3a66db526d49 100644 --- a/tests/ui/match/uninhabited-granular-moves.stderr +++ b/tests/ui/match/uninhabited-granular-moves.stderr @@ -1,3 +1,18 @@ +error[E0382]: use of partially moved value: `thefoo` + --> $DIR/uninhabited-granular-moves.rs:20:11 + | +LL | Foo::Bar((a, _)) => { } + | - value partially moved here +... +LL | match thefoo { + | ^^^^^^ value used here after partial move + | + = note: partial move occurs because value has type `Box`, which does not implement the `Copy` trait +help: borrow this binding in the pattern to avoid moving the value + | +LL | Foo::Bar((ref a, _)) => { } + | +++ + error[E0382]: use of partially moved value: `thefoo` --> $DIR/uninhabited-granular-moves.rs:30:11 | @@ -13,6 +28,21 @@ help: borrow this binding in the pattern to avoid moving the value LL | Foo::Bar((ref a, _)) => { } | +++ -error: aborting due to 1 previous error +error[E0382]: use of partially moved value: `thefoo` + --> $DIR/uninhabited-granular-moves.rs:41:11 + | +LL | Foo::Bar((a, _)) => { } + | - value partially moved here +... +LL | match thefoo { + | ^^^^^^ value used here after partial move + | + = note: partial move occurs because value has type `Box`, which does not implement the `Copy` trait +help: borrow this binding in the pattern to avoid moving the value + | +LL | Foo::Bar((ref a, _)) => { } + | +++ + +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0382`. diff --git a/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-exhaustive.rs b/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-exhaustive.rs index b2ebab382bd93..2e40819d69ad7 100644 --- a/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-exhaustive.rs +++ b/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-exhaustive.rs @@ -14,11 +14,6 @@ enum Local { Variant(u32), } -#[non_exhaustive] -enum LocalNonExhaustive { - Variant(u32), -} - fn main() { let mut x = ExhaustiveMonovariant::Variant(1); let y = &mut x; @@ -34,11 +29,4 @@ fn main() { _ => {}, } drop(y); - let mut x = LocalNonExhaustive::Variant(1); - let y = &mut x; - match x { - LocalNonExhaustive::Variant(_) => {}, - _ => {}, - } - drop(y); } diff --git a/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-non-exhaustive.rs b/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-non-exhaustive.rs index d616f5e5e89a7..a6a369e92a6c4 100644 --- a/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-non-exhaustive.rs +++ b/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-non-exhaustive.rs @@ -6,6 +6,11 @@ extern crate monovariants; use monovariants::NonExhaustiveMonovariant; +#[non_exhaustive] +enum LocalNonExhaustive { + Variant(u32), +} + fn main() { let mut x = NonExhaustiveMonovariant::Variant(1); let y = &mut x; @@ -15,4 +20,11 @@ fn main() { _ => {}, } drop(y); + let mut x = LocalNonExhaustive::Variant(1); + let y = &mut x; + match x { //~ ERROR cannot use `x` because it was mutably borrowed + LocalNonExhaustive::Variant(_) => {}, + _ => {}, + } + drop(y); } diff --git a/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-non-exhaustive.stderr b/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-non-exhaustive.stderr index 70f5b2b84d86f..d6225adc95cb5 100644 --- a/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-non-exhaustive.stderr +++ b/tests/ui/rfcs/rfc-2008-non-exhaustive/borrowck-non-exhaustive.stderr @@ -1,5 +1,5 @@ error[E0503]: cannot use `x` because it was mutably borrowed - --> $DIR/borrowck-non-exhaustive.rs:12:11 + --> $DIR/borrowck-non-exhaustive.rs:17:11 | LL | let y = &mut x; | ------ `x` is borrowed here @@ -9,6 +9,17 @@ LL | match x { LL | drop(y); | - borrow later used here -error: aborting due to 1 previous error +error[E0503]: cannot use `x` because it was mutably borrowed + --> $DIR/borrowck-non-exhaustive.rs:25:11 + | +LL | let y = &mut x; + | ------ `x` is borrowed here +LL | match x { + | ^ use of borrowed `x` +... +LL | drop(y); + | - borrow later used here + +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0503`.