From 669d08feac50df2ddad0bd1a73d558d41fa3312f Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 25 Apr 2026 18:27:37 +0100 Subject: [PATCH 01/12] internal: pass data back to `__make_init` callback Have `__make_init` take `__data` back as an argument. This gives the `#[pin_data]` macro expansion an opportunity to change the type when needed. Currently the type expands the same way as the previous `__ThePinData` type, but this opportunity will be used in the future to enable self-referencing type support. Remove the `Clone` and `Copy` implementation for data types which are no longer needed. Signed-off-by: Gary Guo --- internal/src/init.rs | 5 +++-- internal/src/pin_data.rs | 2 +- src/__internal.rs | 2 +- tests/ui/expand/many_generics.expanded.rs | 1 + tests/ui/expand/pin-data.expanded.rs | 1 + tests/ui/expand/pinned_drop.expanded.rs | 1 + tests/ui/expand/simple-init.expanded.rs | 4 ++-- 7 files changed, 10 insertions(+), 6 deletions(-) diff --git a/internal/src/init.rs b/internal/src/init.rs index 28d30805..978b346e 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -145,6 +145,7 @@ pub(crate) fn expand( let data = Ident::new("__data", Span::mixed_site()); let init_fields = init_fields(&fields, pinned, &data, &slot); let field_check = make_field_check(&fields, init_kind, &path); + Ok(quote! {{ // Get the data about fields from the supplied type. // SAFETY: TODO @@ -156,7 +157,7 @@ pub(crate) fn expand( }; // Ensure that `#data` really is of type `#data` and help with type inference: let init = #data.__make_closure::<_, #error>( - move |slot| { + move |slot, #data| { #zeroable_check #this #init_fields @@ -166,7 +167,7 @@ pub(crate) fn expand( } ); let init = move |slot| -> ::core::result::Result<(), #error> { - init(slot).map(|__InitOk| ()) + init(slot, #data).map(|__InitOk| ()) }; // SAFETY: TODO unsafe { ::pin_init::#init_from_closure::<_, #error>(init) } diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 9fbbd25b..8c681c75 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -437,7 +437,7 @@ fn generate_the_pin_data( #[inline(always)] #vis fn __make_closure<__F, __E>(self, f: __F) -> __F where - __F: FnOnce(*mut #struct_name #ty_generics) -> + __F: FnOnce(*mut #struct_name #ty_generics, __ThePinData #ty_generics) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, { f diff --git a/src/__internal.rs b/src/__internal.rs index 56dc655e..39c3dabf 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -117,7 +117,7 @@ impl AllData { #[inline(always)] pub fn __make_closure(self, f: F) -> F where - F: FnOnce(*mut T) -> Result, + F: FnOnce(*mut T, Self) -> Result, { f } diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index 5e182d65..0f62be87 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -88,6 +88,7 @@ const _: () = { where __F: FnOnce( *mut Foo<'a, 'b, T, SIZE>, + __ThePinData<'a, 'b, T, SIZE>, ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, { f diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index 12f1596f..da874446 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -52,6 +52,7 @@ const _: () = { where __F: FnOnce( *mut Foo, + __ThePinData, ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, { f diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 4cd7407d..b31e9530 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -52,6 +52,7 @@ const _: () = { where __F: FnOnce( *mut Foo, + __ThePinData, ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, { f diff --git a/tests/ui/expand/simple-init.expanded.rs b/tests/ui/expand/simple-init.expanded.rs index ee3d0096..72da52a6 100644 --- a/tests/ui/expand/simple-init.expanded.rs +++ b/tests/ui/expand/simple-init.expanded.rs @@ -10,7 +10,7 @@ fn main() { .__make_closure::< _, ::core::convert::Infallible, - >(move |slot| { + >(move |slot, __data| { #[allow(unreachable_code, clippy::diverging_sub_expression)] let _ = || unsafe { ::core::ptr::write(slot, Foo {}) }; Ok(unsafe { ::pin_init::__internal::InitOk::new() }) @@ -18,7 +18,7 @@ fn main() { let init = move | slot, | -> ::core::result::Result<(), ::core::convert::Infallible> { - init(slot).map(|__InitOk| ()) + init(slot, __data).map(|__InitOk| ()) }; unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } }; From 3c1aba2e096af011b5d4295faf6e6cfbb242b12e Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 28 Apr 2026 17:41:28 +0100 Subject: [PATCH 02/12] move generated projection struct into `const _: ()` block Most users would not need to mention the generated projection struct. With the upcoming self-referential support, there needs to be an additional projection struct. Instead of give it another publicly visible name, simply make the name not publicly accessible. A heavy user user of pin projection outside kernel is for async programming. I've checked tokio codebase there is a single use case and that's for enums. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 9 ++- tests/ui/compile-fail/pin_data/twice.stderr | 21 ------ tests/ui/expand/many_generics.expanded.rs | 78 +++++++++++---------- tests/ui/expand/pin-data.expanded.rs | 54 +++++++------- tests/ui/expand/pinned_drop.expanded.rs | 54 +++++++------- 5 files changed, 100 insertions(+), 116 deletions(-) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 8c681c75..536a514b 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -124,10 +124,10 @@ pub(crate) fn pin_data( Ok(quote! { #struct_ - #projections // We put the rest into this const item, because it then will not be accessible to anything // outside. const _: () = { + #projections #the_pin_data #unpin_impl #drop_impl @@ -272,7 +272,6 @@ fn generate_projections( let mut generics_with_pin_lt = generics.clone(); generics_with_pin_lt.params.insert(0, parse_quote!('__pin)); let (_, ty_generics_with_pin_lt, whr) = generics_with_pin_lt.split_for_impl(); - let projection = format_ident!("{ident}Projection"); let this = format_ident!("this"); let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields @@ -325,7 +324,7 @@ fn generate_projections( // the struct definition. #[allow(dead_code, non_snake_case)] #[doc(hidden)] - #vis struct #projection #generics_with_pin_lt + #vis struct __Projection #generics_with_pin_lt #whr { #(#fields_decl)* @@ -345,10 +344,10 @@ fn generate_projections( #[inline] #vis fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, - ) -> #projection #ty_generics_with_pin_lt { + ) -> __Projection #ty_generics_with_pin_lt { // SAFETY: we only give access to `&mut` for fields not structurally pinned. let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; - #projection { + __Projection { #(#fields_proj)* ___pin_phantom_data: ::core::marker::PhantomData, } diff --git a/tests/ui/compile-fail/pin_data/twice.stderr b/tests/ui/compile-fail/pin_data/twice.stderr index abae43e5..562177ea 100644 --- a/tests/ui/compile-fail/pin_data/twice.stderr +++ b/tests/ui/compile-fail/pin_data/twice.stderr @@ -1,14 +1,3 @@ -error[E0428]: the name `FooProjection` is defined multiple times - --> tests/ui/compile-fail/pin_data/twice.rs:4:1 - | -3 | #[pin_data] - | ----------- previous definition of the type `FooProjection` here -4 | #[pin_data] - | ^^^^^^^^^^^ `FooProjection` redefined here - | - = note: `FooProjection` must be defined only once in the type namespace of this module - = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0119]: conflicting implementations of trait `pin_init::__internal::HasPinData` for type `Foo` --> tests/ui/compile-fail/pin_data/twice.rs:4:1 | @@ -38,13 +27,3 @@ error[E0592]: duplicate definitions with name `project` | ^^^^^^^^^^^ duplicate definitions for `project` | = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0308]: mismatched types - --> tests/ui/compile-fail/pin_data/twice.rs:4:1 - | -4 | #[pin_data] - | ^^^^^^^^^^^ expected `Pin<&mut usize>`, found `&mut usize` - | - = note: expected struct `Pin<&mut usize>` - found mutable reference `&mut usize` - = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index 0f62be87..b9c83697 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -12,44 +12,50 @@ where r: &'b mut [&'a mut T; SIZE], _pin: PhantomPinned, } -/// Pin-projections of [`Foo`] -#[allow(dead_code, non_snake_case)] -#[doc(hidden)] -struct FooProjection<'__pin, 'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> -where - T: Bar<'a, 1>, -{ - array: &'__pin mut [u8; 1024 * 1024], - r: &'__pin mut &'b mut [&'a mut T; SIZE], - _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, -} -impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> Foo<'a, 'b, T, SIZE> -where - T: Bar<'a, 1>, -{ - /// Pin-projects all fields of `Self`. - /// - /// These fields are structurally pinned: - /// - `_pin` - /// - /// These fields are **not** structurally pinned: - /// - `array` - /// - `r` - #[inline] - fn project<'__pin>( - self: ::core::pin::Pin<&'__pin mut Self>, - ) -> FooProjection<'__pin, 'a, 'b, T, SIZE> { - let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; - FooProjection { - array: &mut this.array, - r: &mut this.r, - _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, - ___pin_phantom_data: ::core::marker::PhantomData, +const _: () = { + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __Projection< + '__pin, + 'a, + 'b: 'a, + T: Bar<'b> + ?Sized + 'a, + const SIZE: usize = 0, + > + where + T: Bar<'a, 1>, + { + array: &'__pin mut [u8; 1024 * 1024], + r: &'__pin mut &'b mut [&'a mut T; SIZE], + _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + } + impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> Foo<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + { + /// Pin-projects all fields of `Self`. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + /// - `r` + #[inline] + fn project<'__pin>( + self: ::core::pin::Pin<&'__pin mut Self>, + ) -> __Projection<'__pin, 'a, 'b, T, SIZE> { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + __Projection { + array: &mut this.array, + r: &mut this.r, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + } } } -} -const _: () = { #[doc(hidden)] struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> where diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index da874446..ebd19586 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -4,35 +4,35 @@ struct Foo { array: [u8; 1024 * 1024], _pin: PhantomPinned, } -/// Pin-projections of [`Foo`] -#[allow(dead_code, non_snake_case)] -#[doc(hidden)] -struct FooProjection<'__pin> { - array: &'__pin mut [u8; 1024 * 1024], - _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, -} -impl Foo { - /// Pin-projects all fields of `Self`. - /// - /// These fields are structurally pinned: - /// - `_pin` - /// - /// These fields are **not** structurally pinned: - /// - `array` - #[inline] - fn project<'__pin>( - self: ::core::pin::Pin<&'__pin mut Self>, - ) -> FooProjection<'__pin> { - let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; - FooProjection { - array: &mut this.array, - _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, - ___pin_phantom_data: ::core::marker::PhantomData, +const _: () = { + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __Projection<'__pin> { + array: &'__pin mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + } + impl Foo { + /// Pin-projects all fields of `Self`. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn project<'__pin>( + self: ::core::pin::Pin<&'__pin mut Self>, + ) -> __Projection<'__pin> { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + __Projection { + array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + } } } -} -const _: () = { #[doc(hidden)] struct __ThePinData { __phantom: ::pin_init::__internal::PhantomInvariant, diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index b31e9530..8cc1a023 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -4,35 +4,35 @@ struct Foo { array: [u8; 1024 * 1024], _pin: PhantomPinned, } -/// Pin-projections of [`Foo`] -#[allow(dead_code, non_snake_case)] -#[doc(hidden)] -struct FooProjection<'__pin> { - array: &'__pin mut [u8; 1024 * 1024], - _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, -} -impl Foo { - /// Pin-projects all fields of `Self`. - /// - /// These fields are structurally pinned: - /// - `_pin` - /// - /// These fields are **not** structurally pinned: - /// - `array` - #[inline] - fn project<'__pin>( - self: ::core::pin::Pin<&'__pin mut Self>, - ) -> FooProjection<'__pin> { - let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; - FooProjection { - array: &mut this.array, - _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, - ___pin_phantom_data: ::core::marker::PhantomData, +const _: () = { + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __Projection<'__pin> { + array: &'__pin mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + } + impl Foo { + /// Pin-projects all fields of `Self`. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn project<'__pin>( + self: ::core::pin::Pin<&'__pin mut Self>, + ) -> __Projection<'__pin> { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + __Projection { + array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + } } } -} -const _: () = { #[doc(hidden)] struct __ThePinData { __phantom: ::pin_init::__internal::PhantomInvariant, From 6e3fdda503df305251e625bbf1a8c9819df2340d Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 30 Apr 2026 18:49:47 +0100 Subject: [PATCH 03/12] internal: pin_data: self-referential parsing support As a first step towards adding self-referential data structures in pin-init, introduce parsing support for needed attributes. Two attributes are used to denote variance of the field, `#[covariant]` and `#[not_covariant]`. More types are covariant over lifetimes and therefore `#[covariant]` is the default if no annotations are found. `#[borrows]` attribute is used to mark what other fields that the field can borrow from, and it is also used to mark what types of borrow it is. For example, `#[borrows(foo)]` indicates that `foo` should be shared borrowed and `#[borrows(mut foo)]` borrows `foo` mutably. In absence of `#[borrows]` attribute, `#[pin_data]` would scan the type to see if there're any unbound lifetime. Only parsing is included in this commit. The attribute syntax is inspired by the `ouroboros` crate, although many different design decisions have been taken. Link: https://docs.rs/ouroboros [1] Signed-off-by: Gary Guo --- internal/Cargo.toml | 2 +- internal/src/lib.rs | 2 + internal/src/pin_data.rs | 177 +++++++++++++++++- internal/src/util.rs | 61 ++++++ .../pin_data/selfref_invalid_attr.rs | 17 ++ .../pin_data/selfref_invalid_attr.stderr | 63 +++++++ 6 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 internal/src/util.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr diff --git a/internal/Cargo.toml b/internal/Cargo.toml index 4b77202d..eed20769 100644 --- a/internal/Cargo.toml +++ b/internal/Cargo.toml @@ -16,7 +16,7 @@ proc-macro = true [dependencies] quote = "1.0" proc-macro2 = "1.0" -syn = { version = "2.0.86", features = ["full", "parsing", "visit-mut"] } +syn = { version = "2.0.86", features = ["full", "parsing", "visit", "visit-mut"] } [build-dependencies] rustc_version = "0.4" diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 60d5093f..19470975 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -8,6 +8,7 @@ // Documentation is done in the pin-init crate instead. #![allow(missing_docs)] +#![cfg_attr(USE_RUSTC_FEATURES, feature(extract_if))] use proc_macro::TokenStream; use syn::parse_macro_input; @@ -18,6 +19,7 @@ mod diagnostics; mod init; mod pin_data; mod pinned_drop; +mod util; mod zeroable; #[proc_macro_attribute] diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 536a514b..f57bab0c 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -1,16 +1,23 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -use proc_macro2::TokenStream; +use std::collections::{BTreeMap, BTreeSet}; + +use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{ parse::{End, Nothing, Parse}, parse_quote, parse_quote_spanned, + punctuated::Punctuated, spanned::Spanned, visit_mut::VisitMut, - Attribute, Field, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, + Attribute, Field, Generics, Ident, Item, Lifetime, PathSegment, Token, Type, TypePath, + Visibility, WhereClause, }; -use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; +use crate::{ + diagnostics::{DiagCtxt, ErrorGuaranteed}, + util::TypeExt, +}; pub(crate) mod kw { syn::custom_keyword!(PinnedDrop); @@ -35,9 +42,68 @@ impl Parse for Args { } } +/// Annotation that a field is referenced. +struct Borrow { + mutable: Option, + /// Name of the field that this lifetime can reference. + field: Ident, +} + +impl Parse for Borrow { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + Ok(Borrow { + mutable: input.parse()?, + field: input.parse()?, + }) + } +} + +#[expect(unused)] +enum Variance { + Covariant(Span), + NotCovariant(Span), +} + +impl Variance { + fn parse(dcx: &mut DiagCtxt, attrs: &mut Vec, span: Span) -> Self { + let mut attrs = attrs.extract_if(.., |attr| { + attr.path().is_ident("covariant") || attr.path().is_ident("not_covariant") + }); + + let result = match attrs.next() { + None => { + // By default, infer covariance. + Variance::Covariant(span) + } + Some(attr) => { + if attr.path().is_ident("covariant") { + Variance::Covariant(attr.path().span()) + } else { + Variance::NotCovariant(attr.path().span()) + } + } + }; + + // Emit error on redundant specifications. + for attr in attrs { + dcx.error(attr.path(), "variance marker can only be specified once"); + } + + result + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum Borrowed { + Shared, + Mutable, +} + struct FieldInfo<'a> { field: &'a Field, pinned: bool, + borrowed: Option, + variance: Option, cfg_attrs: Vec<&'a Attribute>, } @@ -79,7 +145,14 @@ pub(crate) fn pin_data( replacer.visit_generics_mut(&mut struct_.generics); replacer.visit_fields_mut(&mut struct_.fields); - let fields: Vec> = struct_ + // Collect all bound lifetimes from generics. + let bound_lifetimes: BTreeSet<&Lifetime> = + struct_.generics.lifetimes().map(|x| &x.lifetime).collect(); + + // Keep track on how fields are borrowed and where. + let mut borrow_info = BTreeMap::<_, (bool, Vec)>::new(); + + let mut fields: Vec> = struct_ .fields .iter_mut() .map(|field| { @@ -87,6 +160,60 @@ pub(crate) fn pin_data( field.attrs.retain(|a| !a.path().is_ident("pin")); let pinned = len != field.attrs.len(); + let ident = field.ident.as_ref().unwrap(); + + // Parse `#[covariant]` and `#[not_covariant]` markers. + let variance = Variance::parse(dcx, &mut field.attrs, ident.span()); + + // Parse `#[borrows]` attribute or infer it from the type. + let borrows: Punctuated = field + .attrs + .extract_if(.., |attr| attr.path().is_ident("borrows")) + .filter_map( + |attr| match attr.parse_args_with(Punctuated::parse_terminated) { + Ok(v) => Some(v), + Err(err) => { + dcx.error(attr, err); + None + } + }, + ) + .next() + .unwrap_or_else(|| { + // If no annotations are attached, infer lifetime based on the field referenced, + // although bound lifetimes from struct generics should take priority. + // + // For example, + // ``` + // struct Foo<'a> { + // bar: &'a (), + // a: u32, + // } + // ``` + // would not be inferred as self-referential because `'a` is already bound by the + // struct generics. + // + // Note that we cannot reliably determine what type of borrow is needed on fields. + // More commonly a shared borrow is required, so it is inferred as such. Mutable + // borrow would require explicit user annotation. + field + .ty + .unbound_lifetimes() + .difference(&bound_lifetimes) + .map(|<| Borrow { + mutable: None, + field: lt.ident.clone(), + }) + .collect() + }); + + // Update borrow information. + for borrow in borrows.iter() { + let entry = borrow_info.entry(borrow.field.clone()).or_default(); + entry.0 |= borrow.mutable.is_some(); + entry.1.push(borrow.field.span()); + } + let cfg_attrs = field .attrs .iter() @@ -96,23 +223,59 @@ pub(crate) fn pin_data( FieldInfo { field: &*field, pinned, + borrowed: None, + variance: if borrows.is_empty() { + None + } else { + Some(variance) + }, cfg_attrs, } }) .collect(); - for field in &fields { + for field in fields.iter_mut() { let ident = field.field.ident.as_ref().unwrap(); if !field.pinned && is_phantom_pinned(&field.field.ty) { dcx.warn( field.field, format!( - "The field `{ident}` of type `PhantomPinned` only has an effect \ + "The field `{}` of type `PhantomPinned` only has an effect \ if it has the `#[pin]` attribute", + ident, ), ); } + + // Update `borrowed` now we have all borrow information recorded. + // This is not `remove()` because fields might be `#[cfg]` on so identifier names are not unique. + if let Some((mutable, users)) = borrow_info.get_mut(ident) { + field.borrowed = Some(if *mutable { + Borrowed::Mutable + } else { + Borrowed::Shared + }); + + users.clear(); + } + } + + // For any residual users in `borrow_info`, the lifetime mention is invalid and report as such. + for (name, (_, users)) in borrow_info { + for user in users { + dcx.error( + user, + format!("`{name}` is neither a lifetime in generics nor a field name"), + ); + } + } + + // Reject self-referential structs. + for f in &fields { + if f.variance.is_some() { + dcx.error(f.field, "self-referential support is not fully implemented"); + } } let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); @@ -276,6 +439,7 @@ fn generate_projections( let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields .iter() + .filter(|f| f.borrowed.is_none() && f.variance.is_none()) .map(|field| { let Field { vis, ident, ty, .. } = &field.field; let cfg_attrs = &field.cfg_attrs; @@ -370,6 +534,7 @@ fn generate_the_pin_data( // `Unpinned` which allows initialization via `Init`. let field_accessors = fields .iter() + .filter(|f| f.borrowed.is_none() && f.variance.is_none()) .map(|f| { let Field { vis, ident, ty, .. } = f.field; let cfg_attrs = &f.cfg_attrs; diff --git a/internal/src/util.rs b/internal/src/util.rs new file mode 100644 index 00000000..d638569f --- /dev/null +++ b/internal/src/util.rs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use std::collections::BTreeSet; + +use syn::visit::Visit; +use syn::GenericParam; +use syn::{Lifetime, Type}; + +pub(crate) trait TypeExt { + /// Get the list of unbound lifetimes referenced by the type. + fn unbound_lifetimes(&self) -> BTreeSet<&Lifetime>; +} + +impl TypeExt for Type { + fn unbound_lifetimes(&self) -> BTreeSet<&Lifetime> { + struct LifetimeVisitor<'a> { + bound: BTreeSet<&'a Lifetime>, + } + + impl<'a> Visit<'a> for LifetimeVisitor<'a> { + fn visit_lifetime(&mut self, lt: &'a Lifetime) { + if lt.ident == "static" { + return; + } + + if !self.bound.contains(lt) { + self.bound.insert(lt); + } + } + + fn visit_trait_bound(&mut self, bound: &'a syn::TraitBound) { + // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`, + // temporarily remove them from `bound` if they're. + + let mut to_remove = Vec::new(); + if let Some(bound_lt) = &bound.lifetimes { + for lt in &bound_lt.lifetimes { + let GenericParam::Lifetime(lt) = lt else { + continue; + }; + if !self.bound.contains(&<.lifetime) { + to_remove.push(<.lifetime); + } + } + } + + self.visit_path(&bound.path); + + for lt in to_remove { + self.bound.remove(lt); + } + } + } + + let mut visitor = LifetimeVisitor { + bound: BTreeSet::new(), + }; + visitor.visit_type(self); + visitor.bound + } +} diff --git a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs new file mode 100644 index 00000000..ad78ce83 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs @@ -0,0 +1,17 @@ +use pin_init::*; + +#[pin_data] +struct InvalidAttr<'a> { + #[covariant] + #[not_covariant] // Duplicate variance attribute + #[borrows(non_exist, mut non_exist_mut)] // Borrows non-existent fields + explicit: u32, + + implicit: &'non_exist u32, + + bound: &'a u32, + okay: &'b u32, + b: u32, +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr new file mode 100644 index 00000000..8eb5c464 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr @@ -0,0 +1,63 @@ +error: variance marker can only be specified once + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:6:7 + | +6 | #[not_covariant] // Duplicate variance attribute + | ^^^^^^^^^^^^^ + +error: `non_exist` is neither a lifetime in generics nor a field name + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:7:15 + | +7 | #[borrows(non_exist, mut non_exist_mut)] // Borrows non-existent fields + | ^^^^^^^^^ + +error: `non_exist` is neither a lifetime in generics nor a field name + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:10:16 + | +10 | implicit: &'non_exist u32, + | ^^^^^^^^^^ + +error: `non_exist_mut` is neither a lifetime in generics nor a field name + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:7:30 + | +7 | #[borrows(non_exist, mut non_exist_mut)] // Borrows non-existent fields + | ^^^^^^^^^^^^^ + +error: self-referential support is not fully implemented + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:8:5 + | +8 | explicit: u32, + | ^^^^^^^^^^^^^ + +error: self-referential support is not fully implemented + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:10:5 + | +10 | implicit: &'non_exist u32, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: self-referential support is not fully implemented + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:13:5 + | +13 | okay: &'b u32, + | ^^^^^^^^^^^^^ + +error[E0261]: use of undeclared lifetime name `'non_exist` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:10:16 + | +10 | implicit: &'non_exist u32, + | ^^^^^^^^^^ undeclared lifetime + | +help: consider introducing lifetime `'non_exist` here + | + 4 | struct InvalidAttr<'non_exist, 'a> { + | +++++++++++ + +error[E0261]: use of undeclared lifetime name `'b` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:13:12 + | +13 | okay: &'b u32, + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'b` here + | + 4 | struct InvalidAttr<'b, 'a> { + | +++ From dabe6355df7ea84c13bbfafc13c0a88b1b69f36e Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 18:32:29 +0100 Subject: [PATCH 04/12] internal: pin_data: rewrite structs that self-references Fields that borrow other fields have lifetimes that are within the struct and these are not part of the struct generics. Therefore, these fields need to have their lifetime erased. A naive implementation would be to replace their lifetimes with `'static`. However, doing so is unsound for multiple reasons: * Users may directly access such field with field access syntax, and get exposed with wrong lifetime; * Auto trait implementations will cause the struct to be implementing auto traits when the type only implements the auto trait for specific lifetime. This is similar to how specialization can be unsound if specialized on lifetime. The first issue is easy to solve by simplying wrapping the field in a struct that blocks direct access. The second issue is more involved. This is where higher-ranked trait bound comes in. We can define a `ForLt` trait, and if we have a `F` where `::Of<'a>` is the user-provided type that makes use of 'a that refers to a borrowed field. Then we can use `for<'a> F::Of<'a>: Send` to constrain that the user-provided type needs to be `Send` for all lifetimes rather than a specific lifetime before the type can be proved by the trait resolver to be `Send`. The above strategies is what underpins the `SelfRef` type, which is used for all fields that borrows from the other field. The actual implementation is a bit more complex version so it can handle multiple lifetimes. Apart from `Send` and `Sync`, we also have an auto-trait `Unpin`. Obviously, self-referential structs need always be `!Unpin`. This is enforced by ensuring `SelfRef: !Unpin` and force the generated `Unpin` implementation never evaluates never satisfy its bound. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 116 +++++++++++++++--- internal/src/util.rs | 66 ++++++++++ src/__internal.rs | 58 +++++++++ .../pin_data/selfref_invalid_attr.stderr | 22 ---- .../pin_data/selfref_lifetime_specialize.rs | 38 ++++++ .../selfref_lifetime_specialize.stderr | 57 +++++++++ .../selfref_lifetime_specialize_unpin.rs | 31 +++++ .../selfref_lifetime_specialize_unpin.stderr | 65 ++++++++++ 8 files changed, 416 insertions(+), 37 deletions(-) create mode 100644 tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.stderr diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index f57bab0c..3bf26eee 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -3,20 +3,20 @@ use std::collections::{BTreeMap, BTreeSet}; use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; use syn::{ parse::{End, Nothing, Parse}, parse_quote, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, visit_mut::VisitMut, - Attribute, Field, Generics, Ident, Item, Lifetime, PathSegment, Token, Type, TypePath, - Visibility, WhereClause, + Attribute, Field, Fields, Generics, Ident, Item, ItemStruct, Lifetime, PathSegment, Token, + Type, TypePath, Visibility, WhereClause, }; use crate::{ diagnostics::{DiagCtxt, ErrorGuaranteed}, - util::TypeExt, + util::{Binder, LifetimeExt, TypeExt}, }; pub(crate) mod kw { @@ -104,6 +104,7 @@ struct FieldInfo<'a> { pinned: bool, borrowed: Option, variance: Option, + ty: Binder, cfg_attrs: Vec<&'a Attribute>, } @@ -145,6 +146,10 @@ pub(crate) fn pin_data( replacer.visit_generics_mut(&mut struct_.generics); replacer.visit_fields_mut(&mut struct_.fields); + // Move `fields` out from the struct. + let mut fields = struct_.fields; + struct_.fields = Fields::Unit; + // Collect all bound lifetimes from generics. let bound_lifetimes: BTreeSet<&Lifetime> = struct_.generics.lifetimes().map(|x| &x.lifetime).collect(); @@ -152,8 +157,7 @@ pub(crate) fn pin_data( // Keep track on how fields are borrowed and where. let mut borrow_info = BTreeMap::<_, (bool, Vec)>::new(); - let mut fields: Vec> = struct_ - .fields + let mut fields: Vec> = fields .iter_mut() .map(|field| { let len = field.attrs.len(); @@ -214,6 +218,14 @@ pub(crate) fn pin_data( entry.1.push(borrow.field.span()); } + let ty = Binder::new( + borrows + .iter() + .map(|field_ref| Lifetime::from_ident(&field_ref.field)) + .collect(), + field.ty.clone(), + ); + let cfg_attrs = field .attrs .iter() @@ -229,6 +241,7 @@ pub(crate) fn pin_data( } else { Some(variance) }, + ty, cfg_attrs, } }) @@ -259,6 +272,15 @@ pub(crate) fn pin_data( users.clear(); } + + // We have a limitation of up to 4 referenced lifetime in a type. + // See `ForLt4` and `SelfRef` in `__internal.rs` on why this limitation exists. + if field.ty.bound.len() > 4 { + dcx.error( + &field.ty.bound[4], + "at most 4 lifetimes can be referenced from a struct at a time", + ); + } } // For any residual users in `borrow_info`, the lifetime mention is invalid and report as such. @@ -278,6 +300,7 @@ pub(crate) fn pin_data( } } + let struct_def = generate_struct_def(&struct_, &fields); let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args); let projections = @@ -286,7 +309,7 @@ pub(crate) fn pin_data( generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields); Ok(quote! { - #struct_ + #struct_def // We put the rest into this const item, because it then will not be accessible to anything // outside. const _: () = { @@ -322,6 +345,59 @@ fn is_phantom_pinned(ty: &Type) -> bool { } } +fn generate_struct_def( + ItemStruct { + attrs, + vis, + struct_token, + ident, + generics, + fields: _, + semi_token, + }: &ItemStruct, + fields: &[FieldInfo<'_>], +) -> TokenStream { + let mut generated_fields = Vec::new(); + + for field in fields { + let Field { + attrs, + vis, + mutability: _, + ident, + colon_token, + ty: _, + } = &field.field; + + let mut ty = field.ty.value.to_token_stream(); + + if field.variance.is_some() { + let bound = field.ty.for_bound_4(); + let bound_lt = bound.lifetimes.iter(); + ty = parse_quote!(::pin_init::__internal::SelfRef< + ::pin_init::__internal::ForLtImpl< + dyn #bound ::pin_init::__internal::WithLt4<#(#bound_lt,)* Of = #ty> + > + >); + }; + + generated_fields.push(quote! { + #(#attrs)* #vis #ident #colon_token #ty + }); + } + + let whr = &generics.where_clause; + + quote!( + #(#attrs)* + #vis + #struct_token #ident #generics #whr { + #(#generated_fields,)* + } + #semi_token + ) +} + fn generate_unpin_impl( ident: &Ident, generics: &Generics, @@ -342,15 +418,25 @@ fn generate_unpin_impl( else { unreachable!() }; - let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| { - let ident = f.field.ident.as_ref().unwrap(); - let ty = &f.field.ty; - let cfg_attrs = &f.cfg_attrs; + + let pinned_fields = if fields.iter().any(|f| f.borrowed.is_some()) { + // Self-referential structs must always be pinned. quote!( - #(#cfg_attrs)* - #ident: #ty + __phantom_pinned: ::core::marker::PhantomPinned, ) - }); + } else { + let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| { + let ident = f.field.ident.as_ref().unwrap(); + let ty = &f.field.ty; + let cfg_attrs = &f.cfg_attrs; + quote!( + #(#cfg_attrs)* + #ident: #ty + ) + }); + quote!(#(#pinned_fields,)*) + }; + quote! { // This struct will be used for the unpin analysis. It is needed, because only structurally // pinned fields are relevant whether the struct should implement `Unpin`. @@ -364,7 +450,7 @@ fn generate_unpin_impl( { __phantom_pin: ::pin_init::__internal::PhantomInvariantLifetime<'__pin>, __phantom: ::pin_init::__internal::PhantomInvariant<#ident #ty_generics>, - #(#pinned_fields),* + #pinned_fields } #[doc(hidden)] diff --git a/internal/src/util.rs b/internal/src/util.rs index d638569f..8dacef9e 100644 --- a/internal/src/util.rs +++ b/internal/src/util.rs @@ -2,8 +2,11 @@ use std::collections::BTreeSet; +use proc_macro2::Span; +use syn::punctuated::Punctuated; use syn::visit::Visit; use syn::GenericParam; +use syn::{BoundLifetimes, Ident, LifetimeParam}; use syn::{Lifetime, Type}; pub(crate) trait TypeExt { @@ -59,3 +62,66 @@ impl TypeExt for Type { visitor.bound } } + +pub(crate) struct Binder { + pub bound: Vec, + pub value: T, +} + +impl Binder { + pub(crate) fn new(bound: Vec, value: T) -> Self { + Binder { bound, value } + } + + /// Obtain a `for<...>` that can be used to construct a higher-ranked trait bound. + #[expect(unused)] + pub fn for_bound(&self) -> BoundLifetimes { + BoundLifetimes { + for_token: Default::default(), + lt_token: Default::default(), + lifetimes: self + .bound + .iter() + .map(|lt| GenericParam::Lifetime(LifetimeParam::new(lt.clone()))) + .collect(), + gt_token: Default::default(), + } + } + + /// Similar to `for_bound`, but the number of lifetimes is padded to 4. + pub(crate) fn for_bound_4(&self) -> BoundLifetimes { + let mut lifetimes: Punctuated<_, _> = self + .bound + .iter() + .map(|lt| GenericParam::Lifetime(LifetimeParam::new(lt.clone()))) + .collect(); + while lifetimes.len() < 4 { + lifetimes.push(GenericParam::Lifetime(LifetimeParam::new(Lifetime::new( + &format!("'__lt{}", lifetimes.len()), + Span::mixed_site(), + )))); + } + BoundLifetimes { + for_token: Default::default(), + lt_token: Default::default(), + lifetimes, + gt_token: Default::default(), + } + } +} + +pub(crate) trait LifetimeExt { + /// Obtain a lifetime from a identifier. + /// + /// The created lifetime has the same span. + fn from_ident(ident: &Ident) -> Self; +} + +impl LifetimeExt for Lifetime { + fn from_ident(ident: &Ident) -> Self { + Lifetime { + apostrophe: ident.span(), + ident: ident.clone(), + } + } +} diff --git a/src/__internal.rs b/src/__internal.rs index 39c3dabf..6cfa9af9 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -5,6 +5,8 @@ //! These items must not be used outside of this crate and the pin-init-internal crate located at //! `../internal`. +use core::marker::PhantomPinned; + use super::*; /// Zero-sized type used to mark a type as invariant. @@ -402,3 +404,59 @@ unsafe impl PinInit for AlwaysFail { Err(()) } } + +/// Representation of types generic over 4 lifetimes. +/// +/// This type limits the maximum amount of self-referential lifetimes supported, as we have checks +/// that requires generalization over lifetime and it is unsound to substitute lifetime. 2 lifetimes +/// can easily happen, 3 lifetimes should be much rarer, and 4 or more lifetimes would be +/// exceedingly rare. +pub trait ForLt4 { + /// The type parameterized by the lifetime. + type Of<'a, 'b, 'c, 'd>; +} + +// This is a helper trait for implementation `ForLt4` to be able to use HRTB. +pub trait WithLt4<'a, 'b, 'c, 'd> { + type Of; +} + +pub struct ForLtImpl(PhantomData); + +impl WithLt4<'a, 'b, 'c, 'd>> ForLt4 for ForLtImpl { + type Of<'a, 'b, 'c, 'd> = >::Of; +} + +/// A wrapper for fields that reference other fields to block direct access. +/// +/// Use the higher-ranked lifetime facility to support this. Note that it is important that this is +/// *not* just a wrapper of `F::Of<'static>`, so we can implement `Send` and `Sync` only when things +/// are true for all lifetimes. +#[repr(transparent)] +pub struct SelfRef( + F::Of<'static, 'static, 'static, 'static>, + // Mark the type as `!Send` and `!Sync` so we can implement it manually. The auto trait + // implementation will cause `SelfRef` to be `Send`/`Sync` when only `'static` is, causing a + // soundness hole similar to that of specialization. + PhantomData<*mut ()>, + // Mark the type as `!Unpin`. This is not actually needed for this type, but it is used to ensure + // tha the containing type is not `Unpin` by auto trait implementation. + // + // In a case of + // ``` + // #[pin_data] + // struct Foo { + // a: &'b u32, + // b: u32, + // } + // ``` + // + // This requires the type to be `!Unpin` despite all field types are `Unpin`. The `b` field is not going + // to transformed, so we rely on `SelfRef` to be the type that is `!Unpin`. + PhantomPinned, +); + +// SAFETY: The bound ensures that `F::Of` is `Send` for all lifetime parameters. +unsafe impl Send for SelfRef where for<'a, 'b, 'c, 'd> F::Of<'a, 'b, 'c, 'd>: Send {} +// SAFETY: The bound ensures that `F::Of` is `Sync` for all lifetime parameters. +unsafe impl Sync for SelfRef where for<'a, 'b, 'c, 'd> F::Of<'a, 'b, 'c, 'd>: Sync {} diff --git a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr index 8eb5c464..d7e3750c 100644 --- a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr +++ b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr @@ -39,25 +39,3 @@ error: self-referential support is not fully implemented | 13 | okay: &'b u32, | ^^^^^^^^^^^^^ - -error[E0261]: use of undeclared lifetime name `'non_exist` - --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:10:16 - | -10 | implicit: &'non_exist u32, - | ^^^^^^^^^^ undeclared lifetime - | -help: consider introducing lifetime `'non_exist` here - | - 4 | struct InvalidAttr<'non_exist, 'a> { - | +++++++++++ - -error[E0261]: use of undeclared lifetime name `'b` - --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:13:12 - | -13 | okay: &'b u32, - | ^^ undeclared lifetime - | -help: consider introducing lifetime `'b` here - | - 4 | struct InvalidAttr<'b, 'a> { - | +++ diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs new file mode 100644 index 00000000..442cdf43 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs @@ -0,0 +1,38 @@ +// Ensure that types that have impl that specialize on a single lifetime can be used to exploit +// pin-init. + +use std::marker::PhantomData; + +use pin_init::*; + +struct LtSpec<'a>(PhantomData<*const &'a u32>); +struct LtSpec2<'a, 'b>(PhantomData<*const &'a &'b u32>); + +unsafe impl Send for LtSpec<'static> {} +unsafe impl Sync for LtSpec<'static> {} +unsafe impl<'a> Send for LtSpec2<'a, 'a> {} +unsafe impl<'a> Sync for LtSpec2<'a, 'a> {} + +#[pin_data] +struct Foo { + lt_spec: LtSpec<'a>, + a: u32, +} + +#[pin_data] +struct Bar { + lt_spec2: LtSpec2<'a, 'b>, + a: u32, + b: u32, +} + +fn assert_send() {} +fn assert_sync() {} + +fn main() { + // All of the below checks must fail. + assert_send::(); + assert_sync::(); + assert_send::(); + assert_sync::(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr new file mode 100644 index 00000000..b22ef8f7 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr @@ -0,0 +1,57 @@ +error: self-referential support is not fully implemented + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:18:5 + | +18 | lt_spec: LtSpec<'a>, + | ^^^^^^^^^^^^^^^^^^^ + +error: self-referential support is not fully implemented + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:24:5 + | +24 | lt_spec2: LtSpec2<'a, 'b>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: implementation of `Send` is not general enough + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:34:5 + | +34 | assert_send::(); + | ^^^^^^^^^^^^^^^^^^^^ implementation of `Send` is not general enough + | + = note: `LtSpec<'0>` must implement `Send`, for any lifetime `'0`... + = note: ...but `Send` is actually implemented for the type `LtSpec<'static>` + +error: implementation of `Sync` is not general enough + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:35:5 + | +35 | assert_sync::(); + | ^^^^^^^^^^^^^^^^^^^^ implementation of `Sync` is not general enough + | + = note: `LtSpec<'0>` must implement `Sync`, for any lifetime `'0`... + = note: ...but `Sync` is actually implemented for the type `LtSpec<'static>` + +error[E0308]: mismatched types + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:36:5 + | +36 | assert_send::(); + | ^^^^^^^^^^^^^^^^^^^^ one type is more general than the other + | + = note: expected associated type `<(dyn for<'a, 'b, '__lt2, '__lt3> pin_init::__internal::WithLt4<'a, 'b, '__lt2, '__lt3, Of = LtSpec2<'a, 'b>> + 'static) as pin_init::__internal::WithLt4<'_, '_, '_, '_>>::Of` + found associated type `<(dyn for<'a, 'b, '__lt2, '__lt3> pin_init::__internal::WithLt4<'a, 'b, '__lt2, '__lt3, Of = LtSpec2<'a, 'b>> + 'static) as pin_init::__internal::WithLt4<'a, 'b, 'c, 'd>>::Of` +note: the lifetime requirement is introduced here + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:29:19 + | +29 | fn assert_send() {} + | ^^^^ + +error[E0308]: mismatched types + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:37:5 + | +37 | assert_sync::(); + | ^^^^^^^^^^^^^^^^^^^^ one type is more general than the other + | + = note: expected associated type `<(dyn for<'a, 'b, '__lt2, '__lt3> pin_init::__internal::WithLt4<'a, 'b, '__lt2, '__lt3, Of = LtSpec2<'a, 'b>> + 'static) as pin_init::__internal::WithLt4<'_, '_, '_, '_>>::Of` + found associated type `<(dyn for<'a, 'b, '__lt2, '__lt3> pin_init::__internal::WithLt4<'a, 'b, '__lt2, '__lt3, Of = LtSpec2<'a, 'b>> + 'static) as pin_init::__internal::WithLt4<'a, 'b, 'c, 'd>>::Of` +note: the lifetime requirement is introduced here + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:30:19 + | +30 | fn assert_sync() {} + | ^^^^ diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs new file mode 100644 index 00000000..30db2403 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs @@ -0,0 +1,31 @@ +// Ensure that types that have impl that specialize on a single lifetime can be used to exploit +// pin-init. Separate from selfref_lifetime_specialize.rs as somehow `Unpin` check suppresses `Send` +// and `Sync` errors. + +use std::marker::PhantomData; + +use pin_init::*; + +struct LtSpec<'a>(PhantomData<*const &'a u32>); +struct LtSpec2<'a, 'b>(PhantomData<*const &'a &'b u32>); + +#[pin_data] +struct Foo { + lt_spec: LtSpec<'a>, + a: u32, +} + +#[pin_data] +struct Bar { + lt_spec2: LtSpec2<'a, 'b>, + a: u32, + b: u32, +} + +fn assert_unpin() {} + +fn main() { + // All of the below checks must fail. + assert_unpin::(); + assert_unpin::(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.stderr b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.stderr new file mode 100644 index 00000000..806ce5aa --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.stderr @@ -0,0 +1,65 @@ +error: self-referential support is not fully implemented + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:14:5 + | +14 | lt_spec: LtSpec<'a>, + | ^^^^^^^^^^^^^^^^^^^ + +error: self-referential support is not fully implemented + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:20:5 + | +20 | lt_spec2: LtSpec2<'a, 'b>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: `PhantomPinned` cannot be unpinned + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:29:20 + | +29 | assert_unpin::(); + | ^^^ within `_::__Unpin<'_>`, the trait `Unpin` is not implemented for `PhantomPinned` + | + = note: consider using the `pin!` macro + consider using `Box::pin` if you need to access the pinned value outside of the current scope +note: required because it appears within the type `_::__Unpin<'_>` + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:12:1 + | +12 | #[pin_data] + | ^^^^^^^^^^^ +note: required for `Foo` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:12:1 + | +12 | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here +13 | struct Foo { + | ^^^ +note: required by a bound in `assert_unpin` + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:25:20 + | +25 | fn assert_unpin() {} + | ^^^^^ required by this bound in `assert_unpin` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `PhantomPinned` cannot be unpinned + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:30:20 + | +30 | assert_unpin::(); + | ^^^ within `_::__Unpin<'_>`, the trait `Unpin` is not implemented for `PhantomPinned` + | + = note: consider using the `pin!` macro + consider using `Box::pin` if you need to access the pinned value outside of the current scope +note: required because it appears within the type `_::__Unpin<'_>` + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:18:1 + | +18 | #[pin_data] + | ^^^^^^^^^^^ +note: required for `Bar` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:18:1 + | +18 | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here +19 | struct Bar { + | ^^^ +note: required by a bound in `assert_unpin` + --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:25:20 + | +25 | fn assert_unpin() {} + | ^^^^^ required by this bound in `assert_unpin` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) From 456208e19efc467e1c32f9e310aa2f33e4445ff1 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 20:50:50 +0100 Subject: [PATCH 05/12] internal: pin_data: self-referential drop checks Check drop order top ensure that usage of lifetime inside self-referential struct is consistent with the order that the fields will dropped in drop glue. First, fields are checked according to their index to ensure that if `a` borrows from `b`, `b` must outlive `a`. This is simple and produce a very good diagnostics when misued. Lifetime bounds can also be indirectly crafted with implied bounds that make fields well-formed. For example, in this struct struct Foo { x: &'b &'a (), a: String, y: PrintOnDrop<&'b str>, b: String, } `&'b &'a ()` will imply that `a` outlive `b`, which is inconsistent with the actual drop order. A more sophisticated method is used to ensure that this cannot happen. With this change, the struct itself can now soundly exist. It cannot yet be initialized with `pin_init!` or projected with `project()` method. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 180 +++++++++++++++++- src/__internal.rs | 20 ++ .../compile-fail/pin_data/selfref_dropck.rs | 34 ++++ .../pin_data/selfref_dropck.stderr | 22 +++ .../pin_data/selfref_invalid_attr.stderr | 21 +- .../selfref_lifetime_specialize.stderr | 12 -- .../selfref_lifetime_specialize_unpin.stderr | 12 -- tests/ui/expand/many_generics.expanded.rs | 1 + tests/ui/expand/pin-data.expanded.rs | 1 + tests/ui/expand/pinned_drop.expanded.rs | 1 + 10 files changed, 256 insertions(+), 48 deletions(-) create mode 100644 tests/ui/compile-fail/pin_data/selfref_dropck.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_dropck.stderr diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 3bf26eee..37ac6036 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -3,15 +3,15 @@ use std::collections::{BTreeMap, BTreeSet}; use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote, ToTokens}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ parse::{End, Nothing, Parse}, parse_quote, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, visit_mut::VisitMut, - Attribute, Field, Fields, Generics, Ident, Item, ItemStruct, Lifetime, PathSegment, Token, - Type, TypePath, Visibility, WhereClause, + Attribute, Field, Fields, GenericParam, Generics, Ident, Item, ItemStruct, Lifetime, + LifetimeParam, PathSegment, Token, Type, TypePath, Visibility, WhereClause, }; use crate::{ @@ -293,16 +293,10 @@ pub(crate) fn pin_data( } } - // Reject self-referential structs. - for f in &fields { - if f.variance.is_some() { - dcx.error(f.field, "self-referential support is not fully implemented"); - } - } - let struct_def = generate_struct_def(&struct_, &fields); let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args); + let drop_check = generate_drop_check(dcx, &struct_, &fields); let projections = generate_projections(&struct_.vis, &struct_.ident, &struct_.generics, &fields); let the_pin_data = @@ -313,6 +307,7 @@ pub(crate) fn pin_data( // We put the rest into this const item, because it then will not be accessible to anything // outside. const _: () = { + #drop_check #projections #the_pin_data #unpin_impl @@ -511,6 +506,171 @@ fn generate_drop_impl(ident: &Ident, generics: &Generics, args: Args) -> TokenSt } } +fn generate_drop_check( + dcx: &mut DiagCtxt, + struct_: &ItemStruct, + fields: &[FieldInfo<'_>], +) -> TokenStream { + let struct_name = &struct_.ident; + // If the struct is not self-referential then we can just skip. However, still leave a + // `__DropCheck` type around which can be used to capture all field lifetimes by other + // generated code. + if fields.iter().all(|f| f.borrowed.is_none()) { + return quote!( + use #struct_name as __DropCheck; + ); + } + + // Make sure fields are dropped earlier than the fields that they borrow. + // + // Note that this only order fields and their borrow, and not establish the order between two + // borrows. The latter is checked below with `__drop_check`. We could also use `__drop_check` + // to perform what we check here, but it'll require synthesize lifetimes for more fields and + // will emit a less clear error message. + for (i, field) in fields.iter().enumerate() { + let ident = field.field.ident.as_ref().unwrap(); + for lt in &field.ty.bound { + let borrowed_field = <.ident; + + fields + .iter() + .enumerate() + .take(i) + .filter(|f| f.1.field.ident.as_ref().unwrap() == borrowed_field) + .for_each(|_| { + dcx.error( + borrowed_field, + format!("field `{ident}` borrows `{borrowed_field}`, but drops later",), + ); + }); + } + } + + // Create a lifetime parameter for each field. + let field_lt_params: Vec<_> = fields + .iter() + .filter(|f| f.borrowed.is_some()) + .map(|f| { + GenericParam::Lifetime(LifetimeParam::new(Lifetime::from_ident( + f.field.ident.as_ref().unwrap(), + ))) + }) + .collect(); + + let mut generics_with_field_lt = struct_.generics.clone(); + generics_with_field_lt + .params + .extend(field_lt_params.iter().cloned()); + + let (_, ty_generics_with_field_lt, _) = generics_with_field_lt.split_for_impl(); + let (impl_generics, ty_generics, whr) = struct_.generics.split_for_impl(); + + // Wrap each field in a `PhantomInvariant`. For borrowed fields, additionally + // use `&#lt mut #ty` so the `lt` becomes associated with `#ty` which deduces + // implied bounds. + let phantom_fields = fields.iter().map(|f| { + let ty = &f.field.ty; + let cfg_attrs = &f.cfg_attrs; + let ident = f.field.ident.as_ref().unwrap(); + + if f.borrowed.is_some() { + let lt = Lifetime::from_ident(ident); + quote!( + #(#cfg_attrs)* + #ident: ::pin_init::__internal::PhantomInvariant<&#lt mut #ty>, + ) + } else { + quote!( + #(#cfg_attrs)* + #[allow(non_snake_case)] + #ident: ::pin_init::__internal::PhantomInvariant<#ty>, + ) + } + }); + + let guards = fields.iter().rev().map(|f| { + let ident = f.field.ident.as_ref().unwrap(); + let cfg_attrs = &f.cfg_attrs; + let span = ident.span(); + if f.borrowed.is_some() { + quote_spanned!(span => + // `LifetimeGuard` implements `Drop` and borrows `#ident`, so Rust must ensure that + // when the drop impl is called, `#ident` is still alive. Because the guards are + // generated in reverse field order, this ensures that the lifetimes of fields + // declared later must strictly outlive the lifetimes of fields declared earlier. + // + // For example, in + // ``` + // struct Foo { + // #[borrows(a, b)] + // x: &'b &'a (), + // a: String, + // y: PrintOnDrop<&'b str>, + // b: String, + // } + // ``` + // it ensures that `b` will strictly outlive `a`. + // + // This is needed because implied bounds exist. Rust needs to ensure that types are + // well-formed; in the above example, `&'b &'a ()` is well-formed only if `a` + // outlive `b`. To avoid requiring everyone from having to express this bound + // explicitly when declaring a struct, the `'b: 'a` bound is inferred by the Rust + // compiler. However this causes an issue, where now `&'a str` can be coerced to + // `&'b str` because compiler thinks that it shorten the lifetime. This is + // catastrophical for self-referencing structs, in the above example we'll be able + // to put a reference to `a` into `y`; but `a` drops first, so when `y` drops, it + // accesses `a` and causes a use-after-free! + // + // The code here essentially reconstruct the dropping order and ask the compiler to + // check that this won't cause an issue. + #(#cfg_attrs)* + let #ident = ::pin_init::__internal::PhantomInvariant::new(); + // `LifetimeGuard::new` has signature of `(&'a PhantomData<&'a mut T>) -> + // LifetimeGuard<'a>`. So it serves two purpose: tie the lifetime of binding and the + // lifetime in the parameter together, and also borrows it. + #(#cfg_attrs)* + let _guard = ::pin_init::__internal::LifetimeGuard::new(&#ident); + ) + } else { + quote!( + // No lifetimes to tie for fields that are not borrowed. + #(#cfg_attrs)* + let #ident = ::pin_init::__internal::PhantomInvariant::new(); + ) + } + }); + + let fields = fields.iter().map(|f| { + let cfg_attrs = &f.cfg_attrs; + let ident = f.field.ident.as_ref().unwrap(); + quote!(#(#cfg_attrs)* #ident,) + }); + + let struct_span = struct_.ident.span().resolved_at(Span::mixed_site()); + quote_spanned! {struct_span => + #[doc(hidden)] + #[allow(non_snake_case)] + struct __DropCheck #generics_with_field_lt + #whr + { + #(#phantom_fields)* + } + + #[allow(non_snake_case)] + fn __drop_check #impl_generics ( + // This must be present so the function can observe the implied bounds. + _: &#struct_name #ty_generics, + f: impl for<#(#field_lt_params,)*>::core::ops::FnOnce(__DropCheck #ty_generics_with_field_lt) + ) #whr { + #(#guards)* + + f(__DropCheck { + #(#fields)* + }) + } + } +} + fn generate_projections( vis: &Visibility, ident: &Ident, diff --git a/src/__internal.rs b/src/__internal.rs index 6cfa9af9..11bf44d2 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -460,3 +460,23 @@ pub struct SelfRef( unsafe impl Send for SelfRef where for<'a, 'b, 'c, 'd> F::Of<'a, 'b, 'c, 'd>: Send {} // SAFETY: The bound ensures that `F::Of` is `Sync` for all lifetime parameters. unsafe impl Sync for SelfRef where for<'a, 'b, 'c, 'd> F::Of<'a, 'b, 'c, 'd>: Sync {} + +pub struct LifetimeGuard<'a> { + _phantom: PhantomInvariantLifetime<'a>, +} + +impl<'a> LifetimeGuard<'a> { + #[inline(always)] + pub fn new(_: &'a PhantomInvariant<&'a mut T>) -> Self { + LifetimeGuard { + _phantom: PhantomInvariantLifetime::new(), + } + } +} + +impl<'a> Drop for LifetimeGuard<'a> { + #[inline(always)] + fn drop(&mut self) { + // Intentionally empty. See `generate_drop_check` in `pin_data.rs` for details. + } +} diff --git a/tests/ui/compile-fail/pin_data/selfref_dropck.rs b/tests/ui/compile-fail/pin_data/selfref_dropck.rs new file mode 100644 index 00000000..0b9c4cc0 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_dropck.rs @@ -0,0 +1,34 @@ +use pin_init::*; + +#[pin_data] +struct WrongDropOrder { + b: u32, + ptr: &'b u32, +} + +struct PrintOnDrop<'a>(&'a str); + +impl<'a> Drop for PrintOnDrop<'a> { + fn drop(&mut self) { + println!("Dropping: {}", self.0); + } +} + +#[pin_data] +struct UnsoundImplied { + // Provides an implied bound that `a` outlives `b`! + ptr: &'b &'a (), + a: String, + cannot_refer_a: PrintOnDrop<'b>, + b: String, +} + +fn main() { + // let _foo = Box::pin_init(pin_init!(UnsoundImplied { + // ptr: &&(), + // a: "hello".to_owned(), + // cannot_refer_a: PrintOnDrop(a), + // b: "world".to_owned(), + // })) + // .unwrap(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_dropck.stderr b/tests/ui/compile-fail/pin_data/selfref_dropck.stderr new file mode 100644 index 00000000..e4817361 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_dropck.stderr @@ -0,0 +1,22 @@ +error: field `ptr` borrows `b`, but drops later + --> tests/ui/compile-fail/pin_data/selfref_dropck.rs:6:11 + | +6 | ptr: &'b u32, + | ^^ + +error[E0597]: `a` does not live long enough + --> tests/ui/compile-fail/pin_data/selfref_dropck.rs:21:5 + | +18 | struct UnsoundImplied { + | - + | | + | `a` dropped here while still borrowed + | borrow might be used here, when `_guard` is dropped and runs the `Drop` code for type `pin_init::__internal::LifetimeGuard` +... +21 | a: String, + | ^ + | | + | borrowed value does not live long enough + | binding `a` declared here + | + = note: values in a scope are dropped in the opposite order they are defined diff --git a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr index d7e3750c..1a349c05 100644 --- a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr +++ b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr @@ -22,20 +22,13 @@ error: `non_exist_mut` is neither a lifetime in generics nor a field name 7 | #[borrows(non_exist, mut non_exist_mut)] // Borrows non-existent fields | ^^^^^^^^^^^^^ -error: self-referential support is not fully implemented - --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:8:5 - | -8 | explicit: u32, - | ^^^^^^^^^^^^^ - -error: self-referential support is not fully implemented - --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:10:5 +error[E0261]: use of undeclared lifetime name `'non_exist` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:10:16 | 10 | implicit: &'non_exist u32, - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: self-referential support is not fully implemented - --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:13:5 + | ^^^^^^^^^^ undeclared lifetime + | +help: consider introducing lifetime `'non_exist` here | -13 | okay: &'b u32, - | ^^^^^^^^^^^^^ + 4 | struct InvalidAttr<'non_exist, 'a> { + | +++++++++++ diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr index b22ef8f7..9d9b5fa6 100644 --- a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.stderr @@ -1,15 +1,3 @@ -error: self-referential support is not fully implemented - --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:18:5 - | -18 | lt_spec: LtSpec<'a>, - | ^^^^^^^^^^^^^^^^^^^ - -error: self-referential support is not fully implemented - --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:24:5 - | -24 | lt_spec2: LtSpec2<'a, 'b>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - error: implementation of `Send` is not general enough --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize.rs:34:5 | diff --git a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.stderr b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.stderr index 806ce5aa..1da729ab 100644 --- a/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.stderr +++ b/tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.stderr @@ -1,15 +1,3 @@ -error: self-referential support is not fully implemented - --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:14:5 - | -14 | lt_spec: LtSpec<'a>, - | ^^^^^^^^^^^^^^^^^^^ - -error: self-referential support is not fully implemented - --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:20:5 - | -20 | lt_spec2: LtSpec2<'a, 'b>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - error[E0277]: `PhantomPinned` cannot be unpinned --> tests/ui/compile-fail/pin_data/selfref_lifetime_specialize_unpin.rs:29:20 | diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index b9c83697..08112d97 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -13,6 +13,7 @@ where _pin: PhantomPinned, } const _: () = { + use Foo as __DropCheck; /// Pin-projections of [`Foo`] #[allow(dead_code, non_snake_case)] #[doc(hidden)] diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index ebd19586..ca776e6e 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -5,6 +5,7 @@ struct Foo { _pin: PhantomPinned, } const _: () = { + use Foo as __DropCheck; /// Pin-projections of [`Foo`] #[allow(dead_code, non_snake_case)] #[doc(hidden)] diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 8cc1a023..669db994 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -5,6 +5,7 @@ struct Foo { _pin: PhantomPinned, } const _: () = { + use Foo as __DropCheck; /// Pin-projections of [`Foo`] #[allow(dead_code, non_snake_case)] #[doc(hidden)] From e58474571bd48b8b36fe4b882777f85f13298cdc Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 22:16:44 +0100 Subject: [PATCH 06/12] internal: pin_data: implement initialization of borrowed structs We now have the checks to ensure that lifetime relations are what is expected, we can generate the slot projections in `generate_pin_data` so self-referential struct can be implemented. New slot and guard types are defined (`SelfRefSlot` and `SelfRefDropGuard`) which gives the generated let bindings longer lifetime than the guard themselves. Higher-ranked trait bound on `__make_init` is used to ensure that the initialization closure cannot make arbitrary assumptions of those lifetimes. Currently, this is only implemented for shared borrows. Signed-off-by: Gary Guo --- examples/selfref.rs | 20 +++ internal/src/init.rs | 2 +- internal/src/pin_data.rs | 106 +++++++++++-- src/__internal.rs | 143 ++++++++++++++++++ src/lib.rs | 1 + .../compile-fail/pin_data/selfref_dropck.rs | 14 +- .../pin_data/selfref_invalid_attr.stderr | 15 ++ .../selfref_not_living_long_enough.rs | 19 +++ .../selfref_not_living_long_enough.stderr | 19 +++ tests/ui/expand/many_generics.expanded.rs | 75 ++++++--- tests/ui/expand/pin-data.expanded.rs | 54 ++++--- tests/ui/expand/pinned_drop.expanded.rs | 54 ++++--- tests/ui/expand/simple-init.expanded.rs | 2 +- 13 files changed, 444 insertions(+), 80 deletions(-) create mode 100644 examples/selfref.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr diff --git a/examples/selfref.rs b/examples/selfref.rs new file mode 100644 index 00000000..2f9ae2b0 --- /dev/null +++ b/examples/selfref.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + })); +} + +fn main() { + use_self_ref(); +} diff --git a/internal/src/init.rs b/internal/src/init.rs index 978b346e..09396537 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -167,7 +167,7 @@ pub(crate) fn expand( } ); let init = move |slot| -> ::core::result::Result<(), #error> { - init(slot, #data).map(|__InitOk| ()) + init(slot, #data.__with_lt()).map(|__InitOk| ()) }; // SAFETY: TODO unsafe { ::pin_init::#init_from_closure::<_, #error>(init) } diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 37ac6036..238f87fb 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -772,27 +772,67 @@ fn generate_the_pin_data( generics: &Generics, fields: &[FieldInfo<'_>], ) -> TokenStream { + let field_lt_params: Vec<_> = fields + .iter() + .filter(|f| f.borrowed.is_some()) + .map(|f| { + GenericParam::Lifetime(LifetimeParam::new(Lifetime::from_ident( + f.field.ident.as_ref().unwrap(), + ))) + }) + .collect(); + + let mut generics_with_field_lt = generics.clone(); + generics_with_field_lt + .params + .extend(field_lt_params.iter().cloned()); + + let (impl_generics_with_lt, ty_generics_with_field_lt, _) = + generics_with_field_lt.split_for_impl(); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); - // For every field, we create an initializing projection function according to its projection - // type. If a field is structurally pinned, we create a `Slot` with `Pinned` which must be - // initialized via `PinInit`; if it is not structurally pinned, then we create a `Slot` with - // `Unpinned` which allows initialization via `Init`. let field_accessors = fields .iter() - .filter(|f| f.borrowed.is_none() && f.variance.is_none()) + .filter(|f| f.borrowed != Some(Borrowed::Mutable)) .map(|f| { - let Field { vis, ident, ty, .. } = f.field; + let vis = &f.field.vis; let cfg_attrs = &f.cfg_attrs; - - let field_name = ident + let field_name = f + .field + .ident .as_ref() .expect("only structs with named fields are supported"); + let ty = &f.field.ty; + + let lt = Lifetime::from_ident(field_name); + let pin_marker = if f.pinned { quote!(Pinned) } else { quote!(Unpinned) }; + + let (slot_ty, slot_arg) = match f.borrowed { + None => (quote!(Slot), quote!()), + Some(Borrowed::Shared) => ( + // For borrowed fields, create a `SelfRefSlot`, which after initialization + // turns into a `SelfRefDropGuard` instead of `DropGuard`. + // + // They're mostly the same, except that `SelfRefDropGuard` returns `&'field T` + // instead of `&'guard T` for let bindings; this allows it to be used to be + // used to initialize other fields. + // + // The soundness of doing so relies on fact that `__make_init` requires a + // higher-ranked trait bound on the closure. Within the closure (which is the + // caller of the generated slot projection functions here), it can make no + // assumptions on the lifetime except for those implied by the struct's bounds, + // and we have validated them in `generate_drop_check`. + quote!(SelfRefSlot), + quote!(#lt,), + ), + Some(Borrowed::Mutable) => unreachable!(), + }; + quote! { /// # Safety /// @@ -808,19 +848,52 @@ fn generate_the_pin_data( #vis unsafe fn #field_name( self, slot: *mut #struct_name #ty_generics, - ) -> ::pin_init::__internal::Slot<::pin_init::__internal::#pin_marker, #ty> { + ) -> ::pin_init::__internal::#slot_ty<#slot_arg ::pin_init::__internal::#pin_marker, #ty> { + // CAST: `as _` is needed to convert types wrapped inside `SelfRef`. // SAFETY: // - If `#pin_marker` is `Pinned`, the corresponding field is structurally // pinned. // - Other safety requirements follows the safety requirement. - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#field_name) } + // - If `#slot_ty` is `SelfRefSlot`, the lifetime `#lt` represents that of the + // field. + unsafe { ::pin_init::__internal::#slot_ty::new(&raw mut (*slot).#field_name as _) } } } }) .collect::(); + quote! { - // We declare this struct which will host all of the projection function for our type. It - // will be invariant over all generic parameters which are inherited from the struct. + // We declare this struct which will host all of the projection function for our type. + #[doc(hidden)] + #vis struct __PinDataLt #generics_with_field_lt + #whr + { + // Use `__DropCheck` to capture all lifetimes (including that of borrowed fields) and + // generics invariantly. + __phantom: ::core::marker::PhantomData<__DropCheck #ty_generics_with_field_lt> + } + + impl #impl_generics_with_lt ::core::clone::Clone for __PinDataLt #ty_generics_with_field_lt + #whr + { + fn clone(&self) -> Self { *self } + } + + impl #impl_generics_with_lt ::core::marker::Copy for __PinDataLt #ty_generics_with_field_lt + #whr + {} + + #[allow(dead_code)] // Some functions might never be used and private. + #[expect(clippy::missing_safety_doc)] + impl #impl_generics_with_lt __PinDataLt #ty_generics_with_field_lt + #whr + { + #field_accessors + } + + // Declare a type that serves as the entry point of interaction with the `pin_init!` macro. + // We use this type instead of defining methods directly on user's type to avoid possibility + // of name conflicts. #[doc(hidden)] #vis struct __ThePinData #generics #whr @@ -838,8 +911,6 @@ fn generate_the_pin_data( #whr {} - #[allow(dead_code)] // Some functions might never be used and private. - #[expect(clippy::missing_safety_doc)] impl #impl_generics __ThePinData #ty_generics #whr { @@ -847,13 +918,16 @@ fn generate_the_pin_data( #[inline(always)] #vis fn __make_closure<__F, __E>(self, f: __F) -> __F where - __F: FnOnce(*mut #struct_name #ty_generics, __ThePinData #ty_generics) -> + __F: for<#(#field_lt_params,)*>::core::ops::FnOnce(*mut #struct_name #ty_generics, __PinDataLt #ty_generics_with_field_lt) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, { f } - #field_accessors + #[inline(always)] + fn __with_lt<#(#field_lt_params,)*>(self) -> __PinDataLt #ty_generics_with_field_lt { + __PinDataLt { __phantom: ::core::marker::PhantomData } + } } // SAFETY: We have added the correct projection functions above to `__ThePinData` and diff --git a/src/__internal.rs b/src/__internal.rs index 11bf44d2..036ab025 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -123,6 +123,11 @@ impl AllData { { f } + + #[inline(always)] + pub fn __with_lt(self) -> Self { + self + } } // SAFETY: TODO. @@ -361,6 +366,144 @@ impl Drop for DropGuard { } } +/// Represent an uninitialized field in a pinned struct that will be referenced by other fields. +/// +/// # Invariants +/// +/// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory +/// and will live longer than `'a`. +/// - If `P` is `Pinned`, then `ptr` is structurally pinned. +pub struct SelfRefSlot<'a, P, T: ?Sized> { + pub ptr: *mut T, + pub _phantom: PhantomData<(P, &'a mut T)>, +} + +impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> { + /// # Safety + /// + /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed + /// memory and will live longer than `'a`. + /// - If `P` is `Pinned`, then `ptr` is structurally pinned. + #[inline] + pub unsafe fn new(ptr: *mut T) -> Self { + // INVARIANT: Per safety requirement. + Self { + ptr, + _phantom: PhantomData, + } + } + + /// Initialize the field by value. + #[inline] + pub fn write(self, value: T) -> SelfRefDropGuard<'a, P, T> + where + T: Sized, + { + // SAFETY: `self.ptr` is a valid and aligned pointer for write. + unsafe { self.ptr.write(value) } + // SAFETY: + // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant. + // - `*self.ptr` is initialized above and the ownership is transferred to the guard. + // - If `P` is `Pinned`, `self.ptr` is pinned. + unsafe { SelfRefDropGuard::new(self.ptr) } + } +} + +impl<'a, T: ?Sized> SelfRefSlot<'a, Unpinned, T> { + /// Initialize the field. + #[inline] + pub fn init(self, init: impl Init) -> Result, E> { + // SAFETY: + // - `self.ptr` is valid and properly aligned. + // - when `Err` is returned, we also propagate the error without touching `slot`; + // also `self` is consumed so it cannot be touched further. + unsafe { init.__init(self.ptr)? }; + + // SAFETY: + // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant. + // - `*self.ptr` is initialized above and the ownership is transferred to the guard. + Ok(unsafe { SelfRefDropGuard::new(self.ptr) }) + } +} + +impl<'a, T: ?Sized> SelfRefSlot<'a, Pinned, T> { + /// Initialize the field. + #[inline] + pub fn init(self, init: impl PinInit) -> Result, E> { + // SAFETY: + // - `ptr` is valid + // - when `Err` is returned, we also propagate the error without touching `ptr`; + // also `self` is consumed so it cannot be touched further. + // - the drop guard will not hand out `&mut` (but only `Pin<&mut T>`) it has been dropped. + unsafe { init.__pinned_init(self.ptr)? }; + + // SAFETY: + // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant. + // - `*self.ptr` is initialized above and the ownership is transferred to the guard. + Ok(unsafe { SelfRefDropGuard::new(self.ptr) }) + } +} +/// When a value of this type is dropped, it drops a `T`. +/// +/// Can be forgotten to prevent the drop. +/// +/// # Invariants +/// +/// - `ptr` is valid, properly aligned and live longer than `'a`. +/// - `*ptr` is initialized and owned by this guard. +/// - if `P` is `Pinned`, `ptr` is pinned. +pub struct SelfRefDropGuard<'a, P, T: ?Sized> { + ptr: *mut T, + phantom: PhantomData<(P, &'a mut T)>, +} + +impl<'a, P, T: ?Sized> SelfRefDropGuard<'a, P, T> { + /// Creates a drop guard and transfer the ownership of the pointer content. + /// + /// The ownership is only relinquished if the guard is forgotten via [`core::mem::forget`]. + /// + /// # Safety + /// + /// - `ptr` is valid, properly aligned and live longer than `'a`. + /// - `*ptr` is initialized, and the ownership is transferred to this guard. + /// - if `P` is `Pinned`, `ptr` is pinned. + #[inline] + pub unsafe fn new(ptr: *mut T) -> Self { + // INVARIANT: By safety requirement. + Self { + ptr, + phantom: PhantomData, + } + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Unpinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> &'a T { + // SAFETY: Per type invariant. + unsafe { &*self.ptr } + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Pinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> Pin<&'a T> { + // SAFETY: `self.ptr` is valid, properly aligned, live longer than `'a`, initialized, + // exclusively accessible and pinned per type invariant. + unsafe { Pin::new_unchecked(&*self.ptr) } + } +} + +impl Drop for SelfRefDropGuard<'_, P, T> { + #[inline] + fn drop(&mut self) { + // SAFETY: `self.ptr` is valid, properly aligned and `*self.ptr` is owned by this guard. + unsafe { ptr::drop_in_place(self.ptr) } + } +} + /// Token used by `PinnedDrop` to prevent calling the function without creating this unsafely /// created struct. This is needed, because the `drop` function is safe, but should not be called /// manually. diff --git a/src/lib.rs b/src/lib.rs index fd40c8f2..1a4b309c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -871,6 +871,7 @@ macro_rules! assert_pinned { let _ = move |ptr: *mut $ty| unsafe { let data = <$ty as $crate::__internal::HasPinData>::__pin_data(); _ = data + .__with_lt() .$field(ptr) .init($crate::__internal::AlwaysFail::<$field_ty>::new()); }; diff --git a/tests/ui/compile-fail/pin_data/selfref_dropck.rs b/tests/ui/compile-fail/pin_data/selfref_dropck.rs index 0b9c4cc0..6c7b9d47 100644 --- a/tests/ui/compile-fail/pin_data/selfref_dropck.rs +++ b/tests/ui/compile-fail/pin_data/selfref_dropck.rs @@ -24,11 +24,11 @@ struct UnsoundImplied { } fn main() { - // let _foo = Box::pin_init(pin_init!(UnsoundImplied { - // ptr: &&(), - // a: "hello".to_owned(), - // cannot_refer_a: PrintOnDrop(a), - // b: "world".to_owned(), - // })) - // .unwrap(); + let _foo = Box::pin_init(pin_init!(UnsoundImplied { + ptr: &&(), + a: "hello".to_owned(), + cannot_refer_a: PrintOnDrop(a), + b: "world".to_owned(), + })) + .unwrap(); } diff --git a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr index 1a349c05..e95526e0 100644 --- a/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr +++ b/tests/ui/compile-fail/pin_data/selfref_invalid_attr.stderr @@ -32,3 +32,18 @@ help: consider introducing lifetime `'non_exist` here | 4 | struct InvalidAttr<'non_exist, 'a> { | +++++++++++ + +error[E0261]: use of undeclared lifetime name `'non_exist` + --> tests/ui/compile-fail/pin_data/selfref_invalid_attr.rs:10:16 + | +10 | implicit: &'non_exist u32, + | ^^^^^^^^^^ undeclared lifetime + | +help: consider introducing lifetime `'non_exist` here + | +10 | implicit<'non_exist>: &'non_exist u32, + | ++++++++++++ +help: consider introducing lifetime `'non_exist` here + | + 4 | struct InvalidAttr<'non_exist, 'a> { + | +++++++++++ diff --git a/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs new file mode 100644 index 00000000..703e2823 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs @@ -0,0 +1,19 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + #[borrows(foo, bar)] + part: &'foo str, + foo: String, + bar: String, +} + +fn self_ref(outer: &str) { + stack_pin_init!(let foo = pin_init!(SelfRef { + foo: "hello world".to_owned(), + bar: "hello world".to_owned(), + part: &outer[..5], + })); +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr new file mode 100644 index 00000000..bc856eb3 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.stderr @@ -0,0 +1,19 @@ +error[E0521]: borrowed data escapes outside of function + --> tests/ui/compile-fail/pin_data/selfref_not_living_long_enough.rs:12:31 + | +11 | fn self_ref(outer: &str) { + | ----- - let's call the lifetime of this reference `'1` + | | + | `outer` is a reference that is only valid in the function body +12 | stack_pin_init!(let foo = pin_init!(SelfRef { + | _______________________________^ +13 | | foo: "hello world".to_owned(), +14 | | bar: "hello world".to_owned(), +15 | | part: &outer[..5], +16 | | })); + | | ^ + | | | + | |______`outer` escapes the function body here + | argument requires that `'1` must outlive `'static` + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index 08112d97..8a8e393a 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -58,14 +58,14 @@ const _: () = { } } #[doc(hidden)] - struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> + struct __PinDataLt<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> where T: Bar<'a, 1>, { - __phantom: ::pin_init::__internal::PhantomInvariant>, + __phantom: ::core::marker::PhantomData<__DropCheck<'a, 'b, T, SIZE>>, } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::clone::Clone - for __ThePinData<'a, 'b, T, SIZE> + for __PinDataLt<'a, 'b, T, SIZE> where T: Bar<'a, 1>, { @@ -74,7 +74,7 @@ const _: () = { } } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::marker::Copy - for __ThePinData<'a, 'b, T, SIZE> + for __PinDataLt<'a, 'b, T, SIZE> where T: Bar<'a, 1>, {} @@ -85,21 +85,10 @@ const _: () = { 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize, - > __ThePinData<'a, 'b, T, SIZE> + > __PinDataLt<'a, 'b, T, SIZE> where T: Bar<'a, 1>, { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo<'a, 'b, T, SIZE>, - __ThePinData<'a, 'b, T, SIZE>, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } /// # Safety /// /// - `slot` is valid and properly aligned. @@ -115,7 +104,7 @@ const _: () = { ::pin_init::__internal::Unpinned, [u8; 1024 * 1024], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array as _) } } /// # Safety /// @@ -132,7 +121,7 @@ const _: () = { ::pin_init::__internal::Unpinned, &'b mut [&'a mut T; SIZE], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).r) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).r as _) } } /// # Safety /// @@ -149,7 +138,55 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin as _) } + } + } + #[doc(hidden)] + struct __ThePinData<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> + where + T: Bar<'a, 1>, + { + __phantom: ::pin_init::__internal::PhantomInvariant>, + } + impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::clone::Clone + for __ThePinData<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + { + fn clone(&self) -> Self { + *self + } + } + impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> ::core::marker::Copy + for __ThePinData<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + {} + impl< + 'a, + 'b: 'a, + T: Bar<'b> + ?Sized + 'a, + const SIZE: usize, + > __ThePinData<'a, 'b, T, SIZE> + where + T: Bar<'a, 1>, + { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo<'a, 'b, T, SIZE>, + __PinDataLt<'a, 'b, T, SIZE>, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt<'a, 'b, T, SIZE> { + __PinDataLt { + __phantom: ::core::marker::PhantomData, + } } } unsafe impl< diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index ca776e6e..0cde0f72 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -35,29 +35,18 @@ const _: () = { } } #[doc(hidden)] - struct __ThePinData { - __phantom: ::pin_init::__internal::PhantomInvariant, + struct __PinDataLt { + __phantom: ::core::marker::PhantomData<__DropCheck>, } - impl ::core::clone::Clone for __ThePinData { + impl ::core::clone::Clone for __PinDataLt { fn clone(&self) -> Self { *self } } - impl ::core::marker::Copy for __ThePinData {} + impl ::core::marker::Copy for __PinDataLt {} #[allow(dead_code)] #[expect(clippy::missing_safety_doc)] - impl __ThePinData { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo, - __ThePinData, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } + impl __PinDataLt { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -73,7 +62,7 @@ const _: () = { ::pin_init::__internal::Unpinned, [u8; 1024 * 1024], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array as _) } } /// # Safety /// @@ -90,7 +79,36 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin as _) } + } + } + #[doc(hidden)] + struct __ThePinData { + __phantom: ::pin_init::__internal::PhantomInvariant, + } + impl ::core::clone::Clone for __ThePinData { + fn clone(&self) -> Self { + *self + } + } + impl ::core::marker::Copy for __ThePinData {} + impl __ThePinData { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo, + __PinDataLt, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt { + __PinDataLt { + __phantom: ::core::marker::PhantomData, + } } } unsafe impl ::pin_init::__internal::HasPinData for Foo { diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 669db994..06f4522e 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -35,29 +35,18 @@ const _: () = { } } #[doc(hidden)] - struct __ThePinData { - __phantom: ::pin_init::__internal::PhantomInvariant, + struct __PinDataLt { + __phantom: ::core::marker::PhantomData<__DropCheck>, } - impl ::core::clone::Clone for __ThePinData { + impl ::core::clone::Clone for __PinDataLt { fn clone(&self) -> Self { *self } } - impl ::core::marker::Copy for __ThePinData {} + impl ::core::marker::Copy for __PinDataLt {} #[allow(dead_code)] #[expect(clippy::missing_safety_doc)] - impl __ThePinData { - /// Type inference helper function. - #[inline(always)] - fn __make_closure<__F, __E>(self, f: __F) -> __F - where - __F: FnOnce( - *mut Foo, - __ThePinData, - ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, - { - f - } + impl __PinDataLt { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -73,7 +62,7 @@ const _: () = { ::pin_init::__internal::Unpinned, [u8; 1024 * 1024], > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).array as _) } } /// # Safety /// @@ -90,7 +79,36 @@ const _: () = { ::pin_init::__internal::Pinned, PhantomPinned, > { - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot)._pin as _) } + } + } + #[doc(hidden)] + struct __ThePinData { + __phantom: ::pin_init::__internal::PhantomInvariant, + } + impl ::core::clone::Clone for __ThePinData { + fn clone(&self) -> Self { + *self + } + } + impl ::core::marker::Copy for __ThePinData {} + impl __ThePinData { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: ::core::ops::FnOnce( + *mut Foo, + __PinDataLt, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + #[inline(always)] + fn __with_lt(self) -> __PinDataLt { + __PinDataLt { + __phantom: ::core::marker::PhantomData, + } } } unsafe impl ::pin_init::__internal::HasPinData for Foo { diff --git a/tests/ui/expand/simple-init.expanded.rs b/tests/ui/expand/simple-init.expanded.rs index 72da52a6..02b95117 100644 --- a/tests/ui/expand/simple-init.expanded.rs +++ b/tests/ui/expand/simple-init.expanded.rs @@ -18,7 +18,7 @@ fn main() { let init = move | slot, | -> ::core::result::Result<(), ::core::convert::Infallible> { - init(slot, __data).map(|__InitOk| ()) + init(slot, __data.__with_lt()).map(|__InitOk| ()) }; unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } }; From e1e689c0beddb0133d60b5d6c1959dc908f6d7d8 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:08:21 +0100 Subject: [PATCH 07/12] internal: pin_data: project self-referential fields This adds the projection for fields that are shared borrowed or that borrows other fields but is covariant. Both cases allow a shared reference to be accessed. No mutable references can be created for these cases for different reasons: * For fields that are shared borrowed, aliasing restriction prevents creation of mutable reference * For fields that borrows other fields, their proper type contains field lifetimes. These lifetimes cannot be made available in the returned `project` struct (because there is no way to represent existential lifetime in return position). For covariant types, it is possible to shorten these lifetimes to that of `&self`; but doing so requires the reference to also be covariant over the pointee type, so we cannot give out `&mut` as it is invariant over the pointee. Due to field-referencing fields being wrapped inside `SelfRef`, the normal accessor syntax stop working; create accessor methods for these fields instead. Signed-off-by: Gary Guo --- examples/selfref.rs | 6 + internal/src/pin_data.rs | 130 ++++++++++++++++-- internal/src/util.rs | 19 +++ .../pin_data/selfref_covariant_check.rs | 9 ++ .../pin_data/selfref_covariant_check.stderr | 15 ++ .../pin_data/selfref_project_mut.rs | 21 +++ .../pin_data/selfref_project_mut.stderr | 5 + tests/ui/expand/many_generics.expanded.rs | 2 +- tests/ui/expand/pin-data.expanded.rs | 2 +- tests/ui/expand/pinned_drop.expanded.rs | 2 +- 10 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 tests/ui/compile-fail/pin_data/selfref_covariant_check.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_project_mut.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_project_mut.stderr diff --git a/examples/selfref.rs b/examples/selfref.rs index 2f9ae2b0..8258c1ae 100644 --- a/examples/selfref.rs +++ b/examples/selfref.rs @@ -13,6 +13,12 @@ fn use_self_ref() { str: "hello world".to_owned(), part: &str[..5], })); + + // Access via projection. + println!("{}", foo.as_mut().project().part); + + // Access via accessor. + println!("{}", foo.part()); } fn main() { diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 238f87fb..3639eca3 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -677,43 +677,86 @@ fn generate_projections( generics: &Generics, fields: &[FieldInfo<'_>], ) -> TokenStream { + let pin_lt = Lifetime::new("'__pin", Span::mixed_site()); + let (impl_generics, ty_generics, _) = generics.split_for_impl(); let mut generics_with_pin_lt = generics.clone(); - generics_with_pin_lt.params.insert(0, parse_quote!('__pin)); + generics_with_pin_lt.params.insert( + 0, + GenericParam::Lifetime(LifetimeParam::new(pin_lt.clone())), + ); let (_, ty_generics_with_pin_lt, whr) = generics_with_pin_lt.split_for_impl(); let this = format_ident!("this"); let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields .iter() - .filter(|f| f.borrowed.is_none() && f.variance.is_none()) - .map(|field| { - let Field { vis, ident, ty, .. } = &field.field; - let cfg_attrs = &field.cfg_attrs; + .filter(|f| { + // Mutably referenced fields cannot be accessed by user at all for aliasing reasons. + if f.borrowed == Some(Borrowed::Mutable) { + return false; + } - let ident = ident + // If the type is not covariant, it must omitted from non-with projection, as such + // projection shortens the lifetime from fields to '__pin. + if matches!(f.variance, Some(Variance::NotCovariant(_))) { + return false; + } + + true + }) + .map(|f| { + let vis = &f.field.vis; + let cfg_attrs = &f.cfg_attrs; + let ident = f + .field + .ident .as_ref() .expect("only structs with named fields are supported"); - if field.pinned { + + // if `f.ty` contains field lifetimes, which we need to replace them with shorter + // `'__pin` lifetime as field lifetimes are not available in this context. + let ty = f.ty.instantiate(&pin_lt); + + // Fields shared-referenced by other fields can only be shared accessed. Fields that + // references other field and are covariant can also only be given shared reference + // as mutable reference is invariant. + let mut_token: Option = if f.borrowed.is_none() && f.variance.is_none() { + Some(Default::default()) + } else { + None + }; + + let mut accessor = quote!(&#mut_token #this.#ident); + if f.variance.is_some() { + accessor = quote!( + // SAFETY: we have `SelfRef<..>` which we know is layout compatible with `f.ty`. + // Field lifetimes in `f.ty` can be shortened to `#ty` due to covariance (which + // is checked later). + unsafe { core::mem::transmute::<_, &#mut_token #ty>(#accessor) } + ) + } + + if f.pinned { ( quote!( #(#cfg_attrs)* - #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, + #vis #ident: ::core::pin::Pin<&'__pin #mut_token #ty>, ), quote!( #(#cfg_attrs)* // SAFETY: this field is structurally pinned. - #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) }, + #ident: unsafe { ::core::pin::Pin::new_unchecked(#accessor) }, ), ) } else { ( quote!( #(#cfg_attrs)* - #vis #ident: &'__pin mut #ty, + #vis #ident: &'__pin #mut_token #ty, ), quote!( #(#cfg_attrs)* - #ident: &mut #this.#ident, + #ident: #accessor, ), ) } @@ -728,6 +771,67 @@ fn generate_projections( .filter(|f| !f.pinned) .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); let docs = format!(" Pin-projections of [`{ident}`]"); + + // For fields that references other fields, field access syntax stops working as they're wrapped + // behind `SelfRef` because their actual lifetime is not on the struct. + // + // Generate an accessor method for them. + let mut accessors = Vec::new(); + for f in fields { + let ident = f.field.ident.as_ref().unwrap(); + match f.variance { + // They can be accessed normally, no accessor to be generated. + None => continue, + + Some(Variance::Covariant(_)) => { + let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`."); + let vis = &f.field.vis; + + // Use the span of type for better error message. + let span = f.ty.value.span().resolved_at(Span::mixed_site()); + + let ty = f.ty.instantiate(&pin_lt); + + let long = Lifetime::new("'__long", span); + let long_ty = f.ty.instantiate(&long); + + let short = Lifetime::new("'__short", span); + let short_ty = f.ty.instantiate(&short); + + // Add `<'__long: '__short, 'short>` as additional generics. + let mut covariance_check_generics = generics.clone(); + covariance_check_generics + .params + .push(GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: long, + colon_token: Some(Default::default()), + bounds: std::iter::once(short.clone()).collect(), + })); + covariance_check_generics + .params + .push(GenericParam::Lifetime(LifetimeParam::new(short))); + + accessors.push(quote_spanned!(span => + #[doc = #f_doc] + #[inline] + #vis fn #ident<#pin_lt>(&#pin_lt self) -> &#pin_lt #ty { + // Emit a check to ensure the type is *really* covariant for soundness. + fn covariance_check #covariance_check_generics (long: #long_ty) -> #short_ty { + long + } + + // SAFETY: `SelfRef` is layout compatible with `#ty` and we have checked + // that it is covariant. + unsafe { core::mem::transmute(&self.#ident) } + } + )) + } + + Some(Variance::NotCovariant(_)) => continue, + } + } + quote! { #[doc = #docs] // Allow `non_snake_case` since the same warning will be emitted on @@ -738,7 +842,7 @@ fn generate_projections( #whr { #(#fields_decl)* - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin #ident #ty_generics>, } impl #impl_generics #ident #ty_generics @@ -762,6 +866,8 @@ fn generate_projections( ___pin_phantom_data: ::core::marker::PhantomData, } } + + #(#accessors)* } } } diff --git a/internal/src/util.rs b/internal/src/util.rs index 8dacef9e..f877046c 100644 --- a/internal/src/util.rs +++ b/internal/src/util.rs @@ -3,6 +3,7 @@ use std::collections::BTreeSet; use proc_macro2::Span; +use quote::quote; use syn::punctuated::Punctuated; use syn::visit::Visit; use syn::GenericParam; @@ -110,6 +111,24 @@ impl Binder { } } +impl Binder { + pub(crate) fn instantiate(&self, lifetime: &Lifetime) -> Type { + // If there's no bound lifetimes, just return. + if self.bound.is_empty() { + return self.value.clone(); + } + + let bound = self.for_bound_4(); + let bound_lt = bound.lifetimes.iter(); + let ty = &self.value; + return syn::Type::Verbatim(quote!( + <::pin_init::__internal::ForLtImpl< + dyn #bound ::pin_init::__internal::WithLt4<#(#bound_lt,)* Of = #ty> + > as ::pin_init::__internal::ForLt4>::Of<#lifetime, #lifetime, #lifetime, #lifetime> + )); + } +} + pub(crate) trait LifetimeExt { /// Obtain a lifetime from a identifier. /// diff --git a/tests/ui/compile-fail/pin_data/selfref_covariant_check.rs b/tests/ui/compile-fail/pin_data/selfref_covariant_check.rs new file mode 100644 index 00000000..d471822e --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_covariant_check.rs @@ -0,0 +1,9 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + not_cov: Box bool + 'str>, + str: String, +} + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr b/tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr new file mode 100644 index 00000000..1bceaa19 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_covariant_check.stderr @@ -0,0 +1,15 @@ +error: lifetime may not live long enough + --> tests/ui/compile-fail/pin_data/selfref_covariant_check.rs:5:14 + | +3 | #[pin_data] + | ----------- in this attribute macro expansion +4 | struct SelfRef { +5 | not_cov: Box bool + 'str>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | lifetime `'__short` defined here + | lifetime `'__long` defined here + | function was supposed to return data with lifetime `'__long` but it is returning data with lifetime `'__short` + | + = help: consider adding the following bound: `'__short: '__long` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/selfref_project_mut.rs b/tests/ui/compile-fail/pin_data/selfref_project_mut.rs new file mode 100644 index 00000000..e2b3e79a --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_project_mut.rs @@ -0,0 +1,21 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + })); + + // Should fail due to reference not being mutable. + *foo.as_mut().project().part = "foo"; +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr b/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr new file mode 100644 index 00000000..855ae319 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr @@ -0,0 +1,5 @@ +error[E0594]: cannot assign to data in a `&` reference + --> tests/ui/compile-fail/pin_data/selfref_project_mut.rs:16:5 + | +16 | *foo.as_mut().project().part = "foo"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index 8a8e393a..b86d7b58 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -30,7 +30,7 @@ const _: () = { array: &'__pin mut [u8; 1024 * 1024], r: &'__pin mut &'b mut [&'a mut T; SIZE], _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin Foo<'a, 'b, T, SIZE>>, } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> Foo<'a, 'b, T, SIZE> where diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index 0cde0f72..c5d3ee85 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -12,7 +12,7 @@ const _: () = { struct __Projection<'__pin> { array: &'__pin mut [u8; 1024 * 1024], _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin Foo>, } impl Foo { /// Pin-projects all fields of `Self`. diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 06f4522e..6bb07263 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -12,7 +12,7 @@ const _: () = { struct __Projection<'__pin> { array: &'__pin mut [u8; 1024 * 1024], _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin Foo>, } impl Foo { /// Pin-projects all fields of `Self`. From 493b022712a0926713ba93cccc2f634dd0989823 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:29:08 +0100 Subject: [PATCH 08/12] internal: pin_data: add `with_project` method The `project` method needs to perform covariant coercion on covariant fields, causing them to no longer being mutable. Implement a `with_project` that does not require covariant coercion by using higher-ranked trait bounds, thus allow the fields to be assignable inside the callback. This mechanism can also be used to access non-covariant fields. Signed-off-by: Gary Guo --- examples/selfref.rs | 7 + internal/src/pin_data.rs | 135 +++++++++++++++++++- tests/ui/compile-fail/pin_data/twice.stderr | 10 ++ tests/ui/expand/many_generics.expanded.rs | 41 ++++++ tests/ui/expand/pin-data.expanded.rs | 27 ++++ tests/ui/expand/pinned_drop.expanded.rs | 27 ++++ 6 files changed, 243 insertions(+), 4 deletions(-) diff --git a/examples/selfref.rs b/examples/selfref.rs index 8258c1ae..fa34cbd2 100644 --- a/examples/selfref.rs +++ b/examples/selfref.rs @@ -19,6 +19,13 @@ fn use_self_ref() { // Access via accessor. println!("{}", foo.part()); + + // Access via `with_project`, gives mutable reference. + foo.as_mut().with_project(|proj| { + *proj.part = &proj.str[5..]; + }); + + println!("{}", foo.part()); } fn main() { diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 3639eca3..cd6e58e1 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -686,6 +686,29 @@ fn generate_projections( GenericParam::Lifetime(LifetimeParam::new(pin_lt.clone())), ); let (_, ty_generics_with_pin_lt, whr) = generics_with_pin_lt.split_for_impl(); + + let field_lt_params: Vec<_> = fields + .iter() + .filter(|f| f.borrowed.is_some()) + .map(|f| { + GenericParam::Lifetime(LifetimeParam::new(Lifetime::from_ident( + f.field.ident.as_ref().unwrap(), + ))) + }) + .collect(); + let mut generics_with_field_lt = generics.clone(); + generics_with_field_lt + .params + .extend(field_lt_params.iter().cloned()); + let (_, ty_generics_with_field_lt, _) = generics_with_field_lt.split_for_impl(); + + let mut generics_with_pin_field_lt = generics_with_field_lt.clone(); + generics_with_pin_field_lt.params.insert( + 0, + GenericParam::Lifetime(LifetimeParam::new(pin_lt.clone())), + ); + let (_, ty_generics_with_pin_field_lt, _) = generics_with_pin_field_lt.split_for_impl(); + let this = format_ident!("this"); let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields @@ -762,14 +785,86 @@ fn generate_projections( } }) .collect(); - let structurally_pinned_fields_docs = fields + + let (fields_decl_lt, fields_proj_lt): (Vec<_>, Vec<_>) = fields + .iter() + .filter(|f| { + // Mutably referenced fields cannot be accessed by user at all for aliasing reasons. + f.borrowed != Some(Borrowed::Mutable) + }) + .map(|f| { + let vis = &f.field.vis; + let cfg_attrs = &f.cfg_attrs; + let ident = f + .field + .ident + .as_ref() + .expect("only structs with named fields are supported"); + + let ty = &f.ty.value; + + // Fields shared-referenced by other fields can only be shared accessed. + let mut_token: Option = if f.borrowed.is_none() { + Some(Default::default()) + } else { + None + }; + + let mut accessor = quote!(&#mut_token #this.#ident); + if f.variance.is_some() { + accessor = quote!( + // SAFETY: we have `SelfRef<..>` which we know is layout compatible with `f.ty`. + // We cannot include explicit type name here as the field lifetimes are nameable + // in this context. + unsafe { core::mem::transmute(#accessor) } + ) + } + + // In `with_project`, borrowed fields have their field lifetime available, so use it + // instead of `'__pin`. + let lt = if f.borrowed.is_some() { + &Lifetime::from_ident(ident) + } else { + &pin_lt + }; + + if f.pinned { + ( + quote!( + #(#cfg_attrs)* + #vis #ident: ::core::pin::Pin<&#lt #mut_token #ty>, + ), + quote!( + #(#cfg_attrs)* + // SAFETY: this field is structurally pinned. + #ident: unsafe { ::core::pin::Pin::new_unchecked(#accessor) }, + ), + ) + } else { + ( + quote!( + #(#cfg_attrs)* + #vis #ident: &#lt #mut_token #ty, + ), + quote!( + #(#cfg_attrs)* + #ident: #accessor, + ), + ) + } + }) + .collect(); + + let structurally_pinned_fields_docs: Vec<_> = fields .iter() .filter(|f| f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); - let not_structurally_pinned_fields_docs = fields + .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())) + .collect(); + let not_structurally_pinned_fields_docs: Vec<_> = fields .iter() .filter(|f| !f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); + .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())) + .collect(); let docs = format!(" Pin-projections of [`{ident}`]"); // For fields that references other fields, field access syntax stops working as they're wrapped @@ -845,6 +940,18 @@ fn generate_projections( ___pin_phantom_data: ::core::marker::PhantomData<&'__pin #ident #ty_generics>, } + #[doc = #docs] + // Allow `non_snake_case` since the same warning will be emitted on + // the struct definition. + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + #vis struct __ProjectionLt #generics_with_pin_field_lt + #whr + { + #(#fields_decl_lt)* + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut __DropCheck #ty_generics_with_field_lt>, + } + impl #impl_generics #ident #ty_generics #whr { @@ -867,6 +974,26 @@ fn generate_projections( } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + #(#[doc = #structurally_pinned_fields_docs])* + /// + /// These fields are **not** structurally pinned: + #(#[doc = #not_structurally_pinned_fields_docs])* + #[inline] + #vis fn with_project<'__pin, R>( + self: ::core::pin::Pin<&'__pin mut Self>, + f: impl for<#(#field_lt_params,)*>::core::ops::FnOnce(__ProjectionLt #ty_generics_with_pin_field_lt) -> R + ) -> R { + // SAFETY: we only give access to `&mut` for fields not structurally pinned. + let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + #(#fields_proj_lt)* + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } + #(#accessors)* } } diff --git a/tests/ui/compile-fail/pin_data/twice.stderr b/tests/ui/compile-fail/pin_data/twice.stderr index 562177ea..4c56344a 100644 --- a/tests/ui/compile-fail/pin_data/twice.stderr +++ b/tests/ui/compile-fail/pin_data/twice.stderr @@ -27,3 +27,13 @@ error[E0592]: duplicate definitions with name `project` | ^^^^^^^^^^^ duplicate definitions for `project` | = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0592]: duplicate definitions with name `with_project` + --> tests/ui/compile-fail/pin_data/twice.rs:4:1 + | +3 | #[pin_data] + | ----------- other definition for `with_project` +4 | #[pin_data] + | ^^^^^^^^^^^ duplicate definitions for `with_project` + | + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index b86d7b58..b7af72f8 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -32,6 +32,26 @@ const _: () = { _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, ___pin_phantom_data: ::core::marker::PhantomData<&'__pin Foo<'a, 'b, T, SIZE>>, } + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt< + '__pin, + 'a, + 'b: 'a, + T: Bar<'b> + ?Sized + 'a, + const SIZE: usize = 0, + > + where + T: Bar<'a, 1>, + { + array: &'__pin mut [u8; 1024 * 1024], + r: &'__pin mut &'b mut [&'a mut T; SIZE], + _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData< + &'__pin mut __DropCheck<'a, 'b, T, SIZE>, + >, + } impl<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize> Foo<'a, 'b, T, SIZE> where T: Bar<'a, 1>, @@ -56,6 +76,27 @@ const _: () = { ___pin_phantom_data: ::core::marker::PhantomData, } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + /// - `r` + #[inline] + fn with_project<'__pin, R>( + self: ::core::pin::Pin<&'__pin mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__pin, 'a, 'b, T, SIZE>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + array: &mut this.array, + r: &mut this.r, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] struct __PinDataLt<'a, 'b: 'a, T: Bar<'b> + ?Sized + 'a, const SIZE: usize = 0> diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index c5d3ee85..537fac08 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -14,6 +14,14 @@ const _: () = { _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, ___pin_phantom_data: ::core::marker::PhantomData<&'__pin Foo>, } + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt<'__pin> { + array: &'__pin mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut __DropCheck>, + } impl Foo { /// Pin-projects all fields of `Self`. /// @@ -33,6 +41,25 @@ const _: () = { ___pin_phantom_data: ::core::marker::PhantomData, } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project<'__pin, R>( + self: ::core::pin::Pin<&'__pin mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__pin>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] struct __PinDataLt { diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 6bb07263..63a0597f 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -14,6 +14,14 @@ const _: () = { _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, ___pin_phantom_data: ::core::marker::PhantomData<&'__pin Foo>, } + /// Pin-projections of [`Foo`] + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + struct __ProjectionLt<'__pin> { + array: &'__pin mut [u8; 1024 * 1024], + _pin: ::core::pin::Pin<&'__pin mut PhantomPinned>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut __DropCheck>, + } impl Foo { /// Pin-projects all fields of `Self`. /// @@ -33,6 +41,25 @@ const _: () = { ___pin_phantom_data: ::core::marker::PhantomData, } } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + /// - `_pin` + /// + /// These fields are **not** structurally pinned: + /// - `array` + #[inline] + fn with_project<'__pin, R>( + self: ::core::pin::Pin<&'__pin mut Self>, + f: impl ::core::ops::FnOnce(__ProjectionLt<'__pin>) -> R, + ) -> R { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(__ProjectionLt { + array: &mut this.array, + _pin: unsafe { ::core::pin::Pin::new_unchecked(&mut this._pin) }, + ___pin_phantom_data: ::core::marker::PhantomData, + }) + } } #[doc(hidden)] struct __PinDataLt { From fca66c43b7ad6a583a5e78a556426e16dfbd0991 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 6 May 2026 18:12:59 +0100 Subject: [PATCH 09/12] internal: pin_data: support mutable borrows Allow fields to be mutably referenced by other fields in addition to shared references. In order for this to be sound, the fields that can be mutably borrowed are blocked from being accessed via field access syntax or projection to maintain the aliasing requirements. Signed-off-by: Gary Guo --- examples/selfref.rs | 13 +++++ internal/src/pin_data.rs | 28 ++++++++- src/__internal.rs | 57 ++++++++++++++----- .../pin_data/selfref_mut_borrow.rs | 28 +++++++++ .../pin_data/selfref_mut_borrow.stderr | 7 +++ .../pin_data/selfref_mut_borrowck.rs | 21 +++++++ .../pin_data/selfref_mut_borrowck.stderr | 15 +++++ .../pin_data/selfref_project_mut.rs | 8 +++ .../pin_data/selfref_project_mut.stderr | 10 +++- .../pin_data/selfref_with_project.rs | 28 +++++++++ .../pin_data/selfref_with_project.stderr | 15 +++++ 11 files changed, 211 insertions(+), 19 deletions(-) create mode 100644 tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr create mode 100644 tests/ui/compile-fail/pin_data/selfref_with_project.rs create mode 100644 tests/ui/compile-fail/pin_data/selfref_with_project.stderr diff --git a/examples/selfref.rs b/examples/selfref.rs index fa34cbd2..1de7d625 100644 --- a/examples/selfref.rs +++ b/examples/selfref.rs @@ -6,12 +6,18 @@ use pin_init::*; struct SelfRef { part: &'str str, str: String, + + #[borrows(mut mut_str)] + mut_part: &'mut_str mut str, + mut_str: String, } fn use_self_ref() { stack_pin_init!(let foo = pin_init!(SelfRef { str: "hello world".to_owned(), part: &str[..5], + mut_str: "hello world".to_owned(), + mut_part: &mut mut_str[..5], })); // Access via projection. @@ -26,6 +32,13 @@ fn use_self_ref() { }); println!("{}", foo.part()); + + // Access fields that mutable borrow others are similar to those of shared borrow. + println!("{}", foo.as_mut().project().mut_part); + println!("{}", foo.mut_part()); + foo.as_mut().with_project(|proj| { + proj.mut_part.make_ascii_uppercase(); + }); } fn main() { diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index cd6e58e1..c757ef8f 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -376,6 +376,14 @@ fn generate_struct_def( >); }; + // For mutably referenced items, it is possible to access `&struct.field` through `Pin<&mut + // Struct>`, which conflicts with the mutable access possible via the mutable borrow when + // constructing. Wrap the type behind `UnsafePinned` so it's not UB to have both and it also + // blocks user from doing anything wiht the value (safely). + if field.borrowed == Some(Borrowed::Mutable) { + ty = parse_quote!(::pin_init::__internal::UnsafePinned<#ty>); + } + generated_fields.push(quote! { #(#attrs)* #vis #ident #colon_token #ty }); @@ -1026,7 +1034,6 @@ fn generate_the_pin_data( let field_accessors = fields .iter() - .filter(|f| f.borrowed != Some(Borrowed::Mutable)) .map(|f| { let vis = &f.field.vis; let cfg_attrs = &f.cfg_attrs; @@ -1061,9 +1068,24 @@ fn generate_the_pin_data( // assumptions on the lifetime except for those implied by the struct's bounds, // and we have validated them in `generate_drop_check`. quote!(SelfRefSlot), - quote!(#lt,), + quote!(#lt, ::pin_init::__internal::Shared, ), + ), + Some(Borrowed::Mutable) => ( + // For borrowed fields, create a `SelfRefSlot`, which after initialization + // turns into a `SelfRefDropGuard` instead of `DropGuard`. + // + // They're mostly the same, except that `SelfRefDropGuard` returns `&'field T` + // instead of `&'guard T` for let bindings; this allows it to be used to be + // used to initialize other fields. + // + // The soundness of doing so relies on fact that `__make_init` requires a + // higher-ranked trait bound on the closure. Within the closure (which is the + // caller of the generated slot projection functions here), it can make no + // assumptions on the lifetime except for those implied by the struct's bounds, + // and we have validated them in `generate_drop_check`. + quote!(SelfRefSlot), + quote!(#lt, ::pin_init::__internal::Mutable, ), ), - Some(Borrowed::Mutable) => unreachable!(), }; quote! { diff --git a/src/__internal.rs b/src/__internal.rs index 036ab025..81955a49 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -9,6 +9,10 @@ use core::marker::PhantomPinned; use super::*; +// Polyfill for the unstable `UnsafePinned` type. +#[repr(transparent)] +pub struct UnsafePinned(PhantomPinned, UnsafeCell); + /// Zero-sized type used to mark a type as invariant. /// /// This is a polyfill for the [unstable type] in the standard library of the same name. @@ -366,6 +370,9 @@ impl Drop for DropGuard { } } +pub struct Shared; +pub struct Mutable; + /// Represent an uninitialized field in a pinned struct that will be referenced by other fields. /// /// # Invariants @@ -373,12 +380,12 @@ impl Drop for DropGuard { /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory /// and will live longer than `'a`. /// - If `P` is `Pinned`, then `ptr` is structurally pinned. -pub struct SelfRefSlot<'a, P, T: ?Sized> { +pub struct SelfRefSlot<'a, M, P, T: ?Sized> { pub ptr: *mut T, - pub _phantom: PhantomData<(P, &'a mut T)>, + pub _phantom: PhantomData<(M, P, &'a mut T)>, } -impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> { +impl<'a, M, P, T: ?Sized> SelfRefSlot<'a, M, P, T> { /// # Safety /// /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed @@ -395,7 +402,7 @@ impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> { /// Initialize the field by value. #[inline] - pub fn write(self, value: T) -> SelfRefDropGuard<'a, P, T> + pub fn write(self, value: T) -> SelfRefDropGuard<'a, M, P, T> where T: Sized, { @@ -409,10 +416,10 @@ impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> { } } -impl<'a, T: ?Sized> SelfRefSlot<'a, Unpinned, T> { +impl<'a, M, T: ?Sized> SelfRefSlot<'a, M, Unpinned, T> { /// Initialize the field. #[inline] - pub fn init(self, init: impl Init) -> Result, E> { + pub fn init(self, init: impl Init) -> Result, E> { // SAFETY: // - `self.ptr` is valid and properly aligned. // - when `Err` is returned, we also propagate the error without touching `slot`; @@ -426,10 +433,13 @@ impl<'a, T: ?Sized> SelfRefSlot<'a, Unpinned, T> { } } -impl<'a, T: ?Sized> SelfRefSlot<'a, Pinned, T> { +impl<'a, M, T: ?Sized> SelfRefSlot<'a, M, Pinned, T> { /// Initialize the field. #[inline] - pub fn init(self, init: impl PinInit) -> Result, E> { + pub fn init( + self, + init: impl PinInit, + ) -> Result, E> { // SAFETY: // - `ptr` is valid // - when `Err` is returned, we also propagate the error without touching `ptr`; @@ -452,12 +462,12 @@ impl<'a, T: ?Sized> SelfRefSlot<'a, Pinned, T> { /// - `ptr` is valid, properly aligned and live longer than `'a`. /// - `*ptr` is initialized and owned by this guard. /// - if `P` is `Pinned`, `ptr` is pinned. -pub struct SelfRefDropGuard<'a, P, T: ?Sized> { +pub struct SelfRefDropGuard<'a, M, P, T: ?Sized> { ptr: *mut T, - phantom: PhantomData<(P, &'a mut T)>, + phantom: PhantomData<(M, P, &'a mut T)>, } -impl<'a, P, T: ?Sized> SelfRefDropGuard<'a, P, T> { +impl<'a, M, P, T: ?Sized> SelfRefDropGuard<'a, M, P, T> { /// Creates a drop guard and transfer the ownership of the pointer content. /// /// The ownership is only relinquished if the guard is forgotten via [`core::mem::forget`]. @@ -477,7 +487,7 @@ impl<'a, P, T: ?Sized> SelfRefDropGuard<'a, P, T> { } } -impl<'a, T: ?Sized> SelfRefDropGuard<'a, Unpinned, T> { +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Shared, Unpinned, T> { /// Create a let binding for accessor use. #[inline] pub fn let_binding(&mut self) -> &'a T { @@ -486,7 +496,7 @@ impl<'a, T: ?Sized> SelfRefDropGuard<'a, Unpinned, T> { } } -impl<'a, T: ?Sized> SelfRefDropGuard<'a, Pinned, T> { +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Shared, Pinned, T> { /// Create a let binding for accessor use. #[inline] pub fn let_binding(&mut self) -> Pin<&'a T> { @@ -496,7 +506,26 @@ impl<'a, T: ?Sized> SelfRefDropGuard<'a, Pinned, T> { } } -impl Drop for SelfRefDropGuard<'_, P, T> { +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Mutable, Unpinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> &'a mut T { + // SAFETY: Per type invariant. + unsafe { &mut *self.ptr } + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Mutable, Pinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> Pin<&'a mut T> { + // SAFETY: `self.ptr` is valid, properly aligned, live longer than `'a`, initialized, + // exclusively accessible and pinned per type invariant. + unsafe { Pin::new_unchecked(&mut *self.ptr) } + } +} + +impl Drop for SelfRefDropGuard<'_, M, P, T> { #[inline] fn drop(&mut self) { // SAFETY: `self.ptr` is valid, properly aligned and `*self.ptr` is owned by this guard. diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs new file mode 100644 index 00000000..995371af --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs @@ -0,0 +1,28 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + str: String, + #[borrows(mut mut_str)] + mut_part: &'mut_str mut str, + mut_str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + mut_str: "hello world".to_owned(), + mut_part: &mut mut_str[..5], + })); + + // Should fail due to not accessible. + foo.as_mut().with_project(|proj| { + let _ = proj.mut_str; + }) +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr new file mode 100644 index 00000000..190baab7 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrow.stderr @@ -0,0 +1,7 @@ +error[E0609]: no field `mut_str` on type `__ProjectionLt<'_, '_, '_>` + --> tests/ui/compile-fail/pin_data/selfref_mut_borrow.rs:22:22 + | +22 | let _ = proj.mut_str; + | ^^^^^^^ unknown field + | + = note: available fields are: `part`, `str`, `mut_part`, `___pin_phantom_data` diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs new file mode 100644 index 00000000..b3cdc955 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs @@ -0,0 +1,21 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + #[borrows(mut str)] + mut_part: &'str mut str, + str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + mut_part: &mut str[..5], + })); +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr new file mode 100644 index 00000000..4ecc07d0 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_mut_borrowck.stderr @@ -0,0 +1,15 @@ +error[E0502]: cannot borrow `*str` as mutable because it is also borrowed as immutable + --> tests/ui/compile-fail/pin_data/selfref_mut_borrowck.rs:15:24 + | +12 | stack_pin_init!(let foo = pin_init!(SelfRef { + | _______________________________- +13 | | str: "hello world".to_owned(), +14 | | part: &str[..5], + | | --- immutable borrow occurs here +15 | | mut_part: &mut str[..5], + | | ^^^ mutable borrow occurs here +16 | | })); + | | - + | | | + | |______has type `__PinDataLt<'1>` + | argument requires that `*str` is borrowed for `'1` diff --git a/tests/ui/compile-fail/pin_data/selfref_project_mut.rs b/tests/ui/compile-fail/pin_data/selfref_project_mut.rs index e2b3e79a..4dd6f02b 100644 --- a/tests/ui/compile-fail/pin_data/selfref_project_mut.rs +++ b/tests/ui/compile-fail/pin_data/selfref_project_mut.rs @@ -4,16 +4,24 @@ use pin_init::*; struct SelfRef { part: &'str str, str: String, + #[borrows(mut mut_str)] + mut_part: &'mut_str mut str, + mut_str: String, } fn use_self_ref() { stack_pin_init!(let foo = pin_init!(SelfRef { str: "hello world".to_owned(), part: &str[..5], + mut_str: "hello world".to_owned(), + mut_part: &mut mut_str[..5], })); // Should fail due to reference not being mutable. *foo.as_mut().project().part = "foo"; + + // Should fail due to reference not being mutable. + foo.as_mut().project().mut_part.make_ascii_uppercase(); } fn main() { diff --git a/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr b/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr index 855ae319..57d88d84 100644 --- a/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr +++ b/tests/ui/compile-fail/pin_data/selfref_project_mut.stderr @@ -1,5 +1,11 @@ error[E0594]: cannot assign to data in a `&` reference - --> tests/ui/compile-fail/pin_data/selfref_project_mut.rs:16:5 + --> tests/ui/compile-fail/pin_data/selfref_project_mut.rs:21:5 | -16 | *foo.as_mut().project().part = "foo"; +21 | *foo.as_mut().project().part = "foo"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign + +error[E0596]: cannot borrow data in a `&` reference as mutable + --> tests/ui/compile-fail/pin_data/selfref_project_mut.rs:24:5 + | +24 | foo.as_mut().project().mut_part.make_ascii_uppercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable diff --git a/tests/ui/compile-fail/pin_data/selfref_with_project.rs b/tests/ui/compile-fail/pin_data/selfref_with_project.rs new file mode 100644 index 00000000..7e63f679 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_with_project.rs @@ -0,0 +1,28 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + str: String, + #[borrows(mut mut_str)] + mut_part: &'mut_str mut str, + mut_str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + mut_str: "hello world".to_owned(), + mut_part: &mut mut_str[..5], + })); + + let local = "hello world".to_owned(); + foo.as_mut().with_project(|proj| { + *proj.part = &local; + }); +} + +fn main() { + use_self_ref(); +} diff --git a/tests/ui/compile-fail/pin_data/selfref_with_project.stderr b/tests/ui/compile-fail/pin_data/selfref_with_project.stderr new file mode 100644 index 00000000..ee1ed52b --- /dev/null +++ b/tests/ui/compile-fail/pin_data/selfref_with_project.stderr @@ -0,0 +1,15 @@ +error[E0597]: `local` does not live long enough + --> tests/ui/compile-fail/pin_data/selfref_with_project.rs:22:23 + | +20 | let local = "hello world".to_owned(); + | ----- binding `local` declared here +21 | foo.as_mut().with_project(|proj| { + | ------ value captured here +22 | *proj.part = &local; + | --------------^^^^^ + | | | + | | borrowed value does not live long enough + | assignment requires that `local` is borrowed for `'static` +23 | }); +24 | } + | - `local` dropped here while still borrowed From a4c274d111c8ee58a47b5972703ded7833325a29 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:40:15 +0100 Subject: [PATCH 10/12] internal: pin_data: complete `#[not_covariant]` support With the previous patch, non-covariant types can already be accessed inside projections. As projections are only generated for `Pin<&mut T>`, they're not accessible otherwise. Add `with_{field_name}` methods so fields can be accessed using closures with just `&T`. Signed-off-by: Gary Guo --- examples/selfref.rs | 12 ++++++++++++ internal/src/pin_data.rs | 18 +++++++++++++++++- internal/src/util.rs | 3 +-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/examples/selfref.rs b/examples/selfref.rs index 1de7d625..5c6e768a 100644 --- a/examples/selfref.rs +++ b/examples/selfref.rs @@ -4,6 +4,9 @@ use pin_init::*; #[pin_data] struct SelfRef { + #[not_covariant] + not_cov: Box bool + 'str>, + part: &'str str, str: String, @@ -18,6 +21,7 @@ fn use_self_ref() { part: &str[..5], mut_str: "hello world".to_owned(), mut_part: &mut mut_str[..5], + not_cov: Box::new(move |s| s == str), })); // Access via projection. @@ -39,6 +43,14 @@ fn use_self_ref() { foo.as_mut().with_project(|proj| { proj.mut_part.make_ascii_uppercase(); }); + + // Access non-covariant type using `with_` accessor. + foo.with_not_cov(|not_cov| { + not_cov(""); + }); + + // Access non-covariant type using `with_project`. + foo.as_mut().with_project(|proj| (proj.not_cov)(proj.str)); } fn main() { diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index c757ef8f..ecf77b50 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -931,7 +931,23 @@ fn generate_projections( )) } - Some(Variance::NotCovariant(_)) => continue, + Some(Variance::NotCovariant(_)) => { + let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`."); + let vis = &f.field.vis; + let with_ident = format_ident!("with_{ident}"); + + let bound = f.ty.for_bound(); + let ty = &f.ty.value; + + accessors.push(quote!( + #[doc = #f_doc] + #[inline] + #vis fn #with_ident<'__this, R>(&'__this self, f: impl #bound ::core::ops::FnOnce(&'__this #ty) -> R) -> R { + // SAFETY: `SelfRef` is layout compatible with `#ty`. + f(unsafe { core::mem::transmute(&self.#ident) }) + } + )) + } } } diff --git a/internal/src/util.rs b/internal/src/util.rs index f877046c..0aaf1edc 100644 --- a/internal/src/util.rs +++ b/internal/src/util.rs @@ -75,8 +75,7 @@ impl Binder { } /// Obtain a `for<...>` that can be used to construct a higher-ranked trait bound. - #[expect(unused)] - pub fn for_bound(&self) -> BoundLifetimes { + pub(crate) fn for_bound(&self) -> BoundLifetimes { BoundLifetimes { for_token: Default::default(), lt_token: Default::default(), From e92a155931c742957a83962cca004c6d23e78464 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:58:33 +0100 Subject: [PATCH 11/12] internal: pin_data: perform AST lifetime replacement if possible Currently lifetimes are replaced with `ForLt4` trait. This is very general approach as it uses generic associated type to replace lifetime, so it can even work when macros are involved. This does cause more generated code, and does not render in documentation nicely. Thus, just replace the lifetime in the AST if no macros are involved. Signed-off-by: Gary Guo --- internal/src/util.rs | 87 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/internal/src/util.rs b/internal/src/util.rs index 0aaf1edc..03b7885a 100644 --- a/internal/src/util.rs +++ b/internal/src/util.rs @@ -6,16 +6,35 @@ use proc_macro2::Span; use quote::quote; use syn::punctuated::Punctuated; use syn::visit::Visit; -use syn::GenericParam; -use syn::{BoundLifetimes, Ident, LifetimeParam}; +use syn::visit_mut::VisitMut; +use syn::{BoundLifetimes, GenericParam, Ident, LifetimeParam}; use syn::{Lifetime, Type}; pub(crate) trait TypeExt { + /// Check if the type includes macro invocations. + /// + /// Proc-macros cannot expand macros and peek into them, so if macro is involved sometimes special handling is required. + fn has_macro(&self) -> bool; + /// Get the list of unbound lifetimes referenced by the type. fn unbound_lifetimes(&self) -> BTreeSet<&Lifetime>; } impl TypeExt for Type { + fn has_macro(&self) -> bool { + struct HasMacro(bool); + + impl<'ast> Visit<'ast> for HasMacro { + fn visit_macro(&mut self, _: &'ast syn::Macro) { + self.0 = true; + } + } + + let mut visitor = HasMacro(false); + visitor.visit_type(self); + visitor.0 + } + fn unbound_lifetimes(&self) -> BTreeSet<&Lifetime> { struct LifetimeVisitor<'a> { bound: BTreeSet<&'a Lifetime>, @@ -117,14 +136,62 @@ impl Binder { return self.value.clone(); } - let bound = self.for_bound_4(); - let bound_lt = bound.lifetimes.iter(); - let ty = &self.value; - return syn::Type::Verbatim(quote!( - <::pin_init::__internal::ForLtImpl< - dyn #bound ::pin_init::__internal::WithLt4<#(#bound_lt,)* Of = #ty> - > as ::pin_init::__internal::ForLt4>::Of<#lifetime, #lifetime, #lifetime, #lifetime> - )); + // If the type has macro, we cannot peek into it. Use some different approach to replace + // the type using GAT. + if self.value.has_macro() { + let bound = self.for_bound_4(); + let bound_lt = bound.lifetimes.iter(); + let ty = &self.value; + return syn::Type::Verbatim(quote!( + <::pin_init::__internal::ForLtImpl< + dyn #bound ::pin_init::__internal::WithLt4<#(#bound_lt,)* Of = #ty> + > as ::pin_init::__internal::ForLt4>::Of<#lifetime, #lifetime, #lifetime, #lifetime> + )); + } + + struct LifetimeReplacer<'a> { + to_replace: BTreeSet<&'a Lifetime>, + replacement: &'a Lifetime, + } + + impl VisitMut for LifetimeReplacer<'_> { + fn visit_lifetime_mut(&mut self, lt: &mut syn::Lifetime) { + if self.to_replace.contains(lt) { + *lt = self.replacement.clone(); + } + } + + fn visit_trait_bound_mut(&mut self, bound: &mut syn::TraitBound) { + // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`, + // temporarily remove them from to_replace if they're. + + let mut removed = Vec::new(); + if let Some(bound_lt) = &bound.lifetimes { + for lt in &bound_lt.lifetimes { + let GenericParam::Lifetime(lt) = lt else { + continue; + }; + if let Some(lt) = self.to_replace.take(<.lifetime) { + removed.push(lt); + } + } + } + + self.visit_path_mut(&mut bound.path); + + for lt in removed { + self.to_replace.insert(lt); + } + } + } + + let mut ret = self.value.clone(); + LifetimeReplacer { + to_replace: self.bound.iter().collect(), + replacement: lifetime, + } + .visit_type_mut(&mut ret); + ret } } From 24119fb7c7807d4d898288605073510cb05723a1 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 4 May 2026 14:33:21 +0100 Subject: [PATCH 12/12] internal: pin_data: allow lifetime to be shortened per field drop order Add the outlive relations per field drop order. This allows a single lifetime to be used when a field potentially borrow from two different fields, by allowing the longer-living field lifetime to be shortened to a shorter-living field lifetime. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 96 ++++++++++++++++++++++------------------ internal/src/util.rs | 29 ++++++++++-- tests/selfref_shorten.rs | 26 +++++++++++ 3 files changed, 105 insertions(+), 46 deletions(-) create mode 100644 tests/selfref_shorten.rs diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index ecf77b50..b20256bb 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -16,7 +16,7 @@ use syn::{ use crate::{ diagnostics::{DiagCtxt, ErrorGuaranteed}, - util::{Binder, LifetimeExt, TypeExt}, + util::{Binder, GenericsExt, LifetimeExt, TypeExt}, }; pub(crate) mod kw { @@ -293,14 +293,48 @@ pub(crate) fn pin_data( } } + // Create a lifetime parameter for each field. + let field_lts: Vec<_> = fields + .iter() + .filter(|f| f.borrowed.is_some()) + .map(|f| Lifetime::from_ident(f.field.ident.as_ref().unwrap())) + .collect(); + let field_lt_generics = Generics { + lt_token: Some(Default::default()), + params: field_lts + .iter() + .zip(std::iter::once(None).chain(field_lts.iter().map(Some))) + .map(|(lt, prev)| { + GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: lt.clone(), + colon_token: prev.map(|_| Default::default()), + bounds: prev.iter().map(|<| lt.clone()).collect(), + }) + }) + .collect(), + gt_token: Some(Default::default()), + where_clause: None, + }; + let struct_def = generate_struct_def(&struct_, &fields); let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args); - let drop_check = generate_drop_check(dcx, &struct_, &fields); - let projections = - generate_projections(&struct_.vis, &struct_.ident, &struct_.generics, &fields); - let the_pin_data = - generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields); + let drop_check = generate_drop_check(dcx, &struct_, &fields, &field_lt_generics); + let projections = generate_projections( + &struct_.vis, + &struct_.ident, + &struct_.generics, + &fields, + &field_lt_generics, + ); + let the_pin_data = generate_the_pin_data( + &struct_.vis, + &struct_.ident, + &struct_.generics, + &fields, + &field_lt_generics, + ); Ok(quote! { #struct_def @@ -518,6 +552,7 @@ fn generate_drop_check( dcx: &mut DiagCtxt, struct_: &ItemStruct, fields: &[FieldInfo<'_>], + field_lt_generics: &Generics, ) -> TokenStream { let struct_name = &struct_.ident; // If the struct is not self-referential then we can just skip. However, still leave a @@ -554,22 +589,12 @@ fn generate_drop_check( } } - // Create a lifetime parameter for each field. - let field_lt_params: Vec<_> = fields - .iter() - .filter(|f| f.borrowed.is_some()) - .map(|f| { - GenericParam::Lifetime(LifetimeParam::new(Lifetime::from_ident( - f.field.ident.as_ref().unwrap(), - ))) - }) - .collect(); - let mut generics_with_field_lt = struct_.generics.clone(); generics_with_field_lt .params - .extend(field_lt_params.iter().cloned()); + .extend(field_lt_generics.params.iter().cloned()); + let field_lt_for_generics = field_lt_generics.for_generics(); let (_, ty_generics_with_field_lt, _) = generics_with_field_lt.split_for_impl(); let (impl_generics, ty_generics, whr) = struct_.generics.split_for_impl(); @@ -668,7 +693,7 @@ fn generate_drop_check( fn __drop_check #impl_generics ( // This must be present so the function can observe the implied bounds. _: &#struct_name #ty_generics, - f: impl for<#(#field_lt_params,)*>::core::ops::FnOnce(__DropCheck #ty_generics_with_field_lt) + f: impl #field_lt_for_generics ::core::ops::FnOnce(__DropCheck #ty_generics_with_field_lt) ) #whr { #(#guards)* @@ -684,6 +709,7 @@ fn generate_projections( ident: &Ident, generics: &Generics, fields: &[FieldInfo<'_>], + field_lt_generics: &Generics, ) -> TokenStream { let pin_lt = Lifetime::new("'__pin", Span::mixed_site()); @@ -695,19 +721,11 @@ fn generate_projections( ); let (_, ty_generics_with_pin_lt, whr) = generics_with_pin_lt.split_for_impl(); - let field_lt_params: Vec<_> = fields - .iter() - .filter(|f| f.borrowed.is_some()) - .map(|f| { - GenericParam::Lifetime(LifetimeParam::new(Lifetime::from_ident( - f.field.ident.as_ref().unwrap(), - ))) - }) - .collect(); let mut generics_with_field_lt = generics.clone(); generics_with_field_lt .params - .extend(field_lt_params.iter().cloned()); + .extend(field_lt_generics.params.iter().cloned()); + let field_lt_for_generics = field_lt_generics.for_generics(); let (_, ty_generics_with_field_lt, _) = generics_with_field_lt.split_for_impl(); let mut generics_with_pin_field_lt = generics_with_field_lt.clone(); @@ -1008,7 +1026,7 @@ fn generate_projections( #[inline] #vis fn with_project<'__pin, R>( self: ::core::pin::Pin<&'__pin mut Self>, - f: impl for<#(#field_lt_params,)*>::core::ops::FnOnce(__ProjectionLt #ty_generics_with_pin_field_lt) -> R + f: impl #field_lt_for_generics ::core::ops::FnOnce(__ProjectionLt #ty_generics_with_pin_field_lt) -> R ) -> R { // SAFETY: we only give access to `&mut` for fields not structurally pinned. let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; @@ -1028,22 +1046,14 @@ fn generate_the_pin_data( struct_name: &Ident, generics: &Generics, fields: &[FieldInfo<'_>], + field_lt_generics: &Generics, ) -> TokenStream { - let field_lt_params: Vec<_> = fields - .iter() - .filter(|f| f.borrowed.is_some()) - .map(|f| { - GenericParam::Lifetime(LifetimeParam::new(Lifetime::from_ident( - f.field.ident.as_ref().unwrap(), - ))) - }) - .collect(); - let mut generics_with_field_lt = generics.clone(); generics_with_field_lt .params - .extend(field_lt_params.iter().cloned()); + .extend(field_lt_generics.params.iter().cloned()); + let field_lt_for_generics = field_lt_generics.for_generics(); let (impl_generics_with_lt, ty_generics_with_field_lt, _) = generics_with_field_lt.split_for_impl(); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); @@ -1189,14 +1199,14 @@ fn generate_the_pin_data( #[inline(always)] #vis fn __make_closure<__F, __E>(self, f: __F) -> __F where - __F: for<#(#field_lt_params,)*>::core::ops::FnOnce(*mut #struct_name #ty_generics, __PinDataLt #ty_generics_with_field_lt) -> + __F: #field_lt_for_generics ::core::ops::FnOnce(*mut #struct_name #ty_generics, __PinDataLt #ty_generics_with_field_lt) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, { f } #[inline(always)] - fn __with_lt<#(#field_lt_params,)*>(self) -> __PinDataLt #ty_generics_with_field_lt { + fn __with_lt #field_lt_generics(self) -> __PinDataLt #ty_generics_with_field_lt { __PinDataLt { __phantom: ::core::marker::PhantomData } } } diff --git a/internal/src/util.rs b/internal/src/util.rs index 03b7885a..147b2a2c 100644 --- a/internal/src/util.rs +++ b/internal/src/util.rs @@ -2,12 +2,12 @@ use std::collections::BTreeSet; -use proc_macro2::Span; -use quote::quote; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; use syn::punctuated::Punctuated; use syn::visit::Visit; use syn::visit_mut::VisitMut; -use syn::{BoundLifetimes, GenericParam, Ident, LifetimeParam}; +use syn::{BoundLifetimes, GenericParam, Generics, Ident, LifetimeParam, Token}; use syn::{Lifetime, Type}; pub(crate) trait TypeExt { @@ -210,3 +210,26 @@ impl LifetimeExt for Lifetime { } } } + +pub(crate) struct ForGenerics<'a>(&'a Generics); + +pub(crate) trait GenericsExt { + fn for_generics(&self) -> ForGenerics<'_>; +} + +impl GenericsExt for Generics { + fn for_generics(&self) -> ForGenerics<'_> { + ForGenerics(self) + } +} + +impl ToTokens for ForGenerics<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + if self.0.params.is_empty() { + return; + } + + ::default().to_tokens(tokens); + self.0.split_for_impl().1.to_tokens(tokens); + } +} diff --git a/tests/selfref_shorten.rs b/tests/selfref_shorten.rs new file mode 100644 index 00000000..01b2a52f --- /dev/null +++ b/tests/selfref_shorten.rs @@ -0,0 +1,26 @@ +use pin_init::*; + +#[pin_data] +struct SelfRef { + #[borrows(foo, bar)] + part: &'foo str, + foo: String, + bar: String, +} + +#[test] +fn self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + foo: "hello world".to_owned(), + bar: "hello world".to_owned(), + part: &foo[..5], + })); + + stack_pin_init!(let bar = pin_init!(SelfRef { + foo: "hello world".to_owned(), + bar: "hello world".to_owned(), + // In this case, we borrow from a field that lives longers. + // We're allowed to coerce it into a shorter-living lifetime. + part: &bar[..5], + })); +}