Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Begin experimental support for pin reborrowing #130526

Merged
merged 5 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
62 changes: 62 additions & 0 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
_ => {}
}

Expand Down Expand Up @@ -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<Box<T>>` as `Pin<&T>`
/// - `Pin<Box<T>>` 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<T>
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<F, G>(
&self,
a: Ty<'tcx>,
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_hir_typeck/src/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
}
Expand Down Expand Up @@ -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))
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_middle/src/ty/adjustment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
Expand Down
64 changes: 63 additions & 1 deletion compiler/rustc_mir_build/src/thir/cx/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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");
};
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,7 @@ symbols! {
pic,
pie,
pin,
pin_ergonomics,
platform_intrinsics,
plugin,
plugin_registrar,
Expand Down
36 changes: 36 additions & 0 deletions tests/ui/async-await/pin-reborrow-arg.rs
Original file line number Diff line number Diff line change
@@ -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() {}
18 changes: 18 additions & 0 deletions tests/ui/async-await/pin-reborrow-const-as-mut.rs
Original file line number Diff line number Diff line change
@@ -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() {}
19 changes: 19 additions & 0 deletions tests/ui/async-await/pin-reborrow-const-as-mut.stderr
Original file line number Diff line number Diff line change
@@ -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`.
13 changes: 13 additions & 0 deletions tests/ui/async-await/pin-reborrow-once.rs
Original file line number Diff line number Diff line change
@@ -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
}
12 changes: 12 additions & 0 deletions tests/ui/async-await/pin-reborrow-once.stderr
Original file line number Diff line number Diff line change
@@ -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`.
24 changes: 24 additions & 0 deletions tests/ui/async-await/pin-reborrow-self.rs
Original file line number Diff line number Diff line change
@@ -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,
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved
// as if the user had written `x.as_mut().foo()`.
}

fn main() {}
14 changes: 14 additions & 0 deletions tests/ui/async-await/pin-reborrow-shorter.rs
Original file line number Diff line number Diff line change
@@ -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() {}
15 changes: 15 additions & 0 deletions tests/ui/feature-gates/feature-gate-pin_ergonomics.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Loading
Loading