From ff0d4bc743086111c841812ec9925efb075d1243 Mon Sep 17 00:00:00 2001 From: dianne Date: Thu, 13 Mar 2025 22:34:26 -0700 Subject: [PATCH] upvar inference for implicit deref patterns --- .../rustc_hir_typeck/src/expr_use_visitor.rs | 54 +++++++++++++++---- .../pattern/deref-patterns/closure_capture.rs | 27 ++++++++++ 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 f43f9b5187b6..c92aa228cc2c 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, 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 fc0ddedac2b7..08586b6c7abd 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]); }