rust/compiler
bors ed0006a7ba Auto merge of #138961 - meithecatte:expr-use-visitor, r=Nadrieril,traviscross,ChayimFriedman2
Make closure capturing have consistent and correct behaviour around patterns

Reference PR:

- https://github.com/rust-lang/reference/pull/1837

This PR has two goals:
- firstly, it fixes rust-lang/rust#137467. In order to do so, it needs to introduce a small breaking change surrounding the interaction of closure captures with matching against enums with uninhabited variants. Yes – to fix an ICE!
    - this also fixes rust-lang/rust#138973, a slightly different case with the same root cause.
    - likewise, fixes rust-lang/rust#140011.
- secondly, it fixes rust-lang/rust#137553, making the closure capturing rules consistent between `let` patterns and `match` patterns. This is new insta-stable behavior.

## Background

This change concerns how precise closure captures interact with patterns. As a little known feature, patterns that require inspecting only part of a value will only cause that part of the value to get captured:

```rust
fn main() {
    let mut a = (21, 37);
    // only captures a.0, writing to a.1 does not invalidate the closure
    let mut f = || {
        let (ref mut x, _) = a;
        *x = 42;
    };
    a.1 = 69;
    f();
}
```

I was not able to find any discussion of this behavior being introduced, or discussion of its edge-cases, but it is [documented in the Rust reference](https://doc.rust-lang.org/reference/types/closure.html#r-type.closure.capture.precision.wildcard).

The currently stable behavior is as follows:
- if any pattern contains a binding, the place it binds gets captured (implemented in current `walk_pat`)
- patterns in refutable positions (`match`, `if let`, `let ... else`, but not destructuring `let` or destructuring function parameters) get processed as follows (`maybe_read_scrutinee`):
    - if matching against the pattern will at any point require inspecting a discriminant, or it includes a variable binding not followed by an ``@`-pattern,` capture *the entire scrutinee* by reference

You will note that this behavior is quite weird and it's hard to imagine a sensible rationale for at least some of its aspects. It has the following issues:
- firstly, it assumes that matching against an irrefutable pattern cannot possibly require inspecting any discriminants. With or-patterns, this isn't true, and it is the cause of the rust-lang/rust#137467 ICE.
- secondly, the presence of an ``@`-pattern` doesn't really have any semantics by itself. This is the weird behavior tracked as rust-lang/rust#137553.
- thirdly, the behavior is different between pattern-matching done through `let` and pattern-matching done through `match` – which is a superficial syntactic difference

This PR aims to address all of the above issues. The new behavior is as follows:
- like before, if a pattern contains a binding, the place it binds gets captured as required by the binding mode
- if matching against the pattern requires inspecting a disciminant, the place whose discriminant needs to be inspected gets captured by reference

"requires inspecting a discriminant" is also used here to mean "compare something with a constant" and other such decisions. For types other than ADTs, the details are not interesting and aren't changing.

## The breaking change

During closure capture analysis, matching an `enum` against a constructor is considered to require inspecting a discriminant if the `enum` has more than one variant. Notably, this is the case even if all the other variants happen to be uninhabited. This is motivated by implementation difficulties involved in querying whether types are inhabited before we're done with type inference – without moving mountains to make it happen, you hit this assert: 43f0014ef0/compiler/rustc_middle/src/ty/inhabitedness/mod.rs (L121)

Now, because the previous implementation did not concern itself with capturing the discriminants for irrefutable patterns at all, this is a breaking change – the following example, adapted from the testsuite, compiles on current stable, but will not compile with this PR:

```rust
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Void {}

pub fn main() {
    let mut r = Result::<Void, (u32, u32)>::Err((0, 0));
    let mut f = || {
        let Err((ref mut a, _)) = r;
        *a = 1;
    };
    let mut g = || {
    //~^ ERROR: cannot borrow `r` as mutable more than once at a time
        let Err((_, ref mut b)) = r;
        *b = 2;
    };
    f();
    g();
    assert_eq!(r, Err((1, 2)));
}
```

## Is the breaking change necessary?

One other option would be to double down, and introduce a set of syntactic rules for determining whether a sub-pattern is in an irrefutable position, instead of querying the types and checking how many variants there are.

**This would not eliminate the breaking change,** but it would limit it to more contrived examples, such as

```rust
let ((true, Err((ref mut a, _, _))) | (false, Err((_, ref mut a, _)))) = x;
```

In this example, the `Err`s would not be considered in an irrefutable position, because they are part of an or-pattern. However, current stable would treat this just like a tuple `(bool, (T, U, _))`.

While introducing such a distinction would limit the impact, I would say that the added complexity would not be commensurate with the benefit it introduces.

## The new insta-stable behavior

If a pattern in a `match` expression or similar has parts it will never read, this part will not be captured anymore:

```rust
fn main() {
    let mut a = (21, 37);
    // now only captures a.0, instead of the whole a
    let mut f = || {
        match a {
            (ref mut x, _) => *x = 42,
        }
    };
    a.1 = 69;
    f();
}
```

Note that this behavior was pretty much already present, but only accessible with this One Weird Trick™:

```rust
fn main() {
    let mut a = (21, 37);
    // both stable and this PR only capture a.0, because of the no-op `@-pattern`
    let mut f = || {
        match a {
            (ref mut x @ _, _) => *x = 42,
        }
    };
    a.1 = 69;
    f();
}
```

## The second, more practically-relevant breaking change

After running crater, we have discovered that the aforementioned insta-stable behavior, where sometimes closures will now capture less, can also manifest as a breaking change. This is because it is possible that previously a closure would capture an entire struct by-move, and now it'll start capturing only part of it – some by move, and some by reference. This then causes the closure to have a more restrictive lifetime than it did previously.

See:
- https://github.com/rust-lang/rust/pull/138961#issuecomment-2761888557
- https://github.com/EC-labs/cec-assignment/pull/1
- https://github.com/tryandromeda/andromeda/pull/43

## Implementation notes

The PR has two main commits:
- "ExprUseVisitor: properly report discriminant reads" makes `walk_pat` perform all necessary capturing. This is the part that fixes rust-lang/rust#137467.
- "ExprUseVisitor: remove maybe_read_scrutinee" removes the unnecessary "capture the entire scrutinee" behavior, fixing rust-lang/rust#137553.

The new logic stops making the distinction between one particular example that used to work, and another ICE, tracked as rust-lang/rust#119786. As this requires an unstable feature, I am leaving this as future work.
2025-12-18 03:54:48 +00:00
..
rustc Auto merge of #148925 - madsmtm:jemalloc-perf, r=Kobzol 2025-11-23 20:34:07 +00:00
rustc_abi abi: Display bound on TyAbiInterface 2025-12-16 11:01:26 +00:00
rustc_arena fix 2025-11-27 17:55:34 +07:00
rustc_ast Remove deny of manual-let-else 2025-12-16 08:42:04 -08:00
rustc_ast_ir Implement &pin patterns and ref pin bindings 2025-11-10 09:57:08 +08:00
rustc_ast_lowering Port #[rustc_legacy_const_generics] to use attribute parser 2025-12-16 20:29:01 +01:00
rustc_ast_passes hir/trait_sel: prohibit scalable vectors in types 2025-12-16 11:00:12 +00:00
rustc_ast_pretty EII ast changes 2025-12-12 11:17:33 +01:00
rustc_attr_parsing Port #[rustc_lint_query_instability] to parsed attribute 2025-12-17 18:28:37 +01:00
rustc_baked_icu_data Unify the configuration of the compiler docs 2025-11-05 11:25:27 +00:00
rustc_borrowck Remove deny of manual-let-else 2025-12-16 08:42:04 -08:00
rustc_builtin_macros Auto merge of #146348 - jdonszelmann:eiiv3, r=lcnr,oli-obk 2025-12-14 04:20:26 +00:00
rustc_codegen_cranelift Rollup merge of #149950 - WaffleLapkin:inlines-ur-mu-into-asm, r=jdonszelmann 2025-12-16 14:40:44 +11:00
rustc_codegen_gcc codegen: implement repr(scalable) 2025-12-16 11:00:12 +00:00
rustc_codegen_llvm Rollup merge of #150008 - androm3da:bcain/va_arg_rs, r=folkertdev 2025-12-17 12:49:20 +01:00
rustc_codegen_ssa debuginfo: no spill <vscale x N x i1> for N!=16 2025-12-16 11:00:12 +00:00
rustc_const_eval Rollup merge of #150063 - workingjubilee:remove-let-else-deny, r=Kivooeo 2025-12-16 20:21:12 +01:00
rustc_data_structures Auto merge of #149273 - bjorn3:crate_locator_improvements, r=petrochenkov 2025-12-14 09:16:11 +00:00
rustc_driver Unify the configuration of the compiler docs 2025-11-05 11:25:27 +00:00
rustc_driver_impl Overhaul filename handling for cross-compiler consistency 2025-12-12 07:33:09 +01:00
rustc_error_codes Rollup merge of #149949 - JonathanBrouwer:error_cleanup, r=jdonszelmann 2025-12-14 20:04:56 +01:00
rustc_error_messages Remove unused pop_span_label method 2025-11-21 14:16:12 +00:00
rustc_errors remove fixme & update stderr files 2025-12-16 13:23:48 +00:00
rustc_expand Auto merge of #149709 - Urgau:overhaul-filenames, r=davidtwco 2025-12-13 14:32:09 +00:00
rustc_feature attr: parse rustc_scalable_vector(N) 2025-12-16 11:00:11 +00:00
rustc_fluent_macro move and rename proc_macro::tracked_{env::var,path::path} 2025-11-26 22:44:25 +01:00
rustc_fs_util Revert introduction of [workspace.dependencies]. 2025-09-02 19:12:54 +10:00
rustc_graphviz Allow internal_features lint in doc tests 2025-11-05 11:25:29 +00:00
rustc_hashes some cleanups in compiler 2025-10-12 08:08:30 +00:00
rustc_hir Rollup merge of #150095 - Bryntet:parse_internal_rustc_attributes, r=JonathanBrouwer 2025-12-17 23:31:21 +01:00
rustc_hir_analysis Rollup merge of #150063 - workingjubilee:remove-let-else-deny, r=Kivooeo 2025-12-16 20:21:12 +01:00
rustc_hir_id rustc_hir_id: Add a comment explaining why the crate exists 2025-08-20 15:04:00 -07:00
rustc_hir_pretty remove support for type-of 2025-11-25 10:19:44 +01:00
rustc_hir_typeck Rewrite the comment on is_multivariant_adt 2025-12-17 20:47:48 +01:00
rustc_incremental Unify the configuration of the compiler docs 2025-11-05 11:25:27 +00:00
rustc_index Unify the configuration of the compiler docs 2025-11-05 11:25:27 +00:00
rustc_index_macros Revert introduction of [workspace.dependencies]. 2025-09-02 19:12:54 +10:00
rustc_infer Moved struct Placeholder<T> 2025-12-09 13:40:18 +00:00
rustc_interface Rollup merge of #150032 - Kivooeo:annotate-snippets-stable, r=Muscraft 2025-12-16 20:21:10 +01:00
rustc_lexer Update memchr to 2.7.6 2025-09-26 17:53:49 +02:00
rustc_lint Port #[rustc_lint_query_instability] to parsed attribute 2025-12-17 18:28:37 +01:00
rustc_lint_defs declare_lint_pass for INLINE_ALWAYS_MISMATCHING_TARGET_FEATURES 2025-12-12 13:25:21 +00:00
rustc_llvm feat: dlopen Enzyme 2025-12-16 00:31:32 +09: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 #149400 - Skgland:tracked_mod, r=Amanieu 2025-12-09 17:36:48 +01:00
rustc_metadata Rollup merge of #149919 - GuillaumeGomez:doc-metadata-encoding, r=lqd 2025-12-17 18:46:17 +01:00
rustc_middle Rollup merge of #150000 - Bryntet:brynte/parse_legacy_const_generic_args, r=jonathanbrouwer,jdonszelmann 2025-12-16 23:10:10 -05:00
rustc_mir_build Auto merge of #138961 - meithecatte:expr-use-visitor, r=Nadrieril,traviscross,ChayimFriedman2 2025-12-18 03:54:48 +00:00
rustc_mir_dataflow Use let...else instead of match foo { ... _ => return }; and if let ... else return 2025-12-12 17:52:39 +00:00
rustc_mir_transform mir_transform: prohibit scalable vectors in async 2025-12-16 11:00:12 +00:00
rustc_monomorphize mono: require target feature for scalable vectors 2025-12-16 11:01:26 +00:00
rustc_next_trait_solver revert one change from rustc_next_trait_solver 2025-12-12 17:55:26 +00:00
rustc_parse Auto merge of #146348 - jdonszelmann:eiiv3, r=lcnr,oli-obk 2025-12-14 04:20:26 +00:00
rustc_parse_format Allow internal_features lint in doc tests 2025-11-05 11:25:29 +00:00
rustc_passes Port #[rustc_lint_query_instability] to parsed attribute 2025-12-17 18:28:37 +01:00
rustc_pattern_analysis fix: Do not ICE when missing match arm with ill-formed subty is met 2025-11-13 01:36:35 +09:00
rustc_privacy Unify the configuration of the compiler docs 2025-11-05 11:25:27 +00:00
rustc_proc_macro Revert introduction of [workspace.dependencies]. 2025-09-02 19:12:54 +10:00
rustc_public codegen: implement repr(scalable) 2025-12-16 11:00:12 +00:00
rustc_public_bridge Overhaul filename handling for cross-compiler consistency 2025-12-12 07:33:09 +01:00
rustc_query_impl Lock shards while collecting active jobs. 2025-11-14 09:01:22 +08:00
rustc_query_system Also check in case it tries to mark red node as green 2025-12-12 16:44:17 +03:00
rustc_resolve Rollup merge of #150000 - Bryntet:brynte/parse_legacy_const_generic_args, r=jonathanbrouwer,jdonszelmann 2025-12-16 23:10:10 -05:00
rustc_sanitizers Rollup merge of #144936 - rcvalle:rust-cfi-fix-144641, r=lcnr 2025-10-28 20:39:32 +11:00
rustc_serialize Allow internal_features lint in doc tests 2025-11-05 11:25:29 +00:00
rustc_session Avoid unhelpful suggestion when crate name is invalid 2025-12-16 20:43:26 -08:00
rustc_span Rollup merge of #150033 - izagawd:try_as_dyn, r=oli-obk 2025-12-16 20:21:10 +01:00
rustc_symbol_mangling Rollup merge of #148452 - Fulgen301:pdb-large-symbols-v0, r=jackh726 2025-11-26 23:32:05 +11:00
rustc_target abi: Display bound on TyAbiInterface 2025-12-16 11:01:26 +00:00
rustc_thread_pool some cleanups in compiler 2025-10-12 08:08:30 +00:00
rustc_trait_selection Auto merge of #149442 - chenyukang:yukang-fix-mark-span-note-144304, r=estebank 2025-12-16 16:06:43 +00:00
rustc_traits add const_of_item query and use it in normalization 2025-11-08 13:50:47 -05:00
rustc_transmute prefer to use repeat_n over repeat and take 2025-10-09 01:24:55 +08:00
rustc_ty_utils Auto merge of #143924 - davidtwco:sve-infrastructure, r=workingjubilee 2025-12-16 12:53:53 +00:00
rustc_type_ir Rollup merge of #150033 - izagawd:try_as_dyn, r=oli-obk 2025-12-16 20:21:10 +01: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