Skip to content

Commit 836be8a

Browse files
Rollup merge of rust-lang#149130 - frank-king:feature/pin-coerce, r=jackh726
Implement coercions between `&pin (mut|const) T` and `&(mut) T` when `T: Unpin` This allows the following (mutual) coercions when `T: Unpin`: - `&T` <-> `Pin<&T>` - `&mut T` <-> `Pin<&mut T>` - `&mut T` -> `Pin<&T>` - `Pin<&mut T>` -> `&T` Part of [Pin Ergonomics](rust-lang#130494).
2 parents 0c68443 + d38c8c0 commit 836be8a

11 files changed

Lines changed: 702 additions & 51 deletions

File tree

compiler/rustc_hir_typeck/src/coercion.rs

Lines changed: 134 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,26 @@ fn success<'tcx>(
114114
Ok(InferOk { value: (adj, target), obligations })
115115
}
116116

117+
/// Data extracted from a reference (pinned or not) for coercion to a reference (pinned or not).
118+
struct CoerceMaybePinnedRef<'tcx> {
119+
/// coercion source, must be a pinned (i.e. `Pin<&T>` or `Pin<&mut T>`) or normal reference (`&T` or `&mut T`)
120+
a: Ty<'tcx>,
121+
/// coercion target, must be a pinned (i.e. `Pin<&T>` or `Pin<&mut T>`) or normal reference (`&T` or `&mut T`)
122+
b: Ty<'tcx>,
123+
/// referent type of the source
124+
a_ty: Ty<'tcx>,
125+
/// pinnedness of the source
126+
a_pin: ty::Pinnedness,
127+
/// mutability of the source
128+
a_mut: ty::Mutability,
129+
/// region of the source
130+
a_r: ty::Region<'tcx>,
131+
/// pinnedness of the target
132+
b_pin: ty::Pinnedness,
133+
/// mutability of the target
134+
b_mut: ty::Mutability,
135+
}
136+
117137
/// Whether to force a leak check to occur in `Coerce::unify_raw`.
118138
/// Note that leak checks may still occur evn with `ForceLeakCheck::No`.
119139
///
@@ -269,16 +289,13 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
269289
return self.coerce_to_raw_ptr(a, b, b_mutbl);
270290
}
271291
ty::Ref(r_b, _, mutbl_b) => {
292+
if let Some(pin_ref_to_ref) = self.maybe_pin_ref_to_ref(a, b) {
293+
return self.coerce_pin_ref_to_ref(pin_ref_to_ref);
294+
}
272295
return self.coerce_to_ref(a, b, r_b, mutbl_b);
273296
}
274-
ty::Adt(pin, _)
275-
if self.tcx.features().pin_ergonomics()
276-
&& self.tcx.is_lang_item(pin.did(), hir::LangItem::Pin) =>
277-
{
278-
let pin_coerce = self.commit_if_ok(|_| self.coerce_to_pin_ref(a, b));
279-
if pin_coerce.is_ok() {
280-
return pin_coerce;
281-
}
297+
_ if let Some(to_pin_ref) = self.maybe_to_pin_ref(a, b) => {
298+
return self.coerce_to_pin_ref(to_pin_ref);
282299
}
283300
_ => {}
284301
}
@@ -790,61 +807,131 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
790807
Ok(())
791808
}
792809

