blob: ded2b52833761893349f2059e28c956c93471938 [file]
// SPDX-License-Identifier: Apache-2.0 OR MIT
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
use syn::{
braced, parenthesized,
parse::{End, Parse},
parse_quote,
punctuated::{Pair, Punctuated},
spanned::Spanned,
token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Index, LitInt, Member, Path, Token,
Type,
};
use crate::{
diagnostics::{DiagCtxt, ErrorGuaranteed},
util::*,
};
pub(crate) struct Initializer<Kind = InitExprKind> {
attrs: Vec<InitializerAttribute>,
this: Option<This>,
kind: Kind,
error: Option<(Token![?], Type)>,
}
pub(crate) struct InitExprStruct {
path: Path,
brace_token: token::Brace,
fields: Punctuated<InitializerField, Token![,]>,
rest: Option<(Token![..], Expr)>,
}
pub(crate) struct InitExprTuple {
path: Path,
paren_token: token::Paren,
fields: Punctuated<InitTupleField, Token![,]>,
}
pub(crate) enum InitExprKind {
Struct(InitExprStruct),
Tuple(InitExprTuple),
}
struct InitTupleField {
attrs: Vec<Attribute>,
value: Expr,
}
impl InitExprTuple {
fn normalize(self) -> InitExprStruct {
let InitExprTuple {
path,
paren_token,
fields,
} = self;
InitExprStruct {
path,
brace_token: token::Brace {
span: paren_token.span,
},
fields: 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.try_into().unwrap(),
span,
}),
value: Some((Token![:](span), field.value)),
},
};
Pair::new(field, comma)
})
.collect(),
rest: None,
}
}
}
struct This {
_and_token: Token![&],
ident: Ident,
_in_token: Token![in],
}
struct InitializerField {
attrs: Vec<Attribute>,
kind: InitializerKind,
}
enum InitializerKind {
Value {
member: Member,
value: Option<(Token![:], Expr)>,
},
Init {
member: Member,
left_arrow_token: Token![<-],
value: Expr,
},
Code {
_underscore_token: Token![_],
_colon_token: Token![:],
block: Block,
},
}
impl InitializerKind {
fn member(&self) -> Option<&Member> {
match self {
Self::Value { member, .. } | Self::Init { member, .. } => Some(member),
Self::Code { .. } => None,
}
}
}
enum InitializerAttribute {
DefaultError(DefaultErrorAttribute),
}
struct DefaultErrorAttribute {
ty: Box<Type>,
}
pub(crate) fn expand_with_cfg(
initializer: Initializer,
default_error: Option<&'static str>,
pinned: bool,
dcx: &mut DiagCtxt,
) -> Result<TokenStream, ErrorGuaranteed> {
let initializer = match initializer.kind {
InitExprKind::Tuple(expr) => {
let mut initializer = Initializer {
attrs: initializer.attrs,
this: initializer.this,
kind: expr,
error: initializer.error,
};
// 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. Struct expression syntax uses explicit numbers, so
// there is no need to pre-expand them and we only need to emit their cfgs on generated
// code.
for (field_idx, field) in initializer.kind.fields.iter_mut().enumerate() {
let cfg = field.attrs.extract_cfg_attrs();
if cfg.is_empty() {
continue;
}
let true_initializer = initializer.to_token_stream();
initializer.kind.fields = initializer
.kind
.fields
.into_pairs()
.enumerate()
.filter(|&(index, _)| index != field_idx)
.map(|(_, pair)| pair)
.collect();
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, otherwise the
// expression becomes unit type.
#[cfg(all(#(#cfg,)*))]
#macro_name! { #true_initializer }
#[cfg(not(all(#(#cfg,)*)))]
#macro_name! { #false_initializer }
}
});
}
// No cfgs left, we can normalize the initializer to the struct kind.
Initializer {
attrs: initializer.attrs,
this: initializer.this,
kind: initializer.kind.normalize(),
error: initializer.error,
}
}
InitExprKind::Struct(expr) => Initializer {
attrs: initializer.attrs,
this: initializer.this,
kind: expr,
error: initializer.error,
},
};
expand(initializer, default_error, pinned, dcx)
}
fn expand(
Initializer {
attrs,
this,
kind:
InitExprStruct {
path,
brace_token,
fields,
rest,
},
error,
}: Initializer<InitExprStruct>,
default_error: Option<&'static str>,
pinned: bool,
dcx: &mut DiagCtxt,
) -> Result<TokenStream, ErrorGuaranteed> {
let error = error.map_or_else(
|| {
if let Some(default_error) = attrs.iter().fold(None, |acc, attr| {
#[expect(irrefutable_let_patterns)]
if let InitializerAttribute::DefaultError(DefaultErrorAttribute { ty }) = attr {
Some(ty.clone())
} else {
acc
}
}) {
default_error
} else if let Some(default_error) = default_error {
syn::parse_str(default_error).unwrap()
} else {
dcx.error(
brace_token.span.close(),
"expected `? <type>` after initializer",
);
parse_quote!(::core::convert::Infallible)
}
},
|(_, err)| Box::new(err),
);
let (get_pin_data, init_from_closure) = if pinned {
(
Some(
quote_spanned! { path.span().resolved_at(Span::mixed_site()) =>
let data = ::pin_init::__internal::HasPinData::__pin_data(data);
},
),
format_ident!("pin_init_from_closure"),
)
} else {
(None, format_ident!("init_from_closure"))
};
let init_kind = get_init_kind(rest, dcx);
let zeroable_check = match init_kind {
InitKind::Normal => quote!(),
InitKind::Zeroing => quote_spanned! { Span::mixed_site() =>
// The user specified `..Zeroable::zeroed()` at the end of the list of fields.
// Therefore we check if the struct implements `Zeroable` and then zero the memory.
// This allows us to also remove the check that all fields are present (since we
// already set the memory to zero and that is a valid bit pattern).
fn assert_zeroable<T: ?::core::marker::Sized>(_: *mut T)
where T: ::pin_init::Zeroable
{}
// Ensure that the struct is indeed `Zeroable`.
assert_zeroable(slot);
// SAFETY: The type implements `Zeroable` by the check above.
unsafe { ::core::ptr::write_bytes(slot, 0, 1) };
},
};
let this = match this {
None => quote!(),
Some(This { ident, .. }) => quote_spanned! { Span::mixed_site() =>
// Create the `this` so it can be referenced by the user inside of the
// expressions creating the individual fields.
let #ident = unsafe { ::core::ptr::NonNull::new_unchecked(slot) };
},
};
// `mixed_site` ensures that the data is not accessible to the user-controlled code.
let init_fields = make_field_init(&fields, pinned, false);
let drop_check = make_field_init(&fields, pinned, true);
let field_check = make_field_check(&fields, init_kind, &path);
Ok(quote_spanned! { Span::mixed_site() => {
// Get the data about fields from the supplied type.
let data = {
use ::pin_init::__internal::HasInitData;
// Can't use `<#path as #has_data_trait>::#get_data`, since the user is able to omit
// generics (which need to be present with that syntax).
#path::__init_data()
};
#get_pin_data
// Ensure that `data` really is of type `data` and help with type inference:
let init = data.__make_closure::<_, #error>(
move |slot, data_lt| {
#zeroable_check
#this
// Generate init twice, which is mostly identical except for lifetimes.
if true {
// In this path, we use the field lifetime from HRTB to prevent environment
// lifetime from entering the fields, and to ensure that the dependency of
// fields is consistent with the field drop of the struct.
#init_fields
} else {
// In this path, we use local lifetime, to make sure that if initialization
// fails, the destructor execution will not cause lifetime issues. This is
// separate as implied bounds between field lifetimes can be inconsistent with
// that of the drop.
#drop_check
}
#field_check
// SAFETY: we are the `init!` macro that is allowed to call this.
Ok(unsafe { ::pin_init::__internal::InitOk::new() })
}
);
let init = move |slot| -> ::core::result::Result<(), #error> {
init(slot, data.__with_lt()).map(|__InitOk| ())
};
// SAFETY: TODO
unsafe { ::pin_init::#init_from_closure::<_, #error>(init) }
}})
}
enum InitKind {
Normal,
Zeroing,
}
fn get_init_kind(rest: Option<(Token![..], Expr)>, dcx: &mut DiagCtxt) -> InitKind {
let Some((dotdot, expr)) = rest else {
return InitKind::Normal;
};
match &expr {
Expr::Call(ExprCall { func, args, .. }) if args.is_empty() => match &**func {
Expr::Path(ExprPath {
attrs,
qself: None,
path:
Path {
leading_colon: None,
segments,
},
}) if attrs.is_empty()
&& segments.len() == 2
&& segments[0].ident == "Zeroable"
&& segments[0].arguments.is_none()
&& segments[1].ident == "init_zeroed"
&& segments[1].arguments.is_none() =>
{
return InitKind::Zeroing;
}
_ => {}
},
_ => {}
}
dcx.error(
dotdot.span().join(expr.span()).unwrap_or(expr.span()),
"expected nothing or `..Zeroable::init_zeroed()`.",
);
InitKind::Normal
}
/// Generate the code that initializes the fields of the struct using the initializers in `field`.
fn make_field_init(
fields: &Punctuated<InitializerField, Token![,]>,
pinned: bool,
dropck: bool,
) -> TokenStream {
let mut forget_guards = vec![];
let mut res = TokenStream::new();
for InitializerField { attrs, kind } in fields {
let cfgs = {
let mut cfgs = attrs.clone();
cfgs.retain(|attr| attr.path().is_ident("cfg"));
cfgs
};
let member = match kind {
InitializerKind::Value { member, .. } => member,
InitializerKind::Init { member, .. } => member,
InitializerKind::Code { block, .. } => {
let stmt = &block.stmts;
res.extend(quote! {
#(#attrs)*
{
#(#stmt)*
}
});
continue;
}
};
let ident = member.as_ident();
let span = Span::mixed_site().located_at(ident.span());
let slot = if pinned {
let data = if !dropck {
quote_spanned!(span => data_lt)
} else {
quote_spanned!(span => data.__with_lt())
};
quote_spanned! { span =>
// SAFETY:
// - `slot` is valid and properly aligned.
// - `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.#ident(slot) })
}
} else {
quote_spanned! { span =>
// For `init!()` macro, everything is unpinned.
// SAFETY:
// - `&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).#member
)
})
}
};
// `mixed_site` ensures that the guard is not accessible to the user-controlled code.
let guard = format_ident!("__{ident}_guard", span = span);
let full_span = kind.span().resolved_at(Span::mixed_site());
let init = match kind {
InitializerKind::Value { value, .. } => {
let (colon_span, value) = value
.as_ref()
.map(|(colon_token, value)| (colon_token.span(), quote!(#value)))
.unwrap_or_else(|| (member.span(), quote!(#member)));
// Use `:` as the span of the method name, so the type requirement appears to come
// from `:`.
let write =
format_ident!("write", span = colon_span.resolved_at(Span::mixed_site()));
quote_spanned! { full_span =>
#(#attrs)*
let mut #guard = #slot.#write(#value);
}
}
InitializerKind::Init {
value,
left_arrow_token,
..
} => {
// Use `<-` as the span of the method name, so the trait bound appears to come from
// `<-`.
let init = format_ident!(
"init",
span = left_arrow_token.span().resolved_at(Span::mixed_site())
);
quote_spanned! { full_span =>
#(#attrs)*
let mut #guard = #slot.#init(#value)?;
}
}
InitializerKind::Code { .. } => unreachable!(),
};
// A tuple field has no name that could be bound here (the `_0` identifiers are considered
// implementation detail and not user-facing).
let let_binding_method = if !dropck {
format_ident!("let_binding", span = span)
} else {
format_ident!("let_binding_in_dropck", span = span)
};
let binding = match member {
Member::Named(ident) => quote_spanned! { span =>
#(#cfgs)*
// Allow `non_snake_case` since the same warning is going to be reported for the
// struct field.
#[allow(unused_variables, non_snake_case)]
// Include `mut` so that `Pin<&mut T>` bindings can be reborrowed via `.as_mut()`.
let mut #ident = #guard.#let_binding_method();
},
Member::Unnamed(_) => quote!(),
};
res.extend(quote! {
#init
#binding
});
forget_guards.push(quote_spanned! { span =>
#(#cfgs)*
::core::mem::forget(#guard);
});
}
forget_guards.reverse();
quote! {
#res
// If execution reaches this point, all fields have been initialized. Therefore we can now
// dismiss the guards by forgetting them.
#(#forget_guards)*
}
}
/// Generate the check for ensuring that every field has been initialized and aligned.
fn make_field_check(
fields: &Punctuated<InitializerField, Token![,]>,
init_kind: InitKind,
path: &Path,
) -> TokenStream {
let align_checks: TokenStream = fields
.iter()
.filter_map(|f| {
let member = f.kind.member()?;
let span = member.span().resolved_at(Span::mixed_site());
let attrs = &f.attrs;
Some(quote_spanned! {span =>
// Create references to ensure that the initialized field is properly aligned.
// Unaligned fields will cause the compiler to emit E0793. We do not support
// unaligned fields since `Init::__init` requires an aligned pointer; the call to
// `ptr::write` for value-initialization case has the same requirement.
#(#attrs)*
let _ = &(*slot).#member;
})
})
.collect();
let fake_field_init: TokenStream = fields
.iter()
.filter_map(|f| {
let member = f.kind.member()?;
let span = member.span().resolved_at(Span::mixed_site());
let attrs = &f.attrs;
Some(quote_spanned! {span =>
#(#attrs)*
#member: loop {},
})
})
.collect();
let zeroing_trailer = match init_kind {
InitKind::Normal => None,
InitKind::Zeroing => Some(quote! {
..::core::mem::zeroed()
}),
};
let field_dup_checks = quote_spanned! { Span::mixed_site() =>
// If the zeroing trailer is not present, this checks that all fields have been
// mentioned exactly once. If the zeroing trailer is present, all missing fields will be
// zeroed, so this checks that all fields have been mentioned at most once. The use of
// struct initializer will still generate very natural error messages for any misuse.
::core::ptr::write(slot, #path {
#fake_field_init
#zeroing_trailer
})
};
quote_spanned! { Span::mixed_site() =>
#[allow(unreachable_code)]
// We use unreachable code to perform field checks. They're still checked by the compiler.
// SAFETY: this code is never executed.
let _ = || unsafe {
#align_checks
#field_dup_checks
};
}
}
impl InitExprStruct {
fn parse_with_path(path: Path, input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
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) {
break;
} 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()?;
Ok(Self {
path,
brace_token,
fields,
rest,
})
}
}
impl InitExprTuple {
fn parse_with_path(path: Path, input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
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 let Some(left_arrow_token) = content.parse::<Option<Token![<-]>>()? {
DiagCtxt::current(|dcx| {
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 }`",
)
});
}
fields.push_value(InitTupleField {
attrs,
value: content.parse()?,
});
if content.is_empty() {
break;
}
fields.push_punct(content.parse()?);
}
Ok(InitExprTuple {
path,
paren_token,
fields,
})
}
}
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 kind = if input.peek(token::Brace) {
InitExprKind::Struct(InitExprStruct::parse_with_path(path, input)?)
} else if input.peek(token::Paren) {
InitExprKind::Tuple(InitExprTuple::parse_with_path(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()?)))
.transpose()?;
let attrs = attrs
.into_iter()
.map(|a| {
if a.path().is_ident("default_error") {
a.parse_args::<DefaultErrorAttribute>()
.map(InitializerAttribute::DefaultError)
} else {
Err(syn::Error::new_spanned(a, "unknown initializer attribute"))
}
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
attrs,
this,
kind,
error,
})
}
}
impl Parse for DefaultErrorAttribute {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
Ok(Self { ty: input.parse()? })
}
}
impl Parse for This {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
Ok(Self {
_and_token: input.parse()?,
ident: input.parse()?,
_in_token: input.parse()?,
})
}
}
impl Parse for InitializerField {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
Ok(Self {
attrs,
kind: input.parse()?,
})
}
}
impl Parse for InitializerKind {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let lh = input.lookahead1();
let member = if lh.peek(Token![_]) {
return Ok(Self::Code {
_underscore_token: input.parse()?,
_colon_token: input.parse()?,
block: input.parse()?,
});
} else if lh.peek(Ident) || lh.peek(LitInt) {
input.parse::<Member>()?
} else {
return Err(lh.error());
};
let lh = input.lookahead1();
if lh.peek(Token![<-]) {
Ok(Self::Init {
member,
left_arrow_token: input.parse()?,
value: input.parse()?,
})
} else if lh.peek(Token![:]) {
Ok(Self::Value {
member,
value: Some((input.parse()?, input.parse()?)),
})
} else if matches!(member, Member::Named(_)) && (lh.peek(Token![,]) || lh.peek(End)) {
// Short-hand syntax, available for named fields only.
Ok(Self::Value {
member,
value: None,
})
} else {
Err(lh.error())
}
}
}
impl<Kind: ToTokens> ToTokens for Initializer<Kind> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
attrs,
this,
kind,
error,
} = self;
tokens.append_all(attrs);
this.to_tokens(tokens);
kind.to_tokens(tokens);
if let Some((question, ty)) = error {
question.to_tokens(tokens);
ty.to_tokens(tokens);
}
}
}
impl ToTokens for InitExprKind {
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 InitExprStruct {
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 InitExprTuple {
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, value } = self;
tokens.append_all(attrs);
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);
}
}
}
}