Skip to content

Commit

Permalink
Move Mutator to a new file
Browse files Browse the repository at this point in the history
  • Loading branch information
idanarye committed Oct 15, 2023
1 parent 7b6e9db commit 9596989
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 132 deletions.
5 changes: 2 additions & 3 deletions typed-builder-macro/src/field_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned;
use syn::{parse::Error, spanned::Spanned};

use crate::util::{
expr_to_lit_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, ApplyMeta, AttrArg, Mutator,
};
use crate::mutator::Mutator;
use crate::util::{expr_to_lit_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, ApplyMeta, AttrArg};

#[derive(Debug)]
pub struct FieldInfo<'a> {
Expand Down
1 change: 1 addition & 0 deletions typed-builder-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use quote::quote;
use syn::{parse::Error, parse_macro_input, spanned::Spanned, DeriveInput};

mod field_info;
mod mutator;
mod struct_info;
mod util;

Expand Down
136 changes: 136 additions & 0 deletions typed-builder-macro/src/mutator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use std::collections::HashSet;

use proc_macro2::Ident;
use syn::{
parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
Error, Expr, FnArg, ItemFn, PatIdent, ReturnType, Signature, Token, Type,
};

use crate::util::{pat_to_ident, ApplyMeta, AttrArg};

#[derive(Debug, Clone)]
pub struct Mutator {
pub fun: ItemFn,
pub required_fields: HashSet<Ident>,
}

#[derive(Default)]
struct MutatorAttribute {
requires: HashSet<Ident>,
}

impl ApplyMeta for MutatorAttribute {
fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> {
if expr.name() != "requires" {
return Err(Error::new_spanned(expr.name(), "Only `requires` is supported"));
}

match expr.key_value()?.parse_value()? {
Expr::Array(syn::ExprArray { elems, .. }) => self.requires.extend(
elems
.into_iter()
.map(|expr| match expr {
Expr::Path(path) if path.path.get_ident().is_some() => {
Ok(path.path.get_ident().cloned().expect("should be ident"))
}
expr => Err(Error::new_spanned(expr, "Expected field name")),
})
.collect::<Result<Vec<_>, _>>()?,
),
expr => {
return Err(Error::new_spanned(
expr,
"Only list of field names [field1, field2, …] supported",
))
}
}
Ok(())
}
}

impl Parse for Mutator {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut fun: ItemFn = input.parse()?;

let mut attribute = MutatorAttribute::default();

let mut i = 0;
while i < fun.attrs.len() {
let attr = &fun.attrs[i];
if attr.path().is_ident("mutator") {
attribute.apply_attr(attr)?;
fun.attrs.remove(i);
} else {
i += 1;
}
}

// Ensure `&mut self` receiver
if let Some(FnArg::Receiver(receiver)) = fun.sig.inputs.first_mut() {
*receiver = parse_quote!(&mut self);
} else {
// Error either on first argument or `()`
return Err(syn::Error::new(
fun.sig
.inputs
.first()
.map(Spanned::span)
.unwrap_or(fun.sig.paren_token.span.span()),
"mutator needs to take a reference to `self`",
));
};

Ok(Self {
fun,
required_fields: attribute.requires,
})
}
}

impl Mutator {
/// Signature for Builder::<mutator> function
pub fn outer_sig(&self, output: Type) -> Signature {
let mut sig = self.fun.sig.clone();
sig.output = ReturnType::Type(Default::default(), output.into());

sig.inputs = sig
.inputs
.into_iter()
.enumerate()
.map(|(i, input)| match input {
FnArg::Receiver(_) => parse_quote!(self),
FnArg::Typed(mut input) => {
input.pat = Box::new(
PatIdent {
attrs: Vec::new(),
by_ref: None,
mutability: None,
ident: pat_to_ident(i, &input.pat),
subpat: None,
}
.into(),
);
FnArg::Typed(input)
}
})
.collect();
sig
}

/// Arguments to call inner mutator function
pub fn arguments(&self) -> Punctuated<Ident, Token![,]> {
self.fun
.sig
.inputs
.iter()
.enumerate()
.filter_map(|(i, input)| match &input {
FnArg::Receiver(_) => None,
FnArg::Typed(input) => Some(pat_to_ident(i, &input.pat)),
})
.collect()
}
}
3 changes: 2 additions & 1 deletion typed-builder-macro/src/struct_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ use syn::punctuated::Punctuated;
use syn::{parse_quote, GenericArgument, ItemFn, Token};

