about summary refs log tree commit diff
diff options
context:
space:
mode:
authordianne <diannes.gm@gmail.com>2025-04-16 18:41:52 -0700
committerdianne <diannes.gm@gmail.com>2025-04-24 14:25:27 -0700
commit0eb3b110f0b14b21de2e7c283ea21bc86e0d311d (patch)
tree5b2040e453a20c590afc7998d12aec5feb882139
parent64c8d5d7f53d7ca053335eeb3ef5756c1bbdb5f2 (diff)
downloadrust-0eb3b110f0b14b21de2e7c283ea21bc86e0d311d.tar.gz
rust-0eb3b110f0b14b21de2e7c283ea21bc86e0d311d.zip
lower deref patterns on boxes using built-in derefs
This allows deref patterns to move out of boxes.

Implementation-wise, I've opted to put the information of whether a
deref pattern uses a built-in deref or a method call in the THIR. It'd
be a bit less code to check `.is_box()` everywhere, but I think this way
feels more robust (and we don't have a `mutability` field in the THIR
that we ignore when the smart pointer's a box). I'm not sure about the
naming (or using `ByRef`), though.
-rw-r--r--compiler/rustc_hir_typeck/src/expr_use_visitor.rs51
-rw-r--r--compiler/rustc_middle/src/thir.rs7
-rw-r--r--compiler/rustc_middle/src/ty/typeck_results.rs15
-rw-r--r--compiler/rustc_mir_build/src/builder/matches/match_pair.rs11
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/mod.rs11
-rw-r--r--tests/ui/pattern/deref-patterns/closure_capture.rs18
-rw-r--r--tests/ui/pattern/deref-patterns/deref-box.rs29
7 files changed, 111 insertions, 31 deletions
diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
index f5e0f01e4c5..17e13ec0a37 100644
--- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
+++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
@@ -1000,13 +1000,15 @@ 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).
+                    // Deref patterns on boxes don't borrow, so we ignore them here.
                     // 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 };
-                    let bk = ty::BorrowKind::from_mutbl(mutability);
-                    self.delegate.borrow_mut().borrow(place, discr_place.hir_id, bk);
+                    if let hir::ByRef::Yes(mutability) =
+                        self.cx.typeck_results().deref_pat_borrow_mode(place.place.ty(), subpattern)
+                    {
+                        let bk = ty::BorrowKind::from_mutbl(mutability);
+                        self.delegate.borrow_mut().borrow(place, discr_place.hir_id, bk);
+                    }
                 }
                 PatKind::Never => {
                     // A `!` pattern always counts as an immutable read of the discriminant,
@@ -1691,18 +1693,19 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
             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.
+                    // This adjustment corresponds to an overloaded deref; unless it's on a box, 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.
+                    // same as it would if this were an explicit deref pattern (including for boxes).
                     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)?
+                    self.pat_deref_place(pat.hir_id, place_with_id, pat, target_ty)?
                 }
             };
         }
@@ -1810,7 +1813,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
             }
             PatKind::Deref(subpat) => {
                 let ty = self.pat_ty_adjusted(subpat)?;
-                let place = self.pat_deref_temp(pat.hir_id, subpat, ty)?;
+                let place = self.pat_deref_place(pat.hir_id, place_with_id, subpat, ty)?;
                 self.cat_pattern(place, subpat, op)?;
             }
 
