From 7b7992fbcf5b8d9af945e32eac241252baa46fab Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Wed, 4 Sep 2024 16:03:47 -0700 Subject: [PATCH] Begin experimental support for pin reborrowing This commit adds basic support for reborrowing `Pin` types in argument position. At the moment it only supports reborrowing `Pin<&mut T>` as `Pin<&mut T>` by inserting a call to `Pin::as_mut()`, and only in argument position (not as the receiver in a method call). --- compiler/rustc_borrowck/messages.ftl | 2 +- .../rustc_borrowck/src/session_diagnostics.rs | 2 +- compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_hir/src/lang_items.rs | 1 + compiler/rustc_hir_typeck/src/coercion.rs | 59 +++++++++++++++++++ .../rustc_hir_typeck/src/expr_use_visitor.rs | 15 +++++ compiler/rustc_middle/src/ty/adjustment.rs | 3 + compiler/rustc_mir_build/src/thir/cx/expr.rs | 45 ++++++++++++++ compiler/rustc_span/src/symbol.rs | 2 + library/core/src/pin.rs | 1 + tests/ui/async-await/pin-reborrow-arg.rs | 27 +++++++++ tests/ui/async-await/pin-reborrow-self.rs | 24 ++++++++ .../feature-gate-pin_ergonomics.rs | 15 +++++ .../feature-gate-pin_ergonomics.stderr | 21 +++++++ 14 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 tests/ui/async-await/pin-reborrow-arg.rs create mode 100644 tests/ui/async-await/pin-reborrow-self.rs create mode 100644 tests/ui/feature-gates/feature-gate-pin_ergonomics.rs create mode 100644 tests/ui/feature-gates/feature-gate-pin_ergonomics.stderr diff --git a/compiler/rustc_borrowck/messages.ftl b/compiler/rustc_borrowck/messages.ftl index edb25e12864..ee4b2f95cb1 100644 --- a/compiler/rustc_borrowck/messages.ftl +++ b/compiler/rustc_borrowck/messages.ftl @@ -207,7 +207,7 @@ borrowck_simd_intrinsic_arg_const = *[other] {$arg}th } argument of `{$intrinsic}` is required to be a `const` item -borrowck_suggest_create_freash_reborrow = +borrowck_suggest_create_fresh_reborrow = consider reborrowing the `Pin` instead of moving it borrowck_suggest_iterate_over_slice = diff --git a/compiler/rustc_borrowck/src/session_diagnostics.rs b/compiler/rustc_borrowck/src/session_diagnostics.rs index 4a50b0f0704..b6c6960d4ca 100644 --- a/compiler/rustc_borrowck/src/session_diagnostics.rs +++ b/compiler/rustc_borrowck/src/session_diagnostics.rs @@ -415,7 +415,7 @@ pub(crate) enum CaptureReasonSuggest<'tcx> { span: Span, }, #[suggestion( - borrowck_suggest_create_freash_reborrow, + borrowck_suggest_create_fresh_reborrow, applicability = "maybe-incorrect", code = ".as_mut()", style = "verbose" diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index d0c0460ddfe..cf20b79dfa9 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -558,6 +558,8 @@ pub fn internal(&self, feature: Symbol) -> bool { (unstable, optimize_attribute, "1.34.0", Some(54882)), /// Allows specifying nop padding on functions for dynamic patching. (unstable, patchable_function_entry, "1.81.0", Some(123115)), + /// Experimental features that make `Pin` more ergonomic. + (incomplete, pin_ergonomics, "CURRENT_RUSTC_VERSION", Some(130494)), /// Allows postfix match `expr.match { ... }` (unstable, postfix_match, "1.79.0", Some(121618)), /// Allows macro attributes on expressions, statements and non-inline modules. diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index e7398fd2226..60e024068e9 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -395,6 +395,7 @@ pub fn extract(attrs: &[ast::Attribute]) -> Option<(Symbol, Span)> { IteratorNext, sym::next, next_fn, Target::Method(MethodKind::Trait { body: false}), GenericRequirement::None; PinNewUnchecked, sym::new_unchecked, new_unchecked_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None; + PinAsMut, sym::pin_as_mut, as_mut_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None; RangeFrom, sym::RangeFrom, range_from_struct, Target::Struct, GenericRequirement::None; RangeFull, sym::RangeFull, range_full_struct, Target::Struct, GenericRequirement::None; diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 3bada1de148..3da4b2893d9 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -214,6 +214,12 @@ fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { ty::Dynamic(predicates, region, ty::DynStar) if self.tcx.features().dyn_star => { return self.coerce_dyn_star(a, b, predicates, region); } + ty::Adt(pin, _) + if self.tcx.features().pin_ergonomics + && pin.did() == self.tcx.lang_items().pin_type().unwrap() => + { + return self.coerce_pin(a, b); + } _ => {} } @@ -774,6 +780,59 @@ fn coerce_dyn_star( }) } + /// Applies reborrowing for `Pin` + /// + /// We currently only support reborrowing `Pin<&mut T>` as `Pin<&mut T>`. This is accomplished + /// by inserting a call to `Pin::as_mut` during MIR building. + /// + /// In the future we might want to support other reborrowing coercions, such as: + /// - `Pin<&mut T>` as `Pin<&T>` + /// - `Pin<&T>` as `Pin<&T>` + /// - `Pin>` as `Pin<&T>` + /// - `Pin>` as `Pin<&mut T>` + #[instrument(skip(self), level = "trace")] + fn coerce_pin(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { + // We need to make sure the two types are compatible for coercion. + // Then we will build a ReborrowPin adjustment and return that as an InferOk. + + // Right now we can only reborrow if this is a `Pin<&mut T>`. + let can_reborrow = |ty: Ty<'tcx>| { + // Get the T out of Pin + let ty = match ty.kind() { + ty::Adt(pin, args) if pin.did() == self.tcx.lang_items().pin_type().unwrap() => { + args[0].expect_ty() + } + _ => { + debug!("can't reborrow {:?} as pinned", ty); + return None; + } + }; + // Make sure the T is something we understand (just `&mut U` for now) + match ty.kind() { + ty::Ref(region, ty, ty::Mutability::Mut) => Some((*region, *ty)), + _ => { + debug!("can't reborrow pin of inner type {:?}", ty); + None + } + } + }; + + let (_, _a_ty) = can_reborrow(a).ok_or(TypeError::Mismatch)?; + let (b_region, _b_ty) = can_reborrow(b).ok_or(TypeError::Mismatch)?; + + // To complete the reborrow, we need to make sure we can unify the inner types, and if so we + // add the adjustments. + self.unify_and(a, b, |_inner_ty| { + vec![Adjustment { + kind: Adjust::ReborrowPin(AutoBorrow::Ref( + b_region, + AutoBorrowMutability::Mut { allow_two_phase_borrow: AllowTwoPhase::No }, + )), + target: b, + }] + }) + } + fn coerce_from_safe_fn( &self, a: Ty<'tcx>, diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index da8c0ad3a30..103a4c72ea8 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -780,6 +780,20 @@ fn walk_adjustment(&self, expr: &hir::Expr<'_>) -> Result<(), Cx::Error> { adjustment::Adjust::Borrow(ref autoref) => { self.walk_autoref(expr, &place_with_id, autoref); } + + adjustment::Adjust::ReborrowPin(ref autoref) => { + // Reborrowing a Pin is like a combinations of a deref and a borrow, so we do + // both. + let bk = match autoref { + adjustment::AutoBorrow::Ref(_, m) => { + ty::BorrowKind::from_mutbl((*m).into()) + } + adjustment::AutoBorrow::RawPtr(m) => ty::BorrowKind::from_mutbl(*m), + }; + self.delegate.borrow_mut().borrow(&place_with_id, place_with_id.hir_id, bk); + + self.walk_autoref(expr, &place_with_id, autoref); + } } place_with_id = self.cat_expr_adjusted(expr, place_with_id, adjustment)?; } @@ -1284,6 +1298,7 @@ fn cat_expr_adjusted_with( adjustment::Adjust::NeverToAny | adjustment::Adjust::Pointer(_) | adjustment::Adjust::Borrow(_) + | adjustment::Adjust::ReborrowPin(_) | adjustment::Adjust::DynStar => { // Result is an rvalue. Ok(self.cat_rvalue(expr.hir_id, target)) diff --git a/compiler/rustc_middle/src/ty/adjustment.rs b/compiler/rustc_middle/src/ty/adjustment.rs index 1236c9efb41..de3619f5b62 100644 --- a/compiler/rustc_middle/src/ty/adjustment.rs +++ b/compiler/rustc_middle/src/ty/adjustment.rs @@ -104,6 +104,9 @@ pub enum Adjust<'tcx> { /// Cast into a dyn* object. DynStar, + + /// Take a Pin and call either as_mut() or as_ref() to get a Pin<&mut T> or Pin<&T>. + ReborrowPin(AutoBorrow<'tcx>), } /// An overloaded autoderef step, representing a `Deref(Mut)::deref(_mut)` diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index aa8ccc8b7dd..8e6e13f94e0 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -74,6 +74,7 @@ pub(super) fn mirror_expr_inner(&mut self, hir_expr: &'tcx hir::Expr<'tcx>) -> E self.thir.exprs.push(expr) } + #[instrument(level = "trace", skip(self, expr, span))] fn apply_adjustment( &mut self, hir_expr: &'tcx hir::Expr<'tcx>, @@ -146,6 +147,50 @@ fn apply_adjustment( ExprKind::RawBorrow { mutability, arg: self.thir.exprs.push(expr) } } Adjust::DynStar => ExprKind::Cast { source: self.thir.exprs.push(expr) }, + Adjust::ReborrowPin(AutoBorrow::Ref(region, m)) => { + debug!("apply ReborrowPin adjustment"); + match m { + AutoBorrowMutability::Mut { .. } => { + // Rewrite `$expr` as `Pin::as_mut(&mut $expr)` + let as_mut_method = + self.tcx().require_lang_item(rustc_hir::LangItem::PinAsMut, Some(span)); + let pin_ty_args = match expr.ty.kind() { + ty::Adt(_, args) => args, + _ => bug!("ReborrowPin with non-Pin type"), + }; + let as_mut_ty = + Ty::new_fn_def(self.tcx, as_mut_method, pin_ty_args.into_iter()); + + let ty = Ty::new_ref(self.tcx, region, expr.ty, ty::Mutability::Mut); + let arg = ExprKind::Borrow { + borrow_kind: BorrowKind::Mut { kind: mir::MutBorrowKind::Default }, + arg: self.thir.exprs.push(expr), + }; + debug!(?arg, "borrow arg"); + let arg = self.thir.exprs.push(Expr { temp_lifetime, ty, span, kind: arg }); + + let kind = ExprKind::Call { + ty: as_mut_ty, + fun: self.thir.exprs.push(Expr { + temp_lifetime, + ty: as_mut_ty, + span, + kind: ExprKind::ZstLiteral { user_ty: None }, + }), + args: Box::new([arg]), + from_hir_call: true, + fn_span: span, + }; + debug!(?kind); + kind + } + AutoBorrowMutability::Not => { + // FIXME: We need to call Pin::as_ref on the expression + bug!("ReborrowPin with shared reference is not implemented yet") + } + } + } + Adjust::ReborrowPin(AutoBorrow::RawPtr(_)) => bug!("ReborrowPin with raw pointer"), }; Expr { temp_lifetime, ty: adjustment.target, span, kind } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index cbe2bafef21..979294563d7 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1418,6 +1418,8 @@ pic, pie, pin, + pin_as_mut, + pin_ergonomics, platform_intrinsics, plugin, plugin_registrar, diff --git a/library/core/src/pin.rs b/library/core/src/pin.rs index 9c13662e08e..bb1196e82de 100644 --- a/library/core/src/pin.rs +++ b/library/core/src/pin.rs @@ -1408,6 +1408,7 @@ impl Pin { /// } /// } /// ``` + #[cfg_attr(not(bootstrap), lang = "pin_as_mut")] #[stable(feature = "pin", since = "1.33.0")] #[inline(always)] pub fn as_mut(&mut self) -> Pin<&mut Ptr::Target> { diff --git a/tests/ui/async-await/pin-reborrow-arg.rs b/tests/ui/async-await/pin-reborrow-arg.rs new file mode 100644 index 00000000000..34f23533b65 --- /dev/null +++ b/tests/ui/async-await/pin-reborrow-arg.rs @@ -0,0 +1,27 @@ +//@ check-pass + +#![feature(pin_ergonomics)] +#![allow(dead_code, incomplete_features)] + +use std::pin::Pin; + +struct Foo; + +impl Foo { + fn baz(self: Pin<&mut Self>) { + } +} + +fn foo(_: Pin<&mut Foo>) { +} + +fn bar(mut x: Pin<&mut Foo>) { + foo(x); + foo(x); // for this to work we need to automatically reborrow, + // as if the user had written `foo(x.as_mut())`. + + Foo::baz(x); + Foo::baz(x); +} + +fn main() {} diff --git a/tests/ui/async-await/pin-reborrow-self.rs b/tests/ui/async-await/pin-reborrow-self.rs new file mode 100644 index 00000000000..b60b6982bb8 --- /dev/null +++ b/tests/ui/async-await/pin-reborrow-self.rs @@ -0,0 +1,24 @@ +//@ check-pass +//@ignore-test + +// Currently ignored due to self reborrowing not being implemented for Pin + +#![feature(pin_ergonomics)] +#![allow(incomplete_features)] + +use std::pin::Pin; + +struct Foo; + +impl Foo { + fn foo(self: Pin<&mut Self>) { + } +} + +fn bar(x: Pin<&mut Foo>) { + x.foo(); + x.foo(); // for this to work we need to automatically reborrow, + // as if the user had written `x.as_mut().foo()`. +} + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-pin_ergonomics.rs b/tests/ui/feature-gates/feature-gate-pin_ergonomics.rs new file mode 100644 index 00000000000..d694531d53a --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-pin_ergonomics.rs @@ -0,0 +1,15 @@ +#![allow(dead_code, incomplete_features)] + +use std::pin::Pin; + +struct Foo; + +fn foo(_: Pin<&mut Foo>) { +} + +fn bar(mut x: Pin<&mut Foo>) { + foo(x); + foo(x); //~ ERROR use of moved value: `x` +} + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-pin_ergonomics.stderr b/tests/ui/feature-gates/feature-gate-pin_ergonomics.stderr new file mode 100644 index 00000000000..6c9029d8176 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-pin_ergonomics.stderr @@ -0,0 +1,21 @@ +error[E0382]: use of moved value: `x` + --> $DIR/feature-gate-pin_ergonomics.rs:12:9 + | +LL | fn bar(mut x: Pin<&mut Foo>) { + | ----- move occurs because `x` has type `Pin<&mut Foo>`, which does not implement the `Copy` trait +LL | foo(x); + | - value moved here +LL | foo(x); + | ^ value used here after move + | +note: consider changing this parameter type in function `foo` to borrow instead if owning the value isn't necessary + --> $DIR/feature-gate-pin_ergonomics.rs:7:11 + | +LL | fn foo(_: Pin<&mut Foo>) { + | --- ^^^^^^^^^^^^^ this parameter takes ownership of the value + | | + | in this function + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0382`.