use std::borrow::Cow;
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::{Data, Fields, FieldsNamed, Ident, Token, Visibility, WhereClause};
use crate::model::*;
use crate::util::*;
mod base;
mod drop;
mod fields;
mod traits;
mod unchecked;
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 = base::emit_main(&ctx);
output.extend(drop::emit_drop(&ctx));
output.extend(fields::emit_fields(&ctx));
output.extend(base::emit_builder_fn(&ctx));
if builder_attrs.default.is_present() {
output.extend(traits::emit_target_default(&ctx));
}
output.extend(unchecked::emit_unchecked(&ctx));
if let Err(err) = acc.finish() {
output.extend(err.write_errors());
}
output
}
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
&& let Fields::Named(raw_fields) = &data.fields
{
Ok(raw_fields)
} else {
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 deprecated = find_deprecated(&raw_field.attrs);
let attrs = acc
.handle(FieldAttrs::from_attributes(&raw_field.attrs))
.unwrap_or_default();
unsized_tail = attrs.unsized_tail;
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));
}
if attrs.skip.is_present() {
let skip_err = |s| Error::custom(s).with_span(&attrs.skip.span());
if attrs.default.is_none() {
acc.push(skip_err("`skip` requires specifying `default`"));
}
if attrs.rename_generic.is_some() {
acc.push(skip_err("`skip` cannot be combined with `rename_generic`"));
}
if attrs.vis.is_some() {
acc.push(skip_err("`skip` cannot be combined with `vis`"));
}
if attrs.setter.is_some() {
acc.push(skip_err("`skip` cannot be combined with `setter`"));
}
}
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 setter = attrs.setter.unwrap_or_default();
if setter.strip_option.is_present() && 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(&setter.strip_option.span()));
}
if 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(&setter.strip_option.span()));
}
let setter = if setter.strip_option.is_present() {
FieldSetter::StripOption
} else if let Some(transform) = setter.transform {
FieldSetter::Transform(to_field_transform(transform, acc))
} else {
FieldSetter::Default
};
let vis = attrs.vis.unwrap_or_else(|| {
if attrs.skip.is_present() {
raw_field.vis.clone()
} else {
Visibility::Public(<Token![pub]>::default())
}
});
fields.push(FieldInfo {
ident,
name,
gen_name,
drop_flag,
ty: &raw_field.ty,
default: attrs.default,
vis,
doc,
deprecated,
leak_on_drop: attrs.leak_on_drop.is_present(),
unsized_tail: attrs.unsized_tail.is_present(),
skip: attrs.skip.is_present(),
setter,
});
}
fields
}