Skip to content

Commit 630b4e8

Browse files
committed
Auto merge of rust-lang#138961 - meithecatte:expr-use-visitor, r=<try>
ExprUseVisitor: murder maybe_read_scrutinee in cold blood This PR fixes rust-lang#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! ## Background The current upvar inference code handles patterns in two parts: - `ExprUseVisitor::walk_pat` finds the *bindings* being done by the pattern and captures the relevant parts - `ExprUseVisitor::maybe_read_scrutinee` determines whether matching against the pattern will at any point require inspecting a discriminant, and if so, captures *the entire scrutinee*. It also has some weird logic around bindings, deciding to also capture the entire scrutinee if *pretty much any binding exists in the pattern*, with some weird behavior like rust-lang#137553. Nevertheless, something like `|| let (a, _) = x;` will only capture `x.0`, because `maybe_read_scrutinee` does not run for irrefutable patterns at all. This causes issues like rust-lang#137467, where the closure wouldn't be capturing enough, because an irrefutable or-pattern can still require inspecting a discriminant, and the match lowering would then panic, because it couldn't find an appropriate upvar in the closure. My thesis is that this is not a reasonable implementation. To that end, I intend to merge the functionality of both these parts into `walk_pat`, which will bring upvar inference closer to what the MIR lowering actually needs – both in making sure that necessary variables get captured, fixing rust-lang#137467, and in reducing the cases where redundant variables do – fixing rust-lang#137553. This PR introduces the necessary logic into `walk_pat`, fixing rust-lang#137467. A subsequent PR will remove `maybe_read_scrutinee` entirely, which should now be redundant, fixing rust-lang#137553. The latter is still pending, as my current revision doesn't handle opaque types correctly for some reason I haven't looked into yet. ## The 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))); } ``` The issue is that, to determine that matching against `Err` here doesn't require inspecting the discriminant, we need to query the `InhabitedPredicate` of the types involved. However, as upvar inference is done during typechecking, the relevant type might not yet be fully inferred. Because of this, performing such a check hits this assertion: https://github.com/rust-lang/rust/blob/43f0014ef0f242418674f49052ed39b70f73bc1c/compiler/rustc_middle/src/ty/inhabitedness/mod.rs#L121 The code used to compile fine, but only because the compiler incorrectly assumed that patterns used within a `let` cannot possibly be inspecting any discriminants. ## Is the breaking change necessary? One other option would be to double down, and introduce a deliberate semantics difference between `let $pat = $expr;` and `match $expr { $pat => ... }`, that syntactically determines whether the pattern is in an irrefutable position, instead of querying the types. **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; ``` The cost here, would be the complexity added with very little benefit. ## Other notes - I performed various cleanups while working on this. The last commit of the PR is the interesting one. - Due to the temporary duplication of logic between `maybe_read_scrutinee` and `walk_pat`, some of the `#[rustc_capture_analysis]` tests report duplicate messages before deduplication. This is harmless.
2 parents 19cab6b + 7d5a892 commit 630b4e8

22 files changed

+520
-363
lines changed

compiler/rustc_hir_typeck/src/expr_use_visitor.rs

