mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-14 02:49:40 +00:00
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.
This commit is contained in:
parent
e2f3ce9568
commit
c0e8bada73
@ -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};
|
||||
@ -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,
|
||||
@ -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) {
|
||||
@ -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())
|
||||
@ -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()
|
||||
@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user