@@ -21,6 +21,7 @@ use rustc_middle::{bug, span_bug};
2121use rustc_session:: lint:: builtin:: NON_EXHAUSTIVE_OMITTED_PATTERNS ;
2222use rustc_session:: parse:: feature_err;
2323use rustc_span:: edit_distance:: find_best_match_for_name;
24+ use rustc_span:: edition:: Edition ;
2425use rustc_span:: hygiene:: DesugaringKind ;
2526use rustc_span:: source_map:: Spanned ;
2627use 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 ) ]
182184enum 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+
216239impl < ' 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