Skip to content

Commit 85d918c

Browse files
committed
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.
1 parent 8c83dcb commit 85d918c

File tree

7 files changed

+111
-31
lines changed

7 files changed

+111
-31
lines changed

compiler/rustc_hir_typeck/src/expr_use_visitor.rs

+30-21
Original file line numberDiff line numberDiff line change
@@ -1000,13 +1000,15 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
10001000
// determines whether to borrow *at the level of the deref pattern* rather than
10011001
// borrowing the bound place (since that inner place is inside the temporary that
10021002
// stores the result of calling `deref()`/`deref_mut()` so can't be captured).
1003+
// Deref patterns on boxes don't borrow, so we ignore them here.
10031004
// HACK: this could be a fake pattern corresponding to a deref inserted by match
10041005
// ergonomics, in which case `pat.hir_id` will be the id of the subpattern.
1005-
let mutable = self.cx.typeck_results().pat_has_ref_mut_binding(subpattern);
1006-
let mutability =
1007-
if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
1008-
let bk = ty::BorrowKind::from_mutbl(mutability);
1009-
self.delegate.borrow_mut().borrow(place, discr_place.hir_id, bk);
1006+
if let hir::ByRef::Yes(mutability) =
1007+
self.cx.typeck_results().deref_pat_borrow_mode(place.place.ty(), subpattern)
1008+
{
1009+
let bk = ty::BorrowKind::from_mutbl(mutability);
1010+
self.delegate.borrow_mut().borrow(place, discr_place.hir_id, bk);
1011+
}
10101012
}
10111013
PatKind::Never => {
10121014
// 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
16911693
place_with_id = match adjust.kind {
16921694
adjustment::PatAdjust::BuiltinDeref => self.cat_deref(pat.hir_id, place_with_id)?,
16931695
adjustment::PatAdjust::OverloadedDeref => {
1694-
// This adjustment corresponds to an overloaded deref; it borrows the scrutinee to
1695-
// call `Deref::deref` or `DerefMut::deref_mut`. Invoke the callback before setting
1696-
// `place_with_id` to the temporary storing the result of the deref.
1696+
// This adjustment corresponds to an overloaded deref; unless it's on a box, it
1697+
// borrows the scrutinee to call `Deref::deref` or `DerefMut::deref_mut`. Invoke
1698+
// the callback before setting `place_with_id` to the temporary storing the
1699+
// result of the deref.
16971700
// HACK(dianne): giving the callback a fake deref pattern makes sure it behaves the
1698-
// same as it would if this were an explicit deref pattern.
1701+
// same as it would if this were an explicit deref pattern (including for boxes).
16991702
op(&place_with_id, &hir::Pat { kind: PatKind::Deref(pat), ..*pat })?;
17001703
let target_ty = match adjusts.peek() {
17011704
Some(&&next_adjust) => next_adjust.source,
17021705
// At the end of the deref chain, we get `pat`'s scrutinee.
17031706
None => self.pat_ty_unadjusted(pat)?,
17041707
};
1705-
self.pat_deref_temp(pat.hir_id, pat, target_ty)?
1708+
self.pat_deref_place(pat.hir_id, place_with_id, pat, target_ty)?
17061709
}
17071710
};
17081711
}
@@ -1810,7 +1813,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
18101813
}
18111814
PatKind::Deref(subpat) => {
18121815
let ty = self.pat_ty_adjusted(subpat)?;
1813-
let place = self.pat_deref_temp(pat.hir_id, subpat, ty)?;
1816+
let place = self.pat_deref_place(pat.hir_id, place_with_id, subpat, ty)?;
18141817
self.cat_pattern(place, subpat, op)?;
18151818
}
18161819

@@ -1863,21 +1866,27 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
18631866
Ok(())
18641867
}
18651868