793-
/// Applies reborrowing for `Pin`
810+
/// Create an obligation for `ty: Unpin`, where .
811+
fn unpin_obligation(
812+
&self,
813+
source: Ty<'tcx>,
814+
target: Ty<'tcx>,
815+
ty: Ty<'tcx>,
816+
) -> PredicateObligation<'tcx> {
817+
let pred = ty::TraitRef::new(
818+
self.tcx,
819+
self.tcx.require_lang_item(hir::LangItem::Unpin, self.cause.span),
820+
[ty],
821+
);
822+
let cause = self.cause(self.cause.span, ObligationCauseCode::Coercion { source, target });
823+
PredicateObligation::new(self.tcx, cause, self.param_env, pred)
824+
}
825+
826+
/// Checks if the given types are compatible for coercion from a pinned reference to a normal reference.
827+
fn maybe_pin_ref_to_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> Option<CoerceMaybePinnedRef<'tcx>> {
828+
if !self.tcx.features().pin_ergonomics() {
829+
return None;
830+
}
831+
if let Some((a_ty, a_pin @ ty::Pinnedness::Pinned, a_mut, a_r)) = a.maybe_pinned_ref()
832+
&& let Some((_, b_pin @ ty::Pinnedness::Not, b_mut, _)) = b.maybe_pinned_ref()
833+
{
834+
return Some(CoerceMaybePinnedRef { a, b, a_ty, a_pin, a_mut, a_r, b_pin, b_mut });
835+
}
836+
debug!("not fitting pinned ref to ref coercion (`{:?}` -> `{:?}`)", a, b);
837+
None
838+
}
839+
840+
/// Coerces from a pinned reference to a normal reference.
841+
#[instrument(skip(self), level = "trace")]
842+
fn coerce_pin_ref_to_ref(
843+
&self,
844+
CoerceMaybePinnedRef { a, b, a_ty, a_pin, a_mut, a_r, b_pin, b_mut }: CoerceMaybePinnedRef<
845+
'tcx,
846+
>,
847+
) -> CoerceResult<'tcx> {
848+
debug_assert!(self.shallow_resolve(a) == a);
849+
debug_assert!(self.shallow_resolve(b) == b);
850+
debug_assert!(self.tcx.features().pin_ergonomics());
851+
debug_assert_eq!(a_pin, ty::Pinnedness::Pinned);
852+
debug_assert_eq!(b_pin, ty::Pinnedness::Not);
853+
854+
coerce_mutbls(a_mut, b_mut)?;
855+
856+
let unpin_obligation = self.unpin_obligation(a, b, a_ty);
857+
858+
let a = Ty::new_ref(self.tcx, a_r, a_ty, b_mut);
859+
let mut coerce = self.unify_and(
860+
a,
861+
b,
862+
[Adjustment { kind: Adjust::Deref(DerefAdjustKind::Pin), target: a_ty }],
863+
Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::new(b_mut, self.allow_two_phase))),
864+
ForceLeakCheck::No,
865+
)?;
866+
coerce.obligations.push(unpin_obligation);
867+
Ok(coerce)
868+
}
869+
870+
/// Checks if the given types are compatible for coercion to a pinned reference.
871+
fn maybe_to_pin_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> Option<CoerceMaybePinnedRef<'tcx>> {
872+
if !self.tcx.features().pin_ergonomics() {
873+
return None;
874+
}
875+
if let Some((a_ty, a_pin, a_mut, a_r)) = a.maybe_pinned_ref()
876+
&& let Some((_, b_pin @ ty::Pinnedness::Pinned, b_mut, _)) = b.maybe_pinned_ref()
877+
{
878+
return Some(CoerceMaybePinnedRef { a, b, a_ty, a_pin, a_mut, a_r, b_pin, b_mut });
879+
}
880+
debug!("not fitting ref to pinned ref coercion (`{:?}` -> `{:?}`)", a, b);
881+
None
882+
}
883+
884+
/// Applies reborrowing and auto-borrowing that results to `Pin<&T>` or `Pin<&mut T>`:
794885
///
795-
/// We currently only support reborrowing `Pin<&mut T>` as `Pin<&mut T>`. This is accomplished
796-
/// by inserting a call to `Pin::as_mut` during MIR building.
886+
/// Currently we only support the following coercions:
887+
/// - Reborrowing `Pin<&mut T>` -> `Pin<&mut T>`
888+
/// - Reborrowing `Pin<&T>` -> `Pin<&T>`
889+
/// - Auto-borrowing `&mut T` -> `Pin<&mut T>` where `T: Unpin`
890+
/// - Auto-borrowing `&mut T` -> `Pin<&T>` where `T: Unpin`
891+
/// - Auto-borrowing `&T` -> `Pin<&T>` where `T: Unpin`
797892
///
798893
/// In the future we might want to support other reborrowing coercions, such as:
799-
/// - `Pin<&mut T>` as `Pin<&T>`
800-
/// - `Pin<&T>` as `Pin<&T>`
801894
/// - `Pin<Box<T>>` as `Pin<&T>`
802895
/// - `Pin<Box<T>>` as `Pin<&mut T>`
803896
#[instrument(skip(self), level = "trace")]
804-
fn coerce_to_pin_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> {
897+
fn coerce_to_pin_ref(
898+
&self,
899+
CoerceMaybePinnedRef { a, b, a_ty, a_pin, a_mut, a_r, b_pin, b_mut }: CoerceMaybePinnedRef<
900+
'tcx,
901+
>,
902+
) -> CoerceResult<'tcx> {
805903
debug_assert!(self.shallow_resolve(a) == a);
806904
debug_assert!(self.shallow_resolve(b) == b);
807-
808-
// We need to make sure the two types are compatible for coercion.
809-
// Then we will build a ReborrowPin adjustment and return that as an InferOk.
810-
811-
// Right now we can only reborrow if this is a `Pin<&mut T>`.
812-
let extract_pin_mut = |ty: Ty<'tcx>| {
813-
// Get the T out of Pin<T>
814-
let (pin, ty) = match ty.kind() {
815-
ty::Adt(pin, args) if self.tcx.is_lang_item(pin.did(), hir::LangItem::Pin) => {
816-
(*pin, args[0].expect_ty())
817-
}
818-
_ => {
819-
debug!("can't reborrow {:?} as pinned", ty);
820-
return Err(TypeError::Mismatch);
821-
}
822-
};
823-
// Make sure the T is something we understand (just `&mut U` for now)
824-
match ty.kind() {
825-
ty::Ref(region, ty, mutbl) => Ok((pin, *region, *ty, *mutbl)),
826-
_ => {
827-
debug!("can't reborrow pin of inner type {:?}", ty);
828-
Err(TypeError::Mismatch)
829-
}
905+
debug_assert!(self.tcx.features().pin_ergonomics());
906+
debug_assert_eq!(b_pin, ty::Pinnedness::Pinned);
907+
908+
// We need to deref the reference first before we reborrow it to a pinned reference.
909+
let (deref, unpin_obligation) = match a_pin {
910+
// no `Unpin` required when reborrowing a pinned reference to a pinned reference
911+
ty::Pinnedness::Pinned => (DerefAdjustKind::Pin, None),
912+
// `Unpin` required when reborrowing a non-pinned reference to a pinned reference
913+
ty::Pinnedness::Not => {
914+
(DerefAdjustKind::Builtin, Some(self.unpin_obligation(a, b, a_ty)))
830915
}
831916
};
832917

833-
let (pin, a_region, a_ty, mut_a) = extract_pin_mut(a)?;
834-
let (_, _, _b_ty, mut_b) = extract_pin_mut(b)?;
835-
836-
coerce_mutbls(mut_a, mut_b)?;
918+
coerce_mutbls(a_mut, b_mut)?;
837919

838920
// update a with b's mutability since we'll be coercing mutability
839-
let a = Ty::new_adt(
840-
self.tcx,
841-
pin,
842-
self.tcx.mk_args(&[Ty::new_ref(self.tcx, a_region, a_ty, mut_b).into()]),
843-
);
921+
let a = Ty::new_pinned_ref(self.tcx, a_r, a_ty, b_mut);
844922

845923
// To complete the reborrow, we need to make sure we can unify the inner types, and if so we
846924
// add the adjustments.
847-
self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b), ForceLeakCheck::No)
925+
let mut coerce = self.unify_and(
926+
a,
927+
b,
928+
[Adjustment { kind: Adjust::Deref(deref), target: a_ty }],
929+
Adjust::Borrow(AutoBorrow::Pin(b_mut)),
930+
ForceLeakCheck::No,
931+
)?;
932+
933+
coerce.obligations.extend(unpin_obligation);
934+
Ok(coerce)
848935
}
849936

