Skip to content

Commit 6b31250

Browse files
committed
ExprUseVisitor: properly report discriminant reads
This should solve the "can't find the upvar" ICEs that resulted from `maybe_read_scrutinee` being unfit for purpose.
1 parent e5ce129 commit 6b31250

File tree

2 files changed

+234
-11
lines changed

2 files changed

+234
-11
lines changed

compiler/rustc_hir_typeck/src/expr_use_visitor.rs

+92-11
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,21 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
948948
}
949949

950950
/// The core driver for walking a pattern
951+
///
952+
/// This should mirror how pattern-matching gets lowered to MIR, as
953+
/// otherwise lowering will ICE when trying to resolve the upvars.
954+
///
955+
/// However, it is okay to approximate it here by doing *more* accesses
956+
/// than the actual MIR builder will, which is useful when some checks
957+
/// are too cumbersome to perform here. For example, if only after type
958+
/// inference it becomes clear that only one variant of an enum is
959+
/// inhabited, and therefore a read of the discriminant is not necessary,
960+
/// `walk_pat` will have over-approximated the necessary upvar capture
961+
/// granularity. (Or, at least, that's what the code seems to be saying.
962+
/// I didn't bother trying to craft an example where this actually happens).
963+
///
964+
/// Do note that discrepancies like these do still create weird language
965+
/// semantics, and should be avoided if possible.
951966
fn walk_pat(
952967
&self,
953968
discr_place: &PlaceWithHirId<'tcx>,
@@ -958,6 +973,11 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
958973

959974
let tcx = self.cx.tcx();
960975
self.cat_pattern(discr_place.clone(), pat, &mut |place, pat| {
976+
debug!("walk_pat: pat.kind={:?}", pat.kind);
977+
let read_discriminant = || {
978+
self.delegate.borrow_mut().borrow(place, discr_place.hir_id, BorrowKind::Immutable);
979+
};
980+
961981
match pat.kind {
962982
PatKind::Binding(_, canonical_id, ..) => {
963983
debug!("walk_pat: binding place={:?} pat={:?}", place, pat);
@@ -980,11 +1000,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
9801000
// binding when lowering pattern guards to ensure that the guard does not
9811001
// modify the scrutinee.
9821002
if has_guard {
983-
self.delegate.borrow_mut().borrow(
984-
place,
985-
discr_place.hir_id,
986-
BorrowKind::Immutable,
987-
);
1003+
read_discriminant();
9881004
}
9891005

9901006
// It is also a borrow or copy/move of the value being matched.
@@ -1016,13 +1032,70 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
10161032
PatKind::Never => {
10171033
// A `!` pattern always counts as an immutable read of the discriminant,
10181034
// even in an irrefutable pattern.
1019-
self.delegate.borrow_mut().borrow(
1020-
place,
1021-
discr_place.hir_id,
1022-
BorrowKind::Immutable,
1023-
);
1035+
read_discriminant();
1036+
}
1037+
PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, span }) => {
1038+
// A `Path` pattern is just a name like `Foo`. This is either a
1039+
// named constant or else it refers to an ADT variant
1040+
1041+
let res = self.cx.typeck_results().qpath_res(qpath, *hir_id);
1042+
match res {
1043+
Res::Def(DefKind::Const, _) | Res::Def(DefKind::AssocConst, _) => {
1044+
// Named constants have to be equated with the value
1045+
// being matched, so that's a read of the value being matched.
1046+
//
1047+
// FIXME: Does the MIR code skip this read when matching on a ZST?
1048+
// If so, we can also skip it here.
1049+
read_discriminant();
1050+
}
1051+
_ => {
1052+
// Otherwise, this is a struct/enum variant, and so it's
1053+
// only a read if we need to read the discriminant.
1054+
if self.is_multivariant_adt(place.place.ty(), *span) {
1055+
read_discriminant();
1056+
}
1057+
}
1058+
}
1059+
}
1060+
PatKind::Expr(_) | PatKind::Range(..) => {
1061+
// When matching against a literal or range, we need to
1062+
// borrow the place to compare it against the pattern.
1063+
//
1064+
// FIXME: What if the type being matched only has one
1065+
// possible value?
1066+
// FIXME: What if the range is the full range of the type
1067+
// and doesn't actually require a discriminant read?
1068+
read_discriminant();
1069+
}
1070+
PatKind::Struct(..) | PatKind::TupleStruct(..) => {
1071+
if self.is_multivariant_adt(place.place.ty(), pat.span) {
1072+
read_discriminant();
1073+
}
1074+
}
1075+
PatKind::Slice(lhs, wild, rhs) => {
1076+
// We don't need to test the length if the pattern is `[..]`
1077+
if matches!((lhs, wild, rhs), (&[], Some(_), &[]))
1078+
// Arrays have a statically known size, so
1079+
// there is no need to read their length
1080+
|| place.place.ty().peel_refs().is_array()
1081+
{
1082+
// No read necessary
1083+
} else {
1084+
read_discriminant();
1085+
}
1086+
}
1087+
PatKind::Or(_)
1088+
| PatKind::Box(_)
1089+
| PatKind::Ref(..)
1090+
| PatKind::Guard(..)
1091+
| PatKind::Tuple(..)
1092+
| PatKind::Wild
1093+
| PatKind::Err(_) => {
1094+
// If the PatKind is Or, Box, Ref, Guard, or Tuple, the relevant accesses
1095+
// are made later as these patterns contains subpatterns.
1096+
// If the PatKind is Wild or Err, they are made when processing the other patterns
1097+
// being examined
10241098
}
1025-
_ => {}
10261099
}
10271100

10281101
Ok(())
@@ -1858,6 +1931,14 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
18581931
Ok(())
18591932
}
18601933

1934+
/// Checks whether a type has multiple variants, and therefore, whether a
1935+
/// read of the discriminant might be necessary. Note that the actual MIR
1936+
/// builder code does a more specific check, filtering out variants that
1937+
/// happen to be uninhabited.
1938+
///
1939+
/// Here, we cannot perform such an accurate checks, because querying
1940+
/// whether a type is inhabited requires that it has been fully inferred,
1941+
/// which cannot be guaranteed at this point.
18611942
fn is_multivariant_adt(&self, ty: Ty<'tcx>, span: Span) -> bool {
18621943
if let ty::Adt(def, _) = self.cx.try_structurally_resolve_type(span, ty).kind() {
18631944
// Note that if a non-exhaustive SingleVariant is defined in another crate, we need
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//@ edition:2024
2+
//@ check-pass
3+
4+
const X: u32 = 0;
5+
6+
fn match_literal(x: (u32, u32, u32)) {
7+
let _ = || {
8+
let ((0, a, _) | (_, _, a)) = x;
9+
a
10+
};
11+
}
12+
13+
fn match_range(x: (u32, u32, u32)) {
14+
let _ = || {
15+
let ((0..5, a, _) | (_, _, a)) = x;
16+
a
17+
};
18+
}
19+
20+
fn match_const(x: (u32, u32, u32)) {
21+
let _ = || {
22+
let ((X, a, _) | (_, _, a)) = x;
23+
a
24+
};
25+
}
26+
27+
enum Choice { A, B }
28+
29+
fn match_unit_variant(x: (Choice, u32, u32)) {
30+
let _ = || {
31+
let ((Choice::A, a, _) | (Choice::B, _, a)) = x;
32+
a
33+
};
34+
}
35+
36+
struct Unit;
37+
38+
fn match_unit_struct(mut x: (Unit, u32)) {
39+
let r = &mut x.0;
40+
let _ = || {
41+
let (Unit, a) = x;
42+
a
43+
};
44+
45+
let _ = *r;
46+
}
47+
48+
enum Also { Unit }
49+
50+
fn match_unit_enum(mut x: (Also, u32)) {
51+
let r = &mut x.0;
52+
let _ = || {
53+
let (Also::Unit, a) = x;
54+
a
55+
};
56+
57+
let _ = *r;
58+
}
59+
60+
enum TEnum {
61+
A(u32),
62+
B(u32),
63+
}
64+
65+
enum SEnum {
66+
A { a: u32 },
67+
B { a: u32 },
68+
}
69+
70+
fn match_tuple_enum(x: TEnum) {
71+
let _ = || {
72+
let (TEnum::A(a) | TEnum::B(a)) = x;
73+
a
74+
};
75+
}
76+
77+
fn match_struct_enum(x: SEnum) {
78+
let _ = || {
79+
let (SEnum::A { a } | SEnum::B { a }) = x;
80+
a
81+
};
82+
}
83+
84+
enum TSingle {
85+
A(u32, u32),
86+
}
87+
88+
enum SSingle {
89+
A { a: u32, b: u32 },
90+
}
91+
92+
struct TStruct(u32, u32);
93+
struct SStruct { a: u32, b: u32 }
94+
95+
fn match_struct(mut x: SStruct) {
96+
let r = &mut x.a;
97+
let _ = || {
98+
let SStruct { b, .. } = x;
99+
b
100+
};
101+
102+
let _ = *r;
103+
}
104+
105+
fn match_tuple_struct(mut x: TStruct) {
106+
let r = &mut x.0;
107+
let _ = || {
108+
let TStruct(_, a) = x;
109+
a
110+
};
111+
112+
let _ = *r;
113+
}
114+
115+
fn match_singleton(mut x: SSingle) {
116+
let SSingle::A { a: ref mut r, .. } = x;
117+
let _ = || {
118+
let SSingle::A { b, .. } = x;
119+
b
120+
};
121+
122+
let _ = *r;
123+
}
124+
125+
fn match_tuple_singleton(mut x: TSingle) {
126+
let TSingle::A(ref mut r, _) = x;
127+
let _ = || {
128+
let TSingle::A(_, a) = x;
129+
a
130+
};
131+
132+
let _ = *r;
133+
}
134+
135+
fn match_slice(x: (&[u32], u32, u32)) {
136+
let _ = || {
137+
let (([], a, _) | ([_, ..], _, a)) = x;
138+
a
139+
};
140+
}
141+
142+
fn main() {}

0 commit comments

Comments
 (0)