Skip to content
Open
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
129 changes: 88 additions & 41 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,18 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
return self.coerce_to_raw_ptr(a, b, b_mutbl);
}
ty::Ref(r_b, _, mutbl_b) => {
if self.tcx.features().pin_ergonomics()
&& let Ok(pin_coerce) =
self.commit_if_ok(|_| self.coerce_pin_ref_to_ref(a, b, mutbl_b))
{
return Ok(pin_coerce);
}
Comment on lines 272 to 277
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sort of thinking that this code (and the lines added under the ty::Adt arm, should go in coerce_to_ref and coerce_to_pin_ref respectively - keeping this function relatively clean.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also would help to eliminate some span_bugs from coerce_maybe_pinned_ref

return self.coerce_to_ref(a, b, r_b, mutbl_b);
}
ty::Adt(pin, _)
if self.tcx.features().pin_ergonomics()
&& self.tcx.is_lang_item(pin.did(), hir::LangItem::Pin) =>
_ if self.tcx.features().pin_ergonomics()
&& let Some((_, ty::Pinnedness::Pinned, mut_b, _)) = b.maybe_pinned_ref() =>
{
let pin_coerce = self.commit_if_ok(|_| self.coerce_to_pin_ref(a, b));
let pin_coerce = self.commit_if_ok(|_| self.coerce_to_pin_ref(a, b, mut_b));
if pin_coerce.is_ok() {
return pin_coerce;
}
Expand Down Expand Up @@ -790,61 +795,103 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
Ok(())
}

/// Applies reborrowing for `Pin`
/// Create an obligation for `ty: Unpin`.
fn unpin_obligation(&self, ty: Ty<'tcx>) -> PredicateObligation<'tcx> {
let pred = ty::TraitRef::new(
self.tcx,
self.tcx.require_lang_item(hir::LangItem::Unpin, self.cause.span),
[ty],
);
PredicateObligation::new(self.tcx, self.cause.clone(), self.param_env, pred)
}

/// Coerces from a pinned reference to a normal reference.
#[instrument(skip(self), level = "trace")]
fn coerce_pin_ref_to_ref(
&self,
a: Ty<'tcx>,
b: Ty<'tcx>,
mut_b: hir::Mutability,
) -> CoerceResult<'tcx> {
debug_assert!(self.shallow_resolve(a) == a);
debug_assert!(self.shallow_resolve(b) == b);
debug_assert!(self.tcx.features().pin_ergonomics());

let Some((a_ty, ty::Pinnedness::Pinned, mut_a, r_a)) = a.maybe_pinned_ref() else {
debug!("not fitting pinned ref to ref coercion (`{:?}` -> `{:?}`)", a, b);
return Err(TypeError::Mismatch);
};

coerce_mutbls(mut_a, mut_b)?;

let a = Ty::new_ref(self.tcx, r_a, a_ty, mut_b);
let mut coerce = self.unify_and(
a,
b,
[Adjustment { kind: Adjust::Deref(DerefAdjustKind::Pin), target: a_ty }],
Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::new(mut_b, self.allow_two_phase))),
ForceLeakCheck::No,
)?;
coerce.obligations.push(self.unpin_obligation(a_ty));
Ok(coerce)
}

/// Applies reborrowing and auto-borrowing that results to `Pin<&T>` or `Pin<&mut T>`:
///
/// 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.
/// Currently we only support the following coercions:
/// - Reborrowing `Pin<&mut T>` -> `Pin<&mut T>`
/// - Reborrowing `Pin<&T>` -> `Pin<&T>`
/// - Auto-borrowing `&mut T` -> `Pin<&mut T>` where `T: Unpin`
/// - Auto-borrowing `&mut T` -> `Pin<&T>` where `T: Unpin`
/// - Auto-borrowing `&T` -> `Pin<&T>` where `T: Unpin`
///
/// 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_to_pin_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> {
fn coerce_to_pin_ref(
&self,
a: Ty<'tcx>,
b: Ty<'tcx>,
mut_b: hir::Mutability,
) -> CoerceResult<'tcx> {
debug_assert!(self.shallow_resolve(a) == a);
debug_assert!(self.shallow_resolve(b) == b);
debug_assert!(self.tcx.features().pin_ergonomics());

