Skip to content

Match Ergonomics 2024: document and reorganize the currently-implemented feature gates #135237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 20, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 120 additions & 53 deletions compiler/rustc_hir_typeck/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use rustc_middle::{bug, span_bug};
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
use rustc_session::parse::feature_err;
use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::edition::Edition;
use rustc_span::hygiene::DesugaringKind;
use rustc_span::source_map::Spanned;
use rustc_span::{BytePos, DUMMY_SP, Ident, Span, kw, sym};
Expand Down Expand Up @@ -213,7 +214,62 @@ impl MutblCap {
}
}

/// Variations on RFC 3627's Rule 4: when do reference patterns match against inherited references?
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum InheritedRefMatchRule {
/// Reference patterns try to consume the inherited reference first.
/// This assumes reference patterns can always match against an inherited reference.
EatOuter,
/// Reference patterns consume inherited references if matching against a non-reference type.
/// This assumes reference patterns can always match against an inherited reference.
EatInner,
/// Reference patterns consume both layers of reference.
/// Currently, this assumes the stable Rust behavior of not allowing reference patterns to eat
/// an inherited reference alone. This will need an additional field or variant to represent the
/// planned edition <= 2021 behavior of experimental match ergonomics, which does allow that.
EatBoth,
}

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// Experimental pattern feature: after matching against a shared reference, do we limit the
/// default binding mode in subpatterns to be `ref` when it would otherwise be `ref mut`?
/// This corresponds to Rule 3 of RFC 3627.
fn downgrade_mut_inside_shared(&self) -> bool {
// NB: RFC 3627 proposes stabilizing Rule 3 in all editions. If we adopt the same behavior
// across all editions, this may be removed.
self.tcx.features().ref_pat_eat_one_layer_2024()
|| self.tcx.features().ref_pat_eat_one_layer_2024_structural()
}

/// Experimental pattern feature: when do reference patterns match against inherited references?
/// This corresponds to variations on Rule 4 of RFC 3627.
fn ref_pat_matches_inherited_ref(&self, edition: Edition) -> InheritedRefMatchRule {
// NB: The particular rule used here is likely to differ across editions, so calls to this
// may need to become edition checks after match ergonomics stabilize.
if edition.at_least_rust_2024() {
if self.tcx.features().ref_pat_eat_one_layer_2024() {
InheritedRefMatchRule::EatOuter
} else if self.tcx.features().ref_pat_eat_one_layer_2024_structural() {
InheritedRefMatchRule::EatInner
} else {
// Currently, matching against an inherited ref on edition 2024 is an error.
// Use `EatBoth` as a fallback to be similar to stable Rust.
InheritedRefMatchRule::EatBoth
}
} else {
InheritedRefMatchRule::EatBoth
}
}

/// Experimental pattern feature: do `&` patterns match against `&mut` references, treating them
/// as if they were shared references? This corresponds to Rule 5 of RFC 3627.
fn ref_pat_matches_mut_ref(&self) -> bool {
// NB: RFC 3627 proposes stabilizing Rule 5 in all editions. If we adopt the same behavior
// across all editions, this may be removed.
self.tcx.features().ref_pat_eat_one_layer_2024()
|| self.tcx.features().ref_pat_eat_one_layer_2024_structural()
}

/// Type check the given top level pattern against the `expected` type.
///
/// If a `Some(span)` is provided and `origin_expr` holds,
Expand Down Expand Up @@ -474,9 +530,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
});
}