+162-251
Large diffs are not rendered by default.

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14471447
pub(crate) fn try_structurally_resolve_type(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> {
14481448
let ty = self.resolve_vars_with_obligations(ty);
14491449

1450+
debug!("next_solver = {:?}", self.next_trait_solver());
14501451
if self.next_trait_solver()
14511452
&& let ty::Alias(..) = ty.kind()
14521453
{

compiler/rustc_hir_typeck/src/upvar.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
//! from there).
1919
//!
2020
//! The fact that we are inferring borrow kinds as we go results in a
21-
//! semi-hacky interaction with mem-categorization. In particular,
22-
//! mem-categorization will query the current borrow kind as it
23-
//! categorizes, and we'll return the *current* value, but this may get
21+
//! semi-hacky interaction with the way `ExprUseVisitor` is computing
22+
//! `Place`s. In particular, it will query the current borrow kind as it
23+
//! goes, and we'll return the *current* value, but this may get
2424
//! adjusted later. Therefore, in this module, we generally ignore the
25-
//! borrow kind (and derived mutabilities) that are returned from
26-
//! mem-categorization, since they may be inaccurate. (Another option
25+
//! borrow kind (and derived mutabilities) that `ExprUseVisitor` returns
26+
//! within `Place`s, since they may be inaccurate. (Another option
2727
//! would be to use a unification scheme, where instead of returning a
2828
//! concrete borrow kind like `ty::ImmBorrow`, we return a
2929
//! `ty::InferBorrow(upvar_id)` or something like that, but this would

compiler/rustc_middle/src/hir/place.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ pub struct Projection<'tcx> {
5353
pub kind: ProjectionKind,
5454
}
5555

56-
/// A `Place` represents how a value is located in memory.
56+
/// A `Place` represents how a value is located in memory. This does not
57+
/// always correspond to a syntactic place expression. For example, when
58+
/// processing a pattern, a `Place` can be used to refer to the sub-value
59+
/// currently being inspected.
5760
///
5861
/// This is an HIR version of [`rustc_middle::mir::Place`].
5962
#[derive(Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
@@ -67,7 +70,10 @@ pub struct Place<'tcx> {
6770
pub projections: Vec<Projection<'tcx>>,
6871
}
6972

70-
/// A `PlaceWithHirId` represents how a value is located in memory.
73+
/// A `PlaceWithHirId` represents how a value is located in memory. This does not
74+
/// always correspond to a syntactic place expression. For example, when
75+
/// processing a pattern, a `Place` can be used to refer to the sub-value
76+
/// currently being inspected.
7177
///
7278
/// This is an HIR version of [`rustc_middle::mir::Place`].
7379
#[derive(Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)]
File renamed without changes.

tests/crashes/119786-2.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//@ known-bug: #119786
2+
//@ edition:2021
3+
4+
fn enum_upvar() {
5+
type T = impl Copy;
6+
let foo: T = Some((1u32, 2u32));
7+
let x = move || {
8+
match foo {
9+
None => (),
10+
Some(_) => (),
11+
}
12+
};
13+
}
14+
15+
pub fn main() {}

tests/crashes/119786-3.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//@ known-bug: #119786
2+
//@ edition:2021
3+
4+
fn enum_upvar() {
5+
type T = impl Copy;
6+
let foo: T = Some((1u32, 2u32));
7+
let x = move || {
8+
match foo {
9+
None => (),
10+
Some((a, b)) => (),
11+
}
12+
};
13+
}
14+
15+
pub fn main() {}

tests/crashes/137467-1.rs

-17
This file was deleted.

tests/crashes/137467-2.rs

-18
This file was deleted.

tests/crashes/137467-3.rs

-8
This file was deleted.

tests/ui/closures/2229_closure_analysis/capture-enums.rs

+2
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ fn multi_variant_enum() {
2222
//~| Min Capture analysis includes:
2323
if let Info::Point(_, _, str) = point {
2424
//~^ NOTE: Capturing point[] -> Immutable
25+
//~| NOTE: Capturing point[] -> Immutable
2526
//~| NOTE: Capturing point[(2, 0)] -> ByValue
2627
//~| NOTE: Min Capture point[] -> ByValue
2728
println!("{}", str);
2829
}
2930

3031
if let Info::Meta(_, v) = meta {
3132
//~^ NOTE: Capturing meta[] -> Immutable
33+
//~| NOTE: Capturing meta[] -> Immutable
3234
//~| NOTE: Capturing meta[(1, 1)] -> ByValue
3335
//~| NOTE: Min Capture meta[] -> ByValue
3436
println!("{:?}", v);

tests/ui/closures/2229_closure_analysis/capture-enums.stderr

+18-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ LL | let c = #[rustc_capture_analysis]
99
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
1010

1111
error[E0658]: attributes on expressions are experimental
12-
--> $DIR/capture-enums.rs:48:13
12+
--> $DIR/capture-enums.rs:50:13
1313
|
1414
LL | let c = #[rustc_capture_analysis]
1515
| ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -34,18 +34,28 @@ note: Capturing point[] -> Immutable
3434
|
3535
LL | if let Info::Point(_, _, str) = point {
3636
| ^^^^^
37+
note: Capturing point[] -> Immutable
38+
--> $DIR/capture-enums.rs:23:41
39+
|
40+
LL | if let Info::Point(_, _, str) = point {
41+
| ^^^^^
3742
note: Capturing point[(2, 0)] -> ByValue
3843
--> $DIR/capture-enums.rs:23:41
3944
|
4045
LL | if let Info::Point(_, _, str) = point {
4146
| ^^^^^
4247
note: Capturing meta[] -> Immutable
43-
--> $DIR/capture-enums.rs:30:35
48+
--> $DIR/capture-enums.rs:31:35
49+
|
50+
LL | if let Info::Meta(_, v) = meta {
51+
| ^^^^
52+
note: Capturing meta[] -> Immutable
53+
--> $DIR/capture-enums.rs:31:35
4454
|
4555
LL | if let Info::Meta(_, v) = meta {
4656
| ^^^^
4757
note: Capturing meta[(1, 1)] -> ByValue
48-
--> $DIR/capture-enums.rs:30:35
58+
--> $DIR/capture-enums.rs:31:35
4959
|
5060
LL | if let Info::Meta(_, v) = meta {
5161
| ^^^^
@@ -67,13 +77,13 @@ note: Min Capture point[] -> ByValue
6777
LL | if let Info::Point(_, _, str) = point {
6878
| ^^^^^
6979
note: Min Capture meta[] -> ByValue
70-
--> $DIR/capture-enums.rs:30:35
80+
--> $DIR/capture-enums.rs:31:35
7181
|
7282
LL | if let Info::Meta(_, v) = meta {
7383
| ^^^^
7484

7585
error: First Pass analysis includes:
76-
--> $DIR/capture-enums.rs:52:5
86+
--> $DIR/capture-enums.rs:54:5
7787
|
7888
LL | / || {
7989
LL | |
@@ -85,13 +95,13 @@ LL | | };
8595
| |_____^
8696
|
8797
note: Capturing point[(2, 0)] -> ByValue
88-
--> $DIR/capture-enums.rs:55:47
98+
--> $DIR/capture-enums.rs:57:47
8999
|
90100
LL | let SingleVariant::Point(_, _, str) = point;
91101
| ^^^^^
92102

93103
error: Min Capture analysis includes:
94-
--> $DIR/capture-enums.rs:52:5
104+
--> $DIR/capture-enums.rs:54:5
95105
|
96106
LL | / || {
97107
LL | |
@@ -103,7 +113,7 @@ LL | | };
103113
| |_____^
104114
|
105115
note: Min Capture point[(2, 0)] -> ByValue
106-
--> $DIR/capture-enums.rs:55:47
116+
--> $DIR/capture-enums.rs:57:47
107117
|
108118
LL | let SingleVariant::Point(_, _, str) = point;
109119
| ^^^^^

tests/ui/closures/2229_closure_analysis/match/match-edge-cases_2.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ error[E0505]: cannot move out of `ts` because it is borrowed
44
LL | let _b = || { match ts {
55
| -- -- borrow occurs due to use in closure
66
| |
7-
| borrow of `ts` occurs here
7+
| borrow of `ts.x` occurs here
88
...
99
LL | let mut mut_ts = ts;
1010
| ^^ move out of `ts` occurs here

tests/ui/closures/2229_closure_analysis/match/patterns-capture-analysis.rs

+8-9
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,8 @@ fn test_6_should_capture_single_variant() {
6464
//~^ First Pass analysis includes:
6565
//~| Min Capture analysis includes:
6666
match variant {
67-
//~^ NOTE: Capturing variant[] -> Immutable
68-
//~| NOTE: Capturing variant[(0, 0)] -> Immutable
69-
//~| NOTE: Min Capture variant[] -> Immutable
67+
//~^ NOTE: Capturing variant[(0, 0)] -> Immutable
68+
//~| NOTE: Min Capture variant[(0, 0)] -> Immutable
7069
SingleVariant::Points(a) => {
7170
println!("{:?}", a);
7271
}
@@ -149,8 +148,8 @@ fn test_7_should_capture_slice_len() {
149148
//~^ First Pass analysis includes:
150149
//~| Min Capture analysis includes:
151150
match slice {
152-
//~^ NOTE: Capturing slice[] -> Immutable
153-
//~| NOTE: Min Capture slice[] -> Immutable
151+
//~^ NOTE: Capturing slice[Deref] -> Immutable
152+
//~| NOTE: Min Capture slice[Deref] -> Immutable
154153
[_,_,_] => {},
155154
_ => {}
156155
}
@@ -161,8 +160,8 @@ fn test_7_should_capture_slice_len() {
161160
//~^ First Pass analysis includes:
162161
//~| Min Capture analysis includes:
163162
match slice {
164-
//~^ NOTE: Capturing slice[] -> Immutable
165-
//~| NOTE: Min Capture slice[] -> Immutable
163+
//~^ NOTE: Capturing slice[Deref] -> Immutable
164+
//~| NOTE: Min Capture slice[Deref] -> Immutable
166165
[] => {},
167166
_ => {}
168167
}
@@ -173,8 +172,8 @@ fn test_7_should_capture_slice_len() {
173172
//~^ First Pass analysis includes:
174173
//~| Min Capture analysis includes:
175174
match slice {
176-
//~^ NOTE: Capturing slice[] -> Immutable
177-
//~| NOTE: Min Capture slice[] -> Immutable
175+
//~^ NOTE: Capturing slice[Deref] -> Immutable
176+
//~| NOTE: Min Capture slice[Deref] -> Immutable
178177
[_, .. ,_] => {},
179178
_ => {}
180179
}

0 commit comments

Comments
 (0)