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
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ symbols! {
PartialOrd,
Pending,
PinDerefMutHelper,
PinMacroHelper,
Pointer,
Poll,
Range,
Expand Down
41 changes: 38 additions & 3 deletions compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ use rustc_middle::traits::PatternOriginExpr;
use rustc_middle::ty::error::{ExpectedFound, TypeError, TypeErrorToStringExt};
use rustc_middle::ty::print::{PrintTraitRefExt as _, WrapBinderMode, with_forced_trimmed_paths};
use rustc_middle::ty::{
self, List, ParamEnv, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
TypeVisitableExt, Unnormalized,
self, List, Mutability, ParamEnv, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable,
TypeVisitable, TypeVisitableExt, Unnormalized,
};
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Pos, Span, sym};
use thin_vec::ThinVec;
Expand Down Expand Up @@ -2103,7 +2103,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {

pub fn report_and_explain_type_error(
&self,
trace: TypeTrace<'tcx>,
mut trace: TypeTrace<'tcx>,
param_env: ty::ParamEnv<'tcx>,
terr: TypeError<'tcx>,
) -> Diag<'a> {
Expand All @@ -2112,6 +2112,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
let span = trace.cause.span;
let mut path = None;

self.simplify_pin_macro_arg_ty_mismatch(&mut trace);

// Check for on_type_error attribute
let on_type_error_notes = if let Some((expected_ty, found_ty)) = trace.values.ty() {
self.check_on_type_error_attribute(expected_ty, found_ty)
Expand Down Expand Up @@ -2144,6 +2146,39 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
diag
}

/// If the `pin!()` macro gets a wrong argument type, don't show its internals
/// in user-facing diagnostics.
/// See the `ui/pin/dont-deref-coerce-pinned-value` test.
fn simplify_pin_macro_arg_ty_mismatch(&self, trace: &mut TypeTrace<'tcx>) {
// Check whether `expected_ty` and `found_ty` are both `&mut PinMacroHelper<....>`,
// in which case we peel off the wrapping.
if let Some((expected_ty, found_ty)) = trace.values.ty()
&& let ty::Ref(_, expected_ty_kind_inside_mut, Mutability::Mut) = expected_ty.kind()
&& let ty::Adt(expected_adt, expected_generics) = expected_ty_kind_inside_mut.kind()
&& self.tcx.is_diagnostic_item(sym::PinMacroHelper, expected_adt.did())
&& let ty::Ref(_, found_ty_kind_inside_mut, Mutability::Mut) = found_ty.kind()
&& let ty::Adt(found_adt, found_generics) = found_ty_kind_inside_mut.kind()
&& self.tcx.is_diagnostic_item(sym::PinMacroHelper, found_adt.did())
{
let [expected_generic] = expected_generics
.as_slice()
.try_into()
.expect("PinMacroHelper should only have one generic");
let [found_generic] = found_generics
.as_slice()
.try_into()
.expect("PinMacroHelper should only have one generic");
let expected_ty_inner =
expected_generic.as_type().expect("PinMacroHelper should have a generic type");
let found_ty_inner =
found_generic.as_type().expect("PinMacroHelper should have a generic type");
trace.values = ValuePairs::Terms(ExpectedFound::new(
expected_ty_inner.into(),
found_ty_inner.into(),
));
}
}

fn suggest_wrap_to_build_a_tuple(
&self,
span: Span,
Expand Down
76 changes: 64 additions & 12 deletions library/core/src/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2026,24 +2026,76 @@ unsafe impl<T: PinCoerceUnsized> PinCoerceUnsized for Pin<T> {}
// `super` gets removed by rustfmt
#[rustfmt::skip]
pub macro pin($value:expr $(,)?) {
'p: {
super let mut pinned = $value;
// SAFETY: The value is pinned: it is the local above which cannot be named outside this macro.
break 'p unsafe { $crate::pin::Pin::new_unchecked(&mut pinned) };
{
super let mut pinned: $crate::pin::PinMacroHelper<_> = $crate::pin::PinMacroHelper { value: $value };

// HACK: We need to ensure that, given `$value: T`, `pin!($value)` has type `Pin<&mut T>`.
// Otherwise, it's possible for a type annotation on the result of `pin!` to unsoundly add
// deref coercions. E.g. for `$value: &mut T`, we could get `pin!($value): Pin<&mut T>`,
// violating the pinning invariant; see <https://github.com/rust-lang/rust/issues/153438>.
#[expect(unreachable_code)]
$crate::pin::unreachable_pin_macro_type_constraint(pinned)
// SAFETY: The value is pinned: it is the local above which cannot be named outside this macro.
//
// In order to make sure that the above local is passed to the below function call
// without any coercions in between, we wrap the user-provided value in the
// `PinMacroHelper` type, which we know doesn't implement `DerefMut`. Without such
// a wrapper, a user might cause a deref coercion on `&mut pinned`, causing the
// `Pin::new_unchecked` call to pin the wrong thing, which is unsound.
// See <https://github.com/rust-lang/rust/issues/153438>.
//
// To verify that there are no problematic coercions in the below line, we enumerate
// the list of all possible kinds of coercions that currently exist in the language,
// and verify that they can't be used to coerce a `&mut PinMacroHelper<T>` into a
// `&mut PinMacroHelper<U>` (as enforced by the above type annotation on `pinned`,
// and the type signature of `pin_new_unchecked_in_helper`), where `T` and `U` are
// different types:
//
// * Subtype coercions: `&mut` is invariant over the type being referenced, so subtype
// coercion can only change the lifetime of the `&mut` reference itself, which poses
// no problems.
// * Coercions that change the kind of references/pointers: We have the following kinds
// of coercions, none of which can produce a `&mut`: `&mut`-to-`&`, `*mut`-to-`*const`,
// `&`-to-`*const`, `&mut`-to-`*mut`.
// * Deref coercions: Does not apply here, since `PinMacroHelper` does not implement
// `Deref` or `DerefMut`. And since `PinMacroHelper` is not a fundamental type, users
// cannot add any such implementations to the type.
// * Unsize coercions: Does not apply here, since unsize coercions can only produce a
// reference/pointer to an unsized type, and `PinMacroHelper` is always `Sized`.
// * Coercions from function items or non-capturing closures to function pointers:
// Does not apply here. `&mut _` is not a function item or closure.
// * Never-to-any coercions: If this coercion applies, then we're in unreachable code,
// so whatever we do can't possibly cause unsoundness.
//
// Furthermore, if we ever add more kinds of coercions to the language, it seems
// extremely unlikely that user code would be allowed to define new coercions
// on a stdlib-defined type such as `PinMacroHelper`.
//
// I have not verified whether it is possible to cause a coercion in the `let`
// statement above, before assigning to the `pinned` variable. However, even if
// such a coercion were possible, it would not affect the soundness of the macro,
// since the soundness argument only relies on the type of `pinned` being
// `PinMacroHelper<_>`, which is enforced by the type annotation.
unsafe { $crate::pin::pin_new_unchecked_in_helper(&mut pinned) }
}
}

/// Helper for `pin!` to enforce its type signature.
/// See <https://github.com/rust-lang/rust/issues/153438>.
#[unstable(feature = "pin_macro_internals", issue = "none")]
#[doc(hidden)]
pub fn unreachable_pin_macro_type_constraint<'a, T>(_: T) -> Pin<&'a mut T> {
unreachable!()
#[expect(missing_debug_implementations, reason = "this type is only used by the `pin!` macro")]
#[rustc_diagnostic_item = "PinMacroHelper"]
pub struct PinMacroHelper<T> {
pub value: T,
}

/// Helper for `pin!` to enforce its type signature.
/// See <https://github.com/rust-lang/rust/issues/153438>.
///
/// # Safety
/// Calling this function has the same safety requirements as calling
/// `Pin::new_unchecked` on `&mut pinned.value`
#[unstable(feature = "pin_macro_internals", issue = "none")]
#[doc(hidden)]
#[inline]
pub const unsafe fn pin_new_unchecked_in_helper<'a, T>(
Comment thread
theemathas marked this conversation as resolved.
pinned: &'a mut PinMacroHelper<T>,
) -> Pin<&'a mut T> {
// SAFETY: Ensured by the caller.
unsafe { Pin::new_unchecked(&mut pinned.value) }
}
4 changes: 2 additions & 2 deletions tests/ui/coercion/subtyping-inhibits-deref-coercion.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// For some reason, subtyping due to higher ranked function pointers,
// even in an invariant position, causes deref coercion to not happen.
// This might be a compiler bug. However, as of this writing,
// the `pin!()` macro's soundness relies on code like this not compiling.
// This might be a compiler bug. Notably, the `pin!()` macro's soundness
// previously relied on code like this not compiling.
// See https://github.com/rust-lang/rust/issues/153438#issuecomment-4517609119

use std::marker::PhantomData;
Expand Down
1 change: 0 additions & 1 deletion tests/ui/iterators/iter-macro-not-async-closure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ fn main() {
//~^^^ ERROR AsyncFnOnce()` is not satisfied
//~^^^^ ERROR AsyncFnOnce()` is not satisfied
//~^^^^^ ERROR AsyncFnOnce()` is not satisfied
//~^^^^^^ ERROR AsyncFnOnce()` is not satisfied
x.poll(&mut Context::from_waker(Waker::noop()));
//~^ ERROR AsyncFnOnce()` is not satisfied
}
16 changes: 2 additions & 14 deletions tests/ui/iterators/iter-macro-not-async-closure.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,7 @@ LL | async fn call_async_once(f: impl AsyncFnOnce()) {
| ^^^^^^^^^^^^^ required by this bound in `call_async_once`

error[E0277]: the trait bound `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}: AsyncFnOnce()` is not satisfied
--> $DIR/iter-macro-not-async-closure.rs:25:13
|
LL | let x = pin!(call_async_once(f));
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `AsyncFnOnce()` is not implemented for `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}`
|
note: required by a bound in `call_async_once`
--> $DIR/iter-macro-not-async-closure.rs:14:34
|
LL | async fn call_async_once(f: impl AsyncFnOnce()) {
| ^^^^^^^^^^^^^ required by this bound in `call_async_once`

error[E0277]: the trait bound `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}: AsyncFnOnce()` is not satisfied
--> $DIR/iter-macro-not-async-closure.rs:32:5
--> $DIR/iter-macro-not-async-closure.rs:31:5
|
LL | x.poll(&mut Context::from_waker(Waker::noop()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `AsyncFnOnce()` is not implemented for `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}`
Expand All @@ -84,6 +72,6 @@ note: required by a bound in `call_async_once`
LL | async fn call_async_once(f: impl AsyncFnOnce()) {
| ^^^^^^^^^^^^^ required by this bound in `call_async_once`

error: aborting due to 7 previous errors
error: aborting due to 6 previous errors

For more information about this error, try `rustc --explain E0277`.
5 changes: 0 additions & 5 deletions tests/ui/pin-macro/pin_move.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ LL | struct NotCopy<T>(T);
LL | let mut pointee = NotCopy(PhantomPinned);
LL | pin!(*&mut pointee);
| ------------- you could clone this value
help: consider removing the dereference here
|
LL - pin!(*&mut pointee);
LL + pin!(&mut pointee);
|

error: aborting due to 2 previous errors

Expand Down
5 changes: 0 additions & 5 deletions tests/ui/pin/dont-deref-coerce-pinned-value.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ LL | callback(pin!(data));
|
= note: expected type parameter `_`
found mutable reference `&mut _`
help: the return type of this call is `&mut T` due to the type of the argument passed
--> $DIR/dont-deref-coerce-pinned-value.rs:9:14
|
LL | callback(pin!(data));
| ^^^^^^^^^^ this argument influences the return type of `unreachable_pin_macro_type_constraint`
note: function defined here
--> $SRC_DIR/core/src/pin.rs:LL:COL

Expand Down
Loading