let features = self.tcx.features();
if features.ref_pat_eat_one_layer_2024() || features.ref_pat_eat_one_layer_2024_structural()
{
if self.downgrade_mut_inside_shared() {
def_br = def_br.cap_ref_mutability(max_ref_mutbl.as_mutbl());
}
if def_br == ByRef::Yes(Mutability::Not) {
Expand Down Expand Up @@ -708,6 +762,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Determine the binding mode...
let bm = match user_bind_annot {
BindingMode(ByRef::No, Mutability::Mut) if matches!(def_br, ByRef::Yes(_)) => {
// Only mention the experimental `mut_ref` feature if if we're in edition 2024 and
// using other experimental matching features compatible with it.
if pat.span.at_least_rust_2024()
&& (self.tcx.features().ref_pat_eat_one_layer_2024()
|| self.tcx.features().ref_pat_eat_one_layer_2024_structural())
Expand Down Expand Up @@ -2205,51 +2261,68 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
mut pat_info: PatInfo<'_, 'tcx>,
) -> Ty<'tcx> {
let tcx = self.tcx;
let features = tcx.features();
let ref_pat_eat_one_layer_2024 = features.ref_pat_eat_one_layer_2024();
let ref_pat_eat_one_layer_2024_structural =
features.ref_pat_eat_one_layer_2024_structural();

let no_ref_mut_behind_and =
ref_pat_eat_one_layer_2024 || ref_pat_eat_one_layer_2024_structural;
let new_match_ergonomics = pat.span.at_least_rust_2024() && no_ref_mut_behind_and;

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

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

expected = self.try_structurally_resolve_type(pat.span, expected);
if new_match_ergonomics {
if let ByRef::Yes(inh_mut) = pat_info.binding_mode {
if !ref_pat_eat_one_layer_2024 && let ty::Ref(_, _, r_mutbl) = *expected.kind() {
// Don't attempt to consume inherited reference
pat_info.binding_mode = pat_info.binding_mode.cap_ref_mutability(r_mutbl);
} else {
// Determine whether we're consuming an inherited reference and resetting the default
// binding mode, based on edition and enabled experimental features.
if let ByRef::Yes(inh_mut) = pat_info.binding_mode {
match self.ref_pat_matches_inherited_ref(pat.span.edition()) {
InheritedRefMatchRule::EatOuter => {
// ref pattern attempts to consume inherited reference
if pat_mutbl > inh_mut {
// Tried to match inherited `ref` with `&mut`
if !ref_pat_eat_one_layer_2024_structural {
let err_msg = "mismatched types";
let err = if let Some(span) = pat_prefix_span {
let mut err = self.dcx().struct_span_err(span, err_msg);
err.code(E0308);
err.note("cannot match inherited `&` with `&mut` pattern");
err.span_suggestion_verbose(
span,
"replace this `&mut` pattern with `&`",
"&",
Applicability::MachineApplicable,
);
err
} else {
self.dcx().struct_span_err(pat.span, err_msg)
};
err.emit();
// NB: This assumes that `&` patterns can match against mutable references
// (RFC 3627, Rule 5). If we implement a pattern typing ruleset with Rule 4E
// but not Rule 5, we'll need to check that here.
let err_msg = "mismatched types";
let err = if let Some(span) = pat_prefix_span {
let mut err = self.dcx().struct_span_err(span, err_msg);
err.code(E0308);
err.note("cannot match inherited `&` with `&mut` pattern");
err.span_suggestion_verbose(
span,
"replace this `&mut` pattern with `&`",
"&",
Applicability::MachineApplicable,
);
err
} else {
self.dcx().struct_span_err(pat.span, err_msg)
};
err.emit();
}

pat_info.binding_mode = ByRef::No;
self.typeck_results.borrow_mut().skipped_ref_pats_mut().insert(pat.hir_id);
self.check_pat(inner, expected, pat_info);
return expected;
}
InheritedRefMatchRule::EatInner => {
if let ty::Ref(_, _, r_mutbl) = *expected.kind() {
// Match against the reference type; don't consume the inherited ref.
pat_info.binding_mode = pat_info.binding_mode.cap_ref_mutability(r_mutbl);
} else {
// The expected type isn't a reference, so match against the inherited ref.
if pat_mutbl > inh_mut {
// We can't match an inherited shared reference with `&mut`. This will
// be a type error later, since we're matching a reference pattern
// against a non-reference type.
// NB: This assumes that `&` patterns can match against mutable
// references (RFC 3627, Rule 5). If we implement a pattern typing
// ruleset with Rule 4 but not Rule 5, we'll need to check that here.
} else {
pat_info.binding_mode = ByRef::No;
self.typeck_results
.borrow_mut()
Expand All @@ -2258,24 +2331,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.check_pat(inner, expected, pat_info);
return expected;
}
} else {
pat_info.binding_mode = ByRef::No;
self.typeck_results.borrow_mut().skipped_ref_pats_mut().insert(pat.hir_id);
self.check_pat(inner, expected, pat_info);
return expected;
}
}
}
} else {
// Reset binding mode on old editions
if pat_info.binding_mode != ByRef::No {
pat_info.binding_mode = ByRef::No;
self.add_rust_2024_migration_desugared_pat(
pat_info.top_info.hir_id,
pat.span,
inner.span,
"cannot implicitly match against multiple layers of reference",
)
InheritedRefMatchRule::EatBoth => {
// Reset binding mode on old editions
pat_info.binding_mode = ByRef::No;
self.add_rust_2024_migration_desugared_pat(
pat_info.top_info.hir_id,
pat.span,
inner.span,
"cannot implicitly match against multiple layers of reference",
)
}
}
}

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