diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index c2c7041c663df..4da387875bea6 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -270,6 +270,7 @@ symbols! { PartialOrd, Pending, PinDerefMutHelper, + PinMacroHelper, Pointer, Poll, Range, diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 99594d68e7428..16fb609dcd911 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -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; @@ -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> { @@ -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) @@ -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, diff --git a/library/core/src/pin.rs b/library/core/src/pin.rs index 40f774806046a..baf761c584415 100644 --- a/library/core/src/pin.rs +++ b/library/core/src/pin.rs @@ -2026,17 +2026,51 @@ unsafe impl PinCoerceUnsized for Pin {} // `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 . - #[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 . + // + // 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` into a + // `&mut PinMacroHelper` (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) } } } @@ -2044,6 +2078,24 @@ pub macro pin($value:expr $(,)?) { /// See . #[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 { + pub value: T, +} + +/// Helper for `pin!` to enforce its type signature. +/// See . +/// +/// # 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>( + pinned: &'a mut PinMacroHelper, +) -> Pin<&'a mut T> { + // SAFETY: Ensured by the caller. + unsafe { Pin::new_unchecked(&mut pinned.value) } } diff --git a/tests/ui/coercion/subtyping-inhibits-deref-coercion.rs b/tests/ui/coercion/subtyping-inhibits-deref-coercion.rs index 8232dcc6b1f18..6a785aa2f3ac8 100644 --- a/tests/ui/coercion/subtyping-inhibits-deref-coercion.rs +++ b/tests/ui/coercion/subtyping-inhibits-deref-coercion.rs @@ -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; diff --git a/tests/ui/iterators/iter-macro-not-async-closure.rs b/tests/ui/iterators/iter-macro-not-async-closure.rs index 38ea33ccd7732..c46be769b3a67 100644 --- a/tests/ui/iterators/iter-macro-not-async-closure.rs +++ b/tests/ui/iterators/iter-macro-not-async-closure.rs @@ -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 } diff --git a/tests/ui/iterators/iter-macro-not-async-closure.stderr b/tests/ui/iterators/iter-macro-not-async-closure.stderr index 735003207793a..0d7bfaca8bab2 100644 --- a/tests/ui/iterators/iter-macro-not-async-closure.stderr +++ b/tests/ui/iterators/iter-macro-not-async-closure.stderr @@ -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}` @@ -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`. diff --git a/tests/ui/pin-macro/pin_move.stderr b/tests/ui/pin-macro/pin_move.stderr index 3f46602098850..c9b8ad9b2021f 100644 --- a/tests/ui/pin-macro/pin_move.stderr +++ b/tests/ui/pin-macro/pin_move.stderr @@ -31,11 +31,6 @@ LL | struct NotCopy(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 diff --git a/tests/ui/pin/dont-deref-coerce-pinned-value.stderr b/tests/ui/pin/dont-deref-coerce-pinned-value.stderr index 1d5afaa3ad717..0b44527a590b7 100644 --- a/tests/ui/pin/dont-deref-coerce-pinned-value.stderr +++ b/tests/ui/pin/dont-deref-coerce-pinned-value.stderr @@ -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