Skip to content

Commit

Permalink
Merge pull request #127 from itchyny/refactor-struct-info-derive-buil…
Browse files Browse the repository at this point in the history
…der-attr

Refactor implementations of StructInfo::derive and TypeBuilderAttr
  • Loading branch information
idanarye committed Nov 1, 2023
2 parents 5930669 + 6b5e3b1 commit 168e076
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 245 deletions.
202 changes: 202 additions & 0 deletions typed-builder-macro/src/builder_attr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::parse::Error;

use crate::field_info::FieldBuilderAttr;
use crate::mutator::Mutator;
use crate::util::{path_to_single_string, ApplyMeta, AttrArg};

#[derive(Debug, Default, Clone)]
pub struct CommonDeclarationSettings {
pub vis: Option<syn::Visibility>,
pub name: Option<syn::Expr>,
pub doc: Option<syn::Expr>,
}

impl ApplyMeta for CommonDeclarationSettings {
fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> {
match expr.name().to_string().as_str() {
"vis" => {
let expr_str = expr.key_value()?.parse_value::<syn::LitStr>()?.value();
self.vis = Some(syn::parse_str(&expr_str)?);
Ok(())
}
"name" => {
self.name = Some(expr.key_value()?.parse_value()?);
Ok(())
}
"doc" => {
self.doc = Some(expr.key_value()?.parse_value()?);
Ok(())
}
_ => Err(Error::new_spanned(
expr.name(),
format!("Unknown parameter {:?}", expr.name().to_string()),
)),
}
}
}

impl CommonDeclarationSettings {
pub fn get_name(&self) -> Option<TokenStream> {
self.name.as_ref().map(|name| name.to_token_stream())
}

pub fn get_doc_or(&self, gen_doc: impl FnOnce() -> String) -> TokenStream {
if let Some(ref doc) = self.doc {
quote!(#[doc = #doc])
} else {
let doc = gen_doc();
quote!(#[doc = #doc])
}
}
}

/// Setting of the `into` argument.
#[derive(Debug, Clone)]
pub enum IntoSetting {
/// Do not run any conversion on the built value.
NoConversion,
/// Convert the build value into the generic parameter passed to the `build` method.
GenericConversion,
/// Convert the build value into a specific type specified in the attribute.
TypeConversionToSpecificType(syn::TypePath),
}

impl Default for IntoSetting {
fn default() -> Self {
Self::NoConversion
}
}

#[derive(Debug, Default, Clone)]
pub struct BuildMethodSettings {
pub common: CommonDeclarationSettings,

/// Whether to convert the built type into another while finishing the build.
pub into: IntoSetting,
}

impl ApplyMeta for BuildMethodSettings {
fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> {
match expr.name().to_string().as_str() {
"into" => match expr {
AttrArg::Flag(_) => {
self.into = IntoSetting::GenericConversion;
Ok(())
}
AttrArg::KeyValue(key_value) => {
let type_path = key_value.parse_value::<syn::TypePath>()?;
self.into = IntoSetting::TypeConversionToSpecificType(type_path);
Ok(())
}
_ => Err(expr.incorrect_type()),
},
_ => self.common.apply_meta(expr),
}
}
}

#[derive(Debug)]
pub struct TypeBuilderAttr<'a> {
/// Whether to show docs for the `TypeBuilder` type (rather than hiding them).
pub doc: bool,

/// Customize builder method, ex. visibility, name
pub builder_method: CommonDeclarationSettings,

/// Customize builder type, ex. visibility, name
pub builder_type: CommonDeclarationSettings,

/// Customize build method, ex. visibility, name
pub build_method: BuildMethodSettings,

pub field_defaults: FieldBuilderAttr<'a>,

pub crate_module_path: syn::Path,

/// Functions that are able to mutate fields in the builder that are already set
pub mutators: Vec<Mutator>,
}

impl Default for TypeBuilderAttr<'_> {
fn default() -> Self {
Self {
doc: Default::default(),
builder_method: Default::default(),
builder_type: Default::default(),
build_method: Default::default(),
field_defaults: Default::default(),
crate_module_path: syn::parse_quote!(::typed_builder),
mutators: Default::default(),
}
}
}

impl<'a> TypeBuilderAttr<'a> {
pub fn new(attrs: &[syn::Attribute]) -> Result<Self, Error> {
let mut result = Self::default();

for attr in attrs {
let list = match &attr.meta {
syn::Meta::List(list) => {
if path_to_single_string(&list.path).as_deref() != Some("builder") {
continue;
}

list
}
_ => continue,
};

result.apply_subsections(list)?;
}

if result.builder_type.doc.is_some() || result.build_method.common.doc.is_some() {
result.doc = true;
}

Ok(result)
}
}

impl ApplyMeta for TypeBuilderAttr<'_> {
fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> {
match expr.name().to_string().as_str() {
"crate_module_path" => {
let crate_module_path = expr.key_value()?.parse_value::<syn::ExprPath>()?;
self.crate_module_path = crate_module_path.path;
Ok(())
}
"builder_method_doc" => Err(Error::new_spanned(
expr.name(),
"`builder_method_doc` is deprecated - use `builder_method(doc = \"...\")`",
)),
"builder_type_doc" => Err(Error::new_spanned(
expr.name(),
"`builder_typemethod_doc` is deprecated - use `builder_type(doc = \"...\")`",
)),
"build_method_doc" => Err(Error::new_spanned(
expr.name(),
"`build_method_doc` is deprecated - use `build_method(doc = \"...\")`",
)),
"doc" => {
expr.flag()?;
self.doc = true;
Ok(())
}
"mutators" => {
self.mutators.extend(expr.sub_attr()?.undelimited()?);
Ok(())
}
"field_defaults" => self.field_defaults.apply_sub_attr(expr.sub_attr()?),
"builder_method" => self.builder_method.apply_sub_attr(expr.sub_attr()?),
"builder_type" => self.builder_type.apply_sub_attr(expr.sub_attr()?),
"build_method" => self.build_method.apply_sub_attr(expr.sub_attr()?),
_ => Err(Error::new_spanned(
expr.name(),
format!("Unknown parameter {:?}", expr.name().to_string()),
)),
}
}
}
31 changes: 2 additions & 29 deletions typed-builder-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse::Error, parse_macro_input, spanned::Spanned, DeriveInput};

mod builder_attr;
mod field_info;
mod mutator;
mod struct_info;
Expand All @@ -19,34 +19,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
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) => {
let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?;
let builder_creation = struct_info.builder_creation_impl()?;
let fields = struct_info
.setter_fields()
.map(|f| struct_info.field_impl(f))
.collect::<Result<TokenStream, _>>()?;
let required_fields = struct_info
.setter_fields()
.filter(|f| f.builder_attr.default.is_none())
.map(|f| struct_info.required_field_impl(f));
let mutators = struct_info
.fields
.iter()
.flat_map(|f| &f.builder_attr.mutators)
.chain(&struct_info.builder_attr.mutators)
.map(|m| struct_info.mutator_impl(m))
.collect::<Result<TokenStream, _>>()?;
let build_method = struct_info.build_method_impl();

quote! {
#builder_creation
#fields
#(#required_fields)*
#mutators
#build_method
}
}
syn::Fields::Named(fields) => struct_info::StructInfo::new(ast, fields.named.iter())?.derive()?,
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")),
},
Expand Down

0 comments on commit 168e076

Please sign in to comment.