about summary refs log tree commit diff
diff options
context:
space:
mode:
authordianne <diannes.gm@gmail.com>2025-03-13 22:34:26 -0700
committerdianne <diannes.gm@gmail.com>2025-04-16 15:42:12 -0700
commitff0d4bc743086111c841812ec9925efb075d1243 (patch)
tree3ed45163c0601ff83abd8a9544089e36efa4c736
parent977c9ab7a2ead006e06b1b78cc1b6ac63245560b (diff)
downloadrust-ff0d4bc743086111c841812ec9925efb075d1243.tar.gz
rust-ff0d4bc743086111c841812ec9925efb075d1243.zip
upvar inference for implicit deref patterns
-rw-r--r--compiler/rustc_hir_typeck/src/expr_use_visitor.rs54
-rw-r--r--tests/ui/pattern/deref-patterns/closure_capture.rs27
2 files changed, 70 insertions, 11 deletions
diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
index f43f9b5187b..c92aa228cc2 100644
--- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
+++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
@@ -1000,6 +1000,8 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
                     // determines whether to borrow *at the level of the deref pattern* rather than
                     // borrowing the bound place (since that inner place is inside the temporary that
                     // stores the result of calling `deref()`/`deref_mut()` so can't be captured).
+                    // HACK: this could be a fake pattern corresponding to a deref inserted by match
+                    // ergonomics, in which case `pat.hir_id` will be the id of the subpattern.
                     let mutable = self.cx.typeck_results().pat_has_ref_mut_binding(subpattern);
                     let mutability =
                         if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
@@ -1675,12 +1677,31 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
         // Then we see that to get the same result, we must start with
         // `deref { deref { place_foo }}` instead of `place_foo` since the pattern is now `Some(x,)`
         // and not `&&Some(x,)`, even though its assigned type is that of `&&Some(x,)`.
-        for _ in
-            0..self.cx.typeck_results().pat_adjustments().get(pat.hir_id).map_or(0, |v| v.len())
-        {
+        let typeck_results = self.cx.typeck_results();
+        let adjustments: &[adjustment::PatAdjustment<'tcx>] =
+            typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v);
+        let mut adjusts = adjustments.iter().peekable();
+        while let Some(adjust) = adjusts.next() {
             debug!("applying adjustment to place_with_id={:?}", place_with_id);
-            place_with_id = self.cat_deref(pat.hir_id, place_with_id)?;
+            place_with_id = match adjust.kind {
+                adjustment::PatAdjust::BuiltinDeref => self.cat_deref(pat.hir_id, place_with_id)?,
+                adjustment::PatAdjust::OverloadedDeref => {
+                    // This adjustment corresponds to an overloaded deref; it borrows the scrutinee to
+                    // call `Deref::deref` or `DerefMut::deref_mut`. Invoke the callback before setting
+                    // `place_with_id` to the temporary storing the result of the deref.
+                    // HACK(dianne): giving the callback a fake deref pattern makes sure it behaves the
+                    // same as it would if this were an explicit deref pattern.
+                    op(&place_with_id, &hir::Pat { kind: PatKind::Deref(pat), ..*pat })?;
+                    let target_ty = match adjusts.peek() {
+                        Some(&&next_adjust) => next_adjust.source,
+                        // At the end of the deref chain, we get `pat`'s scrutinee.
+                        None => self.pat_ty_unadjusted(pat)?,
+                    };
+                    self.pat_deref_temp(pat.hir_id, pat, target_ty)?
+                }
+            };
         }
+        drop(typeck_results); // explicitly release borrow of typeck results, just in case.
         let place_with_id = place_with_id; // lose mutability
         debug!("applied adjustment derefs to get place_with_id={:?}", place_with_id);
 
@@ -1783,14 +1804,8 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
                 self.cat_pattern(subplace, subpat, op)?;
             }
             PatKind::Deref(subpat) => {
-                let mutable = self.cx.typeck_results().pat_has_ref_mut_binding(subpat);
-                let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
-                let re_erased = self.cx.tcx().lifetimes.re_erased;
                 let ty = self.pat_ty_adjusted(subpat)?;
-                let ty = Ty::new_ref(self.cx.tcx(), re_erased, ty, mutability);
-                // A deref pattern generates a temporary.
-                let base = self.cat_rvalue(pat.hir_id, ty);
-                let place = self.cat_deref(pat.hir_id, base)?;
+                let place = self.pat_deref_temp(pat.hir_id, subpat, ty)?;
                 self.cat_pattern(place, subpat, op)?;
             }
 
@@ -1843,6 +1858,23 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
         Ok(())
     }
 
+    /// Represents the place of the temp that stores the scrutinee of a deref pattern's interior.
+    fn pat_deref_temp(
+        &self,
+        hir_id: HirId,
+        inner: &hir::Pat<'_>,
+        target_ty: Ty<'tcx>,
+    ) -> Result<PlaceWithHirId<'tcx>, Cx::Error> {
+        let mutable = self.cx.typeck_results().pat_has_ref_mut_binding(inner);
+        let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
+        let re_erased = self.cx.tcx().lifetimes.re_erased;
+        let ty = Ty::new_ref(self.cx.tcx(), re_erased, target_ty, mutability);
+        // A deref pattern stores the result of `Deref::deref` or `DerefMut::deref_mut` ...
+        let base = self.cat_rvalue(hir_id, ty);
+        // ... and the inner pattern matches on the place behind that reference.
+        self.cat_deref(hir_id, base)
+    }
+
     fn is_multivariant_adt(&self, ty: Ty<'tcx>, span: Span) -> bool {
         if let ty::Adt(def, _) = self.cx.try_structurally_resolve_type(span, ty).kind() {
             // Note that if a non-exhaustive SingleVariant is defined in another crate, we need
diff --git a/tests/ui/pattern/deref-patterns/closure_capture.rs b/tests/ui/pattern/deref-patterns/closure_capture.rs
index fc0ddedac2b..08586b6c7ab 100644
--- a/tests/ui/pattern/deref-patterns/closure_capture.rs
+++ b/tests/ui/pattern/deref-patterns/closure_capture.rs
@@ -11,6 +11,15 @@ fn main() {
     assert_eq!(b.len(), 3);
     f();
 
+    let v = vec![1, 2, 3];
+    let f = || {
+        // this should count as a borrow of `v` as a whole
+        let [.., x] = v else { unreachable!() };
+        assert_eq!(x, 3);
+    };
+    assert_eq!(v, [1, 2, 3]);
+    f();
+
     let mut b = Box::new("aaa".to_string());
     let mut f = || {
         let deref!(ref mut s) = b else { unreachable!() };
@@ -18,4 +27,22 @@ fn main() {
     };
     f();
     assert_eq!(b.len(), 5);
+
+    let mut v = vec![1, 2, 3];
+    let mut f = || {
+        // this should count as a mutable borrow of `v` as a whole
+        let [.., ref mut x] = v else { unreachable!() };
+        *x = 4;
+    };
+    f();
+    assert_eq!(v, [1, 2, 4]);
+
+    let mut v = vec![1, 2, 3];
+    let mut f = || {
+        // here, `[.., x]` is adjusted by both an overloaded deref and a builtin deref
+        let [.., x] = &mut v else { unreachable!() };
+        *x = 4;
+    };
+    f();
+    assert_eq!(v, [1, 2, 4]);
 }