Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added

- Support tuple structs in `#[pin_data]`, including tuple struct projections, and tuple struct
initialization in `init!` and `pin_init!`.
- `[pin_]init_scope` functions to run arbitrary code inside of an initializer.
- `&'static mut MaybeUninit<T>` now implements `InPlaceWrite`. This enables users to use external
allocation mechanisms such as `static_cell`.
Expand Down
230 changes: 172 additions & 58 deletions internal/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
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};
Expand All @@ -17,8 +18,9 @@ pub(crate) struct Initializer {
attrs: Vec<InitializerAttribute>,
this: Option<This>,
path: Path,
brace_token: token::Brace,
delim_close_span: Span,
fields: Punctuated<InitializerField, Token![,]>,
is_tuple_constructor: bool,
rest: Option<(Token![..], Expr)>,
error: Option<(Token![?], Type)>,
}
Expand All @@ -36,11 +38,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,
},
Expand All @@ -52,14 +54,28 @@ enum InitializerKind {
}

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_ident(member: &Member) -> Ident {
match member {
Member::Named(ident) => ident.clone(),
Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"),
}
}

fn member_binding(member: &Member) -> Option<Ident> {
match member {
Member::Named(ident) => Some(ident.clone()),
Member::Unnamed(_) => None,
}
}

enum InitializerAttribute {
DefaultError(DefaultErrorAttribute),
}
Expand All @@ -73,15 +89,20 @@ pub(crate) fn expand(
attrs,
this,
path,
brace_token,
delim_close_span,
fields,
is_tuple_constructor,
rest,
error,
}: Initializer,
default_error: Option<&'static str>,
pinned: bool,
dcx: &mut DiagCtxt,
) -> Result<TokenStream, ErrorGuaranteed> {
if is_tuple_constructor {
check_tuple_constructor_cfgs(&fields, dcx)?;
}

let error = error.map_or_else(
|| {
if let Some(default_error) = attrs.iter().fold(None, |acc, attr| {
Expand All @@ -96,7 +117,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 `? <type>` after `}`");
dcx.error(delim_close_span, "expected `? <type>` after initializer");
parse_quote!(::core::convert::Infallible)
}
},
Expand Down Expand Up @@ -229,9 +250,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, .. } => {
res.extend(quote! {
#(#attrs)*
Expand All @@ -241,27 +262,28 @@ fn init_fields(
continue;
}
};
let ident = 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.
// - `make_field_check` checks that the field is properly aligned.
// - `make_field_check` prevents the field from being used twice, therefore
// it is exclusively accessed and has not been initialized.
(unsafe { #data.#ident(#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.
// - The field pointer is valid.
// - `make_field_check` checks that the field is properly aligned.
// - `make_field_check` prevents the field from being used twice, therefore
// it 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
)
})
}
Expand All @@ -271,7 +293,7 @@ fn init_fields(
let guard = format_ident!("__{ident}_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))
Expand All @@ -291,15 +313,20 @@ fn init_fields(
}
InitializerKind::Code { .. } => unreachable!(),
};
let binding = member_binding(member).map(|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();
}
});

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);
Expand All @@ -324,9 +351,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! {
Expand Down Expand Up @@ -362,36 +389,101 @@ fn make_field_check(
}
}

impl Parse for Initializer {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let this = input.peek(Token![&]).then(|| input.parse()).transpose()?;
let path = input.parse()?;
let content;
let brace_token = braced!(content in input);
let mut fields = Punctuated::new();
loop {
type InitFields = Punctuated<InitializerField, Token![,]>;
type InitRest = Option<(Token![..], Expr)>;

fn parse_brace_initializer(
input: syn::parse::ParseStream<'_>,
) -> syn::Result<(Span, InitFields, InitRest)> {
let content;
let brace_token = braced!(content in input);
let mut fields = Punctuated::new();
loop {
let lh = content.lookahead1();
if lh.peek(End) || lh.peek(Token![..]) {
break;
} else if lh.peek(Ident) || lh.peek(LitInt) || lh.peek(Token![_]) || lh.peek(Token![#]) {
fields.push_value(content.parse()?);
let lh = content.lookahead1();
if lh.peek(End) || lh.peek(Token![..]) {
if lh.peek(End) {
break;
} else if lh.peek(Ident) || lh.peek(Token![_]) || lh.peek(Token![#]) {
fields.push_value(content.parse()?);
let lh = content.lookahead1();
if lh.peek(End) {
break;
} else if lh.peek(Token![,]) {
fields.push_punct(content.parse()?);
} else {
return Err(lh.error());
}
} else if lh.peek(Token![,]) {
fields.push_punct(content.parse()?);
} else {
return Err(lh.error());
}
} else {
return Err(lh.error());
}
let rest = content
.peek(Token![..])
.then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?)))
.transpose()?;
}
let rest = content
.peek(Token![..])
.then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?)))
.transpose()?;
Ok((brace_token.span.close(), fields, rest))
}

fn parse_paren_initializer(input: syn::parse::ParseStream<'_>) -> syn::Result<(Span, InitFields)> {
let content;
let paren_token = parenthesized!(content in input);
let mut fields = Punctuated::new();

while !content.is_empty() {
let attrs = content.call(Attribute::parse_outer)?;
if content.peek(Token![<-]) {
return Err(content.error(
"`<-` is not supported in tuple constructor syntax; use braces with indices, e.g. `Type { 0 <- init, 1: value }`",
));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be checked after parsing, thus would allow it to use the dcx mechanism and provide better error recovery.

}
let value: Expr = content.parse()?;
let index = fields.len() as u32;
fields.push_value(InitializerField {
attrs,
kind: InitializerKind::Value {
member: Member::Unnamed(Index {
index,
span: value.span(),
}),
value: Some((Token![:](value.span()), value)),
},
});
if content.is_empty() {
break;
}
fields.push_punct(content.parse()?);
}
Ok((paren_token.span.close(), fields))
}

fn check_tuple_constructor_cfgs(
fields: &Punctuated<InitializerField, Token![,]>,
dcx: &mut DiagCtxt,
) -> Result<(), ErrorGuaranteed> {
for field in fields.iter().take(fields.len().saturating_sub(1)) {
if let Some(attr) = field.attrs.iter().find(|attr| attr.path().is_ident("cfg")) {
return Err(dcx.error(
attr,
"`#[cfg]` on tuple constructor arguments is only supported on the last argument",
));
}
}
Ok(())
}

impl Parse for Initializer {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let this = input.peek(Token![&]).then(|| input.parse()).transpose()?;
let path = input.parse()?;
let (delim_close_span, fields, is_tuple_constructor, rest) = if input.peek(token::Brace) {
let (close_span, fields, rest) = parse_brace_initializer(input)?;
(close_span, fields, false, rest)
} else if input.peek(token::Paren) {
let (close_span, fields) = parse_paren_initializer(input)?;
(close_span, fields, true, None)
} else {
return Err(input.error("expected curly braces or parentheses"));
};
let error = input
.peek(Token![?])
.then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?)))
Expand All @@ -411,8 +503,9 @@ impl Parse for Initializer {
attrs,
this,
path,
brace_token,
delim_close_span,
fields,
is_tuple_constructor,
rest,
error,
})
Expand Down Expand Up @@ -454,22 +547,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())
}
Expand Down
Loading
Loading