Rollup merge of #140022 - dianne:box-deref-pats, r=Nadrieril

allow deref patterns to move out of boxes

This adds a case to lower deref patterns on boxes using a built-in deref instead of a `Deref::deref` or `DerefMut::deref_mut` call: if `deref!(inner): Box<T>` is matching on place `place`, the inner pattern `inner` now matches on `*place` rather than a temporary. No longer needing to call a method also means it won't borrow the scrutinee in match arms. This allows for bindings in `inner` to move out of `*place`.

For comparison with box patterns, this uses the same MIR lowering but different THIR. Consequently, deref patterns on boxes are treated the same as any other deref patterns in match exhaustiveness analysis. Box patterns can't quite be implemented in terms of deref patterns until exhaustiveness checking for deref patterns is implemented (I'll open a PR for exhaustiveness soon!).

Tracking issue: #87121

r? ``@Nadrieril``
This commit is contained in:
Chris Denton
2025-04-28 23:29:15 +00:00
committed by GitHub
14 changed files with 229 additions and 82 deletions

View File

@@ -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 {

View File

@@ -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:

View File

@@ -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(

View File

@@ -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(

View File

@@ -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.

View File

@@ -7,7 +7,7 @@ The tracking issue for this feature is: [#87121]
------------------------
> **Note**: This feature is incomplete. In the future, it is meant to supersede
> [`box_patterns`](./box-patterns.md) and [`string_deref_patterns`](./string-deref-patterns.md).
> [`box_patterns`] and [`string_deref_patterns`].
This feature permits pattern matching on [smart pointers in the standard library] through their
`Deref` target types, either implicitly or with explicit `deref!(_)` patterns (the syntax of which
@@ -54,6 +54,17 @@ if let [b] = &mut *v {
assert_eq!(v, [Box::new(Some(2))]);
```
Like [`box_patterns`], deref patterns may move out of boxes:
```rust
# #![feature(deref_patterns)]
# #![allow(incomplete_features)]
struct NoCopy;
// Match exhaustiveness analysis is not yet implemented.
let deref!(x) = Box::new(NoCopy) else { unreachable!() };
drop::<NoCopy>(x);
```
Additionally, when `deref_patterns` is enabled, string literal patterns may be written where `str`
is expected. Likewise, byte string literal patterns may be written where `[u8]` or `[u8; _]` is
expected. This lets them be used in `deref!(_)` patterns:
@@ -75,4 +86,6 @@ match *"test" {
Implicit deref pattern syntax is not yet supported for string or byte string literals.
[`box_patterns`]: ./box-patterns.md
[`string_deref_patterns`]: ./string-deref-patterns.md
[smart pointers in the standard library]: https://doc.rust-lang.org/std/ops/trait.DerefPure.html#implementors

View File

@@ -3,6 +3,8 @@
#![feature(deref_patterns)]
#![allow(incomplete_features)]
use std::rc::Rc;
#[cfg(explicit)]
fn simple_vec(vec: Vec<u32>) -> u32 {
match vec {
@@ -53,29 +55,29 @@ fn nested_vec(vecvec: Vec<Vec<u32>>) -> u32 {
#[cfg(explicit)]
fn ref_mut(val: u32) -> u32 {
let mut b = Box::new(0u32);
let mut b = vec![0u32];
match &mut b {
deref!(_x) if false => unreachable!(),
deref!(x) => {
deref!([_x]) if false => unreachable!(),
deref!([x]) => {
*x = val;
}
_ => unreachable!(),
}
let deref!(x) = &b else { unreachable!() };
let deref!([x]) = &b else { unreachable!() };
*x
}
#[cfg(implicit)]
fn ref_mut(val: u32) -> u32 {
let mut b = Box::new((0u32,));
let mut b = vec![0u32];
match &mut b {
(_x,) if false => unreachable!(),
(x,) => {
[_x] if false => unreachable!(),
[x] => {
*x = val;
}
_ => unreachable!(),
}
let (x,) = &b else { unreachable!() };
let [x] = &b else { unreachable!() };
*x
}
@@ -83,7 +85,7 @@ fn ref_mut(val: u32) -> u32 {
#[rustfmt::skip]
fn or_and_guard(tuple: (u32, u32)) -> u32 {
let mut sum = 0;
let b = Box::new(tuple);
let b = Rc::new(tuple);
match b {
deref!((x, _) | (_, x)) if { sum += x; false } => {},
_ => {},
@@ -95,7 +97,7 @@ fn or_and_guard(tuple: (u32, u32)) -> u32 {
#[rustfmt::skip]
fn or_and_guard(tuple: (u32, u32)) -> u32 {
let mut sum = 0;
let b = Box::new(tuple);
let b = Rc::new(tuple);
match b {
(x, _) | (_, x) if { sum += x; false } => {},
_ => {},

View File

@@ -5,11 +5,11 @@ use std::rc::Rc;
struct Struct;
fn cant_move_out_box(b: Box<Struct>) -> Struct {
fn cant_move_out_vec(b: Vec<Struct>) -> Struct {
match b {
//~^ ERROR: cannot move out of a shared reference
deref!(x) => x,
_ => unreachable!(),
//~^ ERROR: cannot move out of type `[Struct]`, a non-copy slice
deref!([x]) => x,
_ => panic!(),
}
}
@@ -21,16 +21,16 @@ fn cant_move_out_rc(rc: Rc<Struct>) -> Struct {
}
}
struct Container(Struct);
fn cant_move_out_box_implicit(b: Box<Container>) -> Struct {
fn cant_move_out_vec_implicit(b: Vec<Struct>) -> Struct {
match b {
//~^ ERROR: cannot move out of a shared reference
Container(x) => x,
_ => unreachable!(),
//~^ ERROR: cannot move out of type `[Struct]`, a non-copy slice
[x] => x,
_ => panic!(),
}
}
struct Container(Struct);
fn cant_move_out_rc_implicit(rc: Rc<Container>) -> Struct {
match rc {
//~^ ERROR: cannot move out of a shared reference

View File

@@ -1,19 +1,19 @@
error[E0507]: cannot move out of a shared reference
error[E0508]: cannot move out of type `[Struct]`, a non-copy slice
--> $DIR/cant_move_out_of_pattern.rs:9:11
|
LL | match b {
| ^
| ^ cannot move out of here
LL |
LL | deref!(x) => x,
| -
| |
| data moved here
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
LL | deref!([x]) => x,
| -
| |
| data moved here
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
|
help: consider borrowing the pattern binding
|
LL | deref!(ref x) => x,
| +++
LL | deref!([ref x]) => x,
| +++
error[E0507]: cannot move out of a shared reference
--> $DIR/cant_move_out_of_pattern.rs:17:11
@@ -32,22 +32,22 @@ help: consider borrowing the pattern binding
LL | deref!(ref x) => x,
| +++
error[E0507]: cannot move out of a shared reference
--> $DIR/cant_move_out_of_pattern.rs:27:11
error[E0508]: cannot move out of type `[Struct]`, a non-copy slice
--> $DIR/cant_move_out_of_pattern.rs:25:11
|
LL | match b {
| ^
| ^ cannot move out of here
LL |
LL | Container(x) => x,
| -
| |
| data moved here
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
LL | [x] => x,
| -
| |
| data moved here
| move occurs because `x` has type `Struct`, which does not implement the `Copy` trait
|
help: consider borrowing the pattern binding
|
LL | Container(ref x) => x,
| +++
LL | [ref x] => x,
| +++
error[E0507]: cannot move out of a shared reference
--> $DIR/cant_move_out_of_pattern.rs:35:11
@@ -68,4 +68,5 @@ LL | Container(ref x) => x,
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0507`.
Some errors have detailed explanations: E0507, E0508.
For more information about an error, try `rustc --explain E0507`.

View File

@@ -2,8 +2,12 @@
#![feature(deref_patterns)]
#![allow(incomplete_features)]
use std::rc::Rc;
struct NoCopy;
fn main() {
let b = Box::new("aaa".to_string());
let b = Rc::new("aaa".to_string());
let f = || {
let deref!(ref s) = b else { unreachable!() };
assert_eq!(s.len(), 3);
@@ -20,13 +24,13 @@ fn main() {
assert_eq!(v, [1, 2, 3]);
f();
let mut b = Box::new("aaa".to_string());
let mut b = "aaa".to_string();
let mut f = || {
let deref!(ref mut s) = b else { unreachable!() };
s.push_str("aa");
s.make_ascii_uppercase();
};
f();
assert_eq!(b.len(), 5);
assert_eq!(b, "AAA");
let mut v = vec![1, 2, 3];
let mut f = || {
@@ -45,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();
}

View File

@@ -0,0 +1,37 @@
//@ 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 unbox_separately<T>(b: Box<(T, T)>) -> (T, T) {
let (x, _) = b else { unreachable!() };
let (_, y) = b else { unreachable!() };
(x, y)
}
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));
let b3 = Box::new((1, 2));
assert_eq!(unbox_separately(b3), (1, 2));
// 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");
}

View File

@@ -3,6 +3,23 @@
#[rustfmt::skip]
fn main() {
let mut v = vec![false];
match v {
deref!([true]) => {}
_ if { v[0] = true; false } => {}
//~^ ERROR cannot borrow `v` as mutable because it is also borrowed as immutable
deref!([false]) => {}
_ => {},
}
match v {
[true] => {}
_ if { v[0] = true; false } => {}
//~^ ERROR cannot borrow `v` as mutable because it is also borrowed as immutable
[false] => {}
_ => {},
}
// deref patterns on boxes are lowered specially; test them separately.
let mut b = Box::new(false);
match b {
deref!(true) => {}

View File

@@ -1,6 +1,28 @@
error[E0510]: cannot assign `*b` in match guard
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> $DIR/fake_borrows.rs:9:16
|
LL | match v {
| - immutable borrow occurs here
LL | deref!([true]) => {}
LL | _ if { v[0] = true; false } => {}
| ^ - immutable borrow later used here
| |
| mutable borrow occurs here
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> $DIR/fake_borrows.rs:16:16
|
LL | match v {
| - immutable borrow occurs here
LL | [true] => {}
LL | _ if { v[0] = true; false } => {}
| ^ - immutable borrow later used here
| |
| mutable borrow occurs here
error[E0510]: cannot assign `*b` in match guard
--> $DIR/fake_borrows.rs:26:16
|
LL | match b {
| - value is immutable in match guard
LL | deref!(true) => {}
@@ -8,7 +30,7 @@ LL | _ if { *b = true; false } => {}
| ^^^^^^^^^ cannot assign
error[E0510]: cannot assign `*b` in match guard
--> $DIR/fake_borrows.rs:16:16
--> $DIR/fake_borrows.rs:33:16
|
LL | match b {
| - value is immutable in match guard
@@ -16,6 +38,7 @@ LL | true => {}
LL | _ if { *b = true; false } => {}
| ^^^^^^^^^ cannot assign
error: aborting due to 2 previous errors
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0510`.
Some errors have detailed explanations: E0502, E0510.
For more information about an error, try `rustc --explain E0502`.

View File

@@ -4,6 +4,7 @@
#![allow(incomplete_features)]
use std::borrow::Cow;
use std::rc::Rc;
fn main() {
let cow: Cow<'static, [u8]> = Cow::Borrowed(&[1, 2, 3]);
@@ -18,7 +19,7 @@ fn main() {
Cow::Owned(_) => unreachable!(),
}
match Box::new(&cow) {
match Rc::new(&cow) {
Cow::Borrowed { 0: _ } => {}
Cow::Owned { 0: _ } => unreachable!(),
_ => unreachable!(),
@@ -37,7 +38,7 @@ fn main() {
Cow::Owned(_) => {}
}
match Box::new(&cow_of_cow) {
match Rc::new(&cow_of_cow) {
Cow::Borrowed { 0: _ } => unreachable!(),
Cow::Owned { 0: _ } => {}
_ => unreachable!(),