1866-
/// Represents the place of the temp that stores the scrutinee of a deref pattern's interior.
1867-
fn pat_deref_temp(
1869+
/// Represents the place matched on by a deref pattern's interior.
1870+
fn pat_deref_place(
18681871
&self,
18691872
hir_id: HirId,
1873+
base_place: PlaceWithHirId<'tcx>,
18701874
inner: &hir::Pat<'_>,
18711875
target_ty: Ty<'tcx>,
18721876
) -> Result<PlaceWithHirId<'tcx>, Cx::Error> {
1873-
let mutable = self.cx.typeck_results().pat_has_ref_mut_binding(inner);
1874-
let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
1875-
let re_erased = self.cx.tcx().lifetimes.re_erased;
1876-
let ty = Ty::new_ref(self.cx.tcx(), re_erased, target_ty, mutability);
1877-
// A deref pattern stores the result of `Deref::deref` or `DerefMut::deref_mut` ...
1878-
let base = self.cat_rvalue(hir_id, ty);
1879-
// ... and the inner pattern matches on the place behind that reference.
1880-
self.cat_deref(hir_id, base)
1877+
match self.cx.typeck_results().deref_pat_borrow_mode(base_place.place.ty(), inner) {
1878+
// Deref patterns on boxes are lowered using a built-in deref.
1879+
hir::ByRef::No => self.cat_deref(hir_id, base_place),
1880+
// For other types, we create a temporary to match on.
1881+
hir::ByRef::Yes(mutability) => {
1882+
let re_erased = self.cx.tcx().lifetimes.re_erased;
1883+
let ty = Ty::new_ref(self.cx.tcx(), re_erased, target_ty, mutability);
1884+
// A deref pattern stores the result of `Deref::deref` or `DerefMut::deref_mut` ...
1885+
let base = self.cat_rvalue(hir_id, ty);
1886+
// ... and the inner pattern matches on the place behind that reference.
1887+
self.cat_deref(hir_id, base)
1888+
}
1889+
}
18811890
}
18821891

