Skip to content
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

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

Merged
merged 11 commits into from
Jan 20, 2025
Merged
9 changes: 5 additions & 4 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,8 @@ impl Features {

/// Some features are not allowed to be used together at the same time, if
/// the two are present, produce an error.
///
/// Currently empty, but we will probably need this again in the future,
/// so let's keep it in for now.
pub const INCOMPATIBLE_FEATURES: &[(Symbol, Symbol)] = &[];
pub const INCOMPATIBLE_FEATURES: &[(Symbol, Symbol)] = &[
// Experimental match ergonomics rulesets are incompatible with each other, to simplify the
// boolean logic required to tell which typing rules to use.
(sym::ref_pat_eat_one_layer_2024, sym::ref_pat_eat_one_layer_2024_structural),
];
204 changes: 134 additions & 70 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 @@ -168,15 +169,16 @@ enum AdjustMode {
Pass,
}

/// `ref mut` patterns (explicit or match-ergonomics)
/// are not allowed behind an `&` reference.
/// `ref mut` bindings (explicit or match-ergonomics) are not allowed behind an `&` reference.
/// Normally, the borrow checker enforces this, but for (currently experimental) match ergonomics,
/// we track this when typing patterns for two purposes:
///
/// This includes explicit `ref mut` behind `&` patterns
/// that match against `&mut` references,
/// where the code would have compiled
/// had the pattern been written as `&mut`.
/// However, the borrow checker will not catch
/// this last case, so we need to throw an error ourselves.
/// - For RFC 3627's Rule 3, when this would prevent us from binding with `ref mut`, we limit the
/// default binding mode to be by shared `ref` when it would otherwise be `ref mut`.
///
/// - For RFC 3627's Rule 5, we allow `&` patterns to match against `&mut` references, treating them
/// as if they were shared references. Since the scrutinee is mutable in this case, the borrow
/// checker won't catch if we bind with `ref mut`, so we need to throw an error ourselves.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum MutblCap {
/// Mutability restricted to immutable.
Expand Down Expand Up @@ -212,7 +214,62 @@ impl MutblCap {
}
}

/// Variations on RFC 3627's Rule 4: when do reference patterns match against inherited references?
dianne marked this conversation as resolved.
Show resolved Hide resolved
#[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.
dianne marked this conversation as resolved.
Show resolved Hide resolved
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.
dianne marked this conversation as resolved.
Show resolved Hide resolved
/// 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
dianne marked this conversation as resolved.
Show resolved Hide resolved
}
} 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 @@ -473,13 +530,11 @@ 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) {
max_ref_mutbl = MutblCap::Not;
}
}
if def_br == ByRef::Yes(Mutability::Not) {
max_ref_mutbl = MutblCap::Not;
}

if !pat_adjustments.is_empty() {
Expand Down Expand Up @@ -707,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 @@ -2204,55 +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 {
if pat_mutbl == Mutability::Not {
// Prevent the inner pattern from binding with `ref mut`.
pat_info.max_ref_mutbl = pat_info.max_ref_mutbl.cap_to_weakly_not(pat_prefix_span);
}
} else {
pat_info.max_ref_mutbl = MutblCap::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.
dianne marked this conversation as resolved.
Show resolved Hide resolved
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.
dianne marked this conversation as resolved.
Show resolved Hide resolved
// 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.
dianne marked this conversation as resolved.
Show resolved Hide resolved
} else {
pat_info.binding_mode = ByRef::No;
self.typeck_results
.borrow_mut()
Expand All @@ -2261,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 @@ -2293,10 +2357,10 @@ 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 no_ref_mut_behind_and && r_mutbl == Mutability::Not {
if r_mutbl == Mutability::Not {
pat_info.max_ref_mutbl = MutblCap::Not;
}

Expand Down
Loading
Loading