Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor implementations of StructInfo::derive and TypeBuilderAttr #127

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading