rust/tests/ui/match
bors f8463896a9 Auto merge of #150681 - meithecatte:always-discriminate, r=JonathanBrouwer,Nadrieril
Make operational semantics of pattern matching independent of crate and module

The question of "when does matching an enum against a pattern of one of its variants read its discriminant" is currently an underspecified part of the language, causing weird behavior around borrowck, drop order, and UB.

Of course, in the common cases, the discriminant must be read to distinguish the variant of the enum, but currently the following exceptions are implemented:

1. If the enum has only one variant, we currently skip the discriminant read.
     - This has the advantage that single-variant enums behave the same way as structs in this regard.
     - However, it means that if the discriminant exists in the layout, we can't say that this discriminant being invalid is UB. This makes me particularly uneasy in its interactions with niches – consider the following example ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=5904a6155cbdd39af4a2e7b1d32a9b1a)), where miri currently doesn't detect any UB (because the semantics don't specify any):

        <details><summary>Example 1</summary>

        ```rust
        #![allow(dead_code)]
        use core::mem::{size_of, transmute};
        
        #[repr(u8)]
        enum Inner {
            X(u8),
        }
        
        enum Outer {
            A(Inner),
            B(u8),
        }
        
        fn f(x: &Inner) {
            match x {
                Inner::X(v) => {
                    println!("{v}");
                }
            }
        }
        
        fn main() {
            assert_eq!(size_of::<Inner>(), 2);
            assert_eq!(size_of::<Outer>(), 2);
            let x = Outer::B(42);
            let y = &x;
            f(unsafe { transmute(y) });
        }
        ```

      </details>

2. For the purpose of the above, enums with marked with `#[non_exhaustive]` are always considered to have multiple variants when observed from foreign crates, but the actual number of variants is considered in the current crate.
    - This means that whether code has UB can depend on which crate it is in: https://github.com/rust-lang/rust/issues/147722
    - In another case of `#[non_exhaustive]` affecting the runtime semantics, its presence or absence can change what gets captured by a closure, and by extension, the drop order: https://github.com/rust-lang/rust/issues/147722#issuecomment-3674554872
    - Also at the above link, there is an example where removing `#[non_exhaustive]` can cause borrowck to suddenly start failing in another crate.
3. Moreover, we currently make a more specific check: we only read the discriminant if there is more than one *inhabited* variant in the enum.
    - This means that the semantics can differ between `foo<!>`, and a copy of `foo` where `T` was manually replaced with `!`: rust-lang/rust#146803
    - Moreover, due to the privacy rules for inhabitedness, it means that the semantics of code can depend on the *module* in which it is located.
    - Additionally, this inhabitedness rule is even uglier due to the fact that closure capture analysis needs to happen before we can determine whether types are uninhabited, which means that whether the discriminant read happens has a different answer specifically for capture analysis.
    - For the two above points, see the following example ([playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=a07d8a3ec0b31953942e96e2130476d9)):

        <details><summary>Example 2</summary>

        ```rust
        #![allow(unused)]
        
        mod foo {
            enum Never {}
            struct PrivatelyUninhabited(Never);
            pub enum A {
                V(String, String),
                Y(PrivatelyUninhabited),
            }
            
            fn works(mut x: A) {
                let a = match x {
                    A::V(ref mut a, _) => a,
                    _ => unreachable!(),
                };
                
                let b = match x {
                    A::V(_, ref mut b) => b,
                    _ => unreachable!(),
                };
            
                a.len(); b.len();
            }
            
            fn fails(mut x: A) {
                let mut f = || match x {
                    A::V(ref mut a, _) => (),
                    _ => unreachable!(),
                };
                
                let mut g = || match x {
                    A::V(_, ref mut b) => (),
                    _ => unreachable!(),
                };
            
                f(); g();
            }
        }
        
        use foo::A;
        
        fn fails(mut x: A) {
            let a = match x {
                A::V(ref mut a, _) => a,
                _ => unreachable!(),
            };
            
            let b = match x {
                A::V(_, ref mut b) => b,
                _ => unreachable!(),
            };
        
            a.len(); b.len();
        }
        
        
        fn fails2(mut x: A) {
            let mut f = || match x {
                A::V(ref mut a, _) => (),
                _ => unreachable!(),
            };
            
            let mut g = || match x {
                A::V(_, ref mut b) => (),
                _ => unreachable!(),
            };
        
            f(); g();
        }
        ```

        </details>

In light of the above, and following the discussion at rust-lang/rust#138961 and rust-lang/rust#147722, this PR ~~makes it so that, operationally, matching on an enum *always* reads its discriminant.~~ introduces the following changes to this behavior:

 - matching on a `#[non_exhaustive]` enum will always introduce a discriminant read, regardless of whether the enum is from an external crate
 - uninhabited variants now count just like normal ones, and don't get skipped in the checks

As per the discussion below, the resolution for point (1) above is that it should land as part of a separate PR, so that the subtler decision can be more carefully considered.

Note that this is a breaking change, due to the aforementioned changes in borrow checking behavior, new UB (or at least UB newly detected by miri), as well as drop order around closure captures. However, it seems to me that the combination of this PR with rust-lang/rust#138961 should have smaller real-world impact than rust-lang/rust#138961 by itself.

Fixes rust-lang/rust#142394 
Fixes rust-lang/rust#146590
Fixes rust-lang/rust#146803 (though already marked as duplicate)
Fixes parts of rust-lang/rust#147722
Fixes rust-lang/miri#4778

r? @Nadrieril @RalfJung 

@rustbot label +A-closures +A-patterns +T-opsem +T-lang
2026-02-14 12:53:09 +00:00
..
auxiliary Move /src/test to /tests 2023-01-11 09:32:08 +00:00
postfix-match UI tests: add missing diagnostic kinds where possible 2025-04-08 23:06:31 +03:00
borrowck-uninhabited.rs discriminant reads: make semantics independent of module/crate 2026-01-15 19:12:13 +01:00
borrowck-uninhabited.stderr discriminant reads: make semantics independent of module/crate 2026-01-15 19:12:13 +01:00
closure-in-match-guard.rs clean up some tests 2026-01-31 14:01:54 +01:00
const_non_normal_zst_ref_pattern.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
dont-highlight-diverging-arms.rs Remove a suggestion that is redundant 2024-02-15 17:20:44 +00:00
dont-highlight-diverging-arms.stderr Remove a suggestion that is redundant 2024-02-15 17:20:44 +00:00
enum-and-break-in-match-issue-41213.rs Quickfix //@ check-pass is enough 2025-02-04 21:42:43 +05:30
expr-match-panic-fn.rs tests: use needs-subprocess instead of ignore-{wasm32,emscripten,sgx} 2025-01-23 20:51:29 +08:00
expr-match-panic.rs tests: use needs-subprocess instead of ignore-{wasm32,emscripten,sgx} 2025-01-23 20:51:29 +08:00
expr_before_ident_pat.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
expr_before_ident_pat.stderr Change wording 2024-04-29 14:53:38 +02:00
guard-pattern-ordering-14865.rs comments 2025-08-05 19:34:46 +05:00
guards-parenthesized-and.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
guards.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
innocent-looking-match-crash-46964.rs Rehome tests/ui/issues/ tests [1/?] 2025-07-24 17:01:44 -04:00
intended-binding-pattern-is-const.rs UI tests: add missing diagnostic kinds where possible 2025-04-08 23:06:31 +03:00
intended-binding-pattern-is-const.stderr Point at const when intended binding fall-through pattern is a const 2024-11-17 23:40:00 +00:00
issue-5530.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-11319.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
issue-11319.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
issue-11940.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-12552.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
issue-12552.stderr Modify primary span label for E0308 2023-01-30 20:12:19 +00:00
issue-18060.rs Allow lint where we don't care 2024-03-09 01:13:42 +01:00
issue-26251.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-26996.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-27021.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-33498.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-36401.rs Move tests 2024-03-03 16:30:48 -03:00
issue-37598.rs Move tests 2024-03-03 16:30:48 -03:00
issue-42679.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-46920-byte-array-patterns.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-56685.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
issue-56685.stderr Trim suggestion parts to the subset that is purely additive 2025-02-14 00:44:10 -08:00
issue-70972-dyn-trait.rs Only rewrite valtree-constants to patterns and keep other constants opaque 2023-05-31 14:02:57 +00:00
issue-70972-dyn-trait.stderr Specify type kind of constant that can't be used in patterns 2024-12-04 20:29:36 +00:00
issue-72680.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-72896-non-partial-eq-const.rs Reword message for non-structural type constant in pattern 2024-12-04 20:29:36 +00:00
issue-72896-non-partial-eq-const.stderr Add more context to fall-through "const pattern of non-structural type" error 2024-12-04 20:29:36 +00:00
issue-74050-end-span.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
issue-74050-end-span.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
issue-82392.rs Explicitly annotate edition for unpretty=expanded and unpretty=hir tests 2025-04-16 11:10:10 +02:00
issue-82392.stdout Fix pretty print tests with #[prelude_import] 2026-02-12 17:43:58 +00:00
issue-82866.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
issue-82866.stderr Merge E0412 into E0425 2025-12-02 18:25:13 +00:00
issue-84434.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-91058.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
issue-91058.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
issue-92100.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
issue-92100.stderr Apply review suggestions 2025-12-17 14:53:14 +00:00
issue-113012.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-114691.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
issue-115681.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
large-match-mir-gen.rs clean up some tests 2026-01-31 14:01:54 +01:00
match-arm-resolving-to-never.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-arm-resolving-to-never.stderr Only point out non-diverging arms for match suggestions 2024-02-15 15:44:46 +00:00
match-bot-panic.rs tests: use needs-subprocess instead of ignore-{wasm32,emscripten,sgx} 2025-01-23 20:51:29 +08:00
match-const-tuple-type-mismatch.rs Cleaned up some tests 2025-12-16 02:10:08 +09:00
match-const-tuple-type-mismatch.stderr Cleaned up some tests 2025-12-16 02:10:08 +09:00
match-disc-bot.rs tests: use needs-subprocess instead of ignore-{wasm32,emscripten,sgx} 2025-01-23 20:51:29 +08:00
match-float.rs f16,f128: Resolve cfg-releated instances of FIXME(f16_f128) 2026-01-22 23:41:57 -06:00
match-fn-call.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-fn-call.stderr Update tests for new TRPL chapter order 2024-11-23 08:57:25 -07:00
match-ill-type2.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-ill-type2.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-incompat-type-semi.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-incompat-type-semi.stderr Filter empty lines, comments and delimiters from previous to last multiline span rendering 2024-12-12 23:36:27 +00:00
match-join.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-join.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-large-array.rs clean up some tests 2026-01-22 19:50:00 +01:00
match-nested-enum-box-3121.rs renamed few tests 2026-02-04 04:45:52 +00:00
match-no-arms-unreachable-after.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-no-arms-unreachable-after.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-on-negative-integer-ranges.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
match-option-result-mismatch.rs cleaned up some tests 2025-12-07 10:37:45 +09:00
match-option-result-mismatch.stderr cleaned up some tests 2025-12-07 10:37:45 +09:00
match-pattern-field-mismatch-2.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-pattern-field-mismatch-2.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-pattern-field-mismatch.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-pattern-field-mismatch.stderr Show diff suggestion format on verbose replacement 2025-02-10 20:21:39 +00:00
match-range-char-const.rs Cleaned up some tests 2025-12-16 02:10:08 +09:00
match-range-fail-2.rs reword error for invalid range patterns 2025-12-02 18:28:05 +00:00
match-range-fail-2.stderr reword error for invalid range patterns 2025-12-02 18:28:05 +00:00
match-range-fail.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-range-fail.stderr Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-ref-mut-invariance.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-ref-mut-invariance.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-ref-mut-let-invariance.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-ref-mut-let-invariance.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-ref-mut-stability.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
match-ref-option-pattern.rs Cleaned up some tests 2025-12-08 07:06:13 +09:00
match-stack-overflow-72933-.rs Cleaned up some tests 2025-12-08 07:06:13 +09:00
match-static-pattern.rs Cleaned up some tests 2025-12-16 02:10:08 +09:00
match-static-pattern.stderr Cleaned up some tests 2025-12-16 02:10:08 +09:00
match-struct.rs compiletest: Make diagnostic kind mandatory on line annotations 2025-04-30 10:44:24 +03:00
match-struct.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-tag-nullary.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-tag-nullary.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-tag-unary.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-tag-unary.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-tail-expr-never-type-error.rs Account for ! arm in tail match expr 2023-11-27 16:19:02 +00:00
match-tail-expr-never-type-error.stderr Account for ! arm in tail match expr 2023-11-27 16:19:02 +00:00
match-tuple-slice.rs clean up some tests 2026-01-22 19:50:00 +01:00
match-type-err-first-arm.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-type-err-first-arm.stderr Filter empty lines, comments and delimiters from previous to last multiline span rendering 2024-12-12 23:36:27 +00:00
match-unresolved-one-arm.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-unresolved-one-arm.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-usize-min-max-pattern.rs cleaned up some tests 2025-12-13 00:54:20 +09:00
match-vec-mismatch-2.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
match-vec-mismatch-2.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
match-wildcards.rs tests: use needs-subprocess instead of ignore-{wasm32,emscripten,sgx} 2025-01-23 20:51:29 +08:00
match_non_exhaustive.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
match_non_exhaustive.stderr Tweak spans for "adt defined here" note 2023-11-03 18:26:16 +01:00
mismatched-types-in-match-5358.rs Add test batch 1 2025-08-27 00:23:26 -04:00
mismatched-types-in-match-5358.stderr Add test batch 1 2025-08-27 00:23:26 -04:00
mismatched-types-in-match-7867.rs Add test batch 2 2025-08-27 15:06:05 -04:00
mismatched-types-in-match-7867.stderr Add test batch 2 2025-08-27 15:06:05 -04:00
multiple-refutable-patterns-13867.rs comments 2025-08-05 19:34:46 +05:00
non-first-arm-doesnt-match-expected-return-type.rs Point at return type when it influences non-first match arm 2023-08-14 21:43:56 +00:00
non-first-arm-doesnt-match-expected-return-type.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
option-result-mismatch-11844.rs comments 2025-07-31 21:25:49 +05:00
option-result-mismatch-11844.stderr comments 2025-07-31 21:25:49 +05:00
option-result-type-param-mismatch-13466.rs Gate 2018 UI tests 2025-11-27 14:13:58 -05:00
option-result-type-param-mismatch-13466.stderr Gate 2018 UI tests 2025-11-27 14:13:58 -05:00
overeager-sub-match-pruning-13027.rs comments 2025-07-31 21:25:49 +05:00
pattern-deref-miscompile.rs [AUTO-GENERATED] Migrate ui tests from // to //@ directives 2024-02-16 20:02:50 +00:00
privately-uninhabited-issue-137999.rs mir_build: consider privacy when checking for irrefutable patterns 2025-03-07 16:16:41 +01:00
privately-uninhabited-issue-137999.stderr mir_build: consider privacy when checking for irrefutable patterns 2025-03-07 16:16:41 +01:00
single-line.rs Move /src/test to /tests 2023-01-11 09:32:08 +00:00
single-line.stderr Show number in error message even for one error 2023-11-24 19:15:52 +01:00
slice-move-out-error-12567.rs comments 2025-07-31 21:25:49 +05:00
slice-move-out-error-12567.stderr comments 2025-07-31 21:25:49 +05:00
struct-reference-patterns-12285.rs comments 2025-07-31 21:25:49 +05:00
tuple-usize-pattern-14393.rs comments 2025-08-05 19:34:46 +05:00
uninhabited-granular-moves.rs discriminant reads: make semantics independent of module/crate 2026-01-15 19:12:13 +01:00
uninhabited-granular-moves.stderr discriminant reads: make semantics independent of module/crate 2026-01-15 19:12:13 +01:00
unit-pattern-error-on-tuple-and-struct-variants-63983.rs Add test batch 4 2025-10-11 21:59:51 -04:00
unit-pattern-error-on-tuple-and-struct-variants-63983.stderr Add test batch 4 2025-10-11 21:59:51 -04:00
unreachable-pattern-if-variant-not-imported-19100.fixed Rehome tests/ui/issues/ tests [3/?] 2025-08-04 16:43:53 -04:00
unreachable-pattern-if-variant-not-imported-19100.rs Rehome tests/ui/issues/ tests [3/?] 2025-08-04 16:43:53 -04:00
unreachable-pattern-if-variant-not-imported-19100.stderr Rehome tests/ui/issues/ tests [3/?] 2025-08-04 16:43:53 -04:00
validate-range-endpoints.rs reword error for invalid range patterns 2025-12-02 18:28:05 +00:00
validate-range-endpoints.stderr reword error for invalid range patterns 2025-12-02 18:28:05 +00:00