diff --git a/CHANGELOG.md b/CHANGELOG.md index 556cf880..82d0c0ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `#[pin_data]` and `{pin_}init!` now support tuple structs. + e.g. `Foo(a, <- b, c)` and brace syntax e.g. `Foo{0 : a, 1 <- b, 2: c}`. - `[pin_]init_scope` functions to run arbitrary code inside of an initializer. - `&'static mut MaybeUninit` now implements `InPlaceWrite`. This enables users to use external allocation mechanisms such as `static_cell`. diff --git a/internal/src/init.rs b/internal/src/init.rs index 1a1e3cb4..9628fdf9 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -3,12 +3,13 @@ use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::{ - braced, + braced, parenthesized, parse::{End, Parse}, parse_quote, punctuated::Punctuated, spanned::Spanned, - token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, + token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Index, LitInt, Member, Path, Token, + Type, }; use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; @@ -17,7 +18,7 @@ pub(crate) struct Initializer { attrs: Vec, this: Option, path: Path, - brace_token: token::Brace, + close_span: Span, fields: Punctuated, rest: Option<(Token![..], Expr)>, error: Option<(Token![?], Type)>, @@ -34,13 +35,18 @@ struct InitializerField { kind: InitializerKind, } +struct TupleInitializerField { + attrs: Vec, + kind: TupleInitializerKind, +} + enum InitializerKind { Value { - ident: Ident, + member: Member, value: Option<(Token![:], Expr)>, }, Init { - ident: Ident, + member: Member, _left_arrow_token: Token![<-], value: Expr, }, @@ -51,15 +57,37 @@ enum InitializerKind { }, } +enum TupleInitializerKind { + Value(Expr), + Init { + _left_arrow_token: Token![<-], + value: Expr, + }, +} + impl InitializerKind { - fn ident(&self) -> Option<&Ident> { + fn member(&self) -> Option<&Member> { match self { - Self::Value { ident, .. } | Self::Init { ident, .. } => Some(ident), + Self::Value { member, .. } | Self::Init { member, .. } => Some(member), Self::Code { .. } => None, } } } +fn member_helper_ident(member: &Member) -> Ident { + match member { + Member::Named(ident) => ident.clone(), + Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"), + } +} + +fn member_project_ident(member: &Member) -> Ident { + match member { + Member::Named(ident) => format_ident!("__project_{ident}"), + Member::Unnamed(Index { index, .. }) => format_ident!("__project_{index}"), + } +} + enum InitializerAttribute { DefaultError(DefaultErrorAttribute), DisableInitializedFieldAccess, @@ -74,7 +102,7 @@ pub(crate) fn expand( attrs, this, path, - brace_token, + close_span, fields, rest, error, @@ -96,7 +124,7 @@ pub(crate) fn expand( } else if let Some(default_error) = default_error { syn::parse_str(default_error).unwrap() } else { - dcx.error(brace_token.span.close(), "expected `? ` after `}`"); + dcx.error(close_span, "expected `? ` after initializer"); parse_quote!(::core::convert::Infallible) } }, @@ -256,32 +284,44 @@ fn init_fields( cfgs }; let init = match kind { - InitializerKind::Value { ident, value } => { - let mut value_ident = ident.clone(); + InitializerKind::Value { member, value } => { + let mut value_ident = member_helper_ident(member); + // Only needed when we generate `_0`, `_1`, ... (i.e. for tuple structs) + let maybe_allow = matches!(member, Member::Unnamed(_)) + .then(|| quote!(#[allow(clippy::just_underscores_and_digits)])); let value_prep = value.as_ref().map(|value| &value.1).map(|value| { // Setting the span of `value_ident` to `value`'s span improves error messages - // when the type of `value` is wrong. - value_ident.set_span(value.span()); - quote!(let #value_ident = #value;) + // when the type of `value` is wrong, but only for unnamed fields. + if matches!(member, Member::Named(_)) { + value_ident.set_span(value.span()); + } + + quote! { + #maybe_allow + let #value_ident = #value; + } }); // Again span for better diagnostics - let write = quote_spanned!(ident.span()=> ::core::ptr::write); + let write = quote_spanned!(member.span()=> ::core::ptr::write); let accessor = if pinned { - let project_ident = format_ident!("__project_{ident}"); + let project_ident = member_project_ident(member); quote! { // SAFETY: TODO - unsafe { #data.#project_ident(&mut (*#slot).#ident) } + unsafe { #data.#project_ident(&mut (*#slot).#member) } } } else { quote! { // SAFETY: TODO - unsafe { &mut (*#slot).#ident } + unsafe { &mut (*#slot).#member } } }; let accessor = generate_initialized_accessors.then(|| { + // Note that we are using `_index` (e.g. _0) for unnamed fields accessors + let ident = member_helper_ident(member); quote! { #(#cfgs)* #[allow(unused_variables)] + #maybe_allow let #ident = #accessor; } }); @@ -290,28 +330,28 @@ fn init_fields( { #value_prep // SAFETY: TODO - unsafe { #write(&raw mut (*#slot).#ident, #value_ident) }; + unsafe { #write(&raw mut (*#slot).#member, #value_ident) }; } #accessor } } - InitializerKind::Init { ident, value, .. } => { + InitializerKind::Init { member, value, .. } => { // Again span for better diagnostics let init = format_ident!("init", span = value.span()); let (value_init, accessor) = if pinned { - let project_ident = format_ident!("__project_{ident}"); + let project_ident = member_project_ident(member); + let member_ident = member_helper_ident(member); ( quote! { // SAFETY: // - `slot` is valid, because we are inside of an initializer closure, we // return when an error/panic occurs. // - We also use `#data` to require the correct trait (`Init` or `PinInit`) - // for `#ident`. - unsafe { #data.#ident(&raw mut (*#slot).#ident, #init)? }; + unsafe { #data.#member_ident(&raw mut (*#slot).#member, #init)? }; }, quote! { // SAFETY: TODO - unsafe { #data.#project_ident(&mut (*#slot).#ident) } + unsafe { #data.#project_ident(&mut (*#slot).#member) } }, ) } else { @@ -322,20 +362,25 @@ fn init_fields( unsafe { ::pin_init::Init::__init( #init, - &raw mut (*#slot).#ident, + &raw mut (*#slot).#member, )? }; }, quote! { // SAFETY: TODO - unsafe { &mut (*#slot).#ident } + unsafe { &mut (*#slot).#member } }, ) }; let accessor = generate_initialized_accessors.then(|| { + // Note that we are using `_index` (e.g. _0) for unnamed fields accessors + let ident = member_helper_ident(member); + let maybe_allow = matches!(member, Member::Unnamed(_)) + .then(|| quote!(#[allow(clippy::just_underscores_and_digits)])); quote! { #(#cfgs)* #[allow(unused_variables)] + #maybe_allow let #ident = #accessor; } }); @@ -355,9 +400,10 @@ fn init_fields( }, }; res.extend(init); - if let Some(ident) = kind.ident() { + if let Some(member) = kind.member() { // `mixed_site` ensures that the guard is not accessible to the user-controlled code. - let guard = format_ident!("__{ident}_guard", span = Span::mixed_site()); + let member_for_guard = member_helper_ident(member); + let guard = format_ident!("__{member_for_guard}_guard", span = Span::mixed_site()); res.extend(quote! { #(#cfgs)* // Create the drop guard: @@ -367,7 +413,7 @@ fn init_fields( // SAFETY: We forget the guard later when initialization has succeeded. let #guard = unsafe { ::pin_init::__internal::DropGuard::new( - &raw mut (*slot).#ident + &raw mut (*slot).#member ) }; }); @@ -394,8 +440,8 @@ fn make_field_check( ) -> TokenStream { let field_attrs = fields .iter() - .filter_map(|f| f.kind.ident().map(|_| &f.attrs)); - let field_name = fields.iter().filter_map(|f| f.kind.ident()); + .filter_map(|f| f.kind.member().map(|_| &f.attrs)); + let field_name = fields.iter().filter_map(|f| f.kind.member()); match init_kind { InitKind::Normal => quote! { // We use unreachable code to ensure that all fields have been mentioned exactly once, @@ -432,36 +478,90 @@ fn make_field_check( } } -impl Parse for Initializer { - fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { - let attrs = input.call(Attribute::parse_outer)?; - let this = input.peek(Token![&]).then(|| input.parse()).transpose()?; - let path = input.parse()?; - let content; - let brace_token = braced!(content in input); - let mut fields = Punctuated::new(); - loop { +type InitFields = Punctuated; +type InitRest = Option<(Token![..], Expr)>; +type NamedInitBody = (Span, InitFields, InitRest); + +fn parse_named_init_body(input: syn::parse::ParseStream<'_>) -> syn::Result { + let content; + let brace_token = braced!(content in input); + let mut fields = Punctuated::new(); + loop { + let lh = content.lookahead1(); + if lh.peek(End) || lh.peek(Token![..]) { + break; + } else if lh.peek(Ident) || lh.peek(LitInt) || lh.peek(Token![_]) || lh.peek(Token![#]) { + fields.push_value(content.parse()?); let lh = content.lookahead1(); - if lh.peek(End) || lh.peek(Token![..]) { + if lh.peek(End) { break; - } else if lh.peek(Ident) || lh.peek(Token![_]) || lh.peek(Token![#]) { - fields.push_value(content.parse()?); - let lh = content.lookahead1(); - if lh.peek(End) { - break; - } else if lh.peek(Token![,]) { - fields.push_punct(content.parse()?); - } else { - return Err(lh.error()); - } + } else if lh.peek(Token![,]) { + fields.push_punct(content.parse()?); } else { return Err(lh.error()); } + } else { + return Err(lh.error()); } - let rest = content - .peek(Token![..]) - .then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?))) - .transpose()?; + } + let rest = content + .peek(Token![..]) + .then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?))) + .transpose()?; + Ok((brace_token.span.close(), fields, rest)) +} + +type TupleInitBody = (Span, InitFields); + +fn parse_tuple_init_body(input: syn::parse::ParseStream<'_>) -> syn::Result { + let content; + let paren_token = parenthesized!(content in input); + let tuple_fields = content.parse_terminated(TupleInitializerField::parse, Token![,])?; + let mut fields = Punctuated::new(); + for (index, pair) in tuple_fields.into_pairs().enumerate() { + let (tuple_field, punct) = pair.into_tuple(); + let member = Member::Unnamed(Index { + index: index as u32, + span: Span::call_site(), + }); + let kind = match tuple_field.kind { + TupleInitializerKind::Value(value) => InitializerKind::Value { + member, + value: Some((Token![:](value.span()), value)), + }, + TupleInitializerKind::Init { + _left_arrow_token, + value, + } => InitializerKind::Init { + member, + _left_arrow_token, + value, + }, + }; + fields.push_value(InitializerField { + attrs: tuple_field.attrs, + kind, + }); + if let Some(punct) = punct { + fields.push_punct(punct); + } + } + Ok((paren_token.span.close(), fields)) +} + +impl Parse for Initializer { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let attrs = input.call(Attribute::parse_outer)?; + let this = input.peek(Token![&]).then(|| input.parse()).transpose()?; + let path = input.parse()?; + let (close_span, fields, rest) = if input.peek(token::Brace) { + parse_named_init_body(input)? + } else if input.peek(token::Paren) { + let (close_span, fields) = parse_tuple_init_body(input)?; + (close_span, fields, None) + } else { + return Err(input.error("expected curly braces or parentheses")); + }; let error = input .peek(Token![?]) .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) @@ -485,7 +585,7 @@ impl Parse for Initializer { attrs, this, path, - brace_token, + close_span, fields, rest, error, @@ -528,22 +628,43 @@ impl Parse for InitializerKind { _colon_token: input.parse()?, block: input.parse()?, }) - } else if lh.peek(Ident) { - let ident = input.parse()?; + } else if lh.peek(Ident) || lh.peek(LitInt) { + let member = if lh.peek(Ident) { + Member::Named(input.parse()?) + } else { + let lit: LitInt = input.parse()?; + let index: u32 = lit.base10_parse().map_err(|_| { + syn::Error::new( + lit.span(), + "tuple field index must be a non-negative integer", + ) + })?; + Member::Unnamed(Index { + index, + span: lit.span(), + }) + }; let lh = input.lookahead1(); if lh.peek(Token![<-]) { Ok(Self::Init { - ident, + member, _left_arrow_token: input.parse()?, value: input.parse()?, }) } else if lh.peek(Token![:]) { Ok(Self::Value { - ident, + member, value: Some((input.parse()?, input.parse()?)), }) } else if lh.peek(Token![,]) || lh.peek(End) { - Ok(Self::Value { ident, value: None }) + if matches!(member, Member::Unnamed(_)) { + Err(lh.error()) + } else { + Ok(Self::Value { + member, + value: None, + }) + } } else { Err(lh.error()) } @@ -552,3 +673,26 @@ impl Parse for InitializerKind { } } } + +impl Parse for TupleInitializerField { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let attrs = input.call(Attribute::parse_outer)?; + Ok(Self { + attrs, + kind: input.parse()?, + }) + } +} + +impl Parse for TupleInitializerKind { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + if input.peek(Token![<-]) { + Ok(Self::Init { + _left_arrow_token: input.parse()?, + value: input.parse()?, + }) + } else { + Ok(Self::Value(input.parse()?)) + } + } +} diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 7d871236..1fd2c5f7 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -7,7 +7,7 @@ use syn::{ parse_quote, parse_quote_spanned, spanned::Spanned, visit_mut::VisitMut, - Field, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, + Field, Generics, Ident, Item, Member, PathSegment, Type, TypePath, Visibility, WhereClause, }; use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; @@ -35,6 +35,133 @@ impl Parse for Args { } } +struct FieldInfo<'a> { + pinned: bool, + field: &'a Field, + member: Member, + projected_ident: Ident, + display_name: String, // for diagnostics and documentation, e.g. "`field_name`" or "index `0`" +} + +impl<'a> FieldInfo<'a> { + fn new(field: &'a mut Field, idx: Option) -> Self { + // Invariant: either it's a named field OR it's a tuple field with an index. + debug_assert_eq!(field.ident.is_some(), idx.is_none()); + + // Strip #[pin] and remember whether it was present. + let mut pinned = false; + field.attrs.retain(|a| { + let is_pin = a.path().is_ident("pin"); + pinned |= is_pin; + !is_pin + }); + + let (member, projected_ident, display_name) = if let Some(ident) = field.ident.as_ref() { + ( + Member::Named(ident.clone()), + ident.clone(), + format!("`{ident}`"), + ) + } else { + let i = idx.expect("tuple struct fields must have an index"); // Invariant + ( + Member::Unnamed(i.into()), + format_ident!("_{i}"), + format!("index `{i}`"), + ) + }; + + Self { + pinned, + field, + member, + projected_ident, + display_name, + } + } + + fn no_doc_attrs(&self) -> Vec { + let mut no_doc_attrs = self.field.attrs.clone(); + no_doc_attrs.retain(|a| !a.path().is_ident("doc")); + no_doc_attrs + } + + fn no_pin_attrs(&self) -> Vec { + let mut no_pin_attrs = self.field.attrs.clone(); + no_pin_attrs.retain(|a| !a.path().is_ident("pin")); + no_pin_attrs + } + + fn unpin_field_decl(&self) -> TokenStream { + let FieldInfo { + field, + projected_ident, + .. + } = self; + let Field { attrs, vis, ty, .. } = field; + quote!( + #(#attrs)* + #vis #projected_ident: #ty + ) + } + + fn projected_field_decl(&self) -> TokenStream { + let FieldInfo { + pinned, + field, + projected_ident, + .. + } = self; + let Field { vis, ty, .. } = field; + + let attrs = self.no_pin_attrs(); + + if *pinned { + quote!( + #(#attrs)* + #vis #projected_ident: ::core::pin::Pin<&'__pin mut #ty>, + ) + } else { + quote!( + #(#attrs)* + #vis #projected_ident: &'__pin mut #ty, + ) + } + } + + fn projected_field_init(&self) -> TokenStream { + let FieldInfo { + pinned, + member, + projected_ident, + .. + } = self; + + let no_doc_attrs = self.no_doc_attrs(); + let this = format_ident!("this"); + + if *pinned { + quote!( + #(#no_doc_attrs)* + // SAFETY: this field is structurally pinned. + #projected_ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) }, + ) + } else { + quote!( + #(#no_doc_attrs)* + #projected_ident: &mut #this.#member, + ) + } + } + + fn project_fn_ident(&self) -> Ident { + match &self.member { + Member::Named(ident) => format_ident!("__project_{ident}"), + Member::Unnamed(index) => format_ident!("__project_{}", index.index), + } + } +} + pub(crate) fn pin_data( args: Args, input: Item, @@ -73,27 +200,25 @@ pub(crate) fn pin_data( replacer.visit_generics_mut(&mut struct_.generics); replacer.visit_fields_mut(&mut struct_.fields); - let fields: Vec<(bool, &Field)> = struct_ + let fields: Vec = struct_ .fields .iter_mut() - .map(|field| { - let len = field.attrs.len(); - field.attrs.retain(|a| !a.path().is_ident("pin")); - (len != field.attrs.len(), &*field) - }) + .enumerate() + .map(|(i, field)| FieldInfo::new(field, field.ident.is_none().then_some(i))) .collect(); - for (pinned, field) in &fields { - if !pinned && is_phantom_pinned(&field.ty) { - dcx.error( - field, - format!( - "The field `{}` of type `PhantomPinned` only has an effect \ - if it has the `#[pin]` attribute", - field.ident.as_ref().unwrap(), - ), - ); - } + for field in fields + .iter() + .filter(|f| !f.pinned && is_phantom_pinned(&f.field.ty)) + { + dcx.error( + field.field, + format!( + "The field {} of type `PhantomPinned` only has an effect \ + if it has the `#[pin]` attribute", + field.display_name + ), + ); } let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); @@ -140,11 +265,7 @@ fn is_phantom_pinned(ty: &Type) -> bool { } } -fn generate_unpin_impl( - ident: &Ident, - generics: &Generics, - fields: &[(bool, &Field)], -) -> TokenStream { +fn generate_unpin_impl(ident: &Ident, generics: &Generics, fields: &[FieldInfo]) -> TokenStream { let (_, ty_generics, _) = generics.split_for_impl(); let mut generics_with_pin_lt = generics.clone(); generics_with_pin_lt.params.insert(0, parse_quote!('__pin)); @@ -160,7 +281,10 @@ fn generate_unpin_impl( else { unreachable!() }; - let pinned_fields = fields.iter().filter_map(|(b, f)| b.then_some(f)); + let pinned_fields = fields + .iter() + .filter(|f| f.pinned) + .map(|f| f.unpin_field_decl()); 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`. @@ -238,7 +362,7 @@ fn generate_projections( vis: &Visibility, ident: &Ident, generics: &Generics, - fields: &[(bool, &Field)], + fields: &[FieldInfo], ) -> TokenStream { let (impl_generics, ty_generics, _) = generics.split_for_impl(); let mut generics_with_pin_lt = generics.clone(); @@ -247,58 +371,22 @@ fn generate_projections( let projection = format_ident!("{ident}Projection"); let this = format_ident!("this"); - let (fields_decl, fields_proj) = collect_tuple(fields.iter().map( - |( - pinned, - Field { - vis, - ident, - ty, - attrs, - .. - }, - )| { - let mut attrs = attrs.clone(); - attrs.retain(|a| !a.path().is_ident("pin")); - let mut no_doc_attrs = attrs.clone(); - no_doc_attrs.retain(|a| !a.path().is_ident("doc")); - let ident = ident - .as_ref() - .expect("only structs with named fields are supported"); - if *pinned { - ( - quote!( - #(#attrs)* - #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, - ), - quote!( - #(#no_doc_attrs)* - // SAFETY: this field is structurally pinned. - #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) }, - ), - ) - } else { - ( - quote!( - #(#attrs)* - #vis #ident: &'__pin mut #ty, - ), - quote!( - #(#no_doc_attrs)* - #ident: &mut #this.#ident, - ), - ) - } - }, - )); + let (fields_decl, fields_proj) = collect_tuple( + fields + .iter() + .map(|f| (f.projected_field_decl(), f.projected_field_init())), + ); + let structurally_pinned_fields_docs = fields .iter() - .filter_map(|(pinned, field)| pinned.then_some(field)) - .map(|Field { ident, .. }| format!(" - `{}`", ident.as_ref().unwrap())); + .filter(|f| f.pinned) + .map(|f| format!(" - {}", f.display_name)); + let not_structurally_pinned_fields_docs = fields .iter() - .filter_map(|(pinned, field)| (!pinned).then_some(field)) - .map(|Field { ident, .. }| format!(" - `{}`", ident.as_ref().unwrap())); + .filter(|f| !f.pinned) + .map(|f| format!(" - {}", f.display_name)); + let docs = format!(" Pin-projections of [`{ident}`]"); quote! { #[doc = #docs] @@ -338,7 +426,7 @@ fn generate_the_pin_data( vis: &Visibility, ident: &Ident, generics: &Generics, - fields: &[(bool, &Field)], + fields: &[FieldInfo], ) -> TokenStream { let (impl_generics, ty_generics, whr) = generics.split_for_impl(); @@ -347,24 +435,20 @@ fn generate_the_pin_data( // not structurally pinned, then it can be initialized via `Init`. // // The functions are `unsafe` to prevent accidentally calling them. - fn handle_field( - Field { - vis, - ident, - ty, - attrs, + fn handle_field(field_info: &FieldInfo, struct_ident: &Ident) -> TokenStream { + let FieldInfo { + pinned, + field, + projected_ident, + display_name, .. - }: &Field, - struct_ident: &Ident, - pinned: bool, - ) -> TokenStream { - let mut attrs = attrs.clone(); - attrs.retain(|a| !a.path().is_ident("pin")); - let ident = ident - .as_ref() - .expect("only structs with named fields are supported"); - let project_ident = format_ident!("__project_{ident}"); - let (init_ty, init_fn, project_ty, project_body, pin_safety) = if pinned { + } = field_info; + let Field { vis, ty, .. } = field; + + let attrs = field_info.no_pin_attrs(); + let project_ident = field_info.project_fn_ident(); + + let (init_ty, init_fn, project_ty, project_body, pin_safety) = if *pinned { ( quote!(PinInit), quote!(__pinned_init), @@ -384,9 +468,11 @@ fn generate_the_pin_data( quote!(), ) }; + let slot_safety = format!( - " `slot` points at the field `{ident}` inside of `{struct_ident}`, which is pinned.", + " `slot` points at the field {display_name} inside of `{struct_ident}`, which is pinned.", ); + quote! { /// # Safety /// @@ -395,7 +481,7 @@ fn generate_the_pin_data( /// to deallocate. #pin_safety #(#attrs)* - #vis unsafe fn #ident( + #vis unsafe fn #projected_ident( self, slot: *mut #ty, init: impl ::pin_init::#init_ty<#ty, E>, @@ -404,7 +490,6 @@ fn generate_the_pin_data( // called below. unsafe { ::pin_init::#init_ty::#init_fn(init, slot) } } - /// # Safety /// #[doc = #slot_safety] @@ -420,7 +505,7 @@ fn generate_the_pin_data( let field_accessors = fields .iter() - .map(|(pinned, field)| handle_field(field, ident, *pinned)) + .map(|f| handle_field(f, ident)) .collect::(); quote! { // We declare this struct which will host all of the projection function for our type. It diff --git a/tests/tuple_struct.rs b/tests/tuple_struct.rs new file mode 100644 index 00000000..91b0cea2 --- /dev/null +++ b/tests/tuple_struct.rs @@ -0,0 +1,218 @@ +#![cfg_attr(USE_RUSTC_FEATURES, feature(lint_reasons))] +#![cfg_attr(USE_RUSTC_FEATURES, feature(raw_ref_op))] +#![cfg_attr(feature = "alloc", feature(allocator_api))] + +use core::{ + pin::Pin, + sync::atomic::{AtomicUsize, Ordering}, +}; +use pin_init::*; + +#[allow(unused_attributes)] +#[path = "../examples/mutex.rs"] +mod mutex; +use mutex::*; + +#[pin_data] +struct TupleStruct(#[pin] CMutex, i32); + +fn assert_pinned_mutex(_: &Pin<&mut CMutex>) {} + +#[test] +fn tuple_struct_values() { + // Baseline tuple-field syntax with index-based struct initializer. + stack_pin_init!(let tuple = pin_init!(TupleStruct:: { 0 <- CMutex::new(42), 1: 24 })); + assert_eq!(*tuple.as_ref().get_ref().0.lock(), 42); + assert_eq!(tuple.as_ref().get_ref().1, 24); +} + +#[test] +fn tuple_struct_init_arrow_and_projection() { + // Checks projection types and that `<-` correctly initializes the pinned tuple field. + stack_pin_init!(let tuple = pin_init!(TupleStruct:: { 0 <- CMutex::new(7), 1: 13 })); + + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected._0); + let projected = tuple.as_mut().project(); + assert_eq!(*projected._0.as_ref().get_ref().lock(), 7); + assert_eq!(*projected._1, 13); +} + +#[test] +fn tuple_struct_constructor_form() { + // Same semantics as `tuple_struct_values`, but using tuple constructor syntax. + stack_pin_init!(let tuple = pin_init!(TupleStruct::(<- CMutex::new(11), 29))); + assert_eq!(*tuple.as_ref().get_ref().0.lock(), 11); + assert_eq!(tuple.as_ref().get_ref().1, 29); +} + +#[pin_data] +struct DualPinned(#[pin] CMutex, #[pin] CMutex, usize); + +#[test] +fn tuple_struct_multi_pinned_fields_projection() { + // Both pinned tuple fields should project to `Pin<&mut CMutex>` and stay usable. + stack_pin_init!(let tuple = pin_init!(DualPinned::(<- CMutex::new(1), <- CMutex::new(2), 3))); + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected._0); + assert_pinned_mutex(&projected._1); + + *projected._0.as_ref().get_ref().lock() = 10; + *projected._1.as_ref().get_ref().lock() = 20; + *projected._2 = 30; + + assert_eq!(*tuple.as_ref().get_ref().0.lock(), 10); + assert_eq!(*tuple.as_ref().get_ref().1.lock(), 20); + assert_eq!(tuple.as_ref().get_ref().2, 30); +} + +#[test] +fn tuple_struct_generic_type_param_behavior() { + // Keep this focused on explicit generic-arg syntax (`::`) with struct-style init. + stack_pin_init!(let tuple = pin_init!(TupleStruct:: { 0 <- CMutex::new(123u16), 1: 7 })); + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected._0); + assert_eq!(*projected._0.as_ref().get_ref().lock(), 123u16); + assert_eq!(*projected._1, 7); +} + +#[pin_data] +struct RefTuple<'a>(#[pin] CMutex<&'a usize>, usize); + +#[test] +fn tuple_struct_lifetime_reference_behavior() { + // Verifies tuple init/projection with borrowed data (`'a`) through the pinned field. + let first = 111usize; + let first_ref = &first; + stack_pin_init!(let tuple = pin_init!(RefTuple { 0 <- CMutex::new(first_ref), 1: 3 })); + assert_eq!(**tuple.as_ref().get_ref().0.lock(), 111usize); + assert_eq!(tuple.as_ref().get_ref().1, 3); + + let second = 222usize; + let second_ref = &second; + stack_pin_init!(let tuple = pin_init!(RefTuple(<- CMutex::new(second_ref), 4))); + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected._0); + assert_eq!(**projected._0.as_ref().get_ref().lock(), 222usize); + assert_eq!(*projected._1, 4); +} + +#[test] +fn tuple_struct_projection_mutation_behavior() { + // Confirms both projected fields can be mutated through their projected references. + stack_pin_init!(let tuple = pin_init!(TupleStruct::(<- CMutex::new(1usize), 2))); + + let projected = tuple.as_mut().project(); + *projected._0.as_ref().get_ref().lock() = 10usize; + *projected._1 = 20; + + assert_eq!(*tuple.as_ref().get_ref().0.lock(), 10usize); + assert_eq!(tuple.as_ref().get_ref().1, 20); +} + +struct DropCounter; + +static FALLIBLE_TUPLE_DROPS: AtomicUsize = AtomicUsize::new(0); + +impl Drop for DropCounter { + fn drop(&mut self) { + FALLIBLE_TUPLE_DROPS.fetch_add(1, Ordering::SeqCst); + } +} + +fn tuple_failing_init() -> impl PinInit, ()> { + // SAFETY: We emulate "initialized first field, then fail" and ensure rollback leaves no + // partially initialized value in `slot`. + unsafe { + pin_init_from_closure(|slot: *mut TupleStruct| { + // Manually initialize only field 0 to model a mid-initialization failure. + let field0 = core::ptr::addr_of_mut!((*slot).0); + let init0 = CMutex::new(DropCounter); + // SAFETY: `field0` points into `slot`, which is valid uninitialized memory. + match init0.__pinned_init(field0) { + Ok(()) => {} + Err(infallible) => match infallible {}, + } + // Explicit rollback is required before returning `Err` to avoid leaking initialized state. + core::ptr::drop_in_place(field0); + Err(()) + }) + } +} + +#[test] +fn tuple_struct_fallible_init_drops_initialized_fields() { + // A failure after partial initialization must still drop the already-initialized field. + FALLIBLE_TUPLE_DROPS.store(0, Ordering::SeqCst); + stack_try_pin_init!(let tuple: TupleStruct = tuple_failing_init()); + assert!(matches!(tuple, Err(()))); + assert_eq!(FALLIBLE_TUPLE_DROPS.load(Ordering::SeqCst), 1); +} + +#[pin_data] +struct TupleConst(#[pin] CMutex<[T; N]>, usize); + +#[test] +fn tuple_struct_const_generic_behavior() { + // Covers tuple-field init/projection when the pinned field contains a const-generic array. + stack_pin_init!(let tuple = pin_init!(TupleConst:: { 0 <- CMutex::new([1, 2, 3]), 1: 9 })); + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected._0); + assert_eq!(*projected._0.as_ref().get_ref().lock(), [1, 2, 3]); + assert_eq!(*projected._1, 9); + + stack_pin_init!(let tuple = pin_init!(TupleConst::(<- CMutex::new([7, 8]), 5))); + assert_eq!(*tuple.as_ref().get_ref().0.lock(), [7, 8]); + assert_eq!(tuple.as_ref().get_ref().1, 5); +} + +#[test] +fn tuple_struct_generic_inference_constructor_form() { + // Ensures tuple constructor form can infer `T` from the pinned field initializer. + stack_pin_init!(let tuple = pin_init!(TupleStruct(<- CMutex::new(9u32), 6))); + assert_eq!(*tuple.as_ref().get_ref().0.lock(), 9u32); + assert_eq!(tuple.as_ref().get_ref().1, 6); +} + +#[pin_data] +struct MixedTuple<'a, T, const N: usize>(#[pin] CMutex>, usize); + +type MixedPayload<'a, T, const N: usize> = (&'a T, [u8; N]); + +#[test] +fn tuple_struct_mixed_lifetime_type_const_generics() { + // Stress case combining lifetime + type + const generics in one tuple pinned field. + let value = 77u16; + let pair = (&value, [1, 2, 3, 4]); + stack_pin_init!(let tuple = pin_init!(MixedTuple(<- CMutex::new(pair), 12))); + + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected._0); + let locked = projected._0.as_ref().get_ref().lock(); + assert_eq!(*locked.0, 77u16); + assert_eq!(locked.1, [1, 2, 3, 4]); + assert_eq!(*projected._1, 12); +} + +static PINNED_DROP_TUPLE_DROPS: AtomicUsize = AtomicUsize::new(0); + +#[pin_data(PinnedDrop)] +struct DropTuple(#[pin] CMutex, usize); + +#[pinned_drop] +impl PinnedDrop for DropTuple { + fn drop(self: Pin<&mut Self>) { + let _ = self; + PINNED_DROP_TUPLE_DROPS.fetch_add(1, Ordering::SeqCst); + } +} + +#[test] +fn tuple_struct_pinned_drop_delegates_from_drop() { + // `#[pin_data(PinnedDrop)]` should call our `PinnedDrop::drop` exactly once. + PINNED_DROP_TUPLE_DROPS.store(0, Ordering::SeqCst); + { + stack_pin_init!(let _tuple = pin_init!(DropTuple(<- CMutex::new(5usize), 1))); + } + assert_eq!(PINNED_DROP_TUPLE_DROPS.load(Ordering::SeqCst), 1); +} diff --git a/tests/ui/compile-fail/init/no_tuple_shorthand.rs b/tests/ui/compile-fail/init/no_tuple_shorthand.rs new file mode 100644 index 00000000..4b0297f4 --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_shorthand.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple { 0, 1: 24 }); +} diff --git a/tests/ui/compile-fail/init/no_tuple_shorthand.stderr b/tests/ui/compile-fail/init/no_tuple_shorthand.stderr new file mode 100644 index 00000000..f78d85fa --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_shorthand.stderr @@ -0,0 +1,5 @@ +error: expected `<-` or `:` + --> tests/ui/compile-fail/init/no_tuple_shorthand.rs:7:32 + | +7 | let _ = pin_init!(Tuple { 0, 1: 24 }); + | ^ diff --git a/tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs b/tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs new file mode 100644 index 00000000..9a7e3c84 --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple (0, 1: 24)); +} diff --git a/tests/ui/compile-fail/init/no_tuple_syntax_mixing.stderr b/tests/ui/compile-fail/init/no_tuple_syntax_mixing.stderr new file mode 100644 index 00000000..81c8fe02 --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_syntax_mixing.stderr @@ -0,0 +1,5 @@ +error: expected `,` + --> tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs:7:34 + | +7 | let _ = pin_init!(Tuple (0, 1: 24)); + | ^ diff --git a/tests/ui/compile-fail/init/tuple_arrow_wrong_way.rs b/tests/ui/compile-fail/init/tuple_arrow_wrong_way.rs new file mode 100644 index 00000000..d8f65a66 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_arrow_wrong_way.rs @@ -0,0 +1,9 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple (0 <-, 1: 24)); +} + diff --git a/tests/ui/compile-fail/init/tuple_arrow_wrong_way.stderr b/tests/ui/compile-fail/init/tuple_arrow_wrong_way.stderr new file mode 100644 index 00000000..cafbe856 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_arrow_wrong_way.stderr @@ -0,0 +1,5 @@ +error: expected an expression + --> tests/ui/compile-fail/init/tuple_arrow_wrong_way.rs:7:34 + | +7 | let _ = pin_init!(Tuple (0 <-, 1: 24)); + | ^ diff --git a/tests/ui/compile-fail/init/tuple_duplicate_field.rs b/tests/ui/compile-fail/init/tuple_duplicate_field.rs new file mode 100644 index 00000000..971b3f97 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_duplicate_field.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple { 0: 1, 0: 2, 1: 3 }); +} diff --git a/tests/ui/compile-fail/init/tuple_duplicate_field.stderr b/tests/ui/compile-fail/init/tuple_duplicate_field.stderr new file mode 100644 index 00000000..dd57ac30 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_duplicate_field.stderr @@ -0,0 +1,8 @@ +error[E0062]: field `0` specified more than once + --> tests/ui/compile-fail/init/tuple_duplicate_field.rs:7:37 + | +7 | let _ = pin_init!(Tuple { 0: 1, 0: 2, 1: 3 }); + | ------------------------^------------ + | | | + | | used more than once + | first use of `0` diff --git a/tests/ui/compile-fail/init/tuple_empty_arrow.rs b/tests/ui/compile-fail/init/tuple_empty_arrow.rs new file mode 100644 index 00000000..1b6c6420 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_empty_arrow.rs @@ -0,0 +1,9 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple (<-,1:24)); +} + diff --git a/tests/ui/compile-fail/init/tuple_empty_arrow.stderr b/tests/ui/compile-fail/init/tuple_empty_arrow.stderr new file mode 100644 index 00000000..f27c15b2 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_empty_arrow.stderr @@ -0,0 +1,5 @@ +error: expected an expression + --> tests/ui/compile-fail/init/tuple_empty_arrow.rs:7:32 + | +7 | let _ = pin_init!(Tuple (<-,1:24)); + | ^ diff --git a/tests/ui/compile-fail/init/tuple_generic_invalid_field.rs b/tests/ui/compile-fail/init/tuple_generic_invalid_field.rs new file mode 100644 index 00000000..38059073 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_generic_invalid_field.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] [T; N], i32); + +fn main() { + let _ = pin_init!(Tuple:: { 0: [1, 2, 3], 1: 2, 2: 3 }); +} diff --git a/tests/ui/compile-fail/init/tuple_generic_invalid_field.stderr b/tests/ui/compile-fail/init/tuple_generic_invalid_field.stderr new file mode 100644 index 00000000..2119f8e6 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_generic_invalid_field.stderr @@ -0,0 +1,33 @@ +error[E0609]: no field `2` on type `Tuple` + --> tests/ui/compile-fail/init/tuple_generic_invalid_field.rs:7:60 + | +7 | let _ = pin_init!(Tuple:: { 0: [1, 2, 3], 1: 2, 2: 3 }); + | ^ unknown field + | + = note: available fields are: `0`, `1` + +error[E0599]: no method named `__project_2` found for struct `__ThePinData` in the current scope + --> tests/ui/compile-fail/init/tuple_generic_invalid_field.rs:7:13 + | +3 | #[pin_data] + | ----------- method `__project_2` not found for this struct +... +7 | let _ = pin_init!(Tuple:: { 0: [1, 2, 3], 1: 2, 2: 3 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0560]: struct `Tuple` has no field named `2` + --> tests/ui/compile-fail/init/tuple_generic_invalid_field.rs:7:60 + | +4 | struct Tuple(#[pin] [T; N], i32); + | ----- `Tuple` defined here +... +7 | let _ = pin_init!(Tuple:: { 0: [1, 2, 3], 1: 2, 2: 3 }); + | ^ field does not exist + | +help: `Tuple` is a tuple struct, use the appropriate syntax + | +7 - let _ = pin_init!(Tuple:: { 0: [1, 2, 3], 1: 2, 2: 3 }); +7 + let _ = Tuple(/* [T; N] */, /* i32 */); + | diff --git a/tests/ui/compile-fail/init/tuple_invalid_field.rs b/tests/ui/compile-fail/init/tuple_invalid_field.rs new file mode 100644 index 00000000..19663284 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_invalid_field.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); +} diff --git a/tests/ui/compile-fail/init/tuple_invalid_field.stderr b/tests/ui/compile-fail/init/tuple_invalid_field.stderr new file mode 100644 index 00000000..a7c2e6d4 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_invalid_field.stderr @@ -0,0 +1,33 @@ +error[E0609]: no field `2` on type `Tuple` + --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:43 + | +7 | let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); + | ^ unknown field + | + = note: available fields are: `0`, `1` + +error[E0599]: no method named `__project_2` found for struct `__ThePinData` in the current scope + --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:13 + | +3 | #[pin_data] + | ----------- method `__project_2` not found for this struct +... +7 | let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0560]: struct `Tuple` has no field named `2` + --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:43 + | +4 | struct Tuple(#[pin] i32, i32); + | ----- `Tuple` defined here +... +7 | let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); + | ^ field does not exist + | +help: `Tuple` is a tuple struct, use the appropriate syntax + | +7 - let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); +7 + let _ = Tuple(/* i32 */, /* i32 */); + | diff --git a/tests/ui/compile-fail/init/tuple_missing_field.rs b/tests/ui/compile-fail/init/tuple_missing_field.rs new file mode 100644 index 00000000..401ded40 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_missing_field.rs @@ -0,0 +1,9 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple { 0: 1 }); + let _ = init!(Tuple { 0: 1 }); +} diff --git a/tests/ui/compile-fail/init/tuple_missing_field.stderr b/tests/ui/compile-fail/init/tuple_missing_field.stderr new file mode 100644 index 00000000..4e5ad4c8 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_missing_field.stderr @@ -0,0 +1,11 @@ +error[E0063]: missing field `1` in initializer of `Tuple` + --> tests/ui/compile-fail/init/tuple_missing_field.rs:7:23 + | +7 | let _ = pin_init!(Tuple { 0: 1 }); + | ^^^^^ missing `1` + +error[E0063]: missing field `1` in initializer of `Tuple` + --> tests/ui/compile-fail/init/tuple_missing_field.rs:8:19 + | +8 | let _ = init!(Tuple { 0: 1 }); + | ^^^^^ missing `1` diff --git a/tests/ui/compile-fail/init/tuple_wrong_generics.rs b/tests/ui/compile-fail/init/tuple_wrong_generics.rs new file mode 100644 index 00000000..64ded5e6 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_wrong_generics.rs @@ -0,0 +1,9 @@ +use pin_init::*; + +struct Foo(T); + +fn main() { + let _ = init!(Foo<()> { + 0 <- (), + }); +} diff --git a/tests/ui/compile-fail/init/tuple_wrong_generics.stderr b/tests/ui/compile-fail/init/tuple_wrong_generics.stderr new file mode 100644 index 00000000..8eedfcfb --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_wrong_generics.stderr @@ -0,0 +1,19 @@ +error: comparison operators cannot be chained + --> tests/ui/compile-fail/init/tuple_wrong_generics.rs:6:22 + | +6 | let _ = init!(Foo<()> { + | ^ ^ + | +help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + | +6 | let _ = init!(Foo::<()> { + | ++ + +error: comparison operators cannot be chained + --> tests/ui/compile-fail/init/tuple_wrong_generics.rs:6:22 + | +6 | let _ = init!(Foo<()> { + | ^ ^ + | + = help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments + = help: or use `(...)` if you meant to specify fn arguments diff --git a/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs new file mode 100644 index 00000000..94a971b5 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs @@ -0,0 +1,6 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(T, core::marker::PhantomPinned); + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.stderr b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.stderr new file mode 100644 index 00000000..8af368f1 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.stderr @@ -0,0 +1,5 @@ +error: The field index `1` of type `PhantomPinned` only has an effect if it has the `#[pin]` attribute + --> tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs:4:20 + | +4 | struct Tuple(T, core::marker::PhantomPinned); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/expand/tuple_struct.expanded.rs b/tests/ui/expand/tuple_struct.expanded.rs new file mode 100644 index 00000000..756170a9 --- /dev/null +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -0,0 +1,312 @@ +use core::marker::PhantomPinned; +use pin_init::*; +struct Foo<'a, T: Copy, const N: usize>(&'a mut [T; N], PhantomPinned, usize); +/// Pin-projections of [`Foo`] +#[allow(dead_code)] +#[doc(hidden)] +struct FooProjection<'__pin, 'a, T: Copy, const N: usize> { + _0: &'__pin mut &'a mut [T; N], + _1: ::core::pin::Pin<&'__pin mut PhantomPinned>, + _2: &'__pin mut usize, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, +} +impl<'a, T: Copy, const N: usize> Foo<'a, T, N> { + /// Pin-projects all fields of `Self`. + /// + /// These fields are structurally pinned: + /// - index `1` + /// + /// These fields are **not** structurally pinned: + /// - index `0` + /// - index `2` + #[inline] + fn project<'__pin>( + self: ::core::pin::Pin<&'__pin mut Self>, + ) -> FooProjection<'__pin, 'a, T, N> { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + FooProjection { + _0: &mut this.0, + _1: unsafe { ::core::pin::Pin::new_unchecked(&mut this.1) }, + _2: &mut this.2, + ___pin_phantom_data: ::core::marker::PhantomData, + } + } +} +const _: () = { + #[doc(hidden)] + struct __ThePinData<'a, T: Copy, const N: usize> { + __phantom: ::core::marker::PhantomData) -> Foo<'a, T, N>>, + } + impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __ThePinData<'a, T, N> { + fn clone(&self) -> Self { + *self + } + } + impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __ThePinData<'a, T, N> {} + #[allow(dead_code)] + #[expect(clippy::missing_safety_doc)] + impl<'a, T: Copy, const N: usize> __ThePinData<'a, T, N> { + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + unsafe fn _0( + self, + slot: *mut &'a mut [T; N], + init: impl ::pin_init::Init<&'a mut [T; N], E>, + ) -> ::core::result::Result<(), E> { + unsafe { ::pin_init::Init::__init(init, slot) } + } + /// # Safety + /// + /// `slot` points at the field index `0` inside of `Foo`, which is pinned. + unsafe fn __project_0<'__slot>( + self, + slot: &'__slot mut &'a mut [T; N], + ) -> &'__slot mut &'a mut [T; N] { + slot + } + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + /// - `slot` will not move until it is dropped, i.e. it will be pinned. + unsafe fn _1( + self, + slot: *mut PhantomPinned, + init: impl ::pin_init::PinInit, + ) -> ::core::result::Result<(), E> { + unsafe { ::pin_init::PinInit::__pinned_init(init, slot) } + } + /// # Safety + /// + /// `slot` points at the field index `1` inside of `Foo`, which is pinned. + unsafe fn __project_1<'__slot>( + self, + slot: &'__slot mut PhantomPinned, + ) -> ::core::pin::Pin<&'__slot mut PhantomPinned> { + unsafe { ::core::pin::Pin::new_unchecked(slot) } + } + /// # Safety + /// + /// - `slot` is a valid pointer to uninitialized memory. + /// - the caller does not touch `slot` when `Err` is returned, they are only permitted + /// to deallocate. + unsafe fn _2( + self, + slot: *mut usize, + init: impl ::pin_init::Init, + ) -> ::core::result::Result<(), E> { + unsafe { ::pin_init::Init::__init(init, slot) } + } + /// # Safety + /// + /// `slot` points at the field index `2` inside of `Foo`, which is pinned. + unsafe fn __project_2<'__slot>( + self, + slot: &'__slot mut usize, + ) -> &'__slot mut usize { + slot + } + } + unsafe impl<'a, T: Copy, const N: usize> ::pin_init::__internal::HasPinData + for Foo<'a, T, N> { + type PinData = __ThePinData<'a, T, N>; + unsafe fn __pin_data() -> Self::PinData { + __ThePinData { + __phantom: ::core::marker::PhantomData, + } + } + } + unsafe impl<'a, T: Copy, const N: usize> ::pin_init::__internal::PinData + for __ThePinData<'a, T, N> { + type Datee = Foo<'a, T, N>; + } + #[allow(dead_code)] + struct __Unpin<'__pin, 'a, T: Copy, const N: usize> { + __phantom_pin: ::core::marker::PhantomData &'__pin ()>, + __phantom: ::core::marker::PhantomData) -> Foo<'a, T, N>>, + _1: PhantomPinned, + } + #[doc(hidden)] + impl<'__pin, 'a, T: Copy, const N: usize> ::core::marker::Unpin for Foo<'a, T, N> + where + __Unpin<'__pin, 'a, T, N>: ::core::marker::Unpin, + {} + trait MustNotImplDrop {} + #[expect(drop_bounds)] + impl MustNotImplDrop for T {} + impl<'a, T: Copy, const N: usize> MustNotImplDrop for Foo<'a, T, N> {} + #[expect(non_camel_case_types)] + trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} + impl< + T: ::pin_init::PinnedDrop + ?::core::marker::Sized, + > UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} + impl< + 'a, + T: Copy, + const N: usize, + > UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for Foo<'a, T, N> {} +}; +fn main() { + let mut first = [1u8, 2, 3]; + let _ = { + struct __InitOk; + let __data = unsafe { + use ::pin_init::__internal::HasInitData; + Foo::__init_data() + }; + let init = ::pin_init::__internal::InitData::make_closure::< + _, + __InitOk, + ::core::convert::Infallible, + >( + __data, + move |slot| { + { + struct __InitOk; + { + #[allow(clippy::just_underscores_and_digits)] + let _0 = &mut first; + unsafe { ::core::ptr::write(&raw mut (*slot).0, _0) }; + } + #[allow(unused_variables)] + #[allow(clippy::just_underscores_and_digits)] + let _0 = unsafe { &mut (*slot).0 }; + let ___0_guard = unsafe { + ::pin_init::__internal::DropGuard::new(&raw mut (*slot).0) + }; + { + #[allow(clippy::just_underscores_and_digits)] + let _1 = PhantomPinned; + unsafe { ::core::ptr::write(&raw mut (*slot).1, _1) }; + } + #[allow(unused_variables)] + #[allow(clippy::just_underscores_and_digits)] + let _1 = unsafe { &mut (*slot).1 }; + let ___1_guard = unsafe { + ::pin_init::__internal::DropGuard::new(&raw mut (*slot).1) + }; + { + let init = 10; + unsafe { ::pin_init::Init::__init(init, &raw mut (*slot).2)? }; + } + #[allow(unused_variables)] + #[allow(clippy::just_underscores_and_digits)] + let _2 = unsafe { &mut (*slot).2 }; + let ___2_guard = unsafe { + ::pin_init::__internal::DropGuard::new(&raw mut (*slot).2) + }; + ::core::mem::forget(___0_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___2_guard); + #[allow(unreachable_code, clippy::diverging_sub_expression)] + let _ = || unsafe { + ::core::ptr::write( + slot, + Foo { + 0: ::core::panicking::panic("explicit panic"), + 1: ::core::panicking::panic("explicit panic"), + 2: ::core::panicking::panic("explicit panic"), + }, + ) + }; + } + Ok(__InitOk) + }, + ); + let init = move | + slot, + | -> ::core::result::Result<(), ::core::convert::Infallible> { + init(slot).map(|__InitOk| ()) + }; + let init = unsafe { + ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) + }; + #[allow( + clippy::let_and_return, + reason = "some clippy versions warn about the let binding" + )] init + }; + let mut second = [4u8, 5, 6]; + let _ = { + struct __InitOk; + let __data = unsafe { + use ::pin_init::__internal::HasInitData; + Foo::__init_data() + }; + let init = ::pin_init::__internal::InitData::make_closure::< + _, + __InitOk, + ::core::convert::Infallible, + >( + __data, + move |slot| { + { + struct __InitOk; + { + #[allow(clippy::just_underscores_and_digits)] + let _0 = &mut second; + unsafe { ::core::ptr::write(&raw mut (*slot).0, _0) }; + } + #[allow(unused_variables)] + #[allow(clippy::just_underscores_and_digits)] + let _0 = unsafe { &mut (*slot).0 }; + let ___0_guard = unsafe { + ::pin_init::__internal::DropGuard::new(&raw mut (*slot).0) + }; + { + let init = PhantomPinned; + unsafe { ::pin_init::Init::__init(init, &raw mut (*slot).1)? }; + } + #[allow(unused_variables)] + #[allow(clippy::just_underscores_and_digits)] + let _1 = unsafe { &mut (*slot).1 }; + let ___1_guard = unsafe { + ::pin_init::__internal::DropGuard::new(&raw mut (*slot).1) + }; + { + #[allow(clippy::just_underscores_and_digits)] + let _2 = 20; + unsafe { ::core::ptr::write(&raw mut (*slot).2, _2) }; + } + #[allow(unused_variables)] + #[allow(clippy::just_underscores_and_digits)] + let _2 = unsafe { &mut (*slot).2 }; + let ___2_guard = unsafe { + ::pin_init::__internal::DropGuard::new(&raw mut (*slot).2) + }; + ::core::mem::forget(___0_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___2_guard); + #[allow(unreachable_code, clippy::diverging_sub_expression)] + let _ = || unsafe { + ::core::ptr::write( + slot, + Foo { + 0: ::core::panicking::panic("explicit panic"), + 1: ::core::panicking::panic("explicit panic"), + 2: ::core::panicking::panic("explicit panic"), + }, + ) + }; + } + Ok(__InitOk) + }, + ); + let init = move | + slot, + | -> ::core::result::Result<(), ::core::convert::Infallible> { + init(slot).map(|__InitOk| ()) + }; + let init = unsafe { + ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) + }; + #[allow( + clippy::let_and_return, + reason = "some clippy versions warn about the let binding" + )] init + }; +} diff --git a/tests/ui/expand/tuple_struct.rs b/tests/ui/expand/tuple_struct.rs new file mode 100644 index 00000000..36867e1f --- /dev/null +++ b/tests/ui/expand/tuple_struct.rs @@ -0,0 +1,17 @@ +use core::marker::PhantomPinned; +use pin_init::*; + +#[pin_data] +struct Foo<'a, T: Copy, const N: usize>(&'a mut [T; N], #[pin] PhantomPinned, usize); + +fn main() { + let mut first = [1u8, 2, 3]; + let _ = init!(Foo { + 0: &mut first, + 1: PhantomPinned, + 2 <- 10, + }); + + let mut second = [4u8, 5, 6]; + let _ = init!(Foo(&mut second, <- PhantomPinned, 20)); +}