850937
fn coerce_from_fn_pointer(

compiler/rustc_hir_typeck/src/expr_use_visitor.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
736736
self.consume_or_copy(&place_with_id, place_with_id.hir_id);
737737
}
738738

739-
adjustment::Adjust::Deref(DerefAdjustKind::Builtin) => {}
739+
adjustment::Adjust::Deref(DerefAdjustKind::Builtin | DerefAdjustKind::Pin) => {}
740740

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

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

797797
self.delegate.borrow_mut().borrow(

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
331331
Adjust::Deref(DerefAdjustKind::Builtin) => {
332332
// FIXME(const_trait_impl): We *could* enforce `&T: [const] Deref` here.
333333
}
334+
Adjust::Deref(DerefAdjustKind::Pin) => {
335+
// FIXME(const_trait_impl): We *could* enforce `Pin<&T>: [const] Deref` here.
336+
}
334337
Adjust::Pointer(_pointer_coercion) => {
335338
// FIXME(const_trait_impl): We should probably enforce these.
336339
}

compiler/rustc_lint/src/autorefs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ fn has_implicit_borrow(Adjustment { kind, .. }: &Adjustment<'_>) -> Option<(Muta
173173
Adjust::NeverToAny
174174
| Adjust::Pointer(..)
175175
| Adjust::ReborrowPin(..)
176-
| Adjust::Deref(DerefAdjustKind::Builtin)
177-
| Adjust::Borrow(AutoBorrow::RawPtr(..)) => None,
176+
| Adjust::Deref(DerefAdjustKind::Builtin | DerefAdjustKind::Pin)
177+
| Adjust::Borrow(AutoBorrow::RawPtr(..) | AutoBorrow::Pin(..)) => None,
178178
}
179179
}

