diff --git a/CHANGELOG.md b/CHANGELOG.md index b91d884b..aed4103c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `#[pin_data]` now supports tuple structs. Their fields have no names, so the generated + projection is a tuple struct too and its fields are accessed by index. +- `[pin_]init!` now supports tuple structs, either by naming the fields by their index, as in + `init!(Foo { 0: value, 1 <- initializer })`, or with constructor syntax, as in + `init!(Foo(value, value))`. - `[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 fd0b5ea4..bf187db5 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -1,28 +1,145 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ - braced, + braced, parenthesized, parse::{End, Parse}, parse_quote, - punctuated::Punctuated, + punctuated::{Pair, 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}; +use crate::{ + diagnostics::{DiagCtxt, ErrorGuaranteed}, + member::member_ident, +}; pub(crate) struct Initializer { attrs: Vec, this: Option, + body: InitializerBody, + error: Option<(Token![?], Type)>, +} + +enum InitializerBody { + Struct(InitStruct), + Tuple(InitTuple), +} + +struct InitStruct { path: Path, brace_token: token::Brace, fields: Punctuated, rest: Option<(Token![..], Expr)>, +} + +struct InitTuple { + path: Path, + paren_token: token::Paren, + fields: Punctuated, +} + +struct InitTupleField { + attrs: Vec, + /// `<-` is not valid in constructor syntax; it is parsed anyway so that it can be rejected + /// with a proper diagnostic instead of a parse error. + left_arrow_token: Option, + value: Expr, +} + +/// An [`Initializer`] with the constructor syntax rewritten into indexed fields. +struct NormalizedInitializer { + attrs: Vec, + this: Option, + path: Path, + delim_close_span: Span, + fields: Punctuated, + rest: Option<(Token![..], Expr)>, error: Option<(Token![?], Type)>, } +impl Initializer { + fn normalize(self) -> NormalizedInitializer { + let Self { + attrs, + this, + body, + error, + } = self; + let (path, delim_close_span, fields, rest) = match body { + InitializerBody::Struct(InitStruct { + path, + brace_token, + fields, + rest, + }) => (path, brace_token.span.close(), fields, rest), + InitializerBody::Tuple(InitTuple { + path, + paren_token, + fields, + }) => ( + path, + paren_token.span.close(), + index_tuple_fields(fields), + None, + ), + }; + NormalizedInitializer { + attrs, + this, + path, + delim_close_span, + fields, + rest, + error, + } + } +} + +impl InitTuple { + fn validate(&self, dcx: &mut DiagCtxt) -> Result<(), ErrorGuaranteed> { + let mut result = Ok(()); + for field in &self.fields { + if let Some(left_arrow_token) = &field.left_arrow_token { + result = Err(dcx.error( + left_arrow_token, + "`<-` is not supported in tuple constructor syntax; name the fields by index \ + instead, e.g. `Type { 0 <- initializer, 1: value }`", + )); + } + } + result + } +} + +/// Rewrites constructor arguments into the indexed fields they are shorthand for. +fn index_tuple_fields( + fields: Punctuated, +) -> Punctuated { + fields + .into_pairs() + .enumerate() + .map(|(index, pair)| { + let (field, comma) = pair.into_tuple(); + let span = field.value.span(); + let field = InitializerField { + attrs: field.attrs, + kind: InitializerKind::Value { + member: Member::Unnamed(Index { + index: index as u32, + span, + }), + value: Some((Token![:](span), field.value)), + }, + }; + Pair::new(field, comma) + }) + .collect() +} + struct This { _and_token: Token![&], ident: Ident, @@ -36,11 +153,11 @@ struct InitializerField { enum InitializerKind { Value { - ident: Ident, + member: Member, value: Option<(Token![:], Expr)>, }, Init { - ident: Ident, + member: Member, _left_arrow_token: Token![<-], value: Expr, }, @@ -52,9 +169,16 @@ enum InitializerKind { } impl InitializerKind { - fn ident(&self) -> Option<&Ident> { + fn member(&self) -> Option<&Member> { + match self { + Self::Value { member, .. } | Self::Init { member, .. } => Some(member), + Self::Code { .. } => None, + } + } + + fn member_mut(&mut self) -> Option<&mut Member> { match self { - Self::Value { ident, .. } | Self::Init { ident, .. } => Some(ident), + Self::Value { member, .. } | Self::Init { member, .. } => Some(member), Self::Code { .. } => None, } } @@ -68,16 +192,142 @@ struct DefaultErrorAttribute { ty: Box, } -pub(crate) fn expand( - Initializer { +pub(crate) fn expand_with_cfg( + mut initializer: Initializer, + default_error: Option<&'static str>, + pinned: bool, + dcx: &mut DiagCtxt, +) -> Result { + // Removing a tuple field renumbers every field after it, which cannot be expressed with a + // `cfg` attribute on the initializer of a single field. Therefore, resolve tuple field cfgs + // before continuing. Named fields do not renumber, so they keep using Rust's normal cfg + // handling and are deliberately left alone here. + // + // We need to perform this after parsing so we can reliably detect field cfgs. + if let Some((field_idx, removed_index, cfg)) = initializer.body.take_first_tuple_cfg() { + let true_initializer = initializer.to_token_stream(); + initializer + .body + .remove_tuple_field(field_idx, removed_index); + let false_initializer = &initializer; + + let macro_name = if pinned { + quote!(::pin_init::pin_init) + } else { + quote!(::pin_init::init) + }; + + // Resolve one field at a time until we've got no more tuple field cfgs. + // + // This is linear time because macro invocations with false cfg will not be expanded. + return Ok(quote! { + { + // Use `{}` delimiter here so semicolon is not required (which becomes unit type). + #[cfg(all(#(#cfg,)*))] + #macro_name! { #true_initializer } + + #[cfg(not(all(#(#cfg,)*)))] + #macro_name! { #false_initializer } + } + }); + } + + if let InitializerBody::Tuple(init) = &initializer.body { + init.validate(dcx)?; + } + expand(initializer.normalize(), default_error, pinned, dcx) +} + +impl InitializerBody { + /// Takes the `cfg` attributes off the first tuple field that has any. + /// + /// Returns the position of that field, its tuple index and the conditions of the removed + /// `cfg`s. + fn take_first_tuple_cfg(&mut self) -> Option<(usize, u32, Vec)> { + match self { + // Only numeric members are tuple fields; named fields keep using Rust's normal cfg + // handling. + Self::Struct(init) => init + .fields + .iter_mut() + .enumerate() + .find_map(|(index, field)| { + let Some(Member::Unnamed(unnamed)) = field.kind.member() else { + return None; + }; + let unnamed = unnamed.index; + take_cfg(&mut field.attrs).map(|cfg| (index, unnamed, cfg)) + }), + // Constructor arguments are tuple fields by construction and their index is their + // position. + Self::Tuple(init) => init + .fields + .iter_mut() + .enumerate() + .find_map(|(index, field)| { + take_cfg(&mut field.attrs).map(|cfg| (index, index as u32, cfg)) + }), + } + } + + /// Removes the field at `field_idx`, which is the tuple field `removed_index`. + fn remove_tuple_field(&mut self, field_idx: usize, removed_index: u32) { + match self { + Self::Struct(init) => { + remove_field(&mut init.fields, field_idx); + // Removing a tuple field shifts every field after it down by one. + for field in init.fields.iter_mut() { + if let Some(Member::Unnamed(index)) = field.kind.member_mut() { + if index.index > removed_index { + index.index -= 1; + } + } + } + } + // Constructor arguments are renumbered implicitly, by their position. + Self::Tuple(init) => remove_field(&mut init.fields, field_idx), + } + } +} + +/// Takes the `cfg` attributes off `attrs` and returns their conditions. +fn take_cfg(attrs: &mut Vec) -> Option> { + let cfg: Vec<_> = attrs + .iter() + .filter(|attr| attr.path().is_ident("cfg")) + .map(|attr| { + attr.parse_args::() + .expect("parse as token stream cannot fail") + }) + .collect(); + + if cfg.is_empty() { + return None; + } + + attrs.retain(|attr| !attr.path().is_ident("cfg")); + Some(cfg) +} + +fn remove_field(fields: &mut Punctuated, field_idx: usize) { + *fields = std::mem::take(fields) + .into_pairs() + .enumerate() + .filter(|&(index, _)| index != field_idx) + .map(|(_, pair)| pair) + .collect(); +} + +fn expand( + NormalizedInitializer { attrs, this, path, - brace_token, + delim_close_span, fields, rest, error, - }: Initializer, + }: NormalizedInitializer, default_error: Option<&'static str>, pinned: bool, dcx: &mut DiagCtxt, @@ -96,7 +346,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(delim_close_span, "expected `? ` after initializer"); parse_quote!(::core::convert::Infallible) } }, @@ -229,9 +479,9 @@ fn init_fields( cfgs }; - let ident = match kind { - InitializerKind::Value { ident, .. } => ident, - InitializerKind::Init { ident, .. } => ident, + let member = match kind { + InitializerKind::Value { member, .. } => member, + InitializerKind::Init { member, .. } => member, InitializerKind::Code { block, .. } => { let stmt = &block.stmts; res.extend(quote! { @@ -244,40 +494,42 @@ fn init_fields( } }; + let accessor = member_ident(member); + let slot = if pinned { quote! { // SAFETY: // - `slot` is valid and properly aligned. - // - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned. - // - `make_field_check` prevents `#ident` from being used twice, therefore - // `(*slot).#ident` is exclusively accessed and has not been initialized. - (unsafe { #data.#ident(#slot) }) + // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. + // - `make_field_check` prevents `#member` from being used twice, therefore + // `(*slot).#member` is exclusively accessed and has not been initialized. + (unsafe { #data.#accessor(#slot) }) } } else { quote! { // For `init!()` macro, everything is unpinned. // SAFETY: - // - `&raw mut (*slot).#ident` is valid. - // - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned. - // - `make_field_check` prevents `#ident` from being used twice, therefore - // `(*slot).#ident` is exclusively accessed and has not been initialized. + // - `&raw mut (*slot).#member` is valid. + // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. + // - `make_field_check` prevents `#member` from being used twice, therefore + // `(*slot).#member` is exclusively accessed and has not been initialized. (unsafe { ::pin_init::__internal::Slot::<::pin_init::__internal::Unpinned, _>::new( - &raw mut (*#slot).#ident + &raw mut (*#slot).#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 guard = format_ident!("__{accessor}_guard", span = Span::mixed_site()); let init = match kind { - InitializerKind::Value { ident, value } => { + InitializerKind::Value { value, .. } => { let value = value .as_ref() .map(|(_, value)| quote!(#value)) - .unwrap_or_else(|| quote!(#ident)); + .unwrap_or_else(|| quote!(#member)); quote! { #(#attrs)* @@ -294,14 +546,23 @@ fn init_fields( InitializerKind::Code { .. } => unreachable!(), }; + // A tuple field has no name that could be bound here, and binding it as `_0` would + // shadow a user variable of that name. + let binding = match member { + Member::Named(ident) => quote! { + #(#cfgs)* + // Allow `non_snake_case` since the same warning is going to be reported for the + // struct field. + #[allow(unused_variables, non_snake_case)] + let #ident = #guard.let_binding(); + }, + Member::Unnamed(_) => quote!(), + }; + res.extend(quote! { #init - #(#cfgs)* - // Allow `non_snake_case` since the same warning is going to be reported for the struct - // field. - #[allow(unused_variables, non_snake_case)] - let #ident = #guard.let_binding(); + #binding }); guards.push(guard); @@ -326,9 +587,9 @@ fn make_field_check( ) -> TokenStream { let field_attrs: Vec<_> = fields .iter() - .filter_map(|f| f.kind.ident().map(|_| &f.attrs)) + .filter_map(|f| f.kind.member().map(|_| &f.attrs)) .collect(); - let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.ident()).collect(); + let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.member()).collect(); let zeroing_trailer = match init_kind { InitKind::Normal => None, InitKind::Zeroing => Some(quote! { @@ -364,36 +625,80 @@ 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 { +fn parse_brace_initializer( + path: Path, + 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(InitStruct { + path, + brace_token, + fields, + rest, + }) +} + +fn parse_paren_initializer( + path: Path, + input: syn::parse::ParseStream<'_>, +) -> syn::Result { + let content; + let paren_token = parenthesized!(content in input); + let mut fields = Punctuated::new(); + while !content.is_empty() { + fields.push_value(InitTupleField { + attrs: content.call(Attribute::parse_outer)?, + left_arrow_token: content.parse()?, + value: content.parse()?, + }); + if content.is_empty() { + break; + } + fields.push_punct(content.parse()?); + } + Ok(InitTuple { + path, + paren_token, + 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 body = if input.peek(token::Brace) { + InitializerBody::Struct(parse_brace_initializer(path, input)?) + } else if input.peek(token::Paren) { + InitializerBody::Tuple(parse_paren_initializer(path, input)?) + } else { + return Err(input.error("expected curly braces or parentheses")); + }; let error = input .peek(Token![?]) .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) @@ -412,10 +717,7 @@ impl Parse for Initializer { Ok(Self { attrs, this, - path, - brace_token, - fields, - rest, + body, error, }) } @@ -456,22 +758,38 @@ 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()?; + Member::Unnamed(Index { + index: lit.base10_parse()?, + 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 }) + // Unlike a named field, a tuple field has no shorthand: `0` is not a variable. + if matches!(member, Member::Unnamed(_)) { + Err(lh.error()) + } else { + Ok(Self::Value { + member, + value: None, + }) + } } else { Err(lh.error()) } @@ -480,3 +798,137 @@ impl Parse for InitializerKind { } } } + +impl ToTokens for Initializer { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + this, + body, + error, + } = self; + tokens.append_all(attrs); + this.to_tokens(tokens); + body.to_tokens(tokens); + if let Some((question, ty)) = error { + question.to_tokens(tokens); + ty.to_tokens(tokens); + } + } +} + +impl ToTokens for InitializerBody { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Struct(init) => init.to_tokens(tokens), + Self::Tuple(init) => init.to_tokens(tokens), + } + } +} + +impl ToTokens for InitStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + path, + brace_token, + fields, + rest, + } = self; + path.to_tokens(tokens); + brace_token.surround(tokens, |tokens| { + fields.to_tokens(tokens); + if let Some((dotdot, expr)) = rest { + dotdot.to_tokens(tokens); + expr.to_tokens(tokens); + } + }); + } +} + +impl ToTokens for InitTuple { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + path, + paren_token, + fields, + } = self; + path.to_tokens(tokens); + paren_token.surround(tokens, |tokens| fields.to_tokens(tokens)); + } +} + +impl ToTokens for InitTupleField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + left_arrow_token, + value, + } = self; + tokens.append_all(attrs); + left_arrow_token.to_tokens(tokens); + value.to_tokens(tokens); + } +} + +impl ToTokens for InitializerAttribute { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::DefaultError(DefaultErrorAttribute { ty }) => { + quote!(#[default_error(#ty)]).to_tokens(tokens); + } + } + } +} + +impl ToTokens for This { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + _and_token, + ident, + _in_token, + } = self; + _and_token.to_tokens(tokens); + ident.to_tokens(tokens); + _in_token.to_tokens(tokens); + } +} + +impl ToTokens for InitializerField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { attrs, kind } = self; + tokens.append_all(attrs); + kind.to_tokens(tokens); + } +} + +impl ToTokens for InitializerKind { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Value { member, value } => { + member.to_tokens(tokens); + if let Some((colon, expr)) = value { + colon.to_tokens(tokens); + expr.to_tokens(tokens); + } + } + Self::Init { + member, + _left_arrow_token, + value, + } => { + member.to_tokens(tokens); + _left_arrow_token.to_tokens(tokens); + value.to_tokens(tokens); + } + Self::Code { + _underscore_token, + _colon_token, + block, + } => { + _underscore_token.to_tokens(tokens); + _colon_token.to_tokens(tokens); + block.to_tokens(tokens); + } + } + } +} diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 60d5093f..8934ae05 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -16,6 +16,7 @@ use crate::diagnostics::DiagCtxt; mod diagnostics; mod init; +mod member; mod pin_data; mod pinned_drop; mod zeroable; @@ -48,12 +49,17 @@ pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream { #[proc_macro] pub fn init(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| init::expand(input, Some("::core::convert::Infallible"), false, dcx)) - .into() + DiagCtxt::with(|dcx| { + init::expand_with_cfg(input, Some("::core::convert::Infallible"), false, dcx) + }) + .into() } #[proc_macro] pub fn pin_init(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| init::expand(input, Some("::core::convert::Infallible"), true, dcx)).into() + DiagCtxt::with(|dcx| { + init::expand_with_cfg(input, Some("::core::convert::Infallible"), true, dcx) + }) + .into() } diff --git a/internal/src/member.rs b/internal/src/member.rs new file mode 100644 index 00000000..91307b56 --- /dev/null +++ b/internal/src/member.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro2::Ident; +use quote::format_ident; +use syn::{Index, Member}; + +/// Returns the identifier used to name the items that `#[pin_data]` generates for `member`. +/// +/// Tuple fields have no name of their own, so they are named `_0`, `_1`, ... instead. This is +/// used for the pin-data accessors and the fields of the `__Unpin` struct. +/// +/// `#[pin_data]` defines the accessors and the `[pin_]init!` macros call them, so both have to +/// agree on this mapping. +pub(crate) fn member_ident(member: &Member) -> Ident { + match member { + Member::Named(ident) => ident.clone(), + Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"), + } +} diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index ff194d27..e795da70 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -7,10 +7,14 @@ use syn::{ parse_quote, parse_quote_spanned, spanned::Spanned, visit_mut::VisitMut, - Field, Fields, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, + Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath, Visibility, + WhereClause, }; -use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; +use crate::{ + diagnostics::{DiagCtxt, ErrorGuaranteed}, + member::member_ident, +}; pub(crate) mod kw { syn::custom_keyword!(PinnedDrop); @@ -46,9 +50,23 @@ impl ToTokens for Args { struct FieldInfo<'a> { field: &'a Field, + member: Member, pinned: bool, } +impl FieldInfo<'_> { + fn ident(&self) -> Ident { + member_ident(&self.member) + } + + fn display_name(&self) -> String { + match &self.member { + Member::Named(ident) => format!("`{ident}`"), + Member::Unnamed(Index { index, .. }) => format!("index `{index}`"), + } + } +} + pub(crate) fn pin_data( args: Args, input: Item, @@ -136,10 +154,12 @@ pub(crate) fn pin_data( replacer.visit_generics_mut(&mut struct_.generics); replacer.visit_fields_mut(&mut struct_.fields); + let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_)); let fields: Vec> = struct_ .fields .iter_mut() - .map(|field| { + .enumerate() + .map(|(index, field)| { let len = field.attrs.len(); field.attrs.retain(|a| !a.path().is_ident("pin")); let pinned_count = len - field.attrs.len(); @@ -151,23 +171,30 @@ pub(crate) fn pin_data( !field.attrs.iter().any(|a| a.path().is_ident("cfg")), "cfgs should be all resolved at this point" ); + let member = match &field.ident { + Some(ident) => Member::Named(ident.clone()), + None => Member::Unnamed(Index { + index: index as u32, + span: field.span(), + }), + }; FieldInfo { field: &*field, + member, pinned: pinned_count != 0, } }) .collect(); for field in &fields { - 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", + field.display_name(), ), ); } @@ -175,8 +202,13 @@ pub(crate) fn pin_data( let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args); - let projections = - generate_projections(&struct_.vis, &struct_.ident, &struct_.generics, &fields); + let projections = generate_projections( + &struct_.vis, + &struct_.ident, + &struct_.generics, + is_tuple_struct, + &fields, + ); let the_pin_data = generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields); @@ -238,7 +270,7 @@ fn generate_unpin_impl( unreachable!() }; let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| { - let ident = f.field.ident.as_ref().unwrap(); + let ident = f.ident(); let ty = &f.field.ty; quote!( #ident: #ty @@ -320,6 +352,7 @@ fn generate_projections( vis: &Visibility, ident: &Ident, generics: &Generics, + is_tuple_struct: bool, fields: &[FieldInfo<'_>], ) -> TokenStream { let (impl_generics, ty_generics, _) = generics.split_for_impl(); @@ -332,28 +365,32 @@ fn generate_projections( let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields .iter() .map(|field| { - let Field { vis, ident, ty, .. } = &field.field; + let Field { vis, ty, .. } = &field.field; + let member = &field.member; + // The projection of a tuple struct is a tuple struct itself, so its fields are + // positional and must not be named. + let name = (!is_tuple_struct).then(|| { + let ident = field.ident(); + quote!(#ident:) + }); - let ident = ident - .as_ref() - .expect("only structs with named fields are supported"); if field.pinned { ( quote!( - #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, + #vis #name ::core::pin::Pin<&'__pin mut #ty>, ), quote!( // SAFETY: this field is structurally pinned. - #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) }, + #name unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) }, ), ) } else { ( quote!( - #vis #ident: &'__pin mut #ty, + #vis #name &'__pin mut #ty, ), quote!( - #ident: &mut #this.#ident, + #name &mut #this.#member, ), ) } @@ -362,24 +399,52 @@ fn generate_projections( let structurally_pinned_fields_docs = fields .iter() .filter(|f| f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); + .map(|f| format!(" - {}", f.display_name())); let not_structurally_pinned_fields_docs = fields .iter() .filter(|f| !f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); + .map(|f| format!(" - {}", f.display_name())); let docs = format!(" Pin-projections of [`{ident}`]"); + let (projection_def, projection_init) = if is_tuple_struct { + ( + quote! { + #vis struct #projection #generics_with_pin_lt ( + #(#fields_decl)* + ::core::marker::PhantomData<&'__pin mut ()>, + ) #whr; + }, + quote! { + #projection( + #(#fields_proj)* + ::core::marker::PhantomData, + ) + }, + ) + } else { + ( + quote! { + #vis struct #projection #generics_with_pin_lt + #whr + { + #(#fields_decl)* + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + } + }, + quote! { + #projection { + #(#fields_proj)* + ___pin_phantom_data: ::core::marker::PhantomData, + } + }, + ) + }; quote! { #[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 #projection #generics_with_pin_lt - #whr - { - #(#fields_decl)* - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, - } + #projection_def impl #impl_generics #ident #ty_generics #whr @@ -397,10 +462,7 @@ fn generate_projections( ) -> #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 { - #(#fields_proj)* - ___pin_phantom_data: ::core::marker::PhantomData, - } + #projection_init } } } @@ -421,11 +483,9 @@ fn generate_the_pin_data( let field_accessors = fields .iter() .map(|f| { - let Field { vis, ident, ty, .. } = f.field; - - let field_name = ident - .as_ref() - .expect("only structs with named fields are supported"); + let Field { vis, ty, .. } = f.field; + let field_name = f.ident(); + let member = &f.member; let pin_marker = if f.pinned { quote!(Pinned) } else { @@ -450,7 +510,7 @@ fn generate_the_pin_data( // - 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) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#member) } } } }) diff --git a/src/lib.rs b/src/lib.rs index 7600cdbb..b28c62fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -304,6 +304,9 @@ pub use alloc::InPlaceInit; /// This macro enables the use of the [`pin_init!`] macro. When pin-initializing a `struct`, /// then `#[pin]` directs the type of initializer that is required. /// +/// Tuple structs are supported as well. Their fields have no names, so the generated projection +/// is a tuple struct too and its fields are accessed by index. +/// /// If your `struct` implements `Drop`, then you need to add `PinnedDrop` as arguments to this /// macro, and change your `Drop` implementation to `PinnedDrop` annotated with /// `#[`[`macro@pinned_drop`]`]`, since dropping pinned values requires extra care. @@ -327,6 +330,26 @@ pub use alloc::InPlaceInit; /// } /// ``` /// +/// The same as a tuple struct, projected by index: +/// +/// ``` +/// # #![feature(allocator_api)] +/// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*; +/// use core::pin::Pin; +/// use pin_init::pin_data; +/// +/// enum Command { +/// /* ... */ +/// } +/// +/// #[pin_data] +/// struct DriverData(#[pin] CMutex>, Box<[u8; 1024 * 1024]>); +/// +/// fn queue(data: Pin<&mut DriverData>) -> Pin<&mut CMutex>> { +/// data.project().0 +/// } +/// ``` +/// /// ``` /// # #![feature(allocator_api)] /// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*; @@ -490,12 +513,14 @@ macro_rules! stack_pin_init { (let $var:ident $(: $t:ty)? = $val:expr) => { let val = $val; let mut $var = ::core::pin::pin!($crate::__internal::StackInit$(::<$t>)?::uninit()); - let mut $var = match $crate::__internal::StackInit::init($var, val) { + // The `Infallible` error type is what requires the initializer to be infallible. It has + // to be annotated here rather than in the `Err` arm below, because binding a value of an + // uninhabited type makes everything following it unreachable. + let res: ::core::result::Result<_, ::core::convert::Infallible> = + $crate::__internal::StackInit::init($var, val); + let mut $var = match res { Ok(res) => res, - Err(x) => { - let x: ::core::convert::Infallible = x; - match x {} - } + Err(x) => match x {}, }; }; } @@ -575,7 +600,7 @@ macro_rules! stack_try_pin_init { }; } -/// Construct an in-place, fallible pinned initializer for `struct`s. +/// Construct an in-place, fallible pinned initializer for structs, including tuple structs. /// /// The error type defaults to [`Infallible`]; if you need a different one, write `? Error` at the /// end, after the struct initializer. @@ -609,6 +634,42 @@ macro_rules! stack_try_pin_init { /// # Box::pin_init(demo()).unwrap(); /// ``` /// +/// The fields of a tuple struct are addressed by their index: +/// +/// ```rust +/// # use pin_init::*; +/// # use core::pin::Pin; +/// #[pin_data] +/// struct Pair(usize, Bar); +/// +/// #[pin_data] +/// struct Bar { +/// x: u32, +/// } +/// +/// # fn demo() -> impl PinInit { +/// let initializer = pin_init!(Pair { +/// 0: 42, +/// 1 <- Bar { x: 64 }, +/// }); +/// # initializer } +/// # Box::pin_init(demo()).unwrap(); +/// ``` +/// +/// A tuple struct whose fields are all set to a value can also be written like a call to its +/// constructor: +/// +/// ```rust +/// # use pin_init::*; +/// #[pin_data] +/// struct Pair(usize, usize); +/// +/// # fn demo() -> impl PinInit { +/// let initializer = pin_init!(Pair(42, 64)); +/// # initializer } +/// # Box::pin_init(demo()).unwrap(); +/// ``` +/// /// Arbitrary Rust expressions can be used to set the value of a variable. /// /// The fields are initialized in the order that they appear in the initializer. So it is possible @@ -727,9 +788,13 @@ macro_rules! stack_try_pin_init { /// /// # Syntax /// -/// As already mentioned in the examples above, inside of `pin_init!` a `struct` initializer with -/// the following modifications is expected: +/// As already mentioned in the examples above, inside of `pin_init!` a struct initializer with the +/// following modifications is expected: /// - Fields that you want to initialize in-place have to use `<-` instead of `:`. +/// - Tuple struct fields are named by their index, as in `0: value` or `0 <- initializer`. They +/// are not exposed by a `let` binding, since they have no name to bind. +/// - A tuple struct can also be initialized with constructor syntax, as in `Type(value, value)`. +/// Since its arguments are not named, they cannot use `<-`; write them out by index instead. /// - You can use `_: { /* run any user-code here */ },` anywhere where you can place fields in /// order to run arbitrary code. /// - In front of the initializer you can write `&this in` to have access to a [`NonNull`] @@ -768,7 +833,7 @@ macro_rules! stack_try_pin_init { /// [`NonNull`]: core::ptr::NonNull pub use pin_init_internal::pin_init; -/// Construct an in-place, fallible initializer for `struct`s. +/// Construct an in-place, fallible initializer for structs, including tuple structs. /// /// This macro defaults the error to [`Infallible`]; if you need a different one, write `? Error` /// at the end, after the struct initializer. diff --git a/tests/attrs.rs b/tests/attrs.rs index d7a7e298..f2ec7663 100644 --- a/tests/attrs.rs +++ b/tests/attrs.rs @@ -13,8 +13,17 @@ struct Foo { member: u8, } +#[pin_data] +#[derive(serde::Serialize)] +struct Tuple( + #[pin] + #[serde()] + u8, +); + #[test] fn test_attribute() { stack_pin_init!(let p = init!(Foo { member: 0 })); println!("{}", p.member); + let _ = Tuple(0); } diff --git a/tests/cfg_explode.rs b/tests/cfg_explode.rs new file mode 100644 index 00000000..eb778122 --- /dev/null +++ b/tests/cfg_explode.rs @@ -0,0 +1,37 @@ +#![allow(unexpected_cfgs)] + +use pin_init::*; + +// `#[pin_data]` and `[pin_]init!` resolve field cfgs by re-invoking themselves once per `cfg`'d +// field. Only one of the two generated branches is ever expanded, so this stays linear; were it +// exponential in the number of `cfg`s, this test would not finish. +macro_rules! explode { + ($($field:ident)*) => { + #[pin_data] + pub struct Tuple( + $( + #[cfg($field)] + u32, + )* + u32, + ); + + fn init_tuple() -> impl PinInit { + pin_init!(Tuple( + $( + #[cfg($field)] + 1, + )* + 0, + )) + } + }; +} + +explode!(a b c d e f g h i j k l m n o p q r s t u v w x y z); + +#[test] +fn cfg_explode() { + stack_pin_init!(let tuple = init_tuple()); + assert_eq!(tuple.as_ref().get_ref().0, 0); +} diff --git a/tests/cfgs.rs b/tests/cfgs.rs index f1be1bc2..07d2bf9a 100644 --- a/tests/cfgs.rs +++ b/tests/cfgs.rs @@ -1,4 +1,4 @@ -use pin_init::{pin_data, pin_init, PinInit}; +use pin_init::{pin_data, pin_init, stack_pin_init, PinInit}; #[pin_data] pub struct Struct { @@ -27,3 +27,77 @@ pub struct Struct2 { #[cfg(any())] non_exist: NonExistentType, } + +#[allow(dead_code)] +struct HiddenField; + +#[pin_data] +pub struct TupleStruct(#[cfg(any())] HiddenField, u32, u32); + +impl TupleStruct { + pub fn new() -> impl PinInit { + pin_init!(Self { + #[cfg(any())] + 0: HiddenField, + 1: 10, + 2: 20, + }) + } + + pub fn new_from_constructor() -> impl PinInit { + pin_init!(Self( + #[cfg(any())] + HiddenField, + 10, + 20, + )) + } +} + +#[test] +fn tuple_fields_are_renumbered_around_cfgd_out_fields() { + // Fields `1` and `2` became `0` and `1`, because field `0` is `cfg`'d out. + stack_pin_init!(let indexed = TupleStruct::new()); + assert_eq!(indexed.as_ref().get_ref().0, 10); + assert_eq!(indexed.as_ref().get_ref().1, 20); + + stack_pin_init!(let constructed = TupleStruct::new_from_constructor()); + assert_eq!(constructed.as_ref().get_ref().0, 10); + assert_eq!(constructed.as_ref().get_ref().1, 20); +} + +#[pin_data] +pub struct FeatureTupleStruct( + #[cfg(not(feature = "std"))] + #[pin] + core::marker::PhantomPinned, + u32, +); + +impl FeatureTupleStruct { + pub fn new() -> impl PinInit { + pin_init!(Self { + #[cfg(not(feature = "std"))] + 0: core::marker::PhantomPinned, + 1: 5, + }) + } +} + +#[test] +fn tuple_fields_follow_feature_cfgs() { + #[cfg(not(feature = "std"))] + fn assert_pinned(_: core::pin::Pin<&mut T>) {} + + stack_pin_init!(let value = FeatureTupleStruct::new()); + let projected = value.as_mut().project(); + #[cfg(not(feature = "std"))] + { + assert_pinned(projected.0); + assert_eq!(*projected.1, 5); + } + #[cfg(feature = "std")] + { + assert_eq!(*projected.0, 5); + } +} diff --git a/tests/tuple_struct.rs b/tests/tuple_struct.rs new file mode 100644 index 00000000..4a5ec4d1 --- /dev/null +++ b/tests/tuple_struct.rs @@ -0,0 +1,202 @@ +#![cfg_attr(feature = "alloc", feature(allocator_api))] + +use core::pin::Pin; +use pin_init::*; + +#[allow(unused_attributes)] +#[path = "../examples/mutex.rs"] +mod mutex; +use mutex::*; + +fn assert_pinned_mutex(_: &Pin<&mut CMutex>) {} + +fn assert_unpin() {} + +#[pin_data] +struct TupleStruct(#[pin] CMutex, i32); + +#[test] +fn tuple_struct_init_and_projection() { + 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); + assert_eq!(*projected.0.as_ref().get_ref().lock(), 7); + assert_eq!(*projected.1, 13); +} + +#[pin_data] +struct Triple(i32, i32, i32); + +#[test] +fn tuple_struct_init_without_pinning() { + stack_pin_init!(let triple = init!(Triple { 0: 37, 1: 41, 2: 43 })); + + assert_eq!(triple.as_ref().get_ref().0, 37); + assert_eq!(triple.as_ref().get_ref().1, 41); + assert_eq!(triple.as_ref().get_ref().2, 43); +} + +#[test] +fn tuple_struct_constructor_syntax() { + stack_pin_init!(let pinned = pin_init!(Triple(11, 29, 31))); + stack_pin_init!(let unpinned = init!(Triple(11, 29, 31))); + + for triple in [pinned.as_ref().get_ref(), unpinned.as_ref().get_ref()] { + assert_eq!(triple.0, 11); + assert_eq!(triple.1, 29); + assert_eq!(triple.2, 31); + } +} + +#[pin_data] +struct ValueTuple(T, i32); + +#[test] +fn tuple_struct_constructor_infers_generics() { + stack_pin_init!(let tuple = pin_init!(ValueTuple(9u32, 6))); + + assert_eq!(tuple.as_ref().get_ref().0, 9u32); + assert_eq!(tuple.as_ref().get_ref().1, 6); +} + +#[test] +#[allow(clippy::just_underscores_and_digits)] +fn tuple_struct_constructor_does_not_shadow_numeric_identifiers() { + let _0 = 6; + stack_pin_init!(let tuple = pin_init!(ValueTuple(9u32, _0))); + + assert_eq!(tuple.as_ref().get_ref().1, 6); +} + +#[pin_data] +struct DualPinned(#[pin] CMutex, #[pin] CMutex, usize); + +#[test] +fn tuple_struct_multi_pinned_fields_projection() { + stack_pin_init!( + let tuple = pin_init!(DualPinned:: { 0 <- CMutex::new(1), 1 <- CMutex::new(2), 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); +} + +#[pin_data] +struct GenericTuple<'a, T, const N: usize>(#[pin] CMutex<(&'a T, [u8; N])>, usize); + +#[test] +fn tuple_struct_generics_are_supported() { + let value = 77u16; + let payload = (&value, [1, 2, 3, 4]); + stack_pin_init!( + let tuple = pin_init!(GenericTuple { 0 <- CMutex::new(payload), 1: 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); +} + +#[pin_data] +struct TupleConst(#[pin] CMutex<[T; N]>, usize); + +#[test] +fn tuple_struct_const_generics_support_explicit_arguments() { + 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); +} + +#[pin_data] +#[allow(dead_code)] +struct UnpinnedMutexTuple(CMutex, usize); + +#[test] +fn tuple_struct_unpin_ignores_unpinned_non_unpin_field() { + assert_unpin::>(); +} + +#[pin_data(PinnedDrop)] +struct DropTuple(#[pin] CMutex, usize); + +static PINNED_DROP_TUPLE_DROPS: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); + +#[pinned_drop] +impl PinnedDrop for DropTuple { + fn drop(self: Pin<&mut Self>) { + let _ = self; + PINNED_DROP_TUPLE_DROPS.fetch_add(1, core::sync::atomic::Ordering::SeqCst); + } +} + +#[test] +fn tuple_struct_pinned_drop_delegates_from_drop() { + PINNED_DROP_TUPLE_DROPS.store(0, core::sync::atomic::Ordering::SeqCst); + { + stack_pin_init!(let _tuple = pin_init!(DropTuple { 0 <- CMutex::new(5usize), 1: 1 })); + } + assert_eq!( + PINNED_DROP_TUPLE_DROPS.load(core::sync::atomic::Ordering::SeqCst), + 1 + ); +} + +static FALLIBLE_TUPLE_DROPS: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); + +struct DropCounter; + +impl Drop for DropCounter { + fn drop(&mut self) { + FALLIBLE_TUPLE_DROPS.fetch_add(1, core::sync::atomic::Ordering::SeqCst); + } +} + +#[derive(Debug)] +struct InitError; + +impl From for InitError { + fn from(error: core::convert::Infallible) -> Self { + match error {} + } +} + +fn fail() -> impl Init { + // SAFETY: The closure returns an error without touching the slot. + unsafe { init_from_closure(|_| Err(InitError)) } +} + +fn tuple_failing_init() -> impl PinInit, InitError> { + pin_init!(TupleStruct { + 0 <- CMutex::new(DropCounter), + 1 <- fail(), + }? InitError) +} + +#[test] +fn tuple_struct_fallible_init_drops_initialized_fields() { + FALLIBLE_TUPLE_DROPS.store(0, core::sync::atomic::Ordering::SeqCst); + stack_try_pin_init!(let tuple: TupleStruct = tuple_failing_init()); + assert!(matches!(tuple, Err(InitError))); + assert_eq!( + FALLIBLE_TUPLE_DROPS.load(core::sync::atomic::Ordering::SeqCst), + 1 + ); +} diff --git a/tests/ui/compile-fail/init/no_error_coercion.stderr b/tests/ui/compile-fail/init/no_error_coercion.stderr index 974c3c15..ddc13df7 100644 --- a/tests/ui/compile-fail/init/no_error_coercion.stderr +++ b/tests/ui/compile-fail/init/no_error_coercion.stderr @@ -7,8 +7,8 @@ error[E0277]: `?` couldn't convert the error to `std::alloc::AllocError` 19 | | }? AllocError) | | ^ | | | - | |______________________the trait `From` is not implemented for `std::alloc::AllocError` - | this can't be annotated with `?` because it has type `Result<_, Infallible>` + | |______________________the trait `From` is not implemented for `std::alloc::AllocError` + | this can't be annotated with `?` because it has type `Result<_, !>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = note: this error originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/init/no_tuple_paren_arrow.rs b/tests/ui/compile-fail/init/no_tuple_paren_arrow.rs new file mode 100644 index 00000000..2df60080 --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_paren_arrow.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple(<- 1, 2)); +} diff --git a/tests/ui/compile-fail/init/no_tuple_paren_arrow.stderr b/tests/ui/compile-fail/init/no_tuple_paren_arrow.stderr new file mode 100644 index 00000000..1edc0812 --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_paren_arrow.stderr @@ -0,0 +1,5 @@ +error: `<-` is not supported in tuple constructor syntax; name the fields by index instead, e.g. `Type { 0 <- initializer, 1: value }` + --> tests/ui/compile-fail/init/no_tuple_paren_arrow.rs:7:29 + | +7 | let _ = pin_init!(Tuple(<- 1, 2)); + | ^^ 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..340fb512 --- /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..fde31e46 --- /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:33 + | +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_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..f18d6e88 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_invalid_field.stderr @@ -0,0 +1,33 @@ +error[E0599]: no method named `_2` found for struct `__ThePinData` in the current scope + --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:13 + | +3 | #[pin_data] + | ----------- method `_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[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[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/pin_data/missing_pin.stderr b/tests/ui/compile-fail/pin_data/missing_pin.stderr index f480b637..fa39129f 100644 --- a/tests/ui/compile-fail/pin_data/missing_pin.stderr +++ b/tests/ui/compile-fail/pin_data/missing_pin.stderr @@ -8,7 +8,7 @@ error[E0277]: the trait bound `impl PinInit: Init` is not satis | |__________- required by a bound introduced by this call | help: the trait `Init` is not implemented for `impl PinInit` - but trait `Init, Infallible>` is implemented for it + but trait `Init, !>` is implemented for it --> src/lib.rs | | unsafe impl Init for T {} 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..bdba06c3 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs @@ -0,0 +1,8 @@ +#![deny(warnings)] + +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..7dfbfdab --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.stderr @@ -0,0 +1,13 @@ +error: use of deprecated function `_::warn`: + 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:6:20 + | +6 | struct Tuple(T, core::marker::PhantomPinned); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs:1:9 + | +1 | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(deprecated)]` implied by `#[deny(warnings)]` diff --git a/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs new file mode 100644 index 00000000..1500cc44 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs @@ -0,0 +1,11 @@ +use core::marker::PhantomPinned; +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] PhantomPinned, T); + +fn assert_unpin() {} + +fn main() { + assert_unpin::>(); +} diff --git a/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.stderr b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.stderr new file mode 100644 index 00000000..1cbfd3fa --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.stderr @@ -0,0 +1,26 @@ +error[E0277]: `PhantomPinned` cannot be unpinned + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs:10:20 + | +10 | assert_unpin::>(); + | ^^^^^^^^^^^^ within `__Unpin<'_, usize>`, 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<'_, usize>` + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs:4:1 + | + 4 | #[pin_data] + | ^^^^^^^^^^^ +note: required for `Tuple` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs:4:1 + | + 4 | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here + 5 | struct Tuple(#[pin] PhantomPinned, T); + | ^^^^^^^^ +note: required by a bound in `assert_unpin` + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs:7:20 + | + 7 | 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) diff --git a/tests/ui/expand/tuple_struct.expanded.rs b/tests/ui/expand/tuple_struct.expanded.rs new file mode 100644 index 00000000..81287de7 --- /dev/null +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -0,0 +1,259 @@ +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, non_snake_case)] +#[doc(hidden)] +struct FooProjection<'__pin, 'a, T: Copy, const N: usize>( + &'__pin mut &'a mut [T; N], + ::core::pin::Pin<&'__pin mut PhantomPinned>, + &'__pin mut usize, + ::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( + &mut this.0, + unsafe { ::core::pin::Pin::new_unchecked(&mut this.1) }, + &mut this.2, + ::core::marker::PhantomData, + ) + } +} +const _: () = { + #[doc(hidden)] + struct __ThePinData<'a, T: Copy, const N: usize> { + __phantom: ::pin_init::__internal::PhantomInvariant>, + } + impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __ThePinData<'a, T, N> { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __ThePinData<'a, T, N> {} + #[allow(dead_code)] + impl<'a, T: Copy, const N: usize> __ThePinData<'a, T, N> { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: FnOnce( + *mut Foo<'a, T, N>, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + /// # Safety + /// + /// - `slot` is valid and properly aligned. + /// - `(*slot).#field_name` is properly aligned. + /// - `(*slot).#field_name` points to uninitialized and exclusively accessed + /// memory. + #[allow(non_snake_case)] + #[inline(always)] + unsafe fn _0( + self, + slot: *mut Foo<'a, T, N>, + ) -> ::pin_init::__internal::Slot< + ::pin_init::__internal::Unpinned, + &'a mut [T; N], + > { + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).0) } + } + /// # Safety + /// + /// - `slot` is valid and properly aligned. + /// - `(*slot).#field_name` is properly aligned. + /// - `(*slot).#field_name` points to uninitialized and exclusively accessed + /// memory. + #[allow(non_snake_case)] + #[inline(always)] + unsafe fn _1( + self, + slot: *mut Foo<'a, T, N>, + ) -> ::pin_init::__internal::Slot< + ::pin_init::__internal::Pinned, + PhantomPinned, + > { + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).1) } + } + /// # Safety + /// + /// - `slot` is valid and properly aligned. + /// - `(*slot).#field_name` is properly aligned. + /// - `(*slot).#field_name` points to uninitialized and exclusively accessed + /// memory. + #[allow(non_snake_case)] + #[inline(always)] + unsafe fn _2( + self, + slot: *mut Foo<'a, T, N>, + ) -> ::pin_init::__internal::Slot<::pin_init::__internal::Unpinned, usize> { + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).2) } + } + } + unsafe impl<'a, T: Copy, const N: usize> ::pin_init::__internal::HasPinData + for Foo<'a, T, N> { + type PinData = __ThePinData<'a, T, N>; + #[inline] + unsafe fn __pin_data() -> Self::PinData { + __ThePinData { + __phantom: ::pin_init::__internal::PhantomInvariant::new(), + } + } + } + #[allow(dead_code, non_snake_case)] + struct __Unpin<'__pin, 'a, T: Copy, const N: usize> { + __phantom_pin: ::pin_init::__internal::PhantomInvariantLifetime<'__pin>, + __phantom: ::pin_init::__internal::PhantomInvariant>, + _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 {} + impl MustNotImplDrop for T {} + impl<'a, T: Copy, const N: usize> MustNotImplDrop for Foo<'a, T, N> {} + 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 _ = { + let __data = unsafe { + use ::pin_init::__internal::HasInitData; + Foo::__init_data() + }; + let init = __data + .__make_closure::< + _, + ::core::convert::Infallible, + >(move |slot| { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut first); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .init(10)?; + ::core::mem::forget(___0_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___2_guard); + #[allow(unreachable_code)] + let _ = || unsafe { + let _ = &(*slot).0; + let _ = &(*slot).1; + let _ = &(*slot).2; + ::core::ptr::write( + slot, + Foo { + 0: loop {}, + 1: loop {}, + 2: loop {}, + }, + ) + }; + Ok(unsafe { ::pin_init::__internal::InitOk::new() }) + }); + let init = move | + slot, + | -> ::core::result::Result<(), ::core::convert::Infallible> { + init(slot).map(|__InitOk| ()) + }; + unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } + }; + let mut second = [4u8, 5, 6]; + let _ = { + let __data = unsafe { + use ::pin_init::__internal::HasInitData; + Foo::__init_data() + }; + let init = __data + .__make_closure::< + _, + ::core::convert::Infallible, + >(move |slot| { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut second); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .write(20); + ::core::mem::forget(___0_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___2_guard); + #[allow(unreachable_code)] + let _ = || unsafe { + let _ = &(*slot).0; + let _ = &(*slot).1; + let _ = &(*slot).2; + ::core::ptr::write( + slot, + Foo { + 0: loop {}, + 1: loop {}, + 2: loop {}, + }, + ) + }; + Ok(unsafe { ::pin_init::__internal::InitOk::new() }) + }); + let init = move | + slot, + | -> ::core::result::Result<(), ::core::convert::Infallible> { + init(slot).map(|__InitOk| ()) + }; + unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } + }; +} diff --git a/tests/ui/expand/tuple_struct.rs b/tests/ui/expand/tuple_struct.rs new file mode 100644 index 00000000..27d8c7f7 --- /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)); +}