18831892
fn is_multivariant_adt(&self, ty: Ty<'tcx>, span: Span) -> bool {

compiler/rustc_middle/src/thir.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,12 @@ pub enum PatKind<'tcx> {
799799
/// Deref pattern, written `box P` for now.
800800
DerefPattern {
801801
subpattern: Box<Pat<'tcx>>,
802-
mutability: hir::Mutability,
802+
/// Whether the pattern scrutinee needs to be borrowed in order to call `Deref::deref` or
803+
/// `DerefMut::deref_mut`, and if so, which. This is `ByRef::No` for deref patterns on
804+
/// boxes; they are lowered using a built-in deref rather than a method call, thus they
805+
/// don't borrow the scrutinee.
806+
#[type_visitable(ignore)]
807+
borrow: ByRef,
803808
},
804809

805810
/// One of the following:

compiler/rustc_middle/src/ty/typeck_results.rs

+15
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,21 @@ impl<'tcx> TypeckResults<'tcx> {
475475
has_ref_mut
476476
}
477477

478+
/// How should a deref pattern find the place for its inner pattern to match on?
479+
///
480+
/// In most cases, if the pattern recursively contains a `ref mut` binding, we find the inner
481+
/// pattern's scrutinee by calling `DerefMut::deref_mut`, and otherwise we call `Deref::deref`.
482+
/// However, for boxes we can use a built-in deref instead, which doesn't borrow the scrutinee;
483+
/// in this case, we return `ByRef::No`.
484+
pub fn deref_pat_borrow_mode(&self, pointer_ty: Ty<'_>, inner: &hir::Pat<'_>) -> ByRef {
485+
if pointer_ty.is_box() {
486+
ByRef::No
487+
} else {
488+
let mutable = self.pat_has_ref_mut_binding(inner);
489+
ByRef::Yes(if mutable { Mutability::Mut } else { Mutability::Not })
490+
}
491+
}
492+
478493
/// For a given closure, returns the iterator of `ty::CapturedPlace`s that are captured
479494
/// by the closure.
480495
pub fn closure_min_captures_flattened(

compiler/rustc_mir_build/src/builder/matches/match_pair.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::sync::Arc;
22

3+
use rustc_hir::ByRef;
34
use rustc_middle::mir::*;
45
use rustc_middle::thir::*;
56
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
@@ -260,7 +261,13 @@ impl<'tcx> MatchPairTree<'tcx> {
260261
None
261262
}
262263

263-
PatKind::Deref { ref subpattern } => {
264+
PatKind::Deref { ref subpattern }
265+
| PatKind::DerefPattern { ref subpattern, borrow: ByRef::No } => {
266+
if cfg!(debug_assertions) && matches!(pattern.kind, PatKind::DerefPattern { .. }) {
267+
// Only deref patterns on boxes can be lowered using a built-in deref.
268+
debug_assert!(pattern.ty.is_box());
269+
}
270+
264271
MatchPairTree::for_pattern(
265272
place_builder.deref(),
266273
subpattern,
@@ -271,7 +278,7 @@ impl<'tcx> MatchPairTree<'tcx> {
271278
None
272279
}
273280

274-
PatKind::DerefPattern { ref subpattern, mutability } => {
281+
PatKind::DerefPattern { ref subpattern, borrow: ByRef::Yes(mutability) } => {
275282
// Create a new temporary for each deref pattern.
276283
// FIXME(deref_patterns): dedup temporaries to avoid multiple `deref()` calls?
277284
let temp = cx.temp(

compiler/rustc_mir_build/src/thir/pattern/mod.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,8 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
111111
let kind = match adjust.kind {
112112
PatAdjust::BuiltinDeref => PatKind::Deref { subpattern: thir_pat },
113113
PatAdjust::OverloadedDeref => {
114-
let mutable = self.typeck_results.pat_has_ref_mut_binding(pat);
115-
let mutability =
116-
if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
117-
PatKind::DerefPattern { subpattern: thir_pat, mutability }
114+
let borrow = self.typeck_results.deref_pat_borrow_mode(adjust.source, pat);
115+
PatKind::DerefPattern { subpattern: thir_pat, borrow }
118116
}
119117
};
120118
Box::new(Pat { span, ty: adjust.source, kind })
@@ -308,9 +306,8 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
308306
}
309307

310308
hir::PatKind::Deref(subpattern) => {
311-
let mutable = self.typeck_results.pat_has_ref_mut_binding(subpattern);
312-
let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not };
313-
PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), mutability }
309+
let borrow = self.typeck_results.deref_pat_borrow_mode(ty, subpattern);
310+
PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), borrow }
314311
}
315312
hir::PatKind::Ref(subpattern, _) => {
316313
// Track the default binding mode for the Rust 2024 migration suggestion.

tests/ui/pattern/deref-patterns/closure_capture.rs

+18
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use std::rc::Rc;
66

7+
struct NoCopy;
8+
79
fn main() {
810
let b = Rc::new("aaa".to_string());
911
let f = || {
@@ -47,4 +49,20 @@ fn main() {
4749
};
4850
f();
4951
assert_eq!(v, [1, 2, 4]);
52+
53+
let b = Box::new(NoCopy);
54+
let f = || {
55+
// this should move out of the box rather than borrow.
56+
let deref!(x) = b else { unreachable!() };
57+
drop::<NoCopy>(x);
58+
};
59+
f();
60+
61+
let b = Box::new((NoCopy,));
62+
let f = || {
63+
// this should move out of the box rather than borrow.
64+
let (x,) = b else { unreachable!() };
65+
drop::<NoCopy>(x);
66+
};
67+
f();
5068
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//@ run-pass
2+
//! Deref patterns on boxes are lowered using built-in derefs, rather than generic `Deref::deref`
3+
//! and `DerefMut::deref_mut`. Test that they work as expected.
4+
5+
#![feature(deref_patterns)]
6+
#![expect(incomplete_features)]
7+
8+
fn unbox_1<T>(b: Box<T>) -> T {
9+
let deref!(x) = b else { unreachable!() };
10+
x
11+
}
12+
13+
fn unbox_2<T>(b: Box<(T,)>) -> T {
14+
let (x,) = b else { unreachable!() };
15+
x
16+
}
17+
18+
fn main() {
19+
// test that deref patterns can move out of boxes
20+
let b1 = Box::new(0);
21+
let b2 = Box::new((0,));
22+
assert_eq!(unbox_1(b1), unbox_2(b2));
23+
24+
// test that borrowing from a box also works
25+
let mut b = "hi".to_owned().into_boxed_str();
26+
let deref!(ref mut s) = b else { unreachable!() };
27+
s.make_ascii_uppercase();
28+
assert_eq!(&*b, "HI");
29+
}

0 commit comments

Comments
 (0)