Skip to content

Commit 659b70e

Browse files
Match ergonomics 2024: let & patterns eat &mut
1 parent 20aa2d8 commit 659b70e

8 files changed

+250
-124
lines changed

compiler/rustc_hir_typeck/src/pat.rs

Lines changed: 135 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ struct TopInfo<'tcx> {
8080
#[derive(Copy, Clone)]
8181
struct PatInfo<'tcx, 'a> {
8282
binding_mode: ByRef,
83-
max_ref_mutbl: Mutability,
83+
max_ref_mutbl: MutblCap,
8484
top_info: TopInfo<'tcx>,
8585
decl_origin: Option<DeclOrigin<'a>>,
8686

@@ -126,6 +126,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
126126
}
127127

128128
/// Mode for adjusting the expected type and binding mode.
129+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129130
enum AdjustMode {
130131
/// Peel off all immediate reference types.
131132
Peel,
@@ -137,11 +138,44 @@ enum AdjustMode {
137138
/// and if the old biding mode was by-reference
138139
/// with mutability matching the pattern,
139140
/// mark the pattern as having consumed this reference.
140-
ResetAndConsumeRef(Mutability),
141+
///
142+
/// `Span` is that of the inside of the reference pattern
143+
ResetAndConsumeRef(Mutability, Span),
141144
/// Pass on the input binding mode and expected type.
142145
Pass,
143146
}
144147

148+
/// `ref mut` patterns (explicit or match-ergonomics)
149+
/// are not allowed behind an `&` reference.
150+
///
151+
/// This includes explicit `ref mut` behind `&` patterns
152+
/// that match against `&mut` references,
153+
/// where the code would have compiled
154+
/// had the pattern been written as `&mut`.
155+
/// However, the borrow checker will not catch
156+
/// this last case, so we need to throw an error ourselves.
157+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158+
enum MutblCap {
159+
/// Mutability restricted to immutable;
160+
/// contained span, if present, should be shown in diagnostics as the reason.
161+
Not(Option<Span>),
162+
/// No restriction on mutability
163+
Mut,
164+
}
165+
166+
impl MutblCap {
167+
fn cap_mutbl_to_not(self, span: Option<Span>) -> Self {
168+
if self == MutblCap::Mut { MutblCap::Not(span) } else { self }
169+
}
170+
171+
fn as_mutbl(self) -> Mutability {
172+
match self {
173+
MutblCap::Not(_) => Mutability::Not,
174+
MutblCap::Mut => Mutability::Mut,
175+
}
176+
}
177+
}
178+
145179
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
146180
/// Type check the given top level pattern against the `expected` type.
147181
///
@@ -162,7 +196,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
162196
let info = TopInfo { expected, origin_expr, span };
163197
let pat_info = PatInfo {
164198
binding_mode: ByRef::No,
165-
max_ref_mutbl: Mutability::Mut,
199+
max_ref_mutbl: MutblCap::Mut,
166200
top_info: info,
167201
decl_origin,
168202
current_depth: 0,
@@ -203,8 +237,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
203237
PatKind::Never => expected,
204238
PatKind::Lit(lt) => self.check_pat_lit(pat.span, lt, expected, ti),
205239
PatKind::Range(lhs, rhs, _) => self.check_pat_range(pat.span, lhs, rhs, expected, ti),
206-
PatKind::Binding(ba, var_id, _, sub) => {
207-
self.check_pat_ident(pat, ba, var_id, sub, expected, pat_info)
240+
PatKind::Binding(ba, var_id, ident, sub) => {
241+
self.check_pat_ident(pat, ba, var_id, ident, sub, expected, pat_info)
208242
}
209243
PatKind::TupleStruct(ref qpath, subpats, ddpos) => {
210244
self.check_pat_tuple_struct(pat, qpath, subpats, ddpos, expected, pat_info)
@@ -296,20 +330,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
296330
expected: Ty<'tcx>,
297331
def_br: ByRef,
298332
adjust_mode: AdjustMode,
299-
max_ref_mutbl: Mutability,
300-
) -> (Ty<'tcx>, ByRef, Mutability, bool) {
301-
if let ByRef::Yes(mutbl) = def_br {
302-
debug_assert!(mutbl <= max_ref_mutbl);
333+
max_ref_mutbl: MutblCap,
334+
) -> (Ty<'tcx>, ByRef, MutblCap, bool) {
335+
if let ByRef::Yes(Mutability::Mut) = def_br {
336+
debug_assert!(max_ref_mutbl == MutblCap::Mut);
303337
}
304338
match adjust_mode {
305339
AdjustMode::Pass => (expected, def_br, max_ref_mutbl, false),
306-
AdjustMode::Reset => (expected, ByRef::No, Mutability::Mut, false),
307-
AdjustMode::ResetAndConsumeRef(ref_pat_mutbl) => {
308-
let mutbls_match = def_br == ByRef::Yes(ref_pat_mutbl);
340+
AdjustMode::Reset => (expected, ByRef::No, MutblCap::Mut, false),
341+
AdjustMode::ResetAndConsumeRef(ref_pat_mutbl, inner_span) => {
342+
// `&` pattern eats `&mut`
343+
let mutbls_match =
344+
if let ByRef::Yes(def_mut) = def_br { ref_pat_mutbl <= def_mut } else { false };
345+
309346
if pat.span.at_least_rust_2024() && self.tcx.features().ref_pat_eat_one_layer_2024 {
347+
let max_ref_mutbl = if ref_pat_mutbl == Mutability::Not {
348+
max_ref_mutbl.cap_mutbl_to_not(Some(pat.span.until(inner_span)))
349+
} else {
350+
max_ref_mutbl
351+
};
352+
310353
if mutbls_match {
311354
debug!("consuming inherited reference");
312-
(expected, ByRef::No, cmp::min(max_ref_mutbl, ref_pat_mutbl), true)
355+
(expected, ByRef::No, max_ref_mutbl, true)
313356
} else {
314357
let (new_ty, new_bm, max_ref_mutbl) = if ref_pat_mutbl == Mutability::Mut {
315358
self.peel_off_references(
@@ -320,7 +363,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
320363
max_ref_mutbl,
321364
)
322365
} else {
323-
(expected, def_br.cap_ref_mutability(Mutability::Not), Mutability::Not)
366+
(expected, def_br.cap_ref_mutability(Mutability::Not), max_ref_mutbl)
324367
};
325368
(new_ty, new_bm, max_ref_mutbl, false)
326369
}
@@ -387,7 +430,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
387430
// ```
388431
//
389432
// See issue #46688.
390-
PatKind::Ref(_, mutbl) => AdjustMode::ResetAndConsumeRef(*mutbl),
433+
PatKind::Ref(inner, mutbl) => AdjustMode::ResetAndConsumeRef(*mutbl, inner.span),
391434
// A `_` pattern works with any expected type, so there's no need to do anything.
392435
PatKind::Wild
393436
// A malformed pattern doesn't have an expected type, so let's just accept any type.
@@ -413,8 +456,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
413456
expected: Ty<'tcx>,
414457
mut def_br: ByRef,
415458
max_peelable_mutability: Mutability,
416-
mut max_ref_mutability: Mutability,
417-
) -> (Ty<'tcx>, ByRef, Mutability) {
459+
mut max_ref_mutability: MutblCap,
460+
) -> (Ty<'tcx>, ByRef, MutblCap) {
418461
let mut expected = self.try_structurally_resolve_type(pat.span, expected);
419462
// Peel off as many `&` or `&mut` from the scrutinee type as possible. For example,
420463
// for `match &&&mut Some(5)` the loop runs three times, aborting when it reaches
@@ -448,9 +491,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
448491
}
449492

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

@@ -667,16 +710,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
667710
fn check_pat_ident(
668711
&self,
669712
pat: &'tcx Pat<'tcx>,
670-
ba: BindingMode,
713+
explicit_ba: BindingMode,
671714
var_id: HirId,
715+
ident: Ident,
672716
sub: Option<&'tcx Pat<'tcx>>,
673717
expected: Ty<'tcx>,
674718
pat_info: PatInfo<'tcx, '_>,
675719
) -> Ty<'tcx> {
676720
let PatInfo { binding_mode: def_br, top_info: ti, .. } = pat_info;
677721

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

@@ -719,7 +777,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
719777
// If there are multiple arms, make sure they all agree on
720778
// what the type of the binding `x` ought to be.
721779
if var_id != pat.hir_id {
722-
self.check_binding_alt_eq_ty(ba, pat.span, var_id, local_ty, ti);
780+
self.check_binding_alt_eq_ty(explicit_ba, pat.span, var_id, local_ty, ti);
723781
}
724782

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

2153-
// Look for a case like `fn foo(&foo: u32)` and suggest
2154-
// `fn foo(foo: &u32)`
2155-
if let Some(mut err) = err {
2156-
self.borrow_pat_suggestion(&mut err, pat);
2157-
err.emit();
2158-
}
2159-
(ref_ty, inner_ty)
2234+
// Look for a case like `fn foo(&foo: u32)` and suggest
2235+
// `fn foo(foo: &u32)`
2236+
if let Some(mut err) = err {
2237+
self.borrow_pat_suggestion(&mut err, pat);
2238+
err.emit();
21602239
}
2240+
(ref_ty, inner_ty, pat_info)
21612241
}
21622242
}
21632243
}
21642244
Err(guar) => {
21652245
let err = Ty::new_error(tcx, guar);
2166-
(err, err)
2246+
(err, err, pat_info)
21672247
}
21682248
};
21692249
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)