diff options
| author | dianne <diannes.gm@gmail.com> | 2025-04-16 18:41:52 -0700 |
|---|---|---|
| committer | dianne <diannes.gm@gmail.com> | 2025-04-24 14:25:27 -0700 |
| commit | 0eb3b110f0b14b21de2e7c283ea21bc86e0d311d (patch) | |
| tree | 5b2040e453a20c590afc7998d12aec5feb882139 | |
| parent | 64c8d5d7f53d7ca053335eeb3ef5756c1bbdb5f2 (diff) | |
| download | rust-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.rs | 51 | ||||
| -rw-r--r-- | compiler/rustc_middle/src/thir.rs | 7 | ||||
| -rw-r--r-- | compiler/rustc_middle/src/ty/typeck_results.rs | 15 | ||||
| -rw-r--r-- | compiler/rustc_mir_build/src/builder/matches/match_pair.rs | 11 | ||||
| -rw-r--r-- | compiler/rustc_mir_build/src/thir/pattern/mod.rs | 11 | ||||
| -rw-r--r-- | tests/ui/pattern/deref-patterns/closure_capture.rs | 18 | ||||
| -rw-r--r-- | tests/ui/pattern/deref-patterns/deref-box.rs | 29 |
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"); +} |
