rust/compiler
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
..
rustc Return ExitCode from rustc_driver::main 2026-01-23 21:04:27 +00:00
rustc_abi Rollup merge of #152469 - mu001999-contrib:cleanup/unused-features, r=nadrieril,jdonszelmann 2026-02-13 13:34:58 +01:00
rustc_arena Replace some feature(core_intrinsics) with stable hints 2026-02-08 18:21:47 +11:00
rustc_ast Allow provisional mgca syntax of type const <IDENT> = <EXPR> to be reconized. 2026-02-09 07:59:24 -08:00
rustc_ast_ir Implement &pin patterns and ref pin bindings 2025-11-10 09:57:08 +08:00
rustc_ast_lowering Feed ErrorGuaranteed from late lifetime resolution to RBV 2026-02-13 16:24:39 +00:00
rustc_ast_passes Rollup merge of #152519 - scottmcm:fix-152501, r=fmease 2026-02-13 15:19:13 +11:00
rustc_ast_pretty Allow provisional mgca syntax of type const <IDENT> = <EXPR> to be reconized. 2026-02-09 07:59:24 -08:00
rustc_attr_parsing Rollup merge of #152568 - JonathanBrouwer:port_lang, r=jdonszelmann 2026-02-13 22:26:35 -05:00
rustc_baked_icu_data Unify the configuration of the compiler docs 2025-11-05 11:25:27 +00:00
rustc_borrowck Rollup merge of #152587 - lqd:tiny-things, r=jackh726 2026-02-13 22:26:36 -05:00
rustc_builtin_macros Auto merge of #152517 - jhpratt:rollup-fGRcId6, r=jhpratt 2026-02-12 06:57:59 +00:00
rustc_codegen_cranelift Rollup merge of #152573 - usamoi:escape-2, r=bjorn3 2026-02-13 22:26:33 -05:00
rustc_codegen_gcc Auto merge of #152533 - flip1995:clippy-subtree-update, r=Manishearth 2026-02-13 18:09:00 +00:00
rustc_codegen_llvm Rollup merge of #152594 - folkertdev:va-list-wasm64, r=alexcrichton 2026-02-13 22:26:34 -05:00
rustc_codegen_ssa Rollup merge of #152568 - JonathanBrouwer:port_lang, r=jdonszelmann 2026-02-13 22:26:35 -05:00
rustc_const_eval Rollup merge of #152356 - JonathanBrouwer:inline_diag4, r=jdonszelmann 2026-02-13 13:35:01 +01:00
rustc_data_structures Rollup merge of #152469 - mu001999-contrib:cleanup/unused-features, r=nadrieril,jdonszelmann 2026-02-13 13:34:58 +01:00
rustc_driver Unify the configuration of the compiler docs 2025-11-05 11:25:27 +00:00
rustc_driver_impl Rollup merge of #152250 - JonathanBrouwer:convert_finish, r=jdonszelmann 2026-02-08 19:15:25 +01:00
rustc_error_codes Port #[lang] to the new attribute parsers 2026-02-13 16:04:19 +00:00
rustc_error_messages Remove SubdiagMessage in favour of the identical DiagMessage 2026-02-10 09:13:45 +00:00
rustc_errors Rollup merge of #152356 - JonathanBrouwer:inline_diag4, r=jdonszelmann 2026-02-13 13:35:01 +01:00
rustc_expand Port #[rustc_diagnostic_item] to the new attribute parsers 2026-02-13 09:46:47 +00:00
rustc_feature Remove deprecated_safe and its corresponding feature gate 2026-02-12 22:16:28 +01:00
rustc_fs_util Avoid using env::temp when linking a binary 2025-12-24 06:41:42 +00:00
rustc_graphviz change non-canonical clone impl to {*self}, fix some doc comments 2025-12-20 13:46:22 +00:00
rustc_hashes some cleanups in compiler 2025-10-12 08:08:30 +00:00
rustc_hir Rollup merge of #152568 - JonathanBrouwer:port_lang, r=jdonszelmann 2026-02-13 22:26:35 -05:00
rustc_hir_analysis Rollup merge of #152076 - eggyal:undeclared-object-lifetime, r=fmease 2026-02-13 22:26:32 -05:00
rustc_hir_id
rustc_hir_pretty Allow provisional mgca syntax of type const <IDENT> = <EXPR> to be reconized. 2026-02-09 07:59:24 -08:00
rustc_hir_typeck Auto merge of #150681 - meithecatte:always-discriminate, r=JonathanBrouwer,Nadrieril 2026-02-14 12:53:09 +00:00
rustc_incremental Replace parallel! macro with par_fns function and rename join to par_join 2026-02-12 12:20:18 +01:00
rustc_index Remove unused features in compiler 2026-02-13 09:25:39 +08:00
rustc_index_macros
rustc_infer Stabilize assert_matches 2026-02-11 14:13:44 +01:00
rustc_interface Move rustc_query_system::query::dep_graph to rustc_middle. 2026-02-14 18:46:05 +11:00
rustc_lexer Remove rustc_lexer::cursor module. 2026-01-27 12:06:55 +11:00
rustc_lint Rollup merge of #152356 - JonathanBrouwer:inline_diag4, r=jdonszelmann 2026-02-13 13:35:01 +01:00
rustc_lint_defs Add FCW for derive helper attributes that will conflict with built-in attributes 2026-02-10 19:39:19 +00:00
rustc_llvm Fix multi-cgu+debug builds using autodiff by delaying autodiff till lto 2026-02-11 14:08:56 -05:00
rustc_log Restrict sysroot crate imports to those defined in this repo. 2025-10-15 13:17:25 +01:00
rustc_macros Rollup merge of #152356 - JonathanBrouwer:inline_diag4, r=jdonszelmann 2026-02-13 13:35:01 +01:00
rustc_metadata Rollup merge of #152469 - mu001999-contrib:cleanup/unused-features, r=nadrieril,jdonszelmann 2026-02-13 13:34:58 +01:00
rustc_middle Move rustc_query_system::query::dep_graph to rustc_middle. 2026-02-14 18:46:05 +11:00
rustc_mir_build Auto merge of #150681 - meithecatte:always-discriminate, r=JonathanBrouwer,Nadrieril 2026-02-14 12:53:09 +00:00
rustc_mir_dataflow Stabilize assert_matches 2026-02-11 14:13:44 +01:00
rustc_mir_transform Rollup merge of #152356 - JonathanBrouwer:inline_diag4, r=jdonszelmann 2026-02-13 13:35:01 +01:00
rustc_monomorphize Rollup merge of #152329 - Zoxc:simple-parallel-macro, r=nnethercote 2026-02-13 15:19:12 +11:00
rustc_next_trait_solver Rollup merge of #152383 - RalfJung:BikeshedGuaranteedNoDrop, r=TaKO8Ki 2026-02-11 13:48:50 +01:00
rustc_parse Rollup merge of #152469 - mu001999-contrib:cleanup/unused-features, r=nadrieril,jdonszelmann 2026-02-13 13:34:58 +01:00
rustc_parse_format rustc_parse_format: improve diagnostics for unsupported debug = syntax 2026-02-06 00:44:03 +05:30
rustc_passes Rollup merge of #152568 - JonathanBrouwer:port_lang, r=jdonszelmann 2026-02-13 22:26:35 -05:00
rustc_pattern_analysis Rollup merge of #152469 - mu001999-contrib:cleanup/unused-features, r=nadrieril,jdonszelmann 2026-02-13 13:34:58 +01:00
rustc_privacy Rollup merge of #151887 - scottmcm:homogeneous-try-in-compiler, r=jackh726 2026-02-08 16:58:23 +11:00
rustc_proc_macro Update literal-escaper version to 0.0.7 2026-01-08 14:10:33 +01:00
rustc_public Rollup merge of #150271 - Jamesbarford:chore/refactor-struct-placeholder-pt2, r=lcnr 2026-01-29 17:47:29 +01:00
rustc_public_bridge Remove unused features in compiler 2026-02-13 09:25:39 +08:00
rustc_query_impl Move rustc_query_system::query::dep_graph to rustc_middle. 2026-02-14 18:46:05 +11:00
rustc_query_system Move rustc_query_system::query::dep_graph to rustc_middle. 2026-02-14 18:46:05 +11:00
rustc_resolve Rollup merge of #152471 - JohnTitor:sugg-assoc-items-from-bounds, r=estebank 2026-02-13 22:26:33 -05:00
rustc_sanitizers Include assoc const projections in CFI trait object 2026-02-01 22:45:43 +08:00
rustc_serialize Remove unused features in compiler 2026-02-13 09:25:39 +08:00
rustc_session Support serializing CodegenContext 2026-02-12 12:44:15 +00:00
rustc_span Rollup merge of #151142 - SpriteOvO:type-info-adt, r=oli-obk 2026-02-12 00:04:15 +01:00
rustc_symbol_mangling Stabilize assert_matches 2026-02-11 14:13:44 +01:00
rustc_target Rollup merge of #152552 - androm3da:hexagon-hvx-abi-rules, r=madsmtm 2026-02-13 15:19:15 +11:00
rustc_thread_pool Fix typos and grammar in compiler and build documentation 2026-02-10 10:22:05 -05:00
rustc_trait_selection Rollup merge of #152392 - TaKO8Ki:missing-generics-in-traits-used-in-const, r=jieyouxu 2026-02-13 13:35:01 +01:00
rustc_traits implied bounds comments 2026-01-19 16:08:54 +00:00
rustc_transmute Clean up src/dst transmute mess. 2026-01-12 09:22:58 +11:00
rustc_ty_utils ICE to delayed bug 2026-02-13 13:50:43 +00:00
rustc_type_ir Remove unused features in compiler 2026-02-13 09:25:39 +08:00
rustc_type_ir_macros Provide an extended framework for type visit, for use in rust-analyzer 2025-12-16 01:47:28 +02:00
rustc_windows_rc [win] Use find-msvc-tools instead of cc to find the linker and rc on Windows 2025-09-19 12:00:30 -07:00