compiler/rustc_middle/src/ty/adjustment.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,15 @@ pub enum Adjust {
105105
Pointer(PointerCoercion),
106106

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

111112
#[derive(Copy, Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable)]
112113
pub enum DerefAdjustKind {
113114
Builtin,
114115
Overloaded(OverloadedDeref),
116+
Pin,
115117
}
116118

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

197199
/// Converts from T to *T.
198200
RawPtr(hir::Mutability),
201+
202+
/// Converts from T to Pin<&T>.
203+
Pin(hir::Mutability),
199204
}
200205

201206
/// Information for `CoerceUnsized` impls, storing information we

compiler/rustc_middle/src/ty/sty.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,14 @@ impl<'tcx> Ty<'tcx> {
13651365
}
13661366
}
13671367

1368+
/// Returns the type, pinnedness, mutability, and the region of a reference (`&T` or `&mut T`)
1369+
/// or a pinned-reference type (`Pin<&T>` or `Pin<&mut T>`).
1370+
///
1371+
/// Regarding the [`pin_ergonomics`] feature, one of the goals is to make pinned references
1372+
/// (`Pin<&T>` and `Pin<&mut T>`) behaves similar to normal references (`&T` and `&mut T`).
1373+
/// This function is useful when references and pinned references are processed similarly.
1374+
///
1375+
/// [`pin_ergonomics`]: https://github.com/rust-lang/rust/issues/130494
13681376
pub fn maybe_pinned_ref(
13691377
self,
13701378
) -> Option<(Ty<'tcx>, ty::Pinnedness, ty::Mutability, Region<'tcx>)> {

compiler/rustc_mir_build/src/thir/cx/expr.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,19 @@ impl<'tcx> ThirBuildCx<'tcx> {
143143
adjust_span(&mut expr);
144144
ExprKind::Deref { arg: self.thir.exprs.push(expr) }
145145
}
146+
Adjust::Deref(DerefAdjustKind::Pin) => {
147+
adjust_span(&mut expr);
148+
// pointer = ($expr).pointer
149+
let pin_ty = expr.ty.pinned_ty().expect("Deref(Pin) with non-Pin type");
150+
let pointer_target = ExprKind::Field {
151+
lhs: self.thir.exprs.push(expr),
152+
variant_index: FIRST_VARIANT,
153+
name: FieldIdx::ZERO,
154+
};
155+
let expr = Expr { temp_scope_id, ty: pin_ty, span, kind: pointer_target };
156+
// expr = *pointer
157+
ExprKind::Deref { arg: self.thir.exprs.push(expr) }
158+
}
146159
Adjust::Deref(DerefAdjustKind::Overloaded(deref)) => {
147160
// We don't need to do call adjust_span here since
148161
// deref coercions always start with a built-in deref.
@@ -177,6 +190,37 @@ impl<'tcx> ThirBuildCx<'tcx> {
177190
Adjust::Borrow(AutoBorrow::RawPtr(mutability)) => {
178191
ExprKind::RawBorrow { mutability, arg: self.thir.exprs.push(expr) }
179192
}
193+
Adjust::Borrow(AutoBorrow::Pin(mutbl)) => {
194+
// expr = &pin (mut|const|) arget
195+
let borrow_kind = match mutbl {
196+
hir::Mutability::Mut => BorrowKind::Mut { kind: mir::MutBorrowKind::Default },
197+
hir::Mutability::Not => BorrowKind::Shared,
198+
};
199+
let new_pin_target =
200+
Ty::new_ref(self.tcx, self.tcx.lifetimes.re_erased, expr.ty, mutbl);
201+
let arg = self.thir.exprs.push(expr);
202+
let expr = self.thir.exprs.push(Expr {
203+
temp_scope_id,
204+
ty: new_pin_target,
205+
span,
206+
kind: ExprKind::Borrow { borrow_kind, arg },
207+
});
208+
209+
// kind = Pin { pointer }
210+
let pin_did = self.tcx.require_lang_item(rustc_hir::LangItem::Pin, span);
211+
let args = self.tcx.mk_args(&[new_pin_target.into()]);
212+
let kind = ExprKind::Adt(Box::new(AdtExpr {
213+
adt_def: self.tcx.adt_def(pin_did),
214+
variant_index: FIRST_VARIANT,
215+
args,
216+
fields: Box::new([FieldExpr { name: FieldIdx::ZERO, expr }]),
217+
user_ty: None,
218+
base: AdtExprBase::None,
219+
}));
220+
221+
debug!(?kind);
222+
kind
223+
}
180224
Adjust::ReborrowPin(mutbl) => {
181225
debug!("apply ReborrowPin adjustment");
182226
// Rewrite `$expr` as `Pin { __pointer: &(mut)? *($expr).__pointer }`

0 commit comments

Comments
 (0)