@@ -177,16 +177,20 @@ enum PeelKind {
177177 /// Only peel reference types. This is used for explicit `deref!(_)` patterns, which dereference
178178 /// any number of `&`/`&mut` references, plus a single smart pointer.
179179 ExplicitDerefPat ,
180- /// Implicitly peel any number of references, and if `deref_patterns` is enabled, smart pointer
181- /// ADTs. In order to peel only as much as necessary for the pattern to match, the `until_adt`
182- /// field contains the ADT def that the pattern is a constructor for, if applicable, so that we
183- /// don't peel it. See [`ResolvedPat`] for more information.
184- Implicit { until_adt : Option < DefId > } ,
180+ /// Implicitly peel references, and if `deref_patterns` is enabled, smart pointer ADTs.
181+ Implicit {
182+ /// The ADT the pattern is a constructor for, if applicable, so that we don't peel it. See
183+ /// [`ResolvedPat`] for more information.
184+ until_adt : Option < DefId > ,
185+ /// The number of references at the head of the pattern's type, so we can leave that many
186+ /// untouched. This is `1` for string literals, and `0` for most patterns.
187+ pat_ref_layers : usize ,
188+ } ,
185189}
186190
187191impl AdjustMode {
188192 const fn peel_until_adt ( opt_adt_def : Option < DefId > ) -> AdjustMode {
189- AdjustMode :: Peel { kind : PeelKind :: Implicit { until_adt : opt_adt_def } }
193+ AdjustMode :: Peel { kind : PeelKind :: Implicit { until_adt : opt_adt_def, pat_ref_layers : 0 } }
190194 }
191195 const fn peel_all ( ) -> AdjustMode {
192196 AdjustMode :: peel_until_adt ( None )
@@ -488,9 +492,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
488492 match pat. kind {
489493 // Peel off a `&` or `&mut` from the scrutinee type. See the examples in
490494 // `tests/ui/rfcs/rfc-2005-default-binding-mode`.
491- _ if let AdjustMode :: Peel { .. } = adjust_mode
495+ _ if let AdjustMode :: Peel { kind : peel_kind } = adjust_mode
492496 && pat. default_binding_modes
493- && let ty:: Ref ( _, inner_ty, inner_mutability) = * expected. kind ( ) =>
497+ && let ty:: Ref ( _, inner_ty, inner_mutability) = * expected. kind ( )
498+ && self . should_peel_ref ( peel_kind, expected) =>
494499 {
495500 debug ! ( "inspecting {:?}" , expected) ;
496501
@@ -531,24 +536,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
531536 // If `deref_patterns` is enabled, peel a smart pointer from the scrutinee type. See the
532537 // examples in `tests/ui/pattern/deref_patterns/`.
533538 _ if self . tcx . features ( ) . deref_patterns ( )
534- && let AdjustMode :: Peel { kind : PeelKind :: Implicit { until_adt } } = adjust_mode
539+ && let AdjustMode :: Peel { kind : peel_kind } = adjust_mode
535540 && pat. default_binding_modes
536- // For simplicity, only apply overloaded derefs if `expected` is a known ADT.
537- // FIXME(deref_patterns): we'll get better diagnostics for users trying to
538- // implicitly deref generics if we allow them here, but primitives, tuples, and
539- // inference vars definitely should be stopped. Figure out what makes most sense.
540- && let ty:: Adt ( scrutinee_adt, _) = * expected. kind ( )
541- // Don't peel if the pattern type already matches the scrutinee. E.g., stop here if
542- // matching on a `Cow<'a, T>` scrutinee with a `Cow::Owned(_)` pattern.
543- && until_adt != Some ( scrutinee_adt. did ( ) )
544- // At this point, the pattern isn't able to match `expected` without peeling. Check
545- // that it implements `Deref` before assuming it's a smart pointer, to get a normal
546- // type error instead of a missing impl error if not. This only checks for `Deref`,
547- // not `DerefPure`: we require that too, but we want a trait error if it's missing.
548- && let Some ( deref_trait) = self . tcx . lang_items ( ) . deref_trait ( )
549- && self
550- . type_implements_trait ( deref_trait, [ expected] , self . param_env )
551- . may_apply ( ) =>
541+ && self . should_peel_smart_pointer ( peel_kind, expected) =>
552542 {
553543 debug ! ( "scrutinee ty {expected:?} is a smart pointer, inserting overloaded deref" ) ;
554544 // The scrutinee is a smart pointer; implicitly dereference it. This adds a
@@ -680,21 +670,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
680670
681671 // String and byte-string literals result in types `&str` and `&[u8]` respectively.
682672 // All other literals result in non-reference types.
683- // As a result, we allow `if let 0 = &&0 {}` but not `if let "foo" = &&"foo" {}`.
684- //
685- // Call `resolve_vars_if_possible` here for inline const blocks.
686- PatKind :: Expr ( lt) => match self . resolve_vars_if_possible ( self . check_pat_expr_unadjusted ( lt) ) . kind ( ) {
687- ty:: Ref ( ..) => AdjustMode :: Pass ,
688- _ => {
689- // Path patterns have already been handled, and inline const blocks currently
690- // aren't possible to write, so any handling for them would be untested.
691- if cfg ! ( debug_assertions)
692- && self . tcx . features ( ) . deref_patterns ( )
693- && !matches ! ( lt. kind, PatExprKind :: Lit { .. } )
694- {
695- span_bug ! ( lt. span, "FIXME(deref_patterns): adjust mode unimplemented for {:?}" , lt. kind) ;
673+ // As a result, we allow `if let 0 = &&0 {}` but not `if let "foo" = &&"foo" {}` unless
674+ // `deref_patterns` is enabled.
675+ PatKind :: Expr ( lt) => {
676+ // Path patterns have already been handled, and inline const blocks currently
677+ // aren't possible to write, so any handling for them would be untested.
678+ if cfg ! ( debug_assertions)
679+ && self . tcx . features ( ) . deref_patterns ( )
680+ && !matches ! ( lt. kind, PatExprKind :: Lit { .. } )
681+ {
682+ span_bug ! ( lt. span, "FIXME(deref_patterns): adjust mode unimplemented for {:?}" , lt. kind) ;
683+ }
684+ // Call `resolve_vars_if_possible` here for inline const blocks.
685+ let lit_ty = self . resolve_vars_if_possible ( self . check_pat_expr_unadjusted ( lt) ) ;
686+ // If `deref_patterns` is enabled, allow `if let "foo" = &&"foo" {}`.
687+ if self . tcx . features ( ) . deref_patterns ( ) {
688+ let mut peeled_ty = lit_ty;
689+ let mut pat_ref_layers = 0 ;
690+ while let ty:: Ref ( _, inner_ty, mutbl) = * peeled_ty. kind ( ) {
691+ // We rely on references at the head of constants being immutable.
692+ debug_assert ! ( mutbl. is_not( ) ) ;
693+ pat_ref_layers += 1 ;
694+ peeled_ty = inner_ty;
696695 }
697- AdjustMode :: peel_all ( )
696+ AdjustMode :: Peel { kind : PeelKind :: Implicit { until_adt : None , pat_ref_layers } }
697+ } else {
698+ if lit_ty. is_ref ( ) { AdjustMode :: Pass } else { AdjustMode :: peel_all ( ) }
698699 }
699700 } ,
700701
@@ -720,6 +721,67 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
720721 }
721722 }
722723
724+ /// Assuming `expected` is a reference type, determine whether to peel it before matching.
725+ fn should_peel_ref ( & self , peel_kind : PeelKind , mut expected : Ty < ' tcx > ) -> bool {
726+ debug_assert ! ( expected. is_ref( ) ) ;
727+ let pat_ref_layers = match peel_kind {
728+ PeelKind :: ExplicitDerefPat => 0 ,
729+ PeelKind :: Implicit { pat_ref_layers, .. } => pat_ref_layers,
730+ } ;
731+
732+ // Most patterns don't have reference types, so we'll want to peel all references from the
733+ // scrutinee before matching. To optimize for the common case, return early.
734+ if pat_ref_layers == 0 {
735+ return true ;
736+ }
737+ debug_assert ! (
738+ self . tcx. features( ) . deref_patterns( ) ,
739+ "Peeling for patterns with reference types is gated by `deref_patterns`."
740+ ) ;
741+
742+ // If the pattern has as many or more layers of reference as the expected type, we can match
743+ // without peeling more, unless we find a smart pointer or `&mut` that we also need to peel.
744+ // We don't treat `&` and `&mut` as interchangeable, but by peeling `&mut`s before matching,
745+ // we can still, e.g., match on a `&mut str` with a string literal pattern. This is because
746+ // string literal patterns may be used where `str` is expected.
747+ let mut expected_ref_layers = 0 ;
748+ while let ty:: Ref ( _, inner_ty, mutbl) = * expected. kind ( ) {
749+ if mutbl. is_mut ( ) {
750+ // Mutable references can't be in the final value of constants, thus they can't be
751+ // at the head of their types, thus we should always peel `&mut`.
752+ return true ;
753+ }
754+ expected_ref_layers += 1 ;
755+ expected = inner_ty;
756+ }
757+ pat_ref_layers < expected_ref_layers || self . should_peel_smart_pointer ( peel_kind, expected)
758+ }
759+
760+ /// Determine whether `expected` is a smart pointer type that should be peeled before matching.
761+ fn should_peel_smart_pointer ( & self , peel_kind : PeelKind , expected : Ty < ' tcx > ) -> bool {
762+ // Explicit `deref!(_)` patterns match against smart pointers; don't peel in that case.
763+ if let PeelKind :: Implicit { until_adt, .. } = peel_kind
764+ // For simplicity, only apply overloaded derefs if `expected` is a known ADT.
765+ // FIXME(deref_patterns): we'll get better diagnostics for users trying to
766+ // implicitly deref generics if we allow them here, but primitives, tuples, and
767+ // inference vars definitely should be stopped. Figure out what makes most sense.
768+ && let ty:: Adt ( scrutinee_adt, _) = * expected. kind ( )
769+ // Don't peel if the pattern type already matches the scrutinee. E.g., stop here if
770+ // matching on a `Cow<'a, T>` scrutinee with a `Cow::Owned(_)` pattern.
771+ && until_adt != Some ( scrutinee_adt. did ( ) )
772+ // At this point, the pattern isn't able to match `expected` without peeling. Check
773+ // that it implements `Deref` before assuming it's a smart pointer, to get a normal
774+ // type error instead of a missing impl error if not. This only checks for `Deref`,
775+ // not `DerefPure`: we require that too, but we want a trait error if it's missing.
776+ && let Some ( deref_trait) = self . tcx . lang_items ( ) . deref_trait ( )
777+ && self . type_implements_trait ( deref_trait, [ expected] , self . param_env ) . may_apply ( )
778+ {
779+ true
780+ } else {
781+ false
782+ }
783+ }
784+
723785 fn check_pat_expr_unadjusted ( & self , lt : & ' tcx hir:: PatExpr < ' tcx > ) -> Ty < ' tcx > {
724786 let ty = match & lt. kind {
725787 rustc_hir:: PatExprKind :: Lit { lit, negated } => {
0 commit comments