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 fa3a7049f4a..40333c3953a 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -558,6 +558,8 @@ declare_features! ( (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_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index fca7babea30..26c9b8dfdc3 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -214,6 +214,12 @@ impl<'f, 'tcx> Coerce<'f, '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 + && self.tcx.is_lang_item(pin.did(), hir::LangItem::Pin) => + { + return self.coerce_pin(a, b); + } _ => {} } @@ -774,6 +780,62 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { }) } + /// 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 extract_pin_mut = |ty: Ty<'tcx>| { + // Get the T out of Pin + let (pin, ty) = match ty.kind() { + ty::Adt(pin, args) if self.tcx.is_lang_item(pin.did(), hir::LangItem::Pin) => { + (*pin, args[0].expect_ty()) + } + _ => { + debug!("can't reborrow {:?} as pinned", ty); + return Err(TypeError::Mismatch); + } + }; + // Make sure the T is something we understand (just `&mut U` for now) + match ty.kind() { + ty::Ref(region, ty, mutbl) => Ok((pin, *region, *ty, *mutbl)), + _ => { + debug!("can't reborrow pin of inner type {:?}", ty); + Err(TypeError::Mismatch) + } + } + }; + + let (pin, a_region, a_ty, mut_a) = extract_pin_mut(a)?; + let (_, b_region, _b_ty, mut_b) = extract_pin_mut(b)?; + + coerce_mutbls(mut_a, mut_b)?; + + // update a with b's mutability since we'll be coercing mutability + let a = Ty::new_adt( + self.tcx, + pin, + self.tcx.mk_args(&[Ty::new_ref(self.tcx, a_region, a_ty, mut_b).into()]), + ); + + // 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(b_region, mut_b), 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..3b2ddf659a1 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -780,6 +780,16 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx adjustment::Adjust::Borrow(ref autoref) => { self.walk_autoref(expr, &place_with_id, autoref); } + + adjustment::Adjust::ReborrowPin(_, mutbl) => { + // Reborrowing a Pin is like a combinations of a deref and a borrow, so we do + // both. + let bk = match mutbl { + ty::Mutability::Not => ty::BorrowKind::ImmBorrow, + ty::Mutability::Mut => ty::BorrowKind::MutBorrow, + }; + self.delegate.borrow_mut().borrow(&place_with_id, place_with_id.hir_id, bk); + } } place_with_id = self.cat_expr_adjusted(expr, place_with_id, adjustment)?; } @@ -1284,6 +1294,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx 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..5a32078760e 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 pinned reference and reborrow as a `Pin<&mut T>` or `Pin<&T>`. + ReborrowPin(ty::Region<'tcx>, hir::Mutability), } /// 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..3f730b5d183 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 @@ impl<'tcx> Cx<'tcx> { 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,67 @@ impl<'tcx> Cx<'tcx> { ExprKind::RawBorrow { mutability, arg: self.thir.exprs.push(expr) } } Adjust::DynStar => ExprKind::Cast { source: self.thir.exprs.push(expr) }, + Adjust::ReborrowPin(region, mutbl) => { + debug!("apply ReborrowPin adjustment"); + // Rewrite `$expr` as `Pin { __pointer: &(mut)? *($expr).__pointer }` + + // We'll need these types later on + let pin_ty_args = match expr.ty.kind() { + ty::Adt(_, args) => args, + _ => bug!("ReborrowPin with non-Pin type"), + }; + let pin_ty = pin_ty_args.iter().next().unwrap().expect_ty(); + let ptr_target_ty = match pin_ty.kind() { + ty::Ref(_, ty, _) => *ty, + _ => bug!("ReborrowPin with non-Ref type"), + }; + + // pointer = ($expr).__pointer + let pointer_target = ExprKind::Field { + lhs: self.thir.exprs.push(expr), + variant_index: FIRST_VARIANT, + name: FieldIdx::from(0u32), + }; + let arg = Expr { temp_lifetime, ty: pin_ty, span, kind: pointer_target }; + let arg = self.thir.exprs.push(arg); + + // arg = *pointer + let expr = ExprKind::Deref { arg }; + let arg = self.thir.exprs.push(Expr { + temp_lifetime, + ty: ptr_target_ty, + span, + kind: expr, + }); + + // expr = &mut target + let borrow_kind = match mutbl { + hir::Mutability::Mut => BorrowKind::Mut { kind: mir::MutBorrowKind::Default }, + hir::Mutability::Not => BorrowKind::Shared, + }; + let new_pin_target = Ty::new_ref(self.tcx, region, ptr_target_ty, mutbl); + let expr = self.thir.exprs.push(Expr { + temp_lifetime, + ty: new_pin_target, + span, + kind: ExprKind::Borrow { borrow_kind, arg }, + }); + + // kind = Pin { __pointer: pointer } + let pin_did = self.tcx.require_lang_item(rustc_hir::LangItem::Pin, Some(span)); + let args = self.tcx.mk_args(&[new_pin_target.into()]); + let kind = ExprKind::Adt(Box::new(AdtExpr { + adt_def: self.tcx.adt_def(pin_did), + variant_index: FIRST_VARIANT, + args, + fields: Box::new([FieldExpr { name: FieldIdx::from(0u32), expr }]), + user_ty: None, + base: None, + })); + + debug!(?kind); + kind + } }; Expr { temp_lifetime, ty: adjustment.target, span, kind } @@ -1014,7 +1076,7 @@ impl<'tcx> Cx<'tcx> { // Reconstruct the output assuming it's a reference with the // same region and mutability as the receiver. This holds for - // `Deref(Mut)::Deref(_mut)` and `Index(Mut)::index(_mut)`. + // `Deref(Mut)::deref(_mut)` and `Index(Mut)::index(_mut)`. let ty::Ref(region, _, mutbl) = *self.thir[args[0]].ty.kind() else { span_bug!(span, "overloaded_place: receiver is not a reference"); }; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 89b27d89364..de4532bcb99 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1453,6 +1453,7 @@ symbols! { pic, pie, pin, + pin_ergonomics, platform_intrinsics, plugin, plugin_registrar, 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..2008bd1f52d --- /dev/null +++ b/tests/ui/async-await/pin-reborrow-arg.rs @@ -0,0 +1,36 @@ +//@ 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 foo_const(_: Pin<&Foo>) { +} + +fn bar(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); + + foo_const(x); // make sure we can reborrow &mut as &. + + let x: Pin<&Foo> = Pin::new(&Foo); + + foo_const(x); // make sure reborrowing from & to & works. +} + +fn main() {} diff --git a/tests/ui/async-await/pin-reborrow-const-as-mut.rs b/tests/ui/async-await/pin-reborrow-const-as-mut.rs new file mode 100644 index 00000000000..27c70a7b4df --- /dev/null +++ b/tests/ui/async-await/pin-reborrow-const-as-mut.rs @@ -0,0 +1,18 @@ +#![feature(pin_ergonomics)] +#![allow(dead_code, incomplete_features)] + +// make sure we can't accidentally reborrow Pin<&T> as Pin<&mut T> + +use std::pin::Pin; + +struct Foo; + +fn foo(_: Pin<&mut Foo>) { +} + +fn bar(x: Pin<&Foo>) { + foo(x); //~ ERROR mismatched types + //| ERROR types differ in mutability +} + +fn main() {} diff --git a/tests/ui/async-await/pin-reborrow-const-as-mut.stderr b/tests/ui/async-await/pin-reborrow-const-as-mut.stderr new file mode 100644 index 00000000000..2c2d9ec2717 --- /dev/null +++ b/tests/ui/async-await/pin-reborrow-const-as-mut.stderr @@ -0,0 +1,19 @@ +error[E0308]: mismatched types + --> $DIR/pin-reborrow-const-as-mut.rs:14:9 + | +LL | foo(x); + | --- ^ types differ in mutability + | | + | arguments to this function are incorrect + | + = note: expected struct `Pin<&mut Foo>` + found struct `Pin<&Foo>` +note: function defined here + --> $DIR/pin-reborrow-const-as-mut.rs:10:4 + | +LL | fn foo(_: Pin<&mut Foo>) { + | ^^^ ---------------- + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/async-await/pin-reborrow-once.rs b/tests/ui/async-await/pin-reborrow-once.rs new file mode 100644 index 00000000000..241efadef7d --- /dev/null +++ b/tests/ui/async-await/pin-reborrow-once.rs @@ -0,0 +1,13 @@ +#![feature(pin_ergonomics)] +#![allow(dead_code, incomplete_features)] + +// Make sure with pin reborrowing that we can only get one mutable reborrow of a pinned reference. + +use std::pin::{pin, Pin}; + +fn twice(_: Pin<&mut i32>, _: Pin<&mut i32>) {} + +fn main() { + let x = pin!(42); + twice(x, x); //~ ERROR cannot borrow +} diff --git a/tests/ui/async-await/pin-reborrow-once.stderr b/tests/ui/async-await/pin-reborrow-once.stderr new file mode 100644 index 00000000000..b8fde8ffee8 --- /dev/null +++ b/tests/ui/async-await/pin-reborrow-once.stderr @@ -0,0 +1,12 @@ +error[E0499]: cannot borrow `*x.__pointer` as mutable more than once at a time + --> $DIR/pin-reborrow-once.rs:12:14 + | +LL | twice(x, x); + | ----- - ^ second mutable borrow occurs here + | | | + | | first mutable borrow occurs here + | first borrow later used by call + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0499`. 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/async-await/pin-reborrow-shorter.rs b/tests/ui/async-await/pin-reborrow-shorter.rs new file mode 100644 index 00000000000..06c266e0035 --- /dev/null +++ b/tests/ui/async-await/pin-reborrow-shorter.rs @@ -0,0 +1,14 @@ +//@check-pass + +#![feature(pin_ergonomics)] +#![allow(dead_code, incomplete_features)] + +use std::pin::Pin; + +fn shorter<'b, T: 'b>(_: Pin<&'b mut T>) {} + +fn test<'a: 'b, 'b, T: 'a>(x: Pin<&'a mut T>) { + shorter::<'b>(x); +} + +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`.