2
0
Fork 0
mirror of https://github.com/DioxusLabs/dioxus synced 2024-12-24 11:33:08 +00:00
dioxus/packages/core-macro/src/props/mod.rs

1173 lines
47 KiB
Rust
Raw Normal View History

//! This code mostly comes from idanarye/rust-typed-builder
//!
//! However, it has been adopted to fit the Dioxus Props builder pattern.
//!
//! For dioxus, we make a few changes:
2021-12-21 05:46:10 +00:00
//! - [ ] automatically implement Into<Option> on the setters (IE the strip setter option)
//! - [ ] automatically implement a default of none for optional fields (those explicitly wrapped with Option<T>)
use proc_macro2::TokenStream;
use syn::parse::Error;
use syn::spanned::Spanned;
2021-03-11 00:42:31 +00:00
use quote::quote;
pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
let data = match &ast.data {
syn::Data::Struct(data) => match &data.fields {
syn::Fields::Named(fields) => {
2021-10-27 21:09:22 +00:00
let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?;
let builder_creation = struct_info.builder_creation_impl()?;
let conversion_helper = struct_info.conversion_helper_impl()?;
let fields = struct_info
.included_fields()
.map(|f| struct_info.field_impl(f))
.collect::<Result<Vec<_>, _>>()?;
let fields = quote!(#(#fields)*).into_iter();
let required_fields = struct_info
.included_fields()
.filter(|f| f.builder_attr.default.is_none())
.map(|f| struct_info.required_field_impl(f))
.collect::<Result<Vec<_>, _>>()?;
let build_method = struct_info.build_method_impl();
quote! {
#builder_creation
#conversion_helper
#( #fields )*
#( #required_fields )*
#build_method
}
}
syn::Fields::Unnamed(_) => {
return Err(Error::new(
ast.span(),
"TypedBuilder is not supported for tuple structs",
))
}
syn::Fields::Unit => {
return Err(Error::new(
ast.span(),
"TypedBuilder is not supported for unit structs",
))
}
},
syn::Data::Enum(_) => {
return Err(Error::new(
ast.span(),
"TypedBuilder is not supported for enums",
))
}
syn::Data::Union(_) => {
return Err(Error::new(
ast.span(),
"TypedBuilder is not supported for unions",
))
}
};
Ok(data)
}
mod util {
use quote::ToTokens;
pub fn path_to_single_string(path: &syn::Path) -> Option<String> {
if path.leading_colon.is_some() {
return None;
}
let mut it = path.segments.iter();
let segment = it.next()?;
if it.next().is_some() {
// Multipart path
return None;
}
if segment.arguments != syn::PathArguments::None {
return None;
}
Some(segment.ident.to_string())
}
pub fn expr_to_single_string(expr: &syn::Expr) -> Option<String> {
if let syn::Expr::Path(path) = &*expr {
path_to_single_string(&path.path)
} else {
None
}
}
pub fn ident_to_type(ident: syn::Ident) -> syn::Type {
let mut path = syn::Path {
leading_colon: None,
segments: Default::default(),
};
path.segments.push(syn::PathSegment {
ident,
arguments: Default::default(),
});
syn::Type::Path(syn::TypePath { qself: None, path })
}
pub fn empty_type() -> syn::Type {
syn::TypeTuple {
paren_token: Default::default(),
elems: Default::default(),
}
.into()
}
pub fn type_tuple(elems: impl Iterator<Item = syn::Type>) -> syn::TypeTuple {
let mut result = syn::TypeTuple {
paren_token: Default::default(),
elems: elems.collect(),
};
if !result.elems.empty_or_trailing() {
result.elems.push_punct(Default::default());
}
result
}
pub fn empty_type_tuple() -> syn::TypeTuple {
syn::TypeTuple {
paren_token: Default::default(),
elems: Default::default(),
}
}
pub fn make_punctuated_single<T, P: Default>(value: T) -> syn::punctuated::Punctuated<T, P> {
let mut punctuated = syn::punctuated::Punctuated::new();
punctuated.push(value);
punctuated
}
pub fn modify_types_generics_hack<F>(
ty_generics: &syn::TypeGenerics,
mut mutator: F,
) -> syn::AngleBracketedGenericArguments
where
F: FnMut(&mut syn::punctuated::Punctuated<syn::GenericArgument, syn::token::Comma>),
{
let mut abga: syn::AngleBracketedGenericArguments =
syn::parse(ty_generics.clone().into_token_stream().into()).unwrap_or_else(|_| {
syn::AngleBracketedGenericArguments {
colon2_token: None,
lt_token: Default::default(),
args: Default::default(),
gt_token: Default::default(),
}
});
mutator(&mut abga.args);
abga
}
pub fn strip_raw_ident_prefix(mut name: String) -> String {
if name.starts_with("r#") {
name.replace_range(0..2, "");
}
name
}
}
mod field_info {
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::Error;
use syn::spanned::Spanned;
use super::util::{
expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix,
};
#[derive(Debug)]
pub struct FieldInfo<'a> {
pub ordinal: usize,
pub name: &'a syn::Ident,
pub generic_ident: syn::Ident,
pub ty: &'a syn::Type,
pub builder_attr: FieldBuilderAttr,
}
impl<'a> FieldInfo<'a> {
pub fn new(
ordinal: usize,
field: &syn::Field,
field_defaults: FieldBuilderAttr,
) -> Result<FieldInfo, Error> {
if let Some(ref name) = field.ident {
2021-12-21 05:46:10 +00:00
let mut builder_attr = field_defaults.with(&field.attrs)?;
// children field is automatically defaulted to None
if name == "children" {
builder_attr.default =
Some(syn::parse(quote!(Default::default()).into()).unwrap());
}
Ok(FieldInfo {
ordinal,
2021-10-27 21:09:22 +00:00
name,
generic_ident: syn::Ident::new(
&format!("__{}", strip_raw_ident_prefix(name.to_string())),
proc_macro2::Span::call_site(),
),
ty: &field.ty,
2021-12-21 05:46:10 +00:00
builder_attr,
})
} else {
Err(Error::new(field.span(), "Nameless field in struct"))
}
}
pub fn generic_ty_param(&self) -> syn::GenericParam {
syn::GenericParam::Type(self.generic_ident.clone().into())
}
pub fn type_ident(&self) -> syn::Type {
ident_to_type(self.generic_ident.clone())
}
pub fn tuplized_type_ty_param(&self) -> syn::Type {
let mut types = syn::punctuated::Punctuated::default();
types.push(self.ty.clone());
types.push_punct(Default::default());
syn::TypeTuple {
paren_token: Default::default(),
elems: types,
}
.into()
}
pub fn type_from_inside_option(&self) -> Option<&syn::Type> {
let path = if let syn::Type::Path(type_path) = self.ty {
if type_path.qself.is_some() {
return None;
} else {
&type_path.path
}
} else {
return None;
};
let segment = path.segments.last()?;
if segment.ident != "Option" {
return None;
}
let generic_params =
if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments {
generic_params
} else {
return None;
};
if let syn::GenericArgument::Type(ty) = generic_params.args.first()? {
Some(ty)
} else {
None
}
}
}
#[derive(Debug, Default, Clone)]
pub struct FieldBuilderAttr {
pub default: Option<syn::Expr>,
pub doc: Option<syn::Expr>,
pub skip: bool,
pub auto_into: bool,
pub strip_option: bool,
}
impl FieldBuilderAttr {
pub fn with(mut self, attrs: &[syn::Attribute]) -> Result<Self, Error> {
let mut skip_tokens = None;
for attr in attrs {
2021-12-21 05:46:10 +00:00
if path_to_single_string(&attr.path).as_deref() != Some("props") {
continue;
}
if attr.tokens.is_empty() {
continue;
}
let as_expr: syn::Expr = syn::parse2(attr.tokens.clone())?;
match as_expr {
syn::Expr::Paren(body) => {
self.apply_meta(*body.expr)?;
}
syn::Expr::Tuple(body) => {
for expr in body.elems.into_iter() {
self.apply_meta(expr)?;
}
}
_ => {
return Err(Error::new_spanned(attr.tokens.clone(), "Expected (<...>)"));
}
}
// Stash its span for later (we dont yet know if itll be an error)
2022-01-10 07:57:03 +00:00
if self.skip && skip_tokens.is_none() {
skip_tokens = Some(attr.tokens.clone());
}
}
2022-01-10 07:57:03 +00:00
if self.skip && self.default.is_none() {
return Err(Error::new_spanned(
skip_tokens.unwrap(),
2021-12-21 05:46:10 +00:00
"#[props(skip)] must be accompanied by default or default_code",
));
}
Ok(self)
}
pub fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
match expr {
2021-12-21 05:46:10 +00:00
// #[props(default = "...")]
syn::Expr::Assign(assign) => {
let name = expr_to_single_string(&assign.left)
.ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
match name.as_str() {
"default" => {
self.default = Some(*assign.right);
Ok(())
}
2022-01-10 07:57:03 +00:00
"doc" => {
self.doc = Some(*assign.right);
Ok(())
}
"default_code" => {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(code),
..
}) = *assign.right
{
use std::str::FromStr;
let tokenized_code = TokenStream::from_str(&code.value())?;
self.default = Some(
syn::parse(tokenized_code.into())
.map_err(|e| Error::new_spanned(code, format!("{}", e)))?,
);
} else {
return Err(Error::new_spanned(assign.right, "Expected string"));
}
Ok(())
}
_ => Err(Error::new_spanned(
&assign,
format!("Unknown parameter {:?}", name),
)),
}
}
2021-12-21 05:46:10 +00:00
2022-01-10 07:57:03 +00:00
// #[props(default)]
syn::Expr::Path(path) => {
let name = path_to_single_string(&path.path)
.ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
match name.as_str() {
"default" => {
self.default =
Some(syn::parse(quote!(Default::default()).into()).unwrap());
Ok(())
}
2022-01-16 20:27:41 +00:00
"optional" => {
self.default =
Some(syn::parse(quote!(Default::default()).into()).unwrap());
self.strip_option = true;
Ok(())
}
2022-01-10 07:57:03 +00:00
_ => {
macro_rules! handle_fields {
( $( $flag:expr, $field:ident, $already:expr; )* ) => {
match name.as_str() {
$(
$flag => {
if self.$field {
Err(Error::new(path.span(), concat!("Illegal setting - field is already ", $already)))
} else {
self.$field = true;
Ok(())
}
}
)*
_ => Err(Error::new_spanned(
&path,
format!("Unknown setter parameter {:?}", name),
))
}
}
}
2022-01-10 07:57:03 +00:00
handle_fields!(
"skip", skip, "skipped";
"into", auto_into, "calling into() on the argument";
"strip_option", strip_option, "putting the argument in Some(...)";
)
}
}
}
2021-12-21 05:46:10 +00:00
syn::Expr::Unary(syn::ExprUnary {
op: syn::UnOp::Not(_),
expr,
..
}) => {
if let syn::Expr::Path(path) = *expr {
let name = path_to_single_string(&path.path)
.ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
match name.as_str() {
"default" => {
self.default = None;
Ok(())
}
"doc" => {
self.doc = None;
Ok(())
}
"skip" => {
self.skip = false;
Ok(())
}
"auto_into" => {
self.auto_into = false;
Ok(())
}
"strip_option" => {
self.strip_option = false;
Ok(())
}
_ => Err(Error::new_spanned(path, "Unknown setting".to_owned())),
}
} else {
Err(Error::new_spanned(
expr,
"Expected simple identifier".to_owned(),
))
}
}
_ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
}
}
}
}
mod struct_info {
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::Error;
use super::field_info::{FieldBuilderAttr, FieldInfo};
use super::util::{
empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single,
modify_types_generics_hack, path_to_single_string, strip_raw_ident_prefix, type_tuple,
};
#[derive(Debug)]
pub struct StructInfo<'a> {
pub vis: &'a syn::Visibility,
pub name: &'a syn::Ident,
pub generics: &'a syn::Generics,
pub fields: Vec<FieldInfo<'a>>,
pub builder_attr: TypeBuilderAttr,
pub builder_name: syn::Ident,
pub conversion_helper_trait_name: syn::Ident,
pub core: syn::Ident,
}
impl<'a> StructInfo<'a> {
pub fn included_fields(&self) -> impl Iterator<Item = &FieldInfo<'a>> {
2022-01-10 07:57:03 +00:00
self.fields.iter().filter(|f| !f.builder_attr.skip)
}
pub fn new(
ast: &'a syn::DeriveInput,
fields: impl Iterator<Item = &'a syn::Field>,
) -> Result<StructInfo<'a>, Error> {
let builder_attr = TypeBuilderAttr::new(&ast.attrs)?;
let builder_name = strip_raw_ident_prefix(format!("{}Builder", ast.ident));
Ok(StructInfo {
vis: &ast.vis,
name: &ast.ident,
generics: &ast.generics,
fields: fields
.enumerate()
.map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone()))
.collect::<Result<_, _>>()?,
builder_attr,
builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()),
conversion_helper_trait_name: syn::Ident::new(
&format!("{}_Optional", builder_name),
proc_macro2::Span::call_site(),
),
core: syn::Ident::new(
&format!("{}_core", builder_name),
proc_macro2::Span::call_site(),
),
})
}
fn modify_generics<F: FnMut(&mut syn::Generics)>(&self, mut mutator: F) -> syn::Generics {
let mut generics = self.generics.clone();
mutator(&mut generics);
generics
}
pub fn builder_creation_impl(&self) -> Result<TokenStream, Error> {
let StructInfo {
ref vis,
ref name,
ref builder_name,
..
} = *self;
// we're generating stuff that goes into unsafe code here
// we use the heuristic: are there *any* generic parameters?
// If so, then they might have non-static lifetimes and we can't compare two generic things that *might borrow*
// Therefore, we will generate code that shortcircuits the "comparison" in memoization
2021-10-27 21:09:22 +00:00
let are_there_generics = !self.generics.params.is_empty();
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let all_fields_param = syn::GenericParam::Type(
syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
);
let b_generics = self.modify_generics(|g| {
g.params.insert(0, all_fields_param.clone());
});
let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type()));
let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| {
args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into()));
});
let phantom_generics = self.generics.params.iter().map(|param| match param {
syn::GenericParam::Lifetime(lifetime) => {
let lifetime = &lifetime.lifetime;
quote!(core::marker::PhantomData<&#lifetime ()>)
}
syn::GenericParam::Type(ty) => {
let ty = &ty.ident;
quote!(core::marker::PhantomData<#ty>)
}
syn::GenericParam::Const(_cnst) => {
quote!()
}
});
let builder_method_doc = match self.builder_attr.builder_method_doc {
Some(ref doc) => quote!(#doc),
None => {
let doc = format!(
"
Create a builder for building `{name}`.
On the builder, call {setters} to set the values of the fields.
Finally, call `.build()` to create the instance of `{name}`.
",
name = self.name,
setters = {
let mut result = String::new();
let mut is_first = true;
for field in self.included_fields() {
use std::fmt::Write;
if is_first {
is_first = false;
} else {
write!(&mut result, ", ").unwrap();
}
write!(&mut result, "`.{}(...)`", field.name).unwrap();
if field.builder_attr.default.is_some() {
write!(&mut result, "(optional)").unwrap();
}
}
result
}
);
quote!(#doc)
}
};
let builder_type_doc = if self.builder_attr.doc {
match self.builder_attr.builder_type_doc {
Some(ref doc) => quote!(#[doc = #doc]),
None => {
let doc = format!(
"Builder for [`{name}`] instances.\n\nSee [`{name}::builder()`] for more info.",
name = name
);
quote!(#[doc = #doc])
}
}
} else {
quote!(#[doc(hidden)])
};
let (b_generics_impl, b_generics_ty, b_generics_where_extras_predicates) =
b_generics.split_for_impl();
let mut b_generics_where: syn::WhereClause = syn::parse2(quote! {
where TypedBuilderFields: Clone
})?;
if let Some(predicates) = b_generics_where_extras_predicates {
b_generics_where
.predicates
.extend(predicates.predicates.clone());
}
let can_memoize = match are_there_generics {
true => quote! { false },
false => quote! { self == other },
};
2021-08-08 19:15:16 +00:00
let is_static = match are_there_generics {
true => quote! { false },
false => quote! { true },
};
Ok(quote! {
impl #impl_generics #name #ty_generics #where_clause {
#[doc = #builder_method_doc]
#[allow(dead_code)]
#vis fn builder() -> #builder_name #generics_with_empty {
#builder_name {
fields: #empties_tuple,
_phantom: core::default::Default::default(),
}
}
}
#[must_use]
#builder_type_doc
#[allow(dead_code, non_camel_case_types, non_snake_case)]
#vis struct #builder_name #b_generics {
fields: #all_fields_param,
_phantom: (#( #phantom_generics ),*),
}
impl #b_generics_impl Clone for #builder_name #b_generics_ty #b_generics_where {
fn clone(&self) -> Self {
Self {
fields: self.fields.clone(),
_phantom: Default::default(),
}
}
}
impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
type Builder = #builder_name #generics_with_empty;
2021-08-08 19:15:16 +00:00
const IS_STATIC: bool = #is_static;
fn builder() -> Self::Builder {
#name::builder()
}
unsafe fn memoize(&self, other: &Self) -> bool {
#can_memoize
}
}
})
}
// TODO: once the proc-macro crate limitation is lifted, make this an util trait of this
// crate.
pub fn conversion_helper_impl(&self) -> Result<TokenStream, Error> {
let trait_name = &self.conversion_helper_trait_name;
Ok(quote! {
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
pub trait #trait_name<T> {
fn into_value<F: FnOnce() -> T>(self, default: F) -> T;
}
impl<T> #trait_name<T> for () {
fn into_value<F: FnOnce() -> T>(self, default: F) -> T {
default()
}
}
impl<T> #trait_name<T> for (T,) {
fn into_value<F: FnOnce() -> T>(self, _: F) -> T {
self.0
}
}
})
}
pub fn field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
let StructInfo {
ref builder_name, ..
} = *self;
let descructuring = self.included_fields().map(|f| {
if f.ordinal == field.ordinal {
quote!(_)
} else {
let name = f.name;
quote!(#name)
}
});
let reconstructing = self.included_fields().map(|f| f.name);
let &FieldInfo {
name: ref field_name,
ty: ref field_type,
..
} = field;
let mut ty_generics: Vec<syn::GenericArgument> = self
.generics
.params
.iter()
.map(|generic_param| match generic_param {
syn::GenericParam::Type(type_param) => {
let ident = type_param.ident.clone();
syn::parse(quote!(#ident).into()).unwrap()
}
syn::GenericParam::Lifetime(lifetime_def) => {
syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())
}
syn::GenericParam::Const(const_param) => {
let ident = const_param.ident.clone();
syn::parse(quote!(#ident).into()).unwrap()
}
})
.collect();
let mut target_generics_tuple = empty_type_tuple();
let mut ty_generics_tuple = empty_type_tuple();
let generics = self.modify_generics(|g| {
let index_after_lifetime_in_generics = g
.params
.iter()
.filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
.count();
for f in self.included_fields() {
if f.ordinal == field.ordinal {
ty_generics_tuple.elems.push_value(empty_type());
target_generics_tuple
.elems
.push_value(f.tuplized_type_ty_param());
} else {
g.params
.insert(index_after_lifetime_in_generics, f.generic_ty_param());
let generic_argument: syn::Type = f.type_ident();
ty_generics_tuple.elems.push_value(generic_argument.clone());
target_generics_tuple.elems.push_value(generic_argument);
}
ty_generics_tuple.elems.push_punct(Default::default());
target_generics_tuple.elems.push_punct(Default::default());
}
});
let mut target_generics = ty_generics.clone();
let index_after_lifetime_in_generics = target_generics
.iter()
.filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))
.count();
target_generics.insert(
index_after_lifetime_in_generics,
syn::GenericArgument::Type(target_generics_tuple.into()),
);
ty_generics.insert(
index_after_lifetime_in_generics,
syn::GenericArgument::Type(ty_generics_tuple.into()),
);
let (impl_generics, _, where_clause) = generics.split_for_impl();
2022-01-10 07:57:03 +00:00
let doc = match field.builder_attr.doc {
Some(ref doc) => quote!(#[doc = #doc]),
None => quote!(),
};
// NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of
// nesting is different so we have to do this little dance.
2022-01-10 07:57:03 +00:00
let arg_type = if field.builder_attr.strip_option {
let internal_type = field.type_from_inside_option().ok_or_else(|| {
Error::new_spanned(
&field_type,
"can't `strip_option` - field is not `Option<...>`",
)
})?;
internal_type
} else {
field_type
};
2022-01-10 07:57:03 +00:00
let (arg_type, arg_expr) = if field.builder_attr.auto_into {
(
quote!(impl core::convert::Into<#arg_type>),
quote!(#field_name.into()),
)
} else {
(quote!(#arg_type), quote!(#field_name))
};
2022-01-10 07:57:03 +00:00
let arg_expr = if field.builder_attr.strip_option {
quote!(Some(#arg_expr))
} else {
arg_expr
};
let repeated_fields_error_type_name = syn::Ident::new(
&format!(
"{}_Error_Repeated_field_{}",
builder_name,
strip_raw_ident_prefix(field_name.to_string())
),
proc_macro2::Span::call_site(),
);
let repeated_fields_error_message = format!("Repeated field {}", field_name);
Ok(quote! {
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause {
#doc
pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > {
let #field_name = (#arg_expr,);
let ( #(#descructuring,)* ) = self.fields;
#builder_name {
fields: ( #(#reconstructing,)* ),
_phantom: self._phantom,
}
}
}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
pub enum #repeated_fields_error_type_name {}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl #impl_generics #builder_name < #( #target_generics ),* > #where_clause {
#[deprecated(
note = #repeated_fields_error_message
)]
pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > {
self
}
}
})
}
pub fn required_field_impl(&self, field: &FieldInfo) -> Result<TokenStream, Error> {
let StructInfo {
ref name,
ref builder_name,
..
} = self;
let FieldInfo {
name: ref field_name,
..
} = field;
let mut builder_generics: Vec<syn::GenericArgument> = self
.generics
.params
.iter()
.map(|generic_param| match generic_param {
syn::GenericParam::Type(type_param) => {
let ident = &type_param.ident;
syn::parse(quote!(#ident).into()).unwrap()
}
syn::GenericParam::Lifetime(lifetime_def) => {
syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone())
}
syn::GenericParam::Const(const_param) => {
let ident = &const_param.ident;
syn::parse(quote!(#ident).into()).unwrap()
}
})
.collect();
let mut builder_generics_tuple = empty_type_tuple();
let generics = self.modify_generics(|g| {
let index_after_lifetime_in_generics = g
.params
.iter()
.filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
.count();
for f in self.included_fields() {
if f.builder_attr.default.is_some() {
// `f` is not mandatory - it does not have it's own fake `build` method, so `field` will need
// to warn about missing `field` whether or not `f` is set.
assert!(
f.ordinal != field.ordinal,
"`required_field_impl` called for optional field {}",
field.name
);
g.params
.insert(index_after_lifetime_in_generics, f.generic_ty_param());
builder_generics_tuple.elems.push_value(f.type_ident());
} else if f.ordinal < field.ordinal {
// Only add a `build` method that warns about missing `field` if `f` is set. If `f` is not set,
// `f`'s `build` method will warn, since it appears earlier in the argument list.
builder_generics_tuple
.elems
.push_value(f.tuplized_type_ty_param());
} else if f.ordinal == field.ordinal {
builder_generics_tuple.elems.push_value(empty_type());
} else {
// `f` appears later in the argument list after `field`, so if they are both missing we will
// show a warning for `field` and not for `f` - which means this warning should appear whether
// or not `f` is set.
g.params
.insert(index_after_lifetime_in_generics, f.generic_ty_param());
builder_generics_tuple.elems.push_value(f.type_ident());
}
builder_generics_tuple.elems.push_punct(Default::default());
}
});
let index_after_lifetime_in_generics = builder_generics
.iter()
.filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_)))
.count();
builder_generics.insert(
index_after_lifetime_in_generics,
syn::GenericArgument::Type(builder_generics_tuple.into()),
);
let (impl_generics, _, where_clause) = generics.split_for_impl();
let (_, ty_generics, _) = self.generics.split_for_impl();
let early_build_error_type_name = syn::Ident::new(
&format!(
"{}_Error_Missing_required_field_{}",
builder_name,
strip_raw_ident_prefix(field_name.to_string())
),
proc_macro2::Span::call_site(),
);
let early_build_error_message = format!("Missing required field {}", field_name);
Ok(quote! {
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
pub enum #early_build_error_type_name {}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, missing_docs, clippy::panic)]
impl #impl_generics #builder_name < #( #builder_generics ),* > #where_clause {
#[deprecated(
note = #early_build_error_message
)]
pub fn build(self, _: #early_build_error_type_name) -> #name #ty_generics {
panic!();
}
}
})
}
pub fn build_method_impl(&self) -> TokenStream {
let StructInfo {
ref name,
ref builder_name,
..
} = *self;
let generics = self.modify_generics(|g| {
let index_after_lifetime_in_generics = g
.params
.iter()
.filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_)))
.count();
for field in self.included_fields() {
if field.builder_attr.default.is_some() {
let trait_ref = syn::TraitBound {
paren_token: None,
lifetimes: None,
modifier: syn::TraitBoundModifier::None,
path: syn::PathSegment {
ident: self.conversion_helper_trait_name.clone(),
arguments: syn::PathArguments::AngleBracketed(
syn::AngleBracketedGenericArguments {
colon2_token: None,
lt_token: Default::default(),
args: make_punctuated_single(syn::GenericArgument::Type(
field.ty.clone(),
)),
gt_token: Default::default(),
},
),
}
.into(),
};
let mut generic_param: syn::TypeParam = field.generic_ident.clone().into();
generic_param.bounds.push(trait_ref.into());
g.params
.insert(index_after_lifetime_in_generics, generic_param.into());
}
}
});
let (impl_generics, _, _) = generics.split_for_impl();
let (_, ty_generics, where_clause) = self.generics.split_for_impl();
let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| {
args.insert(
0,
syn::GenericArgument::Type(
type_tuple(self.included_fields().map(|field| {
if field.builder_attr.default.is_some() {
field.type_ident()
} else {
field.tuplized_type_ty_param()
}
}))
.into(),
),
);
});
let descructuring = self.included_fields().map(|f| f.name);
let helper_trait_name = &self.conversion_helper_trait_name;
// The default of a field can refer to earlier-defined fields, which we handle by
// writing out a bunch of `let` statements first, which can each refer to earlier ones.
// This means that field ordering may actually be significant, which isnt ideal. We could
// relax that restriction by calculating a DAG of field default dependencies and
// reordering based on that, but for now this much simpler thing is a reasonable approach.
let assignments = self.fields.iter().map(|field| {
let name = &field.name;
if let Some(ref default) = field.builder_attr.default {
2022-01-10 07:57:03 +00:00
if field.builder_attr.skip {
quote!(let #name = #default;)
} else {
quote!(let #name = #helper_trait_name::into_value(#name, || #default);)
}
} else {
quote!(let #name = #name.0;)
}
});
let field_names = self.fields.iter().map(|field| field.name);
let doc = if self.builder_attr.doc {
match self.builder_attr.build_method_doc {
Some(ref doc) => quote!(#[doc = #doc]),
None => {
// Id prefer “a” or “an” to “its”, but determining which is grammatically
// correct is roughly impossible.
let doc =
format!("Finalise the builder and create its [`{}`] instance", name);
quote!(#[doc = #doc])
}
}
} else {
quote!()
};
quote!(
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl #impl_generics #builder_name #modified_ty_generics #where_clause {
#doc
pub fn build(self) -> #name #ty_generics {
let ( #(#descructuring,)* ) = self.fields;
#( #assignments )*
#name {
#( #field_names ),*
}
}
}
)
}
}
#[derive(Debug, Default)]
pub struct TypeBuilderAttr {
/// Whether to show docs for the `TypeBuilder` type (rather than hiding them).
pub doc: bool,
/// Docs on the `Type::builder()` method.
pub builder_method_doc: Option<syn::Expr>,
/// Docs on the `TypeBuilder` type. Specifying this implies `doc`, but you can just specify
/// `doc` instead and a default value will be filled in here.
pub builder_type_doc: Option<syn::Expr>,
/// Docs on the `TypeBuilder.build()` method. Specifying this implies `doc`, but you can just
/// specify `doc` instead and a default value will be filled in here.
pub build_method_doc: Option<syn::Expr>,
pub field_defaults: FieldBuilderAttr,
}
impl TypeBuilderAttr {
pub fn new(attrs: &[syn::Attribute]) -> Result<TypeBuilderAttr, Error> {
let mut result = TypeBuilderAttr::default();
for attr in attrs {
if path_to_single_string(&attr.path).as_deref() != Some("builder") {
continue;
}
if attr.tokens.is_empty() {
continue;
}
let as_expr: syn::Expr = syn::parse2(attr.tokens.clone())?;
match as_expr {
syn::Expr::Paren(body) => {
result.apply_meta(*body.expr)?;
}
syn::Expr::Tuple(body) => {
for expr in body.elems.into_iter() {
result.apply_meta(expr)?;
}
}
_ => {
return Err(Error::new_spanned(attr.tokens.clone(), "Expected (<...>)"));
}
}
}
Ok(result)
}
fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
match expr {
syn::Expr::Assign(assign) => {
let name = expr_to_single_string(&assign.left)
.ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
match name.as_str() {
"builder_method_doc" => {
self.builder_method_doc = Some(*assign.right);
Ok(())
}
"builder_type_doc" => {
self.builder_type_doc = Some(*assign.right);
self.doc = true;
Ok(())
}
"build_method_doc" => {
self.build_method_doc = Some(*assign.right);
self.doc = true;
Ok(())
}
_ => Err(Error::new_spanned(
&assign,
format!("Unknown parameter {:?}", name),
)),
}
}
syn::Expr::Path(path) => {
let name = path_to_single_string(&path.path)
.ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
match name.as_str() {
"doc" => {
self.doc = true;
Ok(())
}
_ => Err(Error::new_spanned(
&path,
format!("Unknown parameter {:?}", name),
)),
}
}
syn::Expr::Call(call) => {
let subsetting_name = if let syn::Expr::Path(path) = &*call.func {
path_to_single_string(&path.path)
} else {
None
}
.ok_or_else(|| {
let call_func = &call.func;
let call_func = quote!(#call_func);
Error::new_spanned(
&call.func,
format!("Illegal builder setting group {}", call_func),
)
})?;
match subsetting_name.as_str() {
"field_defaults" => {
for arg in call.args {
self.field_defaults.apply_meta(arg)?;
}
Ok(())
}
_ => Err(Error::new_spanned(
&call.func,
format!("Illegal builder setting group name {}", subsetting_name),
)),
}
}
_ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")),
}
}
}
}