Skip to content

Begin experimental support for pin reborrowing #130526

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

Merged
merged 5 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ language_item_table! {
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;
Expand Down
59 changes: 59 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
&& pin.did() == self.tcx.lang_items().pin_type().unwrap() =>
{
return self.coerce_pin(a, b);
}
_ => {}
}

Expand Down Expand Up @@ -774,6 +780,59 @@ 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 can_reborrow = |ty: Ty<'tcx>| {
// Get the T out of Pin<T>
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<F, G>(
&self,
a: Ty<'tcx>,
Expand Down
15 changes: 15 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,20 @@ 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(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)?;
}
Expand Down Expand Up @@ -1284,6 +1298,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 Pin<Ptr> 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)`
Expand Down
45 changes: 45 additions & 0 deletions 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,50 @@ 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(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 }
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1418,6 +1418,8 @@ symbols! {
pic,
pie,
pin,
pin_as_mut,
pin_ergonomics,
platform_intrinsics,
plugin,
plugin_registrar,
Expand Down
1 change: 1 addition & 0 deletions library/core/src/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,7 @@ impl<Ptr: DerefMut> Pin<Ptr> {
/// }
/// }
/// ```
#[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> {
Expand Down
27 changes: 27 additions & 0 deletions tests/ui/async-await/pin-reborrow-arg.rs
Original file line number Diff line number Diff line change
@@ -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() {}
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,
// as if the user had written `x.as_mut().foo()`.
}

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() {}
21 changes: 21 additions & 0 deletions tests/ui/feature-gates/feature-gate-pin_ergonomics.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading