From d77e852cb438267cf2e9a6baedaf72924a5b9013 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Mon, 2 Mar 2026 21:47:49 +0300 Subject: [PATCH 1/3] internal: pin_data: refactor for tuple struct support Introduce `FieldInfo` struct to encapsulate field and other relevant data (e.g. pinned and member name) to abstract over named/unnamed fields. Also, generate projections and pin-data accessors for unnamed members. Signed-off-by: Mohamad Alsadhan --- CHANGELOG.md | 1 + internal/src/pin_data.rs | 182 +++++++++++++++++++++++++-------------- 2 files changed, 116 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56b248b3..383a7db7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `[pin_data]` now supports tuple structs - `[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/pin_data.rs b/internal/src/pin_data.rs index 7d871236..b63527bf 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,52 @@ impl Parse for Args { } } +struct FieldInfo<'a> { + pinned: bool, + field: &'a Field, + member: Member, + proj_ident: Ident, + display_name: String, +} + +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, proj_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, + proj_ident, + display_name, + } + } +} + pub(crate) fn pin_data( args: Args, input: Item, @@ -73,27 +119,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 +184,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 +200,17 @@ 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( + |FieldInfo { + field, proj_ident, .. + }| { + let Field { attrs, vis, ty, .. } = field; + quote!( + #(#attrs)* + #vis #proj_ident: #ty + ) + }, + ); 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 +288,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(); @@ -248,44 +298,40 @@ fn generate_projections( 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(); + |FieldInfo { + pinned, + field, + member, + proj_ident, + .. + }| { + let Field { vis, ty, .. } = field; + let mut attrs = field.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>, + #vis #proj_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) }, + #proj_ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) }, ), ) } else { ( quote!( #(#attrs)* - #vis #ident: &'__pin mut #ty, + #vis #proj_ident: &'__pin mut #ty, ), quote!( #(#no_doc_attrs)* - #ident: &mut #this.#ident, + #proj_ident: &mut #this.#member, ), ) } @@ -293,12 +339,14 @@ fn generate_projections( )); 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 +386,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 +395,24 @@ 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, - .. - }: &Field, - struct_ident: &Ident, - pinned: bool, - ) -> TokenStream { + fn handle_field(field: &FieldInfo, struct_ident: &Ident) -> TokenStream { + let FieldInfo { + pinned, + field, + member, + proj_ident, + display_name, + } = field; + let Field { attrs, vis, ty, .. } = field; + 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 { + + let project_ident = match member { + Member::Named(ident) => format_ident!("__project_{ident}"), + Member::Unnamed(index) => format_ident!("__project_{}", index.index), + }; + let (init_ty, init_fn, project_ty, project_body, pin_safety) = if *pinned { ( quote!(PinInit), quote!(__pinned_init), @@ -385,7 +433,7 @@ fn generate_the_pin_data( ) }; 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 +443,7 @@ fn generate_the_pin_data( /// to deallocate. #pin_safety #(#attrs)* - #vis unsafe fn #ident( + #vis unsafe fn #proj_ident( self, slot: *mut #ty, init: impl ::pin_init::#init_ty<#ty, E>, @@ -420,7 +468,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 From d5045f459f6fd068a9a3266a1fad96ed1b30e151 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Tue, 3 Mar 2026 00:16:25 +0300 Subject: [PATCH 2/3] internal: init: add support for tuple struct initialisation Refactor to parse initialiser keys as `Member` (named or tuple index). Additionally, extend init parser to support both tuple-like init constructor e.g. `Foo(a, <- b, c)` and brace syntax e.g. `Foo{0 : a, 1 <- b, 2: c}`. Signed-off-by: Mohamad Alsadhan --- CHANGELOG.md | 3 +- internal/src/init.rs | 230 +++++++++++++++++++++++++++++++++---------- 2 files changed, 178 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 383a7db7..c1cd2c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `[pin_data]` now supports tuple structs +- `#[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 27bf9cfe..792cdffe 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,8 +284,8 @@ 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); 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. @@ -265,20 +293,22 @@ fn init_fields( quote!(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)] @@ -290,28 +320,30 @@ fn init_fields( { #value_prep // SAFETY: TODO - unsafe { #write(::core::ptr::addr_of_mut!((*#slot).#ident), #value_ident) }; + unsafe { #write(::core::ptr::addr_of_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(::core::ptr::addr_of_mut!((*#slot).#ident), #init)? }; + // - We also use `#data` to require the correct trait (`Init` or `PinInit`). + unsafe { + #data.#member_ident(::core::ptr::addr_of_mut!((*#slot).#member), #init)? + }; }, quote! { // SAFETY: TODO - unsafe { #data.#project_ident(&mut (*#slot).#ident) } + unsafe { #data.#project_ident(&mut (*#slot).#member) } }, ) } else { @@ -322,17 +354,19 @@ fn init_fields( unsafe { ::pin_init::Init::__init( #init, - ::core::ptr::addr_of_mut!((*#slot).#ident), + ::core::ptr::addr_of_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); quote! { #(#cfgs)* #[allow(unused_variables)] @@ -355,9 +389,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 +402,7 @@ fn init_fields( // SAFETY: We forget the guard later when initialization has succeeded. let #guard = unsafe { ::pin_init::__internal::DropGuard::new( - ::core::ptr::addr_of_mut!((*slot).#ident) + ::core::ptr::addr_of_mut!((*slot).#member) ) }; }); @@ -394,8 +429,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, @@ -437,31 +472,74 @@ impl Parse for Initializer { 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 { - let lh = content.lookahead1(); - if lh.peek(End) || lh.peek(Token![..]) { - break; - } else if lh.peek(Ident) || lh.peek(Token![_]) || lh.peek(Token![#]) { - fields.push_value(content.parse()?); + let (close_span, fields, rest) = if input.peek(token::Brace) { + let content; + let brace_token = braced!(content in input); + let mut fields = Punctuated::new(); + loop { let lh = content.lookahead1(); - if lh.peek(End) { + if lh.peek(End) || lh.peek(Token![..]) { break; - } else if lh.peek(Token![,]) { - fields.push_punct(content.parse()?); + } 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) { + break; + } else if lh.peek(Token![,]) { + fields.push_punct(content.parse()?); + } else { + return Err(lh.error()); + } } 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()?; + (brace_token.span.close(), fields, rest) + } else if input.peek(token::Paren) { + 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); + } + } + (paren_token.span.close(), 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 +563,7 @@ impl Parse for Initializer { attrs, this, path, - brace_token, + close_span, fields, rest, error, @@ -528,22 +606,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 +651,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()?)) + } + } +} From 757cb2e9229abaf62d5d1b95cfa4cddd820f050a Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Tue, 3 Mar 2026 00:26:13 +0300 Subject: [PATCH 3/3] tests: internal: add tests for new tuple struct support Tests include: - `tests/tuple_struct.rs` - `tests/ui/compile-fail/tuple_{duplicate,invalid,missing}_field.rs` - `tests/ui/compile-fail/tuple_shorthand.rs` - `tests/ui/expand/pin_{data,init}` Signed-off-by: Mohamad Alsadhan --- tests/tuple_struct.rs | 71 +++++++ .../init/tuple_duplicate_field.rs | 8 + .../init/tuple_duplicate_field.stderr | 8 + .../compile-fail/init/tuple_invalid_field.rs | 8 + .../init/tuple_invalid_field.stderr | 33 ++++ .../compile-fail/init/tuple_missing_field.rs | 9 + .../init/tuple_missing_field.stderr | 11 ++ tests/ui/compile-fail/init/tuple_shorthand.rs | 8 + .../compile-fail/init/tuple_shorthand.stderr | 5 + .../expand/pin-data.struct_tuple.expanded.rs | 121 ++++++++++++ tests/ui/expand/pin-data.struct_tuple.rs | 4 + tests/ui/expand/pin-init.expanded.rs | 185 ++++++++++++++++++ tests/ui/expand/pin-init.rs | 11 ++ 13 files changed, 482 insertions(+) create mode 100644 tests/tuple_struct.rs create mode 100644 tests/ui/compile-fail/init/tuple_duplicate_field.rs create mode 100644 tests/ui/compile-fail/init/tuple_duplicate_field.stderr create mode 100644 tests/ui/compile-fail/init/tuple_invalid_field.rs create mode 100644 tests/ui/compile-fail/init/tuple_invalid_field.stderr create mode 100644 tests/ui/compile-fail/init/tuple_missing_field.rs create mode 100644 tests/ui/compile-fail/init/tuple_missing_field.stderr create mode 100644 tests/ui/compile-fail/init/tuple_shorthand.rs create mode 100644 tests/ui/compile-fail/init/tuple_shorthand.stderr create mode 100644 tests/ui/expand/pin-data.struct_tuple.expanded.rs create mode 100644 tests/ui/expand/pin-data.struct_tuple.rs create mode 100644 tests/ui/expand/pin-init.expanded.rs create mode 100644 tests/ui/expand/pin-init.rs diff --git a/tests/tuple_struct.rs b/tests/tuple_struct.rs new file mode 100644 index 00000000..05a993d0 --- /dev/null +++ b/tests/tuple_struct.rs @@ -0,0 +1,71 @@ +#![cfg_attr(USE_RUSTC_FEATURES, feature(lint_reasons))] + +use core::ptr; + +use pin_init::*; + +#[pin_data] +struct TupleStruct(#[pin] i32, i32); + +fn init_i32(value: i32) -> impl PinInit { + // SAFETY: The closure always initializes `slot` with a valid `i32` value. + unsafe { + pin_init_from_closure(move |slot| { + // SAFETY: `slot` is provided by the initialization framework and valid for write. + ptr::write(slot, value); + Ok(()) + }) + } +} + +fn init_i32_unpinned(value: i32) -> impl Init { + // SAFETY: The closure always initializes `slot` with a valid `i32` value. + unsafe { + init_from_closure(move |slot| { + // SAFETY: `slot` is provided by the initialization framework and valid for write. + ptr::write(slot, value); + Ok(()) + }) + } +} + +#[test] +#[allow(clippy::just_underscores_and_digits)] +#[allow(clippy::redundant_locals)] +fn tuple_struct_values() { + stack_pin_init!(let foo = pin_init!(TupleStruct { 0: 42, 1: 24 })); + assert_eq!(foo.as_ref().get_ref().0, 42); + assert_eq!(foo.as_ref().get_ref().1, 24); +} + +#[test] +#[allow(clippy::just_underscores_and_digits)] +#[allow(clippy::redundant_locals)] +fn tuple_struct_init_arrow_and_projection() { + stack_pin_init!(let foo = pin_init!(TupleStruct { 0 <- init_i32(7), 1: 13 })); + let mut foo = foo; + let projected = foo.as_mut().project(); + assert_eq!(*projected._0.as_ref().get_ref(), 7); + assert_eq!(*projected._1, 13); +} + +#[test] +#[allow(clippy::just_underscores_and_digits)] +#[allow(clippy::redundant_locals)] +fn tuple_struct_constructor_form() { + stack_pin_init!(let foo = pin_init!(TupleStruct(<- init_i32(11), 29))); + assert_eq!(foo.as_ref().get_ref().0, 11); + assert_eq!(foo.as_ref().get_ref().1, 29); +} + +#[pin_data] +struct Triple(i32, i32, i32); + +#[test] +#[allow(clippy::just_underscores_and_digits)] +fn tuple_struct_constructor_form_mixed_middle_init() { + stack_pin_init!(let triple = pin_init!(Triple(1, <- init_i32_unpinned(2), 3))); + assert_eq!(triple.as_ref().get_ref().0, 1); + assert_eq!(triple.as_ref().get_ref().1, 2); + assert_eq!(triple.as_ref().get_ref().2, 3); +} 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_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_shorthand.rs b/tests/ui/compile-fail/init/tuple_shorthand.rs new file mode 100644 index 00000000..4b0297f4 --- /dev/null +++ b/tests/ui/compile-fail/init/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/tuple_shorthand.stderr b/tests/ui/compile-fail/init/tuple_shorthand.stderr new file mode 100644 index 00000000..60746e62 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_shorthand.stderr @@ -0,0 +1,5 @@ +error: expected `<-` or `:` + --> tests/ui/compile-fail/init/tuple_shorthand.rs:7:32 + | +7 | let _ = pin_init!(Tuple { 0, 1: 24 }); + | ^ diff --git a/tests/ui/expand/pin-data.struct_tuple.expanded.rs b/tests/ui/expand/pin-data.struct_tuple.expanded.rs new file mode 100644 index 00000000..1b1ae05d --- /dev/null +++ b/tests/ui/expand/pin-data.struct_tuple.expanded.rs @@ -0,0 +1,121 @@ +use pin_init::*; +struct Foo([u8; 1024 * 1024], i32); +/// Pin-projections of [`Foo`] +#[allow(dead_code)] +#[doc(hidden)] +struct FooProjection<'__pin> { + _0: &'__pin mut [u8; 1024 * 1024], + _1: ::core::pin::Pin<&'__pin mut i32>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, +} +impl Foo { + /// Pin-projects all fields of `Self`. + /// + /// These fields are structurally pinned: + /// - index `1` + /// + /// These fields are **not** structurally pinned: + /// - index `0` + #[inline] + fn project<'__pin>( + self: ::core::pin::Pin<&'__pin mut Self>, + ) -> FooProjection<'__pin> { + 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) }, + ___pin_phantom_data: ::core::marker::PhantomData, + } + } +} +const _: () = { + #[doc(hidden)] + struct __ThePinData { + __phantom: ::core::marker::PhantomData Foo>, + } + impl ::core::clone::Clone for __ThePinData { + fn clone(&self) -> Self { + *self + } + } + impl ::core::marker::Copy for __ThePinData {} + #[allow(dead_code)] + #[expect(clippy::missing_safety_doc)] + impl __ThePinData { + /// # 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 [u8; 1024 * 1024], + init: impl ::pin_init::Init<[u8; 1024 * 1024], 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 [u8; 1024 * 1024], + ) -> &'__slot mut [u8; 1024 * 1024] { + 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 i32, + 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 i32, + ) -> ::core::pin::Pin<&'__slot mut i32> { + unsafe { ::core::pin::Pin::new_unchecked(slot) } + } + } + unsafe impl ::pin_init::__internal::HasPinData for Foo { + type PinData = __ThePinData; + unsafe fn __pin_data() -> Self::PinData { + __ThePinData { + __phantom: ::core::marker::PhantomData, + } + } + } + unsafe impl ::pin_init::__internal::PinData for __ThePinData { + type Datee = Foo; + } + #[allow(dead_code)] + struct __Unpin<'__pin> { + __phantom_pin: ::core::marker::PhantomData &'__pin ()>, + __phantom: ::core::marker::PhantomData Foo>, + _1: i32, + } + #[doc(hidden)] + impl<'__pin> ::core::marker::Unpin for Foo + where + __Unpin<'__pin>: ::core::marker::Unpin, + {} + trait MustNotImplDrop {} + #[expect(drop_bounds)] + impl MustNotImplDrop for T {} + impl MustNotImplDrop for Foo {} + #[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 UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for Foo {} +}; diff --git a/tests/ui/expand/pin-data.struct_tuple.rs b/tests/ui/expand/pin-data.struct_tuple.rs new file mode 100644 index 00000000..f9a595f6 --- /dev/null +++ b/tests/ui/expand/pin-data.struct_tuple.rs @@ -0,0 +1,4 @@ +use pin_init::*; + +#[pin_data] +struct Foo([u8; 1024 * 1024], #[pin] i32); diff --git a/tests/ui/expand/pin-init.expanded.rs b/tests/ui/expand/pin-init.expanded.rs new file mode 100644 index 00000000..df25193f --- /dev/null +++ b/tests/ui/expand/pin-init.expanded.rs @@ -0,0 +1,185 @@ +use pin_init::*; +struct Foo([u8; 3], i32); +/// Pin-projections of [`Foo`] +#[allow(dead_code)] +#[doc(hidden)] +struct FooProjection<'__pin> { + _0: &'__pin mut [u8; 3], + _1: ::core::pin::Pin<&'__pin mut i32>, + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, +} +impl Foo { + /// Pin-projects all fields of `Self`. + /// + /// These fields are structurally pinned: + /// - index `1` + /// + /// These fields are **not** structurally pinned: + /// - index `0` + #[inline] + fn project<'__pin>( + self: ::core::pin::Pin<&'__pin mut Self>, + ) -> FooProjection<'__pin> { + 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) }, + ___pin_phantom_data: ::core::marker::PhantomData, + } + } +} +const _: () = { + #[doc(hidden)] + struct __ThePinData { + __phantom: ::core::marker::PhantomData Foo>, + } + impl ::core::clone::Clone for __ThePinData { + fn clone(&self) -> Self { + *self + } + } + impl ::core::marker::Copy for __ThePinData {} + #[allow(dead_code)] + #[expect(clippy::missing_safety_doc)] + impl __ThePinData { + /// # 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 [u8; 3], + init: impl ::pin_init::Init<[u8; 3], 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 [u8; 3], + ) -> &'__slot mut [u8; 3] { + 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 i32, + 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 i32, + ) -> ::core::pin::Pin<&'__slot mut i32> { + unsafe { ::core::pin::Pin::new_unchecked(slot) } + } + } + unsafe impl ::pin_init::__internal::HasPinData for Foo { + type PinData = __ThePinData; + unsafe fn __pin_data() -> Self::PinData { + __ThePinData { + __phantom: ::core::marker::PhantomData, + } + } + } + unsafe impl ::pin_init::__internal::PinData for __ThePinData { + type Datee = Foo; + } + #[allow(dead_code)] + struct __Unpin<'__pin> { + __phantom_pin: ::core::marker::PhantomData &'__pin ()>, + __phantom: ::core::marker::PhantomData Foo>, + _1: i32, + } + #[doc(hidden)] + impl<'__pin> ::core::marker::Unpin for Foo + where + __Unpin<'__pin>: ::core::marker::Unpin, + {} + trait MustNotImplDrop {} + #[expect(drop_bounds)] + impl MustNotImplDrop for T {} + impl MustNotImplDrop for Foo {} + #[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 UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for Foo {} +}; +fn main() { + 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; + { + let _0 = [1, 2, 3]; + unsafe { ::core::ptr::write(&raw mut (*slot).0, _0) }; + } + #[allow(unused_variables)] + let _0 = unsafe { &mut (*slot).0 }; + let ___0_guard = unsafe { + ::pin_init::__internal::DropGuard::new(&raw mut (*slot).0) + }; + { + let init = 42; + unsafe { ::pin_init::Init::__init(init, &raw mut (*slot).1)? }; + } + #[allow(unused_variables)] + let _1 = unsafe { &mut (*slot).1 }; + let ___1_guard = unsafe { + ::pin_init::__internal::DropGuard::new(&raw mut (*slot).1) + }; + ::core::mem::forget(___0_guard); + ::core::mem::forget(___1_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"), + }, + ) + }; + } + 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/pin-init.rs b/tests/ui/expand/pin-init.rs new file mode 100644 index 00000000..8f4f268a --- /dev/null +++ b/tests/ui/expand/pin-init.rs @@ -0,0 +1,11 @@ +use pin_init::*; + +#[pin_data] +struct Foo([u8; 3], #[pin] i32); + +fn main() { + let _ = init!(Foo { + 0 : [1, 2, 3], + 1 <- 42, + }); +}