// 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)
}
// We need to deref the reference first before we reborrow it to a pinned reference.
let (deref, a_ty, mut_a, r_a, unpin_obligation) = match a.maybe_pinned_ref() {
// no `Unpin` required when reborrowing a pinned reference to a pinned reference
Some((a_ty, ty::Pinnedness::Pinned, mut_a, r_a)) => {
(DerefAdjustKind::Pin, a_ty, mut_a, r_a, None)
}
// `Unpin` required when reborrowing a non-pinned reference to a pinned reference
Some((a_ty, ty::Pinnedness::Not, mut_a, r_a)) => {
(DerefAdjustKind::Builtin, a_ty, mut_a, r_a, Some(self.unpin_obligation(a_ty)))
}
None => {
debug!("can't reborrow {:?} as pinned", a);
return Err(TypeError::Mismatch);
}
};

let (pin, a_region, a_ty, mut_a) = extract_pin_mut(a)?;
let (_, _, _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()]),
);
let a = Ty::new_pinned_ref(self.tcx, r_a, a_ty, mut_b);

// 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, [], Adjust::ReborrowPin(mut_b), ForceLeakCheck::No)
let mut coerce = self.unify_and(
a,
b,
[Adjustment { kind: Adjust::Deref(deref), target: a_ty }],
Adjust::Borrow(AutoBorrow::Pin(mut_b)),
ForceLeakCheck::No,
)?;

coerce.obligations.extend(unpin_obligation);
Ok(coerce)
}

fn coerce_from_fn_pointer(
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_hir_typeck/src/expr_use_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
self.consume_or_copy(&place_with_id, place_with_id.hir_id);
}

adjustment::Adjust::Deref(DerefAdjustKind::Builtin) => {}
adjustment::Adjust::Deref(DerefAdjustKind::Builtin | DerefAdjustKind::Pin) => {}

// Autoderefs for overloaded Deref calls in fact reference
// their receiver. That is, if we have `(*x)` where `x`
Expand Down Expand Up @@ -789,7 +789,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
);
}

adjustment::AutoBorrow::RawPtr(m) => {
adjustment::AutoBorrow::RawPtr(m) | adjustment::AutoBorrow::Pin(m) => {
debug!("walk_autoref: expr.hir_id={} base_place={:?}", expr.hir_id, base_place);

self.delegate.borrow_mut().borrow(
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Adjust::Deref(DerefAdjustKind::Builtin) => {
// FIXME(const_trait_impl): We *could* enforce `&T: [const] Deref` here.
}
Adjust::Deref(DerefAdjustKind::Pin) => {
// FIXME(const_trait_impl): We *could* enforce `Pin<&T>: [const] Deref` here.
}
Adjust::Pointer(_pointer_coercion) => {
// FIXME(const_trait_impl): We should probably enforce these.
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_lint/src/autorefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ fn has_implicit_borrow(Adjustment { kind, .. }: &Adjustment<'_>) -> Option<(Muta
Adjust::NeverToAny
| Adjust::Pointer(..)
| Adjust::ReborrowPin(..)
| Adjust::Deref(DerefAdjustKind::Builtin)
| Adjust::Borrow(AutoBorrow::RawPtr(..)) => None,
| Adjust::Deref(DerefAdjustKind::Builtin | DerefAdjustKind::Pin)
| Adjust::Borrow(AutoBorrow::RawPtr(..) | AutoBorrow::Pin(..)) => None,
}
}
5 changes: 5 additions & 0 deletions compiler/rustc_middle/src/ty/adjustment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,15 @@ pub enum Adjust {
Pointer(PointerCoercion),

/// Take a pinned reference and reborrow as a `Pin<&mut T>` or `Pin<&T>`.
// FIXME(pin_ergonomics): This can be replaced with a `Deref(Pin)` followed by a `Borrow(Pin)`
ReborrowPin(hir::Mutability),
}

#[derive(Copy, Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
pub enum DerefAdjustKind {
Builtin,
Overloaded(OverloadedDeref),
Pin,
}

/// An overloaded autoderef step, representing a `Deref(Mut)::deref(_mut)`
Expand Down Expand Up @@ -196,6 +198,9 @@ pub enum AutoBorrow {

/// Converts from T to *T.
RawPtr(hir::Mutability),

/// Converts from T to Pin<&T>.
Pin(hir::Mutability),
}

/// Information for `CoerceUnsized` impls, storing information we
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_middle/src/ty/sty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,14 @@ impl<'tcx> Ty<'tcx> {
}
}

/// Returns the type, pinnedness, mutability, and the region of a reference (`&T` or `&mut T`)
/// or a pinned-reference type (`Pin<&T>` or `Pin<&mut T>`).
///
/// Regarding the [`pin_ergonomics`] feature, one of the goals is to make pinned references
/// (`Pin<&T>` and `Pin<&mut T>`) behaves similar to normal references (`&T` and `&mut T`).
/// This function is useful when references and pinned references are processed similarly.
///
/// [`pin_ergonomics`]: https://github.com/rust-lang/rust/issues/130494
pub fn maybe_pinned_ref(
self,
) -> Option<(Ty<'tcx>, ty::Pinnedness, ty::Mutability, Region<'tcx>)> {
Expand Down
44 changes: 44 additions & 0 deletions compiler/rustc_mir_build/src/thir/cx/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,19 @@ impl<'tcx> ThirBuildCx<'tcx> {
adjust_span(&mut expr);
ExprKind::Deref { arg: self.thir.exprs.push(expr) }
}
Adjust::Deref(DerefAdjustKind::Pin) => {
adjust_span(&mut expr);
// pointer = ($expr).pointer
let pin_ty = expr.ty.pinned_ty().expect("Deref(Pin) with non-Pin type");
let pointer_target = ExprKind::Field {
lhs: self.thir.exprs.push(expr),
variant_index: FIRST_VARIANT,
name: FieldIdx::ZERO,
};
let expr = Expr { temp_scope_id, ty: pin_ty, span, kind: pointer_target };
// expr = *pointer
ExprKind::Deref { arg: self.thir.exprs.push(expr) }
}
Adjust::Deref(DerefAdjustKind::Overloaded(deref)) => {
// We don't need to do call adjust_span here since
// deref coercions always start with a built-in deref.
Expand Down Expand Up @@ -178,6 +191,37 @@ impl<'tcx> ThirBuildCx<'tcx> {
Adjust::Borrow(AutoBorrow::RawPtr(mutability)) => {
ExprKind::RawBorrow { mutability, arg: self.thir.exprs.push(expr) }
}
Adjust::Borrow(AutoBorrow::Pin(mutbl)) => {
// expr = &pin (mut|const|) arget
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, self.tcx.lifetimes.re_erased, expr.ty, mutbl);
let arg = self.thir.exprs.push(expr);
let expr = self.thir.exprs.push(Expr {
temp_scope_id,
ty: new_pin_target,
span,
kind: ExprKind::Borrow { borrow_kind, arg },
});

// kind = Pin { pointer }
let pin_did = self.tcx.require_lang_item(rustc_hir::LangItem::Pin, 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::ZERO, expr }]),
user_ty: None,
base: AdtExprBase::None,
}));

debug!(?kind);
kind
}
Adjust::ReborrowPin(mutbl) => {
debug!("apply ReborrowPin adjustment");
// Rewrite `$expr` as `Pin { __pointer: &(mut)? *($expr).__pointer }`
Expand Down
Loading
Loading