use std::borrow::Cow;
use std::slice;
use darling::util::Flag;
use darling::{Error, FromAttributes as _, FromDeriveInput as _};
use proc_macro2::{Span, TokenStream};
use quote::format_ident;
use syn::ext::IdentExt as _;
use syn::spanned::Spanned as _;
use syn::{Data, Fields, FieldsNamed, Ident, Token, Type, Visibility, WhereClause};
use crate::model::*;
use crate::util::*;
const BUILDER_MUST_USE: &str =
"builders do nothing on their own and their methods return new values";
const BUILDER_BUILD_MUST_USE: &str = "dropping the return of `build` will just discard the inputs";
struct EmitContext<'a> {
target: Ident,
builder: Ident,
builder_vis: Visibility,
builder_fn: Option<Ident>,
unchecked_builder: Ident,
unchecked_builder_vis: Visibility,
impl_generics: ImplGenerics<'a>,
ty_generics: TypeGenerics<'a>,
struct_generics: StructGenerics<'a>,
where_clause: WhereClause,
fields: &'a [FieldInfo<'a>],
packed: bool,
}
pub fn entry_point(input: syn::DeriveInput) -> TokenStream {
let mut acc = Error::accumulator();
let builder_attrs = acc
.handle(BuilderAttrs::from_derive_input(&input))
.unwrap_or_default();
let repr_attrs = acc
.handle(ReprAttrs::from_derive_input(&input))
.unwrap_or_default();
let Some(raw_fields) = acc.handle(find_named_fields(&input.data)) else {
return into_write_errors(acc);
};
let ty_generics = TypeGenerics(&input.generics.params);
let impl_generics = ImplGenerics(&input.generics.params);
let struct_generics = StructGenerics(&input.generics.params);
let fields = load_fields(&input.ident, &builder_attrs, raw_fields, &mut acc);
let where_clause = load_where_clause(&input.ident, ty_generics, input.generics.where_clause);
let builder = load_builder_name(&input.ident, builder_attrs.rename);
let builder_vis = builder_attrs.m_vis.unwrap_or(input.vis);
let builder_fn = load_builder_fn_name(builder_attrs.rename_fn);
let unchecked_builder =
load_unchecked_builder_name(&input.ident, builder_attrs.unchecked.rename);
let unchecked_builder_vis = builder_attrs.unchecked.vis.unwrap_or(Visibility::Inherited);
let ctx = EmitContext {
target: input.ident,
builder,
builder_vis,
builder_fn,
unchecked_builder,
unchecked_builder_vis,
impl_generics,
ty_generics,
struct_generics,
where_clause,
fields: &fields,
packed: repr_attrs.packed.is_some(),
};
let mut output = emit_main(&ctx);
output.extend(emit_drop(&ctx));
output.extend(emit_fields(&ctx));
output.extend(emit_builder_fn(&ctx));
if builder_attrs.default.is_present() {
output.extend(emit_default(&ctx));
}
output.extend(emit_unchecked(&ctx));
if let Err(err) = acc.finish() {
output.extend(err.write_errors());
}
output
}
fn emit_main(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
target,
builder,
builder_vis,
unchecked_builder,
unchecked_builder_vis,
impl_generics,
ty_generics,
struct_generics,
where_clause,
fields,
..
} = ctx;
let t_true = simple_ident("true");
let builder_doc = format!("A builder type for [`{target}`].");
let field_generics1 = fields.gen_names();
let field_generics2 = fields.gen_names();
let field_generics3 = fields.gen_names();
let build_gens = fields
.iter()
.map(|f| f.default.is_some().then_some(&f.gen_name));
let build_params = build_gens.clone().flatten();
let build_args = build_gens.map(|f| f.unwrap_or(&t_true));
quote::quote! {
#[doc = #builder_doc]
#[repr(transparent)]
#[must_use = #BUILDER_MUST_USE]
#builder_vis struct #builder < #struct_generics #( const #field_generics1: ::core::primitive::bool = false ),* > #where_clause {
inner: #unchecked_builder < #ty_generics >,
_unsafe: (),
}
impl < #impl_generics > #builder < #ty_generics > #where_clause {
#[inline]
pub const fn new() -> Self {
unsafe { #unchecked_builder::new().assert_init() }
}
}
impl < #impl_generics #( const #field_generics2: ::core::primitive::bool ),* > #builder < #ty_generics #(#field_generics3),* > #where_clause {
#[inline]
#unchecked_builder_vis const fn into_unchecked(self) -> #unchecked_builder < #ty_generics > {
let this = ::core::mem::ManuallyDrop::new(self);
let this = &raw const this as *const Self;
unsafe { ::core::ptr::read(&raw const (*this).inner) }
}
}
impl < #impl_generics #( const #build_params: ::core::primitive::bool ),* > #builder < #ty_generics #(#build_args),* > #where_clause {
#[must_use = #BUILDER_BUILD_MUST_USE]
#[inline]
pub const fn build(self) -> #target < #ty_generics > {
unsafe {
self.into_unchecked().build()
}
}
}
}
}
fn emit_builder_fn(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
target,
builder,
builder_vis,
builder_fn,
impl_generics,
ty_generics,
where_clause,
..
} = ctx;
let Some(builder_fn) = builder_fn else {
return TokenStream::new();
};
quote::quote! {
impl < #impl_generics > #target < #ty_generics > #where_clause {
#[inline]
#builder_vis const fn #builder_fn() -> #builder < #ty_generics > {
#builder::new()
}
}
}
}
fn emit_drop(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
builder,
impl_generics,
ty_generics,
where_clause,
fields,
..
} = ctx;
let field_generics1 = fields.gen_names();
let field_generics2 = fields.gen_names();
let drop_inner = emit_drop_inner(ctx);
quote::quote! {
#[automatically_derived]
impl < #impl_generics #( const #field_generics1: ::core::primitive::bool ),* > ::core::ops::Drop for #builder < #ty_generics #(#field_generics2),* > #where_clause {
#[inline]
fn drop(&mut self) {
#drop_inner
}
}
}
}
fn emit_drop_inner(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
unchecked_builder,
impl_generics,
ty_generics,
where_clause,
fields,
packed,
..
} = ctx;
let body = emit_field_drops(ctx);
if body.is_empty() {
return TokenStream::new();
}
let pack_size = 32;
let dropped_fields = fields
.iter()
.filter(|f| !f.leak_on_drop && if *packed { !f.unsized_tail } else { true });
let field_count = dropped_fields.clone().count();
let mut field_vars = dropped_fields.clone().map(|f| &f.drop_flag);
let mut field_var_generics = dropped_fields.map(|f| &f.gen_name);
let pack_count = usize::div_ceil(field_count, pack_size);
let packed_idents = (0..pack_count)
.map(|i| format_ident!("packed_drop_flags_{i}"))
.collect::<Vec<_>>();
let mut pack = TokenStream::new();
let mut unpack = TokenStream::new();
for pack_ident in &packed_idents {
let field_vars = field_vars.by_ref().take(pack_size);
let field_var_generics = field_var_generics.by_ref().take(pack_size);
let mask1 = (0usize..).map(|i| 1u32 << i);
let mask2 = mask1.clone();
pack.extend(quote::quote! {
0 #( | if #field_var_generics { #mask1 } else { 0 } )*,
});
unpack.extend(quote::quote! {
#( let #field_vars = (#pack_ident & #mask2) != 0; )*
});
}
quote::quote! {
#[cold]
#[inline(never)]
fn drop_inner < #impl_generics > (
this: &mut #unchecked_builder < #ty_generics >,
#( #packed_idents: ::core::primitive::u32 ),*
) #where_clause {
#unpack
#body
}
drop_inner(&mut self.inner, #pack);
}
}
fn emit_field_drops(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext { fields, packed, .. } = ctx;
fn in_place_drop(
FieldInfo {
ident,
drop_flag,
ty,
..
}: &FieldInfo,
) -> TokenStream {
quote::quote! {
if const { ::core::mem::needs_drop::<#ty>() } && #drop_flag {
unsafe {
::core::ptr::drop_in_place(
&raw mut (*::core::mem::MaybeUninit::as_mut_ptr(&mut this.inner)).#ident,
);
}
}
}
}
fn unaligned_drop(
FieldInfo {
ident,
drop_flag,
ty,
..
}: &FieldInfo,
) -> TokenStream {
quote::quote! {
if const { ::core::mem::needs_drop::<#ty>() } && #drop_flag {
unsafe {
::core::mem::drop(::core::ptr::read_unaligned(
&raw mut (*::core::mem::MaybeUninit::as_mut_ptr(&mut this.inner)).#ident
));
}
}
}
}
fn expect_no_drop(FieldInfo { ident, ty, .. }: &FieldInfo) -> TokenStream {
let message = format!("packed struct unsized tail field `{ident}` cannot be dropped");
quote::quote_spanned! {ty.span()=>
const {
::core::assert!(!::core::mem::needs_drop::<#ty>(), #message);
}
}
}
let mut output = TokenStream::new();
for field in *fields {
if !field.leak_on_drop {
output.extend(match (packed, field.unsized_tail) {
(true, false) => unaligned_drop(field),
(true, true) => expect_no_drop(field),
(false, _) => in_place_drop(field),
});
}
}
output
}
fn emit_default(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
target,
impl_generics,
ty_generics,
where_clause,
..
} = ctx;
quote::quote! {
impl < #impl_generics > #target < #ty_generics > #where_clause {
#[inline]
pub const fn default() -> Self {
Self::builder().build()
}
}
#[automatically_derived]
impl < #impl_generics > ::core::default::Default for #target < #ty_generics > #where_clause {
#[inline]
fn default() -> Self {
Self::default()
}
}
}
}
fn emit_fields(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
builder,
impl_generics,
ty_generics,
where_clause,
fields,
..
} = ctx;
let mut output = TokenStream::new();
let t_true = simple_ident("true");
let t_false = simple_ident("false");
for (
index,
FieldInfo {
ident,
name,
ty,
vis,
doc,
deprecated,
setter,
..
},
) in fields.iter().enumerate()
{
let used_gens = fields
.iter()
.enumerate()
.map(|(i, f)| (i != index).then_some(&f.gen_name));
let pre_set_args = used_gens.clone().map(|o| o.unwrap_or(&t_false));
let post_set_args = used_gens.clone().map(|o| o.unwrap_or(&t_true));
let set_params = used_gens.flatten();
let allow_deprecated = allow_deprecated(*deprecated);
let mut ty = *ty;
let (inputs, cast, tys, life) = split_setter(setter, &mut ty);
output.extend(quote::quote_spanned! {ident.span()=>
impl < #impl_generics #( const #set_params: ::core::primitive::bool ),* > #builder < #ty_generics #(#pre_set_args),* > #where_clause {
#(#doc)*
#deprecated
#[inline]
#[allow(clippy::type_repetition_in_bounds, clippy::multiple_bound_locations)]
#vis const fn #name #life (self, #inputs) -> #builder < #ty_generics #(#post_set_args),* >
where
#(#tys: ::core::marker::Sized,)*
{
#cast
#allow_deprecated
unsafe { self.into_unchecked().#name(value).assert_init() }
}
}
});
}
output
}
fn split_setter<'t>(
setter: &'t FieldSetter,
ty: &'t mut &'t Type,
) -> (
TokenStream, // inputs
Option<TokenStream>, // cast
Cow<'t, [&'t Type]>, // tys
Option<&'t TokenStream>, // life
) {
match setter {
FieldSetter::Default => (
quote::quote! { value: #ty },
None,
slice::from_ref(ty).into(),
None,
),
FieldSetter::StripOption => {
*ty = first_generic_arg(ty).unwrap_or(ty);
(
quote::quote! { value: #ty },
Some(quote::quote! { let value = ::core::option::Option::Some(value); }),
slice::from_ref(ty).into(),
None,
)
},
FieldSetter::Transform(transform) => {
let inputs = &transform.inputs;
let body = &transform.body;
(
quote::quote! { #(#inputs),* },
Some(quote::quote! { let value = #body; }),
transform.inputs.iter().map(|t| &*t.ty).collect(),
transform.lifetimes.as_ref(),
)
},
}
}
fn emit_unchecked(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
target,
builder,
builder_vis,
unchecked_builder,
unchecked_builder_vis,
impl_generics,
ty_generics,
struct_generics,
where_clause,
fields,
..
} = ctx;
let builder_doc = format!("An _unchecked_ builder type for [`{target}`].");
let field_generics1 = fields.gen_names();
let field_generics2 = fields.gen_names();
let field_default_names = fields
.iter()
.filter(|f| f.default.is_some())
.map(|f| &f.name);
let field_default_values = fields.iter().filter_map(|f| f.default.as_deref());
let field_setters = emit_unchecked_fields(ctx);
let structure_check = emit_structure_check(ctx);
let deprecated_field = fields.iter().find_map(|f| f.deprecated);
let allow_deprecated_field = allow_deprecated(deprecated_field);
quote::quote! {
#[doc = #builder_doc]
#[repr(transparent)]
#[must_use = #BUILDER_MUST_USE]
#unchecked_builder_vis struct #unchecked_builder < #struct_generics > #where_clause {
inner: ::core::mem::MaybeUninit< #target < #ty_generics > >,
}
impl < #impl_generics > #unchecked_builder < #ty_generics > #where_clause {
#[inline]
pub const fn new() -> Self {
#allow_deprecated_field
Self { inner: ::core::mem::MaybeUninit::uninit() }
#( . #field_default_names ( #field_default_values ) )*
}
#[inline]
#builder_vis const unsafe fn assert_init < #(const #field_generics1: ::core::primitive::bool),* > (self) -> #builder < #ty_generics #(#field_generics2),* > {
#builder {
inner: self,
_unsafe: (),
}
}
#[must_use = #BUILDER_BUILD_MUST_USE]
#[inline]
pub const unsafe fn build(self) -> #target < #ty_generics > {
#structure_check
unsafe {
::core::mem::MaybeUninit::assume_init(self.inner)
}
}
#[inline]
pub const fn as_uninit(&mut self) -> &mut ::core::mem::MaybeUninit< #target < #ty_generics > > {
&mut self.inner
}
#field_setters
}
}
}
fn emit_unchecked_fields(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext { fields, packed, .. } = ctx;
let mut output = TokenStream::new();
let write_ident = if *packed {
simple_ident("write_unaligned")
} else {
simple_ident("write")
};
for FieldInfo {
ident,
name,
ty,
vis,
doc,
deprecated,
..
} in *fields
{
let allow_deprecated = allow_deprecated(*deprecated);
output.extend(quote::quote_spanned! {ident.span()=>
#(#doc)*
#deprecated
#[inline]
#[allow(clippy::used_underscore_binding)]
#vis const fn #name(mut self, value: #ty) -> Self
where
#ty: ::core::marker::Sized,
{
unsafe {
::core::ptr::#write_ident(
#allow_deprecated
&raw mut (*::core::mem::MaybeUninit::as_mut_ptr(&mut self.inner)).#ident,
value,
);
}
self
}
});
}
output
}
fn emit_structure_check(ctx: &EmitContext<'_>) -> TokenStream {
let EmitContext {
target,
impl_generics,
ty_generics,
where_clause,
fields,
packed,
..
} = ctx;
let field_idents1 = fields.iter().map(|f| f.ident);
let field_idents2 = field_idents1.clone();
let field_idents3 = field_idents1.clone();
let field_tys1 = fields.iter().map(|f| f.ty);
let field_tys2 = field_tys1.clone();
let field_alignment_check = if *packed {
TokenStream::new()
} else {
quote::quote! {
fn _all_fields_aligned < #impl_generics > ( value: &#target < #ty_generics > ) #where_clause {
#(_ = &value.#field_idents3;)*
}
}
};
quote::quote! {
#[allow(
// triggers if any field is deprecated, but that doesn't matter here
deprecated,
// these may trigger due to the signature and field/type names
clippy::too_many_arguments,
clippy::multiple_bound_locations,
clippy::type_repetition_in_bounds,
clippy::used_underscore_binding,
)]
const {
fn _derive_includes_every_field < #impl_generics > ( #( #field_idents1: #field_tys1 ),* ) -> #target < #ty_generics >
#where_clause, #(#field_tys2: ::core::marker::Sized),*
{
#target { #(#field_idents2),* }
}
#field_alignment_check
}
}
}
fn load_builder_name(target: &Ident, rename: Option<Ident>) -> Ident {
rename.unwrap_or_else(|| format_ident!("{}Builder", target))
}
fn load_builder_fn_name(rename: Option<BoolOr<Ident>>) -> Option<Ident> {
match rename {
None | Some(BoolOr::Bool(true)) => Some(simple_ident("builder")),
Some(BoolOr::Bool(false)) => None,
Some(BoolOr::Value(ident)) => Some(ident),
}
}
fn load_unchecked_builder_name(target: &Ident, rename: Option<Ident>) -> Ident {
rename.unwrap_or_else(|| format_ident!("{}UncheckedBuilder", target))
}
fn load_where_clause(
target: &Ident,
ty_generics: TypeGenerics<'_>,
where_clause: Option<WhereClause>,
) -> WhereClause {
let mut where_clause = where_clause.unwrap_or_else(empty_where_clause);
let self_clause = syn::parse_quote!(#target < #ty_generics >: ::core::marker::Sized);
where_clause.predicates.push(self_clause);
where_clause
}
fn find_named_fields(data: &Data) -> darling::Result<&FieldsNamed> {
if let Data::Struct(data) = data {
if let Fields::Named(raw_fields) = &data.fields {
return Ok(raw_fields);
}
}
Err(Error::custom(
"`ConstBuilder` can only be derived for structs with named fields",
))
}
fn load_fields<'f>(
target: &Ident,
builder_attrs: &BuilderAttrs,
raw_fields: &'f FieldsNamed,
acc: &mut darling::error::Accumulator,
) -> Vec<FieldInfo<'f>> {
let mut fields = Vec::with_capacity(raw_fields.named.len());
let mut unsized_tail = Flag::default();
for pair in raw_fields.named.pairs() {
let raw_field = pair.into_value();
if unsized_tail.is_present() {
let err = Error::custom(
"`#[builder(unsized_tail)]` must be specified on the last field only",
);
acc.push(err.with_span(&unsized_tail.span()));
}
let ident = raw_field
.ident
.as_ref()
.expect("must be a named field here");
let attrs = acc
.handle(FieldAttrs::from_attributes(&raw_field.attrs))
.unwrap_or_default();
if attrs.default.is_none() && builder_attrs.default.is_present() {
let err = Error::custom(
"structs with `#[builder(default)]` must provide a default value for all fields",
);
acc.push(err.with_span(ident));
}
let name = attrs.rename.unwrap_or_else(|| ident.clone());
let drop_flag = format_ident!("drop_flag_{}", ident, span = Span::call_site());
let gen_name = attrs.rename_generic.unwrap_or_else(|| {
format_ident!(
"_{}",
ident.unraw().to_string().to_uppercase(),
span = ident.span()
)
});
let doc_header = match &attrs.default {
None => format!("Sets the [`{target}::{ident}`] field."),
Some(_) => {
format!("Sets the [`{target}::{ident}`] field, replacing the default value.")
},
};
let mut doc = Vec::new();
doc.push(Cow::Owned(doc_str_attr(&doc_header)));
doc.push(Cow::Owned(doc_str_attr("")));
doc.extend(iter_doc_attrs(&raw_field.attrs).map(Cow::Borrowed));
let deprecated = find_deprecated(&raw_field.attrs);
if attrs.setter.strip_option.is_present() && attrs.setter.transform.is_some() {
let err = Error::custom(
"may only specify one of the following `setter` fields: `strip_option`, `transform`",
);
acc.push(err.with_span(&attrs.setter.strip_option.span()));
}
if attrs.setter.strip_option.is_present() && first_generic_arg(&raw_field.ty).is_none() {
let err = Error::custom(
"cannot determine element type for `strip_option`, use `Option<_>` directly",
);
acc.push(err.with_span(&attrs.setter.strip_option.span()));
}
let setter = if attrs.setter.strip_option.is_present() {
FieldSetter::StripOption
} else if let Some(transform) = attrs.setter.transform {
FieldSetter::Transform(to_field_transform(transform, acc))
} else {
FieldSetter::Default
};
fields.push(FieldInfo {
ident,
name,
gen_name,
drop_flag,
ty: &raw_field.ty,
default: attrs.default,
vis: attrs
.vis
.unwrap_or(Visibility::Public(<Token![pub]>::default())),
doc,
deprecated,
leak_on_drop: attrs.leak_on_drop.is_present(),
unsized_tail: attrs.unsized_tail.is_present(),
setter,
});
unsized_tail = attrs.unsized_tail;
}
fields
}