Skip to content

Commit c0e8bad

Browse files
committed
cleanup: de-tangle experimental pattern typing rules some
The goal of this cleanup is to make it more apparent which feature gates correspond to which typing rules, and which typing rules correspond to what code. My intent is for calls to the "which typing rules do we have?" functions to be replaced by comments (and edition checks, as appropriate), but as long as we're experimenting with multiple rulesets, this seemed to me to be the easiest to document and read. There's still some nontrivial control flow, but I've added comments to try and make it clearer. There's some logic that looks like it could be de-duplicated across different ways of matching against inherited references; however, the duplication is intentional. Once we choose which rulesets we want, we can make this more clever, but until then, my priorities are clarity and ease of modification/extension. That said, I think the diagnostics could use some work; factoring out commonalities there (and separating them from the typing logic) would be ideal. I've opted not to include that here both since it'd make this refactor less obvious and since it affects test output. Also, this doesn't get quite as fine-grained as Typing Rust Patterns, so there's some instances where certain rules are conflated. I'd prefer to minimize dead/untested codepaths for rulesets we're not interested in, so as a compromise I've added comments wherever some aspect of the typing rules is assumed from another. I'm not totally happy with it, but I think it's at least better than plain checks against the feature gates and edition.
1 parent e2f3ce9 commit c0e8bad

File tree

1 file changed

+120
-53
lines changed
  • compiler/rustc_hir_typeck/src

1 file changed

+120
-53
lines changed

compiler/rustc_hir_typeck/src/pat.rs

Lines changed: 120 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use rustc_middle::{bug, span_bug};
2121
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
2222
use rustc_session::parse::feature_err;
2323
use rustc_span::edit_distance::find_best_match_for_name;
24+
use rustc_span::edition::Edition;
2425
use rustc_span::hygiene::DesugaringKind;
2526
use rustc_span::source_map::Spanned;
2627
use rustc_span::{BytePos, DUMMY_SP, Ident, Span, kw, sym};
@@ -213,7 +214,62 @@ impl MutblCap {
213214
}
214215
}
215216

217+
/// Variations on RFC 3627's Rule 4: when do reference patterns match against inherited references?
218+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
219+
enum InheritedRefMatchRule {
220+
/// Reference patterns try to consume the inherited reference first.
221+
/// This assumes reference patterns can always match against an inherited reference.
222+
EatOuter,
223+
/// Reference patterns consume inherited references if matching against a non-reference type.
224+
/// This assumes reference patterns can always match against an inherited reference.
225+
EatInner,
226+
/// Reference patterns consume both layers of reference.
227+
/// Currently, this assumes the stable Rust behavior of not allowing reference patterns to eat
228+
/// an inherited reference alone. This will need an additional field or variant to represent the
229+
/// planned edition <= 2021 behavior of experimental match ergonomics, which does allow that.
230+
EatBoth,
231+
}
232+
216233
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
234+
/// Experimental pattern feature: after matching against a shared reference, do we limit the
235+
/// default binding mode in subpatterns to be `ref` when it would otherwise be `ref mut`?
236+
/// This corresponds to Rule 3 of RFC 3627.
237+
fn downgrade_mut_inside_shared(&self) -> bool {
238+
// NB: RFC 3627 proposes stabilizing Rule 3 in all editions. If we adopt the same behavior
239+
// across all editions, this may be removed.
240+
self.tcx.features().ref_pat_eat_one_layer_2024()
241+
|| self.tcx.features().ref_pat_eat_one_layer_2024_structural()
242+
}
243+
244+
/// Experimental pattern feature: when do reference patterns match against inherited references?
245+
/// This corresponds to variations on Rule 4 of RFC 3627.
246+
fn ref_pat_matches_inherited_ref(&self, edition: Edition) -> InheritedRefMatchRule {
247+
// NB: The particular rule used here is likely to differ across editions, so calls to this
248+
// may need to become edition checks after match ergonomics stabilize.
249+
if edition.at_least_rust_2024() {
250+
if self.tcx.features().ref_pat_eat_one_layer_2024() {
251+
InheritedRefMatchRule::EatOuter
252+
} else if self.tcx.features().ref_pat_eat_one_layer_2024_structural() {
253+
InheritedRefMatchRule::EatInner
254+
} else {
255+
// Currently, matching against an inherited ref on edition 2024 is an error.
256+
// Use `EatBoth` as a fallback to be similar to stable Rust.
257+
InheritedRefMatchRule::EatBoth
258+
}
259+
} else {
260+
InheritedRefMatchRule::EatBoth
261+
}
262+
}
263+
264+
/// Experimental pattern feature: do `&` patterns match against `&mut` references, treating them
265+
/// as if they were shared references? This corresponds to Rule 5 of RFC 3627.
266+
fn ref_pat_matches_mut_ref(&self) -> bool {
267+
// NB: RFC 3627 proposes stabilizing Rule 5 in all editions. If we adopt the same behavior
268+
// across all editions, this may be removed.
269+
self.tcx.features().ref_pat_eat_one_layer_2024()
270+
|| self.tcx.features().ref_pat_eat_one_layer_2024_structural()
271+
}
272+
217273
/// Type check the given top level pattern against the `expected` type.
218274
///
219275
/// If a `Some(span)` is provided and `origin_expr` holds,
@@ -474,9 +530,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
474530
});
475531
}
476532

