From a10273c8c6206bec49a69ab289bdbeace82616f7 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 13 Jul 2026 14:30:01 +0100 Subject: [PATCH 01/13] internal: rework how `#[pin_data]` handles cfg Attribute macros are invoked without cfg being resolved. This adds quite a bit complexity to the macro because all of the macro needs to be careful to attach necessary cfgs. This becomes especially tricky for tuple structs. Thus, it is convenient if cfgs are all resolved like derive macros. The most optimal way to handle this is via `TokenStream::expand_expr`, but that is still unstable. Implement an approach where we generate two cfg-gated macro invocations with cfg resolved within the invocation. This would mean when the loop falls through, all field cfgs are resolved, so remove all handling of cfg_attrs for the rest of the macro. Signed-off-by: Gary Guo --- internal/src/pin_data.rs | 78 ++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 44381076..6aea65e2 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -1,13 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use proc_macro2::TokenStream; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; use syn::{ parse::{End, Nothing, Parse}, parse_quote, parse_quote_spanned, spanned::Spanned, visit_mut::VisitMut, - Attribute, Field, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, + Field, Fields, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, }; use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; @@ -35,10 +35,18 @@ impl Parse for Args { } } +impl ToTokens for Args { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Nothing(_) => (), + Self::PinnedDrop(kw) => kw.to_tokens(tokens), + } + } +} + struct FieldInfo<'a> { field: &'a Field, pinned: bool, - cfg_attrs: Vec<&'a Attribute>, } pub(crate) fn pin_data( @@ -68,6 +76,51 @@ pub(crate) fn pin_data( } }; + // Handling cfg can gets very complicated, especially for tuple structs. + // Therefore, resolve all field cfgs first before continuing. + for (field_idx, field) in struct_.fields.iter_mut().enumerate() { + let cfg: Vec<_> = field + .attrs + .iter() + .filter(|a| a.path().is_ident("cfg")) + .map(|a| { + a.parse_args::() + .expect("parse as token stream cannot fail") + }) + .collect(); + + if cfg.is_empty() { + continue; + } + + field.attrs.retain(|a| !a.path().is_ident("cfg")); + let cfg_true_struct = quote!(#struct_); + + let punctuated = match &mut struct_.fields { + Fields::Named(fields) => &mut fields.named, + Fields::Unnamed(fields) => &mut fields.unnamed, + Fields::Unit => unreachable!(), + }; + *punctuated = std::mem::take(punctuated) + .into_pairs() + .enumerate() + .filter(|&(i, _)| i != field_idx) + .map(|(_, p)| p) + .collect(); + let cfg_false_struct = quote!(#struct_); + + // Resolve one field at a time until we've got no more field cfgs. + return Ok(quote!( + #[cfg(all(#(#cfg,)*))] + #[::pin_init::pin_data(#args)] + #cfg_true_struct + + #[cfg(not(all(#(#cfg,)*)))] + #[::pin_init::pin_data(#args)] + #cfg_false_struct + )); + } + // The generics might contain the `Self` type. Since this macro will define a new type with the // same generics and bounds, this poses a problem: `Self` will refer to the new type as opposed // to this struct definition. Therefore we have to replace `Self` with the concrete name. @@ -90,16 +143,14 @@ pub(crate) fn pin_data( dcx.error(&field, "#[pin] attribute specified more than once"); } - let cfg_attrs = field - .attrs - .iter() - .filter(|a| a.path().is_ident("cfg")) - .collect(); + assert!( + !field.attrs.iter().any(|a| a.path().is_ident("cfg")), + "cfgs should be all resolved at this point" + ); FieldInfo { field: &*field, pinned: pinned_count != 0, - cfg_attrs, } }) .collect(); @@ -185,9 +236,7 @@ fn generate_unpin_impl( 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 ) }); @@ -280,7 +329,6 @@ fn generate_projections( .iter() .map(|field| { let Field { vis, ident, ty, .. } = &field.field; - let cfg_attrs = &field.cfg_attrs; let ident = ident .as_ref() @@ -288,11 +336,9 @@ fn generate_projections( if field.pinned { ( quote!( - #(#cfg_attrs)* #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, ), quote!( - #(#cfg_attrs)* // SAFETY: this field is structurally pinned. #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) }, ), @@ -300,11 +346,9 @@ fn generate_projections( } else { ( quote!( - #(#cfg_attrs)* #vis #ident: &'__pin mut #ty, ), quote!( - #(#cfg_attrs)* #ident: &mut #this.#ident, ), ) @@ -374,7 +418,6 @@ fn generate_the_pin_data( .iter() .map(|f| { let Field { vis, ident, ty, .. } = f.field; - let cfg_attrs = &f.cfg_attrs; let field_name = ident .as_ref() @@ -391,7 +434,6 @@ fn generate_the_pin_data( /// - `(*slot).#field_name` is properly aligned. /// - `(*slot).#field_name` points to uninitialized and exclusively accessed /// memory. - #(#cfg_attrs)* // Allow `non_snake_case` since the same warning will be emitted on // the struct definition. #[allow(non_snake_case)] From 84b46e070f94c88ab5e2dfeff4836bde48f343f6 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 25 Apr 2026 18:27:37 +0100 Subject: [PATCH 02/13] 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, where the callback can mint new lifetimes using higher-ranked trait bound and attach it to the `__ThePinData` type. 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 fd0b5ea4..8c0df91e 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 6aea65e2..6f0fb4e5 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -479,7 +479,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 e5dd67f7..5ad6fc03 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -87,6 +87,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 0df053da..c47fd352 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -51,6 +51,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 6b971eeb..626ad11e 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -51,6 +51,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 d001aa69..3b87079d 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)] 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 9f5caa73d68ec638a5c8f831034a22ec8d560a38 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 28 Apr 2026 17:41:28 +0100 Subject: [PATCH 03/13] 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 6f0fb4e5..d3c77edd 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -178,10 +178,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 @@ -322,7 +322,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 @@ -370,7 +369,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)* @@ -390,10 +389,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 5ad6fc03..9418342d 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 c47fd352..9e10d87d 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 626ad11e..148e1e38 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 c65a3a6897d0c5d6197c517e1b7de6b2c8708dda Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 30 Apr 2026 18:49:47 +0100 Subject: [PATCH 04/13] 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 [1], although apart from the attribute syntax the design decisions are very different. 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 d3c77edd..e2b874e2 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, ToTokens}; use syn::{ parse::{End, Nothing, Parse}, parse_quote, parse_quote_spanned, + punctuated::Punctuated, spanned::Spanned, visit_mut::VisitMut, - Field, Fields, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, + Attribute, Field, Fields, 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); @@ -44,9 +51,68 @@ impl ToTokens 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, } pub(crate) fn pin_data( @@ -132,7 +198,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| { @@ -143,6 +216,60 @@ pub(crate) fn pin_data( dcx.error(&field, "#[pin] attribute specified more than once"); } + 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()); + } + assert!( !field.attrs.iter().any(|a| a.path().is_ident("cfg")), "cfgs should be all resolved at this point" @@ -151,22 +278,58 @@ pub(crate) fn pin_data( FieldInfo { field: &*field, pinned: pinned_count != 0, + borrowed: None, + variance: if borrows.is_empty() { + None + } else { + Some(variance) + }, } }) .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); @@ -326,6 +489,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; @@ -415,6 +579,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; 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 fa058e0975090ea0851262791ed198d9ced698bf Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 18:32:29 +0100 Subject: [PATCH 05/13] 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`. Implement the above strategies to create a `SelfRef` type, and use it 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`. Enforce this by ensuring `SelfRef: !Unpin` and force the generated `Unpin` implementation 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 e2b874e2..f2ad6fd0 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -10,13 +10,13 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, visit_mut::VisitMut, - Attribute, Field, Fields, 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 { @@ -113,6 +113,7 @@ struct FieldInfo<'a> { pinned: bool, borrowed: Option, variance: Option, + ty: Binder, } pub(crate) fn pin_data( @@ -198,6 +199,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(); @@ -205,8 +210,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(); @@ -216,6 +220,11 @@ pub(crate) fn pin_data( dcx.error(&field, "#[pin] attribute specified more than once"); } + assert!( + !field.attrs.iter().any(|a| a.path().is_ident("cfg")), + "cfgs should be all resolved at this point" + ); + let ident = field.ident.as_ref().unwrap(); // Parse `#[covariant]` and `#[not_covariant]` markers. @@ -270,9 +279,12 @@ pub(crate) fn pin_data( entry.1.push(borrow.field.span()); } - assert!( - !field.attrs.iter().any(|a| a.path().is_ident("cfg")), - "cfgs should be all resolved at this point" + let ty = Binder::new( + borrows + .iter() + .map(|field_ref| Lifetime::from_ident(&field_ref.field)) + .collect(), + field.ty.clone(), ); FieldInfo { @@ -284,6 +296,7 @@ pub(crate) fn pin_data( } else { Some(variance) }, + ty, } }) .collect(); @@ -313,6 +326,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. @@ -332,6 +354,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 = @@ -340,7 +363,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 _: () = { @@ -376,6 +399,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, @@ -396,13 +472,23 @@ 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 pinned_fields = if fields.iter().any(|f| f.borrowed.is_some()) { + // Self-referential structs must always be pinned. quote!( - #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; + quote!( + #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`. @@ -416,7 +502,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 e3044b18ebe3faa2986931790a3399288059f12e Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 20:50:50 +0100 Subject: [PATCH 06/13] 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 | 169 ++++++++++++++++-- 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, 245 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 f2ad6fd0..29606dc4 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::{ @@ -347,16 +347,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 = @@ -367,6 +361,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 @@ -561,6 +556,160 @@ 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 ident = f.field.ident.as_ref().unwrap(); + + if f.borrowed.is_some() { + let lt = Lifetime::from_ident(ident); + quote!( + #ident: ::pin_init::__internal::PhantomInvariant<&#lt mut #ty>, + ) + } else { + quote!( + #[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 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. + 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. + let _guard = ::pin_init::__internal::LifetimeGuard::new(&#ident); + ) + } else { + quote!( + // No lifetimes to tie for fields that are not borrowed. + let #ident = ::pin_init::__internal::PhantomInvariant::new(); + ) + } + }); + + let fields = fields.iter().map(|f| f.field.ident.as_ref().unwrap()); + + 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 9418342d..4c510736 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 9e10d87d..f25dcca1 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 148e1e38..ef4ef7e0 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 ab5440cdc71b8e2b927bece32db323f497e0ecca Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 22:16:44 +0100 Subject: [PATCH 07/13] 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 | 105 +++++++++++-- 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 | 76 +++++++--- tests/ui/expand/pin-data.expanded.rs | 55 ++++--- tests/ui/expand/pinned_drop.expanded.rs | 55 ++++--- tests/ui/expand/simple-init.expanded.rs | 2 +- 13 files changed, 447 insertions(+), 79 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 8c0df91e..3e96f8a3 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 29606dc4..85a60dd8 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -806,26 +806,66 @@ 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 field_name = ident + let vis = &f.field.vis; + 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 /// @@ -840,19 +880,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 @@ -870,7 +943,6 @@ fn generate_the_pin_data( #whr {} - #[allow(dead_code)] // Some functions might never be used and private. impl #impl_generics __ThePinData #ty_generics #whr { @@ -878,13 +950,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 ef9f20b1..48293c08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -865,6 +865,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 4c510736..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,31 +74,21 @@ 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>, {} #[allow(dead_code)] + #[expect(clippy::missing_safety_doc)] impl< 'a, '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. @@ -114,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 /// @@ -131,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 /// @@ -148,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 f25dcca1..ac3560aa 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -35,28 +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)] - 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 - } + #[expect(clippy::missing_safety_doc)] + impl __PinDataLt { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -72,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 /// @@ -89,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 ef4ef7e0..06f4522e 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -35,28 +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)] - 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 - } + #[expect(clippy::missing_safety_doc)] + impl __PinDataLt { /// # Safety /// /// - `slot` is valid and properly aligned. @@ -72,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 /// @@ -89,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 3b87079d..ec0c9251 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 548c9313e4e5bf76d8f8a32f677c10e0f253e6ad Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:08:21 +0100 Subject: [PATCH 08/13] 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 | 128 ++++++++++++++++-- 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, 195 insertions(+), 14 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 85a60dd8..2c2b029b 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -716,38 +716,81 @@ 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; + .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 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!( - #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, + #vis #ident: ::core::pin::Pin<&'__pin #mut_token #ty>, ), quote!( // 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!( - #vis #ident: &'__pin mut #ty, + #vis #ident: &'__pin #mut_token #ty, ), quote!( - #ident: &mut #this.#ident, + #ident: #accessor, ), ) } @@ -762,6 +805,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 @@ -772,7 +876,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 @@ -796,6 +900,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 ac3560aa..cd41bd87 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 eb278554ee9d3fa4ca2195eefd1f83cba17a07b6 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:29:08 +0100 Subject: [PATCH 09/13] 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 | 130 +++++++++++++++++++- 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, 238 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 2c2b029b..429f8f95 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -725,6 +725,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 @@ -796,14 +819,81 @@ 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 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!( + #vis #ident: ::core::pin::Pin<&#lt #mut_token #ty>, + ), + quote!( + // SAFETY: this field is structurally pinned. + #ident: unsafe { ::core::pin::Pin::new_unchecked(#accessor) }, + ), + ) + } else { + ( + quote!( + #vis #ident: &#lt #mut_token #ty, + ), + quote!( + #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 @@ -879,6 +969,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 { @@ -901,6 +1003,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 cd41bd87..c76d8047 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 fef06e67815757190574e412e5a4513608a77158 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 6 May 2026 18:12:59 +0100 Subject: [PATCH 10/13] 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 429f8f95..b93b14cb 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -430,6 +430,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 }); @@ -1055,7 +1063,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 field_name = f @@ -1089,9 +1096,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 9c0f903c9708bac8b784fba9262203d01234f9e9 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:40:15 +0100 Subject: [PATCH 11/13] 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 b93b14cb..e15cf35f 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -960,7 +960,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 a604f2df5fe96f856ce311b2996bdb432cf4907a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:58:33 +0100 Subject: [PATCH 12/13] 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 d87ad4c552b4a469554ec410b45d973163ae03ff Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 4 May 2026 14:33:21 +0100 Subject: [PATCH 13/13] 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 e15cf35f..1bac4d7f 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 { @@ -347,14 +347,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 @@ -568,6 +602,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 @@ -604,22 +639,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(); @@ -707,7 +732,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)* @@ -723,6 +748,7 @@ fn generate_projections( ident: &Ident, generics: &Generics, fields: &[FieldInfo<'_>], + field_lt_generics: &Generics, ) -> TokenStream { let pin_lt = Lifetime::new("'__pin", Span::mixed_site()); @@ -734,19 +760,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(); @@ -1037,7 +1055,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) }; @@ -1057,22 +1075,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(); @@ -1216,14 +1226,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], + })); +}