Skip to content

Commit 1419f79

Browse files
authored
Rollup merge of rust-lang#135237 - dianne:match-2024-cleanup, r=Nadrieril
Match Ergonomics 2024: document and reorganize the currently-implemented feature gates The hope here is to make it easier to adjust, understand, and test the experimental pattern typing rules implemented in the compiler. This PR doesn't (or at isn't intended to) change any behavior or add any new tests; I'll be handling that later. I've also included some reasoning/commentary on the more involved changes in the commit messages. Relevant tracking issue: rust-lang#123076 r? `@Nadrieril`
2 parents be8f1f9 + 0263772 commit 1419f79

32 files changed

+772
-762
lines changed

Diff for: compiler/rustc_feature/src/unstable.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,8 @@ impl Features {
722722

723723
/// Some features are not allowed to be used together at the same time, if
724724
/// the two are present, produce an error.
725-
///
726-
/// Currently empty, but we will probably need this again in the future,
727-
/// so let's keep it in for now.
728-
pub const INCOMPATIBLE_FEATURES: &[(Symbol, Symbol)] = &[];
725+
pub const INCOMPATIBLE_FEATURES: &[(Symbol, Symbol)] = &[
726+
// Experimental match ergonomics rulesets are incompatible with each other, to simplify the
727+
// boolean logic required to tell which typing rules to use.
728+
(sym::ref_pat_eat_one_layer_2024, sym::ref_pat_eat_one_layer_2024_structural),
729+
];

Diff for: compiler/rustc_hir_typeck/src/pat.rs

+141-70
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};
@@ -169,15 +170,16 @@ enum AdjustMode {
169170
Pass,
170171
}
171172

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

218+
/// Variations on RFC 3627's Rule 4: when do reference patterns match against inherited references?
219+
///
220+
/// "Inherited reference" designates the `&`/`&mut` types that arise from using match ergonomics, i.e.
221+
/// from matching a reference type with a non-reference pattern. E.g. when `Some(x)` matches on
222+
/// `&mut Option<&T>`, `x` gets type `&mut &T` and the outer `&mut` is considered "inherited".
223+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
224+
enum InheritedRefMatchRule {
225+
/// Reference patterns consume only the inherited reference if possible, regardless of whether
226+
/// the underlying type being matched against is a reference type. If there is no inherited
227+
/// reference, a reference will be consumed from the underlying type.
228+
EatOuter,
229+
/// Reference patterns consume only a reference from the underlying type if possible. If the
230+
/// underlying type is not a reference type, the inherited reference will be consumed.
231+
EatInner,
232+
/// When the underlying type is a reference type, reference patterns consume both layers of
233+
/// reference, i.e. they both reset the binding mode and consume the reference type. Reference
234+
/// patterns are not permitted when there is no underlying reference type, i.e. they can't eat
235+
/// only an inherited reference. This is the current stable Rust behavior.
236+
EatBoth,
237+
}
238+
216239
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
240+
/// Experimental pattern feature: after matching against a shared reference, do we limit the
241+
/// default binding mode in subpatterns to be `ref` when it would otherwise be `ref mut`?
242+
/// This corresponds to Rule 3 of RFC 3627.
243+
fn downgrade_mut_inside_shared(&self) -> bool {
244+
// NB: RFC 3627 proposes stabilizing Rule 3 in all editions. If we adopt the same behavior
245+
// across all editions, this may be removed.
246+
self.tcx.features().ref_pat_eat_one_layer_2024()
247+
|| self.tcx.features().ref_pat_eat_one_layer_2024_structural()
248+
}
249+
250+
/// Experimental pattern feature: when do reference patterns match against inherited references?
251+
/// This corresponds to variations on Rule 4 of RFC 3627.
252+
fn ref_pat_matches_inherited_ref(&self, edition: Edition) -> InheritedRefMatchRule {
253+
// NB: The particular rule used here is likely to differ across editions, so calls to this
254+
// may need to become edition checks after match ergonomics stabilize.
255+
if edition.at_least_rust_2024() {
256+
if self.tcx.features().ref_pat_eat_one_layer_2024() {
257+
InheritedRefMatchRule::EatOuter
258+
} else if self.tcx.features().ref_pat_eat_one_layer_2024_structural() {
259+
InheritedRefMatchRule::EatInner
260+
} else {
261+
// Currently, matching against an inherited ref on edition 2024 is an error.
262+
// Use `EatBoth` as a fallback to be similar to stable Rust.
263+
InheritedRefMatchRule::EatBoth
264+
}
265+
} else {
266+
InheritedRefMatchRule::EatBoth
267+
}
268+
}
269+
270+
/// Experimental pattern feature: do `&` patterns match against `&mut` references, treating them
271+
/// as if they were shared references? This corresponds to Rule 5 of RFC 3627.
272+
fn ref_pat_matches_mut_ref(&self) -> bool {
273+
// NB: RFC 3627 proposes stabilizing Rule 5 in all editions. If we adopt the same behavior
274+
// across all editions, this may be removed.
275+
self.tcx.features().ref_pat_eat_one_layer_2024()
276+
|| self.tcx.features().ref_pat_eat_one_layer_2024_structural()
277+
}
278+
217279
/// Type check the given top level pattern against the `expected` type.
218280
///
219281
/// If a `Some(span)` is provided and `origin_expr` holds,
@@ -474,13 +536,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
474536
});
475537
}
476538