477-
let features = self.tcx.features();
478-
if features.ref_pat_eat_one_layer_2024() || features.ref_pat_eat_one_layer_2024_structural()
479-
{
533+
if self.downgrade_mut_inside_shared() {
480534
def_br = def_br.cap_ref_mutability(max_ref_mutbl.as_mutbl());
481535
}
482536
if def_br == ByRef::Yes(Mutability::Not) {
@@ -708,6 +762,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
708762
// Determine the binding mode...
709763
let bm = match user_bind_annot {
710764
BindingMode(ByRef::No, Mutability::Mut) if matches!(def_br, ByRef::Yes(_)) => {
765+
// Only mention the experimental `mut_ref` feature if if we're in edition 2024 and
766+
// using other experimental matching features compatible with it.
711767
if pat.span.at_least_rust_2024()
712768
&& (self.tcx.features().ref_pat_eat_one_layer_2024()
713769
|| self.tcx.features().ref_pat_eat_one_layer_2024_structural())
@@ -2205,51 +2261,68 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
22052261
mut pat_info: PatInfo<'_, 'tcx>,
22062262
) -> Ty<'tcx> {
22072263
let tcx = self.tcx;
2208-
let features = tcx.features();
2209-
let ref_pat_eat_one_layer_2024 = features.ref_pat_eat_one_layer_2024();
2210-
let ref_pat_eat_one_layer_2024_structural =
2211-
features.ref_pat_eat_one_layer_2024_structural();
2212-
2213-
let no_ref_mut_behind_and =
2214-
ref_pat_eat_one_layer_2024 || ref_pat_eat_one_layer_2024_structural;
2215-
let new_match_ergonomics = pat.span.at_least_rust_2024() && no_ref_mut_behind_and;
22162264

22172265
let pat_prefix_span =
22182266
inner.span.find_ancestor_inside(pat.span).map(|end| pat.span.until(end));
22192267

2220-
if no_ref_mut_behind_and && pat_mutbl == Mutability::Not {
2221-
// Prevent the inner pattern from binding with `ref mut`.
2268+
let ref_pat_matches_mut_ref = self.ref_pat_matches_mut_ref();
2269+
if ref_pat_matches_mut_ref && pat_mutbl == Mutability::Not {
2270+
// If `&` patterns can match against mutable reference types (RFC 3627, Rule 5), we need
2271+
// to prevent subpatterns from binding with `ref mut`. Subpatterns of a shared reference
2272+
// pattern should have read-only access to the scrutinee, and the borrow checker won't
2273+
// catch it in this case.
22222274
pat_info.max_ref_mutbl = pat_info.max_ref_mutbl.cap_to_weakly_not(pat_prefix_span);
22232275
}
22242276

22252277
expected = self.try_structurally_resolve_type(pat.span, expected);
2226-
if new_match_ergonomics {
2227-
if let ByRef::Yes(inh_mut) = pat_info.binding_mode {
2228-
if !ref_pat_eat_one_layer_2024 && let ty::Ref(_, _, r_mutbl) = *expected.kind() {
2229-
// Don't attempt to consume inherited reference
2230-
pat_info.binding_mode = pat_info.binding_mode.cap_ref_mutability(r_mutbl);
2231-
} else {
2278+
// Determine whether we're consuming an inherited reference and resetting the default
2279+
// binding mode, based on edition and enabled experimental features.
2280+
if let ByRef::Yes(inh_mut) = pat_info.binding_mode {
2281+
match self.ref_pat_matches_inherited_ref(pat.span.edition()) {
2282+
InheritedRefMatchRule::EatOuter => {
22322283
// ref pattern attempts to consume inherited reference
22332284
if pat_mutbl > inh_mut {
22342285
// Tried to match inherited `ref` with `&mut`
2235-
if !ref_pat_eat_one_layer_2024_structural {
2236-
let err_msg = "mismatched types";
2237-
let err = if let Some(span) = pat_prefix_span {
2238-
let mut err = self.dcx().struct_span_err(span, err_msg);
2239-
err.code(E0308);
2240-
err.note("cannot match inherited `&` with `&mut` pattern");
2241-
err.span_suggestion_verbose(
2242-
span,
2243-
"replace this `&mut` pattern with `&`",
2244-
"&",
2245-
Applicability::MachineApplicable,
2246-
);
2247-
err
2248-
} else {
2249-
self.dcx().struct_span_err(pat.span, err_msg)
2250-
};
2251-
err.emit();
2286+
// NB: This assumes that `&` patterns can match against mutable references
2287+
// (RFC 3627, Rule 5). If we implement a pattern typing ruleset with Rule 4E
2288+
// but not Rule 5, we'll need to check that here.
2289+
let err_msg = "mismatched types";
2290+
let err = if let Some(span) = pat_prefix_span {
2291+
let mut err = self.dcx().struct_span_err(span, err_msg);
2292+
err.code(E0308);
2293+
err.note("cannot match inherited `&` with `&mut` pattern");
2294+
err.span_suggestion_verbose(
2295+
span,
2296+
"replace this `&mut` pattern with `&`",
2297+
"&",
2298+
Applicability::MachineApplicable,
2299+
);
2300+
err
2301+
} else {
2302+
self.dcx().struct_span_err(pat.span, err_msg)
2303+
};
2304+
err.emit();
2305+
}
22522306

2307+
pat_info.binding_mode = ByRef::No;
2308+
self.typeck_results.borrow_mut().skipped_ref_pats_mut().insert(pat.hir_id);
2309+
self.check_pat(inner, expected, pat_info);
2310+
return expected;
2311+
}
2312+
InheritedRefMatchRule::EatInner => {
2313+
if let ty::Ref(_, _, r_mutbl) = *expected.kind() {
2314+
// Match against the reference type; don't consume the inherited ref.
2315+
pat_info.binding_mode = pat_info.binding_mode.cap_ref_mutability(r_mutbl);
2316+
} else {
2317+
// The expected type isn't a reference, so match against the inherited ref.
2318+
if pat_mutbl > inh_mut {
2319+
// We can't match an inherited shared reference with `&mut`. This will
2320+
// be a type error later, since we're matching a reference pattern
2321+
// against a non-reference type.
2322+
// NB: This assumes that `&` patterns can match against mutable
2323+
// references (RFC 3627, Rule 5). If we implement a pattern typing
2324+
// ruleset with Rule 4 but not Rule 5, we'll need to check that here.
2325+
} else {
22532326
pat_info.binding_mode = ByRef::No;
22542327
self.typeck_results
22552328
.borrow_mut()
@@ -2258,24 +2331,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
22582331
self.check_pat(inner, expected, pat_info);
22592332
return expected;
22602333
}
2261-
} else {
2262-
pat_info.binding_mode = ByRef::No;
2263-
self.typeck_results.borrow_mut().skipped_ref_pats_mut().insert(pat.hir_id);
2264-
self.check_pat(inner, expected, pat_info);
2265-
return expected;
22662334
}
22672335
}
2268-
}
2269-
} else {
2270-
// Reset binding mode on old editions
2271-
if pat_info.binding_mode != ByRef::No {
2272-
pat_info.binding_mode = ByRef::No;
2273-
self.add_rust_2024_migration_desugared_pat(
2274-
pat_info.top_info.hir_id,
2275-
pat.span,
2276-
inner.span,
2277-
"cannot implicitly match against multiple layers of reference",
2278-
)
2336+
InheritedRefMatchRule::EatBoth => {
2337+
// Reset binding mode on old editions
2338+
pat_info.binding_mode = ByRef::No;
2339+
self.add_rust_2024_migration_desugared_pat(
2340+
pat_info.top_info.hir_id,
2341+
pat.span,
2342+
inner.span,
2343+
"cannot implicitly match against multiple layers of reference",
2344+
)
2345+
}
22792346
}
22802347
}
22812348

@@ -2290,7 +2357,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
22902357
debug!("check_pat_ref: expected={:?}", expected);
22912358
match *expected.kind() {
22922359
ty::Ref(_, r_ty, r_mutbl)
2293-
if (no_ref_mut_behind_and && r_mutbl >= pat_mutbl)
2360+
if (ref_pat_matches_mut_ref && r_mutbl >= pat_mutbl)
22942361
|| r_mutbl == pat_mutbl =>
22952362
{
22962363
if r_mutbl == Mutability::Not {

0 commit comments

Comments
 (0)