@@ -942,6 +942,19 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
942942 }
943943
944944 /// The core driver for walking a pattern
945+ ///
946+ /// This should mirror how pattern-matching gets lowered to MIR, as
947+ /// otherwise lowering will ICE when trying to resolve the upvars.
948+ ///
949+ /// However, it is okay to approximate it here by doing *more* accesses than
950+ /// the actual MIR builder will, which is useful when some checks are too
951+ /// cumbersome to perform here. For example, if after typeck it becomes
952+ /// clear that only one variant of an enum is inhabited, and therefore a
953+ /// read of the discriminant is not necessary, `walk_pat` will have
954+ /// over-approximated the necessary upvar capture granularity.
955+ ///
956+ /// Do note that discrepancies like these do still create obscure corners
957+ /// in the semantics of the language, and should be avoided if possible.
945958 #[ instrument( skip( self ) , level = "debug" ) ]
946959 fn walk_pat (
947960 & self ,
@@ -951,6 +964,11 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
951964 ) -> Result < ( ) , Cx :: Error > {
952965 let tcx = self . cx . tcx ( ) ;
953966 self . cat_pattern ( discr_place. clone ( ) , pat, & mut |place, pat| {
967+ debug ! ( "walk_pat: pat.kind={:?}" , pat. kind) ;
968+ let read_discriminant = || {
969+ self . delegate . borrow_mut ( ) . borrow ( place, discr_place. hir_id , BorrowKind :: Immutable ) ;
970+ } ;
971+
954972 match pat. kind {
955973 PatKind :: Binding ( _, canonical_id, ..) => {
956974 debug ! ( "walk_pat: binding place={:?} pat={:?}" , place, pat) ;
@@ -973,11 +991,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
973991 // binding when lowering pattern guards to ensure that the guard does not
974992 // modify the scrutinee.
975993 if has_guard {
976- self . delegate . borrow_mut ( ) . borrow (
977- place,
978- discr_place. hir_id ,
979- BorrowKind :: Immutable ,
980- ) ;
994+ read_discriminant ( ) ;
981995 }
982996
983997 // It is also a borrow or copy/move of the value being matched.
@@ -1011,13 +1025,71 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
10111025 PatKind :: Never => {
10121026 // A `!` pattern always counts as an immutable read of the discriminant,
10131027 // even in an irrefutable pattern.
1014- self . delegate . borrow_mut ( ) . borrow (
1015- place,
1016- discr_place. hir_id ,
1017- BorrowKind :: Immutable ,
1018- ) ;
1028+ read_discriminant ( ) ;
1029+ }
1030+ PatKind :: Expr ( PatExpr { kind : PatExprKind :: Path ( qpath) , hir_id, span } ) => {
1031+ // A `Path` pattern is just a name like `Foo`. This is either a
1032+ // named constant or else it refers to an ADT variant
1033+
1034+ let res = self . cx . typeck_results ( ) . qpath_res ( qpath, * hir_id) ;
1035+ match res {
1036+ Res :: Def ( DefKind :: Const , _) | Res :: Def ( DefKind :: AssocConst , _) => {
1037+ // Named constants have to be equated with the value
1038+ // being matched, so that's a read of the value being matched.
1039+ //
1040+ // FIXME: Does the MIR code skip this read when matching on a ZST?
1041+ // If so, we can also skip it here.
1042+ read_discriminant ( ) ;
1043+ }
1044+ _ => {
1045+ // Otherwise, this is a struct/enum variant, and so it's
1046+ // only a read if we need to read the discriminant.
1047+ if self . is_multivariant_adt ( place. place . ty ( ) , * span) {
1048+ read_discriminant ( ) ;
1049+ }
1050+ }
1051+ }
1052+ }
1053+ PatKind :: Expr ( _) | PatKind :: Range ( ..) => {
1054+ // When matching against a literal or range, we need to
1055+ // borrow the place to compare it against the pattern.
1056+ //
1057+ // FIXME: What if the type being matched only has one
1058+ // possible value?
1059+ // FIXME: What if the range is the full range of the type
1060+ // and doesn't actually require a discriminant read?
1061+ read_discriminant ( ) ;
1062+ }
1063+ PatKind :: Struct ( ..) | PatKind :: TupleStruct ( ..) => {
1064+ if self . is_multivariant_adt ( place. place . ty ( ) , pat. span ) {
1065+ read_discriminant ( ) ;
1066+ }
1067+ }
1068+ PatKind :: Slice ( lhs, wild, rhs) => {
1069+ // We don't need to test the length if the pattern is `[..]`
1070+ if matches ! ( ( lhs, wild, rhs) , ( & [ ] , Some ( _) , & [ ] ) )
1071+ // Arrays have a statically known size, so
1072+ // there is no need to read their length
1073+ || place. place . ty ( ) . peel_refs ( ) . is_array ( )
1074+ {
1075+ // No read necessary
1076+ } else {
1077+ read_discriminant ( ) ;
1078+ }
1079+ }
1080+ PatKind :: Or ( _)
1081+ | PatKind :: Box ( _)
1082+ | PatKind :: Ref ( ..)
1083+ | PatKind :: Guard ( ..)
1084+ | PatKind :: Tuple ( ..)
1085+ | PatKind :: Wild
1086+ | PatKind :: Missing
1087+ | PatKind :: Err ( _) => {
1088+ // If the PatKind is Or, Box, Ref, Guard, or Tuple, the relevant accesses
1089+ // are made later as these patterns contains subpatterns.
1090+ // If the PatKind is Missing, Wild or Err, any relevant accesses are made when processing
1091+ // the other patterns that are part of the match
10191092 }
1020- _ => { }
10211093 }
10221094
10231095 Ok ( ( ) )
@@ -1880,6 +1952,14 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
18801952 self . cat_deref ( hir_id, base)
18811953 }
18821954
1955+ /// Checks whether a type has multiple variants, and therefore, whether a
1956+ /// read of the discriminant might be necessary. Note that the actual MIR
1957+ /// builder code does a more specific check, filtering out variants that
1958+ /// happen to be uninhabited.
1959+ ///
1960+ /// Here, we cannot perform such an accurate checks, because querying
1961+ /// whether a type is inhabited requires that it has been fully inferred,
1962+ /// which cannot be guaranteed at this point.
18831963 fn is_multivariant_adt ( & self , ty : Ty < ' tcx > , span : Span ) -> bool {
18841964 if let ty:: Adt ( def, _) = self . cx . try_structurally_resolve_type ( span, ty) . kind ( ) {
18851965 // Note that if a non-exhaustive SingleVariant is defined in another crate, we need
0 commit comments