477-
let features = self.tcx.features();
478-
if features.ref_pat_eat_one_layer_2024() || features.ref_pat_eat_one_layer_2024_structural()
479-
{
539+
if self.downgrade_mut_inside_shared() {
480540
def_br = def_br.cap_ref_mutability(max_ref_mutbl.as_mutbl());
481-
if def_br == ByRef::Yes(Mutability::Not) {
482-
max_ref_mutbl = MutblCap::Not;
483-
}
541+
}
542+
if def_br == ByRef::Yes(Mutability::Not) {
543+
max_ref_mutbl = MutblCap::Not;
484544
}
485545

486546
if !pat_adjustments.is_empty() {
@@ -731,6 +791,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
731791
// Determine the binding mode...
732792
let bm = match user_bind_annot {
733793
BindingMode(ByRef::No, Mutability::Mut) if matches!(def_br, ByRef::Yes(_)) => {
794+
// Only mention the experimental `mut_ref` feature if if we're in edition 2024 and
795+
// using other experimental matching features compatible with it.
734796
if pat.span.at_least_rust_2024()
735797
&& (self.tcx.features().ref_pat_eat_one_layer_2024()
736798
|| self.tcx.features().ref_pat_eat_one_layer_2024_structural())
@@ -2228,55 +2290,70 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
22282290
mut pat_info: PatInfo<'_, 'tcx>,
22292291
) -> Ty<'tcx> {
22302292
let tcx = self.tcx;
2231-
let features = tcx.features();
2232-
let ref_pat_eat_one_layer_2024 = features.ref_pat_eat_one_layer_2024();
2233-
let ref_pat_eat_one_layer_2024_structural =
2234-
features.ref_pat_eat_one_layer_2024_structural();
2235-
2236-
let no_ref_mut_behind_and =
2237-
ref_pat_eat_one_layer_2024 || ref_pat_eat_one_layer_2024_structural;
2238-
let new_match_ergonomics = pat.span.at_least_rust_2024() && no_ref_mut_behind_and;
22392293

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

2243-
if no_ref_mut_behind_and {
2244-
if pat_mutbl == Mutability::Not {
2245-
// Prevent the inner pattern from binding with `ref mut`.
2246-
pat_info.max_ref_mutbl = pat_info.max_ref_mutbl.cap_to_weakly_not(pat_prefix_span);
2247-
}
2248-
} else {
2249-
pat_info.max_ref_mutbl = MutblCap::Mut;
2297+
let ref_pat_matches_mut_ref = self.ref_pat_matches_mut_ref();
2298+
if ref_pat_matches_mut_ref && pat_mutbl == Mutability::Not {
2299+
// If `&` patterns can match against mutable reference types (RFC 3627, Rule 5), we need
2300+
// to prevent subpatterns from binding with `ref mut`. Subpatterns of a shared reference
2301+
// pattern should have read-only access to the scrutinee, and the borrow checker won't
2302+
// catch it in this case.
2303+
pat_info.max_ref_mutbl = pat_info.max_ref_mutbl.cap_to_weakly_not(pat_prefix_span);
22502304
}
22512305

22522306
expected = self.try_structurally_resolve_type(pat.span, expected);
2253-
if new_match_ergonomics {
2254-
if let ByRef::Yes(inh_mut) = pat_info.binding_mode {
2255-
if !ref_pat_eat_one_layer_2024 && let ty::Ref(_, _, r_mutbl) = *expected.kind() {
2256-
// Don't attempt to consume inherited reference
2257-
pat_info.binding_mode = pat_info.binding_mode.cap_ref_mutability(r_mutbl);
2258-
} else {
2307+
// Determine whether we're consuming an inherited reference and resetting the default
2308+
// binding mode, based on edition and enabled experimental features.
2309+
if let ByRef::Yes(inh_mut) = pat_info.binding_mode {
2310+
match self.ref_pat_matches_inherited_ref(pat.span.edition()) {
2311+
InheritedRefMatchRule::EatOuter => {
22592312
// ref pattern attempts to consume inherited reference
22602313
if pat_mutbl > inh_mut {
22612314
// Tried to match inherited `ref` with `&mut`
2262-
if !ref_pat_eat_one_layer_2024_structural {
2263-
let err_msg = "mismatched types";
2264-
let err = if let Some(span) = pat_prefix_span {
2265-
let mut err = self.dcx().struct_span_err(span, err_msg);
2266-
err.code(E0308);
2267-
err.note("cannot match inherited `&` with `&mut` pattern");
2268-
err.span_suggestion_verbose(
2269-
span,
2270-
"replace this `&mut` pattern with `&`",
2271-
"&",
2272-
Applicability::MachineApplicable,
2273-
);
2274-
err
2275-
} else {
2276-
self.dcx().struct_span_err(pat.span, err_msg)
2277-
};
2278-
err.emit();
2315+
// NB: This assumes that `&` patterns can match against mutable references
2316+
// (RFC 3627, Rule 5). If we implement a pattern typing ruleset with Rule 4E
2317+
// but not Rule 5, we'll need to check that here.
2318+
debug_assert!(ref_pat_matches_mut_ref);
2319+
let err_msg = "mismatched types";
2320+
let err = if let Some(span) = pat_prefix_span {
2321+
let mut err = self.dcx().struct_span_err(span, err_msg);
2322+
err.code(E0308);
2323+
err.note("cannot match inherited `&` with `&mut` pattern");
2324+
err.span_suggestion_verbose(
2325+
span,
2326+
"replace this `&mut` pattern with `&`",
2327+
"&",
2328+
Applicability::MachineApplicable,
2329+
);
2330+
err
2331+
} else {
2332+
self.dcx().struct_span_err(pat.span, err_msg)
2333+
};
2334+
err.emit();
2335+
}
22792336

2337+
pat_info.binding_mode = ByRef::No;
2338+
self.typeck_results.borrow_mut().skipped_ref_pats_mut().insert(pat.hir_id);
2339+
self.check_pat(inner, expected, pat_info);
2340+
return expected;
2341+
}
2342+
InheritedRefMatchRule::EatInner => {
2343+
if let ty::Ref(_, _, r_mutbl) = *expected.kind() {
2344+
// Match against the reference type; don't consume the inherited ref.
2345+
pat_info.binding_mode = pat_info.binding_mode.cap_ref_mutability(r_mutbl);
2346+
} else {
2347+
// The expected type isn't a reference, so match against the inherited ref.
2348+
if pat_mutbl > inh_mut {
2349+
// We can't match an inherited shared reference with `&mut`. This will
2350+
// be a type error later, since we're matching a reference pattern
2351+
// against a non-reference type.
2352+
// NB: This assumes that `&` patterns can match against mutable
2353+
// references (RFC 3627, Rule 5). If we implement a pattern typing
2354+
// ruleset with Rule 4 but not Rule 5, we'll need to check that here.
2355+
debug_assert!(ref_pat_matches_mut_ref);
2356+
} else {
22802357
pat_info.binding_mode = ByRef::No;
22812358
self.typeck_results
22822359
.borrow_mut()
@@ -2285,24 +2362,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
22852362
self.check_pat(inner, expected, pat_info);
22862363
return expected;
22872364
}
2288-
} else {
2289-
pat_info.binding_mode = ByRef::No;
2290-
self.typeck_results.borrow_mut().skipped_ref_pats_mut().insert(pat.hir_id);
2291-
self.check_pat(inner, expected, pat_info);
2292-
return expected;
22932365
}
22942366
}
2295-
}
2296-
} else {
2297-
// Reset binding mode on old editions
2298-
if pat_info.binding_mode != ByRef::No {
2299-
pat_info.binding_mode = ByRef::No;
2300-
self.add_rust_2024_migration_desugared_pat(
2301-
pat_info.top_info.hir_id,
2302-
pat.span,
2303-
inner.span,
2304-
"cannot implicitly match against multiple layers of reference",
2305-
)
2367+
InheritedRefMatchRule::EatBoth => {
2368+
// Reset binding mode on old editions
2369+
pat_info.binding_mode = ByRef::No;
2370+
self.add_rust_2024_migration_desugared_pat(
2371+
pat_info.top_info.hir_id,
2372+
pat.span,
2373+
inner.span,
2374+
"cannot implicitly match against multiple layers of reference",
2375+
)
2376+
}
23062377
}
23072378
}
23082379

@@ -2317,10 +2388,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
23172388
debug!("check_pat_ref: expected={:?}", expected);
23182389
match *expected.kind() {
23192390
ty::Ref(_, r_ty, r_mutbl)
2320-
if (no_ref_mut_behind_and && r_mutbl >= pat_mutbl)
2391+
if (ref_pat_matches_mut_ref && r_mutbl >= pat_mutbl)
23212392
|| r_mutbl == pat_mutbl =>
23222393
{
2323-
if no_ref_mut_behind_and && r_mutbl == Mutability::Not {
2394+
if r_mutbl == Mutability::Not {
23242395
pat_info.max_ref_mutbl = MutblCap::Not;
23252396
}
23262397

0 commit comments

Comments
 (0)