Skip to content

Commit 4f76f10

Browse files
Match ergonomics 2024: let & patterns eat &mut
1 parent 66f8770 commit 4f76f10

File tree

8 files changed

+247
-121
lines changed

8 files changed

+247
-121
lines changed

compiler/rustc_hir_typeck/src/pat.rs

Lines changed: 132 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ struct TopInfo<'tcx> {
7878
#[derive(Copy, Clone)]
7979
struct PatInfo<'tcx, 'a> {
8080
binding_mode: ByRef,
81-
max_ref_mutbl: Mutability,
81+
max_ref_mutbl: MutblCap,
8282
top_info: TopInfo<'tcx>,
8383
decl_origin: Option<DeclOrigin<'a>>,
8484

@@ -124,6 +124,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
124124
}
125125

126126
/// Mode for adjusting the expected type and binding mode.
127+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
127128
enum AdjustMode {
128129
/// Peel off all immediate reference types.
129130
Peel,
@@ -135,11 +136,44 @@ enum AdjustMode {
135136
/// and if the old biding mode was by-reference
136137
/// with mutability matching the pattern,
137138
/// mark the pattern as having consumed this reference.
138-
ResetAndConsumeRef(Mutability),
139+
///
140+
/// `Span` is that of the inside of the reference pattern
141+
ResetAndConsumeRef(Mutability, Span),
139142
/// Pass on the input binding mode and expected type.
140143
Pass,
141144
}
142145

146+
/// `ref mut` patterns (explicit or match-ergonomics)
147+
/// are not allowed behind an `&` reference.
148+
///
149+
/// This includes explicit `ref mut` behind `&` patterns
150+
/// that match against `&mut` references,
151+
/// where the code would have compiled
152+
/// had the pattern been written as `&mut`.
153+
/// However, the borrow checker will not catch
154+
/// this last case, so we need to throw an error ourselves.
155+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
156+
enum MutblCap {
157+
/// Mutability restricted to immutable;
158+
/// contained span, if present, should be shown in diagnostics as the reason.
159+
Not(Option<Span>),
160+
/// No restriction on mutability
161+
Mut,
162+
}
163+
164+
impl MutblCap {
165+
fn cap_mutbl_to_not(self, span: Option<Span>) -> Self {
166+
if self == MutblCap::Mut { MutblCap::Not(span) } else { self }
167+
}
168+
169+
fn as_mutbl(self) -> Mutability {
170+
match self {
171+
MutblCap::Not(_) => Mutability::Not,
172+
MutblCap::Mut => Mutability::Mut,
173+
}
174+
}
175+
}
176+
143177
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
144178
/// Type check the given top level pattern against the `expected` type.
145179
///
@@ -160,7 +194,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
160194
let info = TopInfo { expected, origin_expr, span };
161195
let pat_info = PatInfo {
162196
binding_mode: ByRef::No,
163-
max_ref_mutbl: Mutability::Mut,
197+
max_ref_mutbl: MutblCap::Mut,
164198
top_info: info,
165199
decl_origin,
166200
current_depth: 0,
@@ -201,8 +235,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
201235
PatKind::Never => expected,
202236
PatKind::Lit(lt) => self.check_pat_lit(pat.span, lt, expected, ti),
203237
PatKind::Range(lhs, rhs, _) => self.check_pat_range(pat.span, lhs, rhs, expected, ti),
204-
PatKind::Binding(ba, var_id, _, sub) => {
205-
self.check_pat_ident(pat, ba, var_id, sub, expected, pat_info)
238+
PatKind::Binding(ba, var_id, ident, sub) => {
239+
self.check_pat_ident(pat, ba, var_id, ident, sub, expected, pat_info)
206240
}
207241
PatKind::TupleStruct(ref qpath, subpats, ddpos) => {
208242
self.check_pat_tuple_struct(pat, qpath, subpats, ddpos, expected, pat_info)
@@ -294,20 +328,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
294328
expected: Ty<'tcx>,
295329
def_br: ByRef,
296330
adjust_mode: AdjustMode,
297-
max_ref_mutbl: Mutability,
298-
) -> (Ty<'tcx>, ByRef, Mutability, bool) {
299-
if let ByRef::Yes(mutbl) = def_br {
300-
debug_assert!(mutbl <= max_ref_mutbl);
331+
max_ref_mutbl: MutblCap,
332+
) -> (Ty<'tcx>, ByRef, MutblCap, bool) {
333+
if let ByRef::Yes(Mutability::Mut) = def_br {
334+
debug_assert!(max_ref_mutbl == MutblCap::Mut);
301335
}
302336
match adjust_mode {
303337
AdjustMode::Pass => (expected, def_br, max_ref_mutbl, false),
304-
AdjustMode::Reset => (expected, ByRef::No, Mutability::Mut, false),
305-
AdjustMode::ResetAndConsumeRef(ref_pat_mutbl) => {
306-
let mutbls_match = def_br == ByRef::Yes(ref_pat_mutbl);
338+
AdjustMode::Reset => (expected, ByRef::No, MutblCap::Mut, false),
339+
AdjustMode::ResetAndConsumeRef(ref_pat_mutbl, inner_span) => {
340+
// `&` pattern eats `&mut`
341+
let mutbls_match =
342+
if let ByRef::Yes(def_mut) = def_br { ref_pat_mutbl <= def_mut } else { false };
343+
307344
if pat.span.at_least_rust_2024() && self.tcx.features().ref_pat_eat_one_layer_2024 {
345+
let max_ref_mutbl = if ref_pat_mutbl == Mutability::Not {
346+
max_ref_mutbl.cap_mutbl_to_not(Some(pat.span.until(inner_span)))
347+
} else {
348+
max_ref_mutbl
349+
};
350+
308351
if mutbls_match {
309352
debug!("consuming inherited reference");
310-
(expected, ByRef::No, cmp::min(max_ref_mutbl, ref_pat_mutbl), true)
353+
(expected, ByRef::No, max_ref_mutbl, true)
311354
} else {
312355
let (new_ty, new_bm, max_ref_mutbl) = if ref_pat_mutbl == Mutability::Mut {
313356
self.peel_off_references(
@@ -318,7 +361,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
318361
max_ref_mutbl,
319362
)
320363
} else {
321-
(expected, def_br.cap_ref_mutability(Mutability::Not), Mutability::Not)
364+
(expected, def_br.cap_ref_mutability(Mutability::Not), max_ref_mutbl)
322365
};
323366
(new_ty, new_bm, max_ref_mutbl, false)
324367
}
@@ -385,7 +428,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
385428
// ```
386429
//
387430
// See issue #46688.
388-
PatKind::Ref(_, mutbl) => AdjustMode::ResetAndConsumeRef(*mutbl),
431+
PatKind::Ref(inner, mutbl) => AdjustMode::ResetAndConsumeRef(*mutbl, inner.span),
389432
// A `_` pattern works with any expected type, so there's no need to do anything.
390433
PatKind::Wild
391434
// A malformed pattern doesn't have an expected type, so let's just accept any type.
@@ -411,8 +454,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
411454
expected: Ty<'tcx>,
412455
mut def_br: ByRef,
413456
max_peelable_mutability: Mutability,
414-
mut max_ref_mutability: Mutability,
415-
) -> (Ty<'tcx>, ByRef, Mutability) {
457+
mut max_ref_mutability: MutblCap,
458+
) -> (Ty<'tcx>, ByRef, MutblCap) {
416459
let mut expected = self.try_structurally_resolve_type(pat.span, expected);
417460
// Peel off as many `&` or `&mut` from the scrutinee type as possible. For example,
418461
// for `match &&&mut Some(5)` the loop runs three times, aborting when it reaches
@@ -446,9 +489,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
446489
}
447490

448491
if pat.span.at_least_rust_2024() && self.tcx.features().ref_pat_eat_one_layer_2024 {
449-
def_br = def_br.cap_ref_mutability(max_ref_mutability);
492+
def_br = def_br.cap_ref_mutability(max_ref_mutability.as_mutbl());
450493
if def_br == ByRef::Yes(Mutability::Not) {
451-
max_ref_mutability = Mutability::Not;
494+
max_ref_mutability = max_ref_mutability.cap_mutbl_to_not(None);
452495
}
453496
}
454497

@@ -665,16 +708,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
665708
fn check_pat_ident(
666709
&self,
667710
pat: &'tcx Pat<'tcx>,
668-
ba: BindingMode,
711+
explicit_ba: BindingMode,
669712
var_id: HirId,
713+
ident: Ident,
670714
sub: Option<&'tcx Pat<'tcx>>,
671715
expected: Ty<'tcx>,
672716
pat_info: PatInfo<'tcx, '_>,
673717
) -> Ty<'tcx> {
674718
let PatInfo { binding_mode: def_br, top_info: ti, .. } = pat_info;
675719

676720
// Determine the binding mode...
677-
let bm = match ba {
721+
let bm = match explicit_ba {
678722
BindingMode(ByRef::No, Mutability::Mut)
679723
if !(pat.span.at_least_rust_2024()
680724
&& self.tcx.features().mut_preserve_binding_mode_2024)
@@ -690,8 +734,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
690734
BindingMode(ByRef::No, Mutability::Mut)
691735
}
692736
BindingMode(ByRef::No, mutbl) => BindingMode(def_br, mutbl),
693-
BindingMode(ByRef::Yes(_), _) => ba,
737+
BindingMode(ByRef::Yes(_), _) => explicit_ba,
694738
};
739+
740+
if bm.0 == ByRef::Yes(Mutability::Mut)
741+
&& let MutblCap::Not(Some(and_pat_span)) = pat_info.max_ref_mutbl
742+
{
743+
let mut err = struct_span_code_err!(
744+
self.tcx.dcx(),
745+
ident.span,
746+
E0596,
747+
"cannot bind with `ref mut` behind an `&` pattern"
748+
);
749+
err.span_help(and_pat_span, "change this `&` pattern to an `&mut`");
750+
err.emit();
751+
}
752+
695753
// ...and store it in a side table:
696754
self.typeck_results.borrow_mut().pat_binding_modes_mut().insert(pat.hir_id, bm);
697755

@@ -717,7 +775,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
717775
// If there are multiple arms, make sure they all agree on
718776
// what the type of the binding `x` ought to be.
719777
if var_id != pat.hir_id {
720-
self.check_binding_alt_eq_ty(ba, pat.span, var_id, local_ty, ti);
778+
self.check_binding_alt_eq_ty(explicit_ba, pat.span, var_id, local_ty, ti);
721779
}
722780

723781
if let Some(p) = sub {
@@ -2117,7 +2175,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
21172175
} else {
21182176
let tcx = self.tcx;
21192177
let expected = self.shallow_resolve(expected);
2120-
let (ref_ty, inner_ty) = match self.check_dereferenceable(pat.span, expected, inner) {
2178+
let (ref_ty, inner_ty, pat_info) = match self
2179+
.check_dereferenceable(pat.span, expected, inner)
2180+
{
21212181
Ok(()) => {
21222182
// `demand::subtype` would be good enough, but using `eqtype` turns
21232183
// out to be equally general. See (note_1) for details.
@@ -2127,42 +2187,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
21272187
// the bad interactions of the given hack detailed in (note_1).
21282188
debug!("check_pat_ref: expected={:?}", expected);
21292189
match *expected.kind() {
2130-
ty::Ref(_, r_ty, r_mutbl) if r_mutbl == mutbl => (expected, r_ty),
2190+
ty::Ref(_, r_ty, r_mutbl) if r_mutbl == mutbl => (expected, r_ty, pat_info),
2191+
2192+
// `&` pattern eats `&mut` reference
2193+
ty::Ref(_, r_ty, Mutability::Mut)
2194+
if mutbl == Mutability::Not
2195+
&& ((pat.span.at_least_rust_2024()
2196+
&& self.tcx.features().ref_pat_eat_one_layer_2024)
2197+
|| self.tcx.features().ref_pat_everywhere) =>
2198+
{
2199+
(
2200+
expected,
2201+
r_ty,
2202+
PatInfo {
2203+
max_ref_mutbl: pat_info
2204+
.max_ref_mutbl
2205+
.cap_mutbl_to_not(Some(pat.span.until(inner.span))),
2206+
..pat_info
2207+
},
2208+
)
2209+
}
2210+
2211+
_ if consumed_inherited_ref && self.tcx.features().ref_pat_everywhere => {
2212+
// We already matched against a match-ergonmics inserted reference,
2213+
// so we don't need to match against a reference from the original type.
2214+
// Save this infor for use in lowering later
2215+
self.typeck_results
2216+
.borrow_mut()
2217+
.skipped_ref_pats_mut()
2218+
.insert(pat.hir_id);
2219+
(expected, expected, pat_info)
2220+
}
2221+
21312222
_ => {
2132-
if consumed_inherited_ref && self.tcx.features().ref_pat_everywhere {
2133-
// We already matched against a match-ergonmics inserted reference,
2134-
// so we don't need to match against a reference from the original type.
2135-
// Save this infor for use in lowering later
2136-
self.typeck_results
2137-
.borrow_mut()
2138-
.skipped_ref_pats_mut()
2139-
.insert(pat.hir_id);
2140-
(expected, expected)
2141-
} else {
2142-
let inner_ty = self.next_ty_var(inner.span);
2143-
let ref_ty = self.new_ref_ty(pat.span, mutbl, inner_ty);
2144-
debug!("check_pat_ref: demanding {:?} = {:?}", expected, ref_ty);
2145-
let err = self.demand_eqtype_pat_diag(
2146-
pat.span,
2147-
expected,
2148-
ref_ty,
2149-
pat_info.top_info,
2150-
);
2223+
let inner_ty = self.next_ty_var(inner.span);
2224+
let ref_ty = self.new_ref_ty(pat.span, mutbl, inner_ty);
2225+
debug!("check_pat_ref: demanding {:?} = {:?}", expected, ref_ty);
2226+
let err = self.demand_eqtype_pat_diag(
2227+
pat.span,
2228+
expected,
2229+
ref_ty,
2230+
pat_info.top_info,
2231+
);
21512232

2152-
// Look for a case like `fn foo(&foo: u32)` and suggest
2153-
// `fn foo(foo: &u32)`
2154-
if let Some(mut err) = err {
2155-
self.borrow_pat_suggestion(&mut err, pat);
2156-
err.emit();
2157-
}
2158-
(ref_ty, inner_ty)
2233+
// Look for a case like `fn foo(&foo: u32)` and suggest
2234+
// `fn foo(foo: &u32)`
2235+
if let Some(mut err) = err {
2236+
self.borrow_pat_suggestion(&mut err, pat);
2237+
err.emit();
21592238
}
2239+
(ref_ty, inner_ty, pat_info)
21602240
}
21612241
}
21622242
}
21632243
Err(guar) => {
21642244
let err = Ty::new_error(tcx, guar);
2165-
(err, err)
2245+
(err, err, pat_info)
21662246
}
21672247
};
21682248
self.check_pat(inner, inner_ty, pat_info);

tests/ui/match/ref_pat_eat_one_layer_2024/ref_pat_eat_one_layer_2024.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ pub fn main() {
5353
if let Some(&Some(Some(&x))) = &Some(Some(&mut Some(0))) {
5454
let _: u32 = x;
5555
}
56+
if let Some(&Some(&x)) = Some(&Some(&mut 0)) {
57+
let _: u32 = x;
58+
}
59+
if let Some(&Some(x)) = &mut Some(Some(0)) {
60+
let _: u32 = x;
61+
}
5662

5763
let &mut x = &&mut 0;
5864
let _: &u32 = x;

tests/ui/match/ref_pat_eat_one_layer_2024/ref_pat_eat_one_layer_2024_fail.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,24 @@ pub fn main() {
1414
let _: &mut u32 = x;
1515
//~^ ERROR: mismatched types
1616
}
17-
if let Some(&Some(&_)) = Some(&Some(&mut 0)) {
18-
//~^ ERROR: mismatched types
19-
}
2017
if let Some(&Some(&mut _)) = &mut Some(&Some(0)) {
2118
//~^ ERROR: mismatched types
2219
}
2320
if let Some(&Some(Some((&mut _)))) = &Some(Some(&mut Some(0))) {
2421
//~^ ERROR: mismatched types
2522
}
26-
23+
if let Some(&mut Some(x)) = &Some(Some(0)) {
24+
//~^ ERROR: mismatched types
25+
}
26+
if let Some(&Some(ref mut x)) = &mut Some(Some(0)) {
27+
//~^ ERROR: cannot bind with `ref mut` behind an `&` pattern
28+
}
29+
if let &Some(Some(ref mut x)) = &mut Some(Some(0)) {
30+
//~^ ERROR: cannot bind with `ref mut` behind an `&` pattern
31+
}
32+
if let Some(&mut Some(x)) = &Some(Some(0)) {
33+
//~^ ERROR: mismatched types
34+
}
2735

2836
let &mut _= &&0;
2937
//~^ ERROR: mismatched types

0 commit comments

Comments
 (0)