@@ -1863,21 +1866,27 @@ 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(
+    /// Represents the place matched on by a deref pattern's interior.
+    fn pat_deref_place(
         &self,
         hir_id: HirId,
+        base_place: PlaceWithHirId<'tcx>,
         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)
+        match self.cx.typeck_results().deref_pat_borrow_mode(base_place.place.ty(), inner) {
+            // Deref patterns on boxes are lowered using a built-in deref.
+            hir::ByRef::No => self.cat_deref(hir_id, base_place),
+            // For other types, we create a temporary to match on.
+            hir::ByRef::Yes(mutability) => {
+                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 {
diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs
index c168142fb1e..086ec529f33 100644
--- a/compiler/rustc_middle/src/thir.rs
+++ b/compiler/rustc_middle/src/thir.rs
@@ -799,7 +799,12 @@ pub enum PatKind<'tcx> {
     /// Deref pattern, written `box P` for now.
     DerefPattern {
         subpattern: Box<Pat<'tcx>>,
-        mutability: hir::Mutability,
+        /// Whether the pattern scrutinee needs to be borrowed in order to call `Deref::deref` or
+        /// `DerefMut::deref_mut`, and if so, which. This is `ByRef::No` for deref patterns on
+        /// boxes; they are lowered using a built-in deref rather than a method call, thus they
+        /// don't borrow the scrutinee.
+        #[type_visitable(ignore)]
+        borrow: ByRef,
     },
 
     /// One of the following:
diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs
index 4c5c669771f..8c5827d36df 100644
--- a/compiler/rustc_middle/src/ty/typeck_results.rs
+++ b/compiler/rustc_middle/src/ty/typeck_results.rs
@@ -475,6 +475,21 @@ impl<'tcx> TypeckResults<'tcx> {
         has_ref_mut
     }
 
+    /// How should a deref pattern find the place for its inner pattern to match on?
+    ///
+    /// In most cases, if the pattern recursively contains a `ref mut` binding, we find the inner
+    /// pattern's scrutinee by calling `DerefMut::deref_mut`, and otherwise we call `Deref::deref`.
+    /// However, for boxes we can use a built-in deref instead, which doesn't borrow the scrutinee;
+    /// in this case, we return `ByRef::No`.
+    pub fn deref_pat_borrow_mode(&self, pointer_ty: Ty<'_>, inner: &hir::Pat<'_>) -> ByRef {
+        if pointer_ty.is_box() {
+            ByRef::No
+        } else {
+            let mutable = self.pat_has_ref_mut_binding(inner);
+            ByRef::Yes(if mutable { Mutability::Mut } else { Mutability::Not })
+        }
+    }
+
     /// For a given closure, returns the iterator of `ty::CapturedPlace`s that are captured
     /// by the closure.
     pub fn closure_min_captures_flattened(
diff --git a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs
index d66b38c5b00..3a7854a5e11 100644
--- a/compiler/rustc_mir_build/src/builder/matches/match_pair.rs
+++ b/compiler/rustc_mir_build/src/builder/matches/match_pair.rs
@@ -1,5 +1,6 @@
 use std::sync::Arc;
 
+use rustc_hir::ByRef;
 use rustc_middle::mir::*;
 use rustc_middle::thir::*;
 use rustc_middle::ty::{self, Ty, TypeVisitableExt};
@@ -260,7 +261,13 @@ impl<'tcx> MatchPairTree<'tcx> {
                 None
             }
 
-            PatKind::Deref { ref subpattern } => {
+            PatKind::Deref { ref subpattern }
+            | PatKind::DerefPattern { ref subpattern, borrow: ByRef::No } => {
+                if cfg!(debug_assertions) && matches!(pattern.kind, PatKind::DerefPattern { .. }) {
+                    // Only deref patterns on boxes can be lowered using a built-in deref.
+                    debug_assert!(pattern.ty.is_box());
+                }
+
                 MatchPairTree::for_pattern(
                     place_builder.deref(),
                     subpattern,
@@ -271,7 +278,7 @@ impl<'tcx> MatchPairTree<'tcx> {
                 None
             }
 
-            PatKind::DerefPattern { ref subpattern, mutability } => {
+            PatKind::DerefPattern { ref subpattern, borrow: ByRef::Yes(mutability) } => {
                 // Create a new temporary for each deref pattern.
                 // FIXME(deref_patterns): dedup temporaries to avoid multiple `deref()` calls?
                 let temp = cx.temp(
diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
index 8f058efdfac..8e69ff568b9 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs
@@ -111,10 +111,8 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
             let kind = match adjust.kind {
                 PatAdjust::BuiltinDeref => PatKind::Deref { subpattern: thir_pat },
                 PatAdjust::OverloadedDeref => {
-                    let mutable = self.typeck_results.pat_has_ref_mut_binding(pat);
-                    let mutability =
-                        if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
-                    PatKind::DerefPattern { subpattern: thir_pat, mutability }
+                    let borrow = self.typeck_results.deref_pat_borrow_mode(adjust.source, pat);
+                    PatKind::DerefPattern { subpattern: thir_pat, borrow }
                 }
             };
             Box::new(Pat { span, ty: adjust.source, kind })
@@ -308,9 +306,8 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
             }
 
             hir::PatKind::Deref(subpattern) => {
-                let mutable = self.typeck_results.pat_has_ref_mut_binding(subpattern);
-                let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
-                PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), mutability }
+                let borrow = self.typeck_results.deref_pat_borrow_mode(ty, subpattern);
+                PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), borrow }
             }
             hir::PatKind::Ref(subpattern, _) => {
                 // Track the default binding mode for the Rust 2024 migration suggestion.
diff --git a/tests/ui/pattern/deref-patterns/closure_capture.rs b/tests/ui/pattern/deref-patterns/closure_capture.rs
index 5d1aa371eee..cf78eeda1d5 100644
--- a/tests/ui/pattern/deref-patterns/closure_capture.rs
+++ b/tests/ui/pattern/deref-patterns/closure_capture.rs
@@ -4,6 +4,8 @@
 
 use std::rc::Rc;
 
+struct NoCopy;
+
 fn main() {
     let b = Rc::new("aaa".to_string());
     let f = || {
@@ -47,4 +49,20 @@ fn main() {
     };
     f();
     assert_eq!(v, [1, 2, 4]);
+
+    let b = Box::new(NoCopy);
+    let f = || {
+        // this should move out of the box rather than borrow.
+        let deref!(x) = b else { unreachable!() };
+        drop::<NoCopy>(x);
+    };
+    f();
+
+    let b = Box::new((NoCopy,));
+    let f = || {
+        // this should move out of the box rather than borrow.
+        let (x,) = b else { unreachable!() };
+        drop::<NoCopy>(x);
+    };
+    f();
 }
diff --git a/tests/ui/pattern/deref-patterns/deref-box.rs b/tests/ui/pattern/deref-patterns/deref-box.rs
new file mode 100644
index 00000000000..3917c8971d9
--- /dev/null
+++ b/tests/ui/pattern/deref-patterns/deref-box.rs
@@ -0,0 +1,29 @@
+//@ run-pass
+//! Deref patterns on boxes are lowered using built-in derefs, rather than generic `Deref::deref`
+//! and `DerefMut::deref_mut`. Test that they work as expected.
+
+#![feature(deref_patterns)]
+#![expect(incomplete_features)]
+
+fn unbox_1<T>(b: Box<T>) -> T {
+    let deref!(x) = b else { unreachable!() };
+    x
+}
+
+fn unbox_2<T>(b: Box<(T,)>) -> T {
+    let (x,) = b else { unreachable!() };
+    x
+}
+
+fn main() {
+    // test that deref patterns can move out of boxes
+    let b1 = Box::new(0);
+    let b2 = Box::new((0,));
+    assert_eq!(unbox_1(b1), unbox_2(b2));
+
+    // test that borrowing from a box also works
+    let mut b = "hi".to_owned().into_boxed_str();
+    let deref!(ref mut s) = b else { unreachable!() };
+    s.make_ascii_uppercase();
+    assert_eq!(&*b, "HI");
+}