Skip to content
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
181 changes: 134 additions & 47 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,26 @@ fn success<'tcx>(
Ok(InferOk { value: (adj, target), obligations })
}

/// Data extracted from a reference (pinned or not) for coercion to a reference (pinned or not).
struct CoerceMaybePinnedRef<'tcx> {
/// coercion source, must be a pinned (i.e. `Pin<&T>` or `Pin<&mut T>`) or normal reference (`&T` or `&mut T`)
a: Ty<'tcx>,
/// coercion target, must be a pinned (i.e. `Pin<&T>` or `Pin<&mut T>`) or normal reference (`&T` or `&mut T`)
b: Ty<'tcx>,
/// referent type of the source
a_ty: Ty<'tcx>,
/// pinnedness of the source
a_pin: ty::Pinnedness,
/// mutability of the source
a_mut: ty::Mutability,
/// region of the source
a_r: ty::Region<'tcx>,
/// pinnedness of the target
b_pin: ty::Pinnedness,
/// mutability of the target
b_mut: ty::Mutability,
}

/// Whether to force a leak check to occur in `Coerce::unify_raw`.
/// Note that leak checks may still occur evn with `ForceLeakCheck::No`.
///
Expand Down Expand Up @@ -269,16 +289,13 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
return self.coerce_to_raw_ptr(a, b, b_mutbl);
}
ty::Ref(r_b, _, mutbl_b) => {
if let Some(pin_ref_to_ref) = self.maybe_pin_ref_to_ref(a, b) {
return self.coerce_pin_ref_to_ref(pin_ref_to_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) =>
{
let pin_coerce = self.commit_if_ok(|_| self.coerce_to_pin_ref(a, b));
if pin_coerce.is_ok() {
return pin_coerce;
}
_ if let Some(to_pin_ref) = self.maybe_to_pin_ref(a, b) => {
return self.coerce_to_pin_ref(to_pin_ref);
}
_ => {}
}
Expand Down Expand Up @@ -790,61 +807,131 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
Ok(())
}

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

/// Checks if the given types are compatible for coercion from a pinned reference to a normal reference.
fn maybe_pin_ref_to_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> Option<CoerceMaybePinnedRef<'tcx>> {
if !self.tcx.features().pin_ergonomics() {
return None;
}
if let Some((a_ty, a_pin @ ty::Pinnedness::Pinned, a_mut, a_r)) = a.maybe_pinned_ref()
&& let Some((_, b_pin @ ty::Pinnedness::Not, b_mut, _)) = b.maybe_pinned_ref()
{
return Some(CoerceMaybePinnedRef { a, b, a_ty, a_pin, a_mut, a_r, b_pin, b_mut });
}
debug!("not fitting pinned ref to ref coercion (`{:?}` -> `{:?}`)", a, b);
None
}

/// Coerces from a pinned reference to a normal reference.
#[instrument(skip(self), level = "trace")]
fn coerce_pin_ref_to_ref(
&self,
CoerceMaybePinnedRef { a, b, a_ty, a_pin, a_mut, a_r, b_pin, b_mut }: CoerceMaybePinnedRef<
'tcx,
>,
) -> CoerceResult<'tcx> {
debug_assert!(self.shallow_resolve(a) == a);
debug_assert!(self.shallow_resolve(b) == b);
debug_assert!(self.tcx.features().pin_ergonomics());
debug_assert_eq!(a_pin, ty::Pinnedness::Pinned);
debug_assert_eq!(b_pin, ty::Pinnedness::Not);

coerce_mutbls(a_mut, b_mut)?;

let unpin_obligation = self.unpin_obligation(a, b, a_ty);

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

/// Checks if the given types are compatible for coercion to a pinned reference.
fn maybe_to_pin_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> Option<CoerceMaybePinnedRef<'tcx>> {
if !self.tcx.features().pin_ergonomics() {
return None;
}
if let Some((a_ty, a_pin, a_mut, a_r)) = a.maybe_pinned_ref()
&& let Some((_, b_pin @ ty::Pinnedness::Pinned, b_mut, _)) = b.maybe_pinned_ref()
{
return Some(CoerceMaybePinnedRef { a, b, a_ty, a_pin, a_mut, a_r, b_pin, b_mut });
}
debug!("not fitting ref to pinned ref coercion (`{:?}` -> `{:?}`)", a, b);
None
}

/// 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,
CoerceMaybePinnedRef { a, b, a_ty, a_pin, a_mut, a_r, b_pin, b_mut }: CoerceMaybePinnedRef<
'tcx,
>,
) -> CoerceResult<'tcx> {
debug_assert!(self.shallow_resolve(a) == a);
debug_assert!(self.shallow_resolve(b) == b);

// 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)
}
debug_assert!(self.tcx.features().pin_ergonomics());
debug_assert_eq!(b_pin, ty::Pinnedness::Pinned);

// We need to deref the reference first before we reborrow it to a pinned reference.
let (deref, unpin_obligation) = match a_pin {
// no `Unpin` required when reborrowing a pinned reference to a pinned reference
ty::Pinnedness::Pinned => (DerefAdjustKind::Pin, None),
// `Unpin` required when reborrowing a non-pinned reference to a pinned reference
ty::Pinnedness::Not => {
(DerefAdjustKind::Builtin, Some(self.unpin_obligation(a, b, a_ty)))
}
};

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)?;
coerce_mutbls(a_mut, b_mut)?;

// 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, a_r, a_ty, b_mut);

// 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(b_mut)),
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 @@ -736,7 +736,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 @@ -791,7 +791,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 @@ -331,6 +331,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 @@ -173,7 +173,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 @@ -1365,6 +1365,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 @@ -143,6 +143,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 @@ -177,6 +190,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