use crate::field_info::{FieldBuilderAttr, FieldInfo};
use crate::mutator::Mutator;
use crate::util::{
empty_type, empty_type_tuple, first_visibility, modify_types_generics_hack, path_to_single_string, public_visibility,
strip_raw_ident_prefix, type_tuple, ApplyMeta, AttrArg, Mutator,
strip_raw_ident_prefix, type_tuple, ApplyMeta, AttrArg,
};

#[derive(Debug)]
Expand Down
131 changes: 3 additions & 128 deletions typed-builder-macro/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::{collections::HashSet, iter};
use std::iter;

use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use quote::{format_ident, ToTokens};
use syn::{
parenthesized,
parse::{Parse, ParseStream, Parser},
parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token, Attribute, Error, Expr, FnArg, ItemFn, Pat, PatIdent, ReturnType, Signature, Token, Type,
token, Attribute, Error, Pat, PatIdent, Token,
};

pub fn path_to_single_string(path: &syn::Path) -> Option<String> {
Expand Down Expand Up @@ -351,134 +350,10 @@ pub trait ApplyMeta {
}
}

#[derive(Debug, Clone)]
pub struct Mutator {
pub fun: ItemFn,
pub required_fields: HashSet<Ident>,
}

#[derive(Default)]
struct MutatorAttribute {
requires: HashSet<Ident>,
}

impl ApplyMeta for MutatorAttribute {
fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> {
if expr.name() != "requires" {
return Err(Error::new_spanned(expr.name(), "Only `requires` is supported"));
}

match expr.key_value()?.parse_value()? {
Expr::Array(syn::ExprArray { elems, .. }) => self.requires.extend(
elems
.into_iter()
.map(|expr| match expr {
Expr::Path(path) if path.path.get_ident().is_some() => {
Ok(path.path.get_ident().cloned().expect("should be ident"))
}
expr => Err(Error::new_spanned(expr, "Expected field name")),
})
.collect::<Result<Vec<_>, _>>()?,
),
expr => {
return Err(Error::new_spanned(
expr,
"Only list of field names [field1, field2, …] supported",
))
}
}
Ok(())
}
}

impl Parse for Mutator {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut fun: ItemFn = input.parse()?;

let mut attribute = MutatorAttribute::default();

let mut i = 0;
while i < fun.attrs.len() {
let attr = &fun.attrs[i];
if attr.path().is_ident("mutator") {
attribute.apply_attr(attr)?;
fun.attrs.remove(i);
} else {
i += 1;
}
}

// Ensure `&mut self` receiver
if let Some(FnArg::Receiver(receiver)) = fun.sig.inputs.first_mut() {
*receiver = parse_quote!(&mut self);
} else {
// Error either on first argument or `()`
return Err(syn::Error::new(
fun.sig
.inputs
.first()
.map(Spanned::span)
.unwrap_or(fun.sig.paren_token.span.span()),
"mutator needs to take a reference to `self`",
));
};

Ok(Self {
fun,
required_fields: attribute.requires,
})
}
}

fn pat_to_ident(i: usize, pat: &Pat) -> Ident {
pub fn pat_to_ident(i: usize, pat: &Pat) -> Ident {
if let Pat::Ident(PatIdent { ident, .. }) = pat {
ident.clone()
} else {
format_ident!("__{i}", span = pat.span())
}
}

impl Mutator {
/// Signature for Builder::<mutator> function
pub fn outer_sig(&self, output: Type) -> Signature {
let mut sig = self.fun.sig.clone();
sig.output = ReturnType::Type(Default::default(), output.into());

sig.inputs = sig
.inputs
.into_iter()
.enumerate()
.map(|(i, input)| match input {
FnArg::Receiver(_) => parse_quote!(self),
FnArg::Typed(mut input) => {
input.pat = Box::new(
PatIdent {
attrs: Vec::new(),
by_ref: None,
mutability: None,
ident: pat_to_ident(i, &input.pat),
subpat: None,
}
.into(),
);
FnArg::Typed(input)
}
})
.collect();
sig
}

/// Arguments to call inner mutator function
pub fn arguments(&self) -> Punctuated<Ident, Token![,]> {
self.fun
.sig
.inputs
.iter()
.enumerate()
.filter_map(|(i, input)| match &input {
FnArg::Receiver(_) => None,
FnArg::Typed(input) => Some(pat_to_ident(i, &input.pat)),
})
.collect()
}
}

0 comments on commit 9596989

Please sign in to comment.