Skip to content

Commit

Permalink
feat: add strip_bool(fallback = field_bool)
Browse files Browse the repository at this point in the history
  • Loading branch information
ifiokjr committed Aug 22, 2024
1 parent 60ae4b6 commit 278c256
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added
- Add `#[builder(setter(strip_option(fallback = field_opt)))]` to add a fallback unstripped method to the builder struct.
- Add `#[builder(setter(strip_bool(fallback = field_bool)))]` to add a fallback setter that takes the `bool` value to the builder struct.

## 0.19.1 - 2024-07-14
### Fixed
Expand Down
40 changes: 40 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ use core::ops::FnOnce;
/// - `strip_bool`: for `bool` fields only, this makes the setter receive no arguments and simply
/// set the field's value to `true`. When used, the `default` is automatically set to `false`.
///
/// - `strip_bool(fallback = field_bool)`: for `bool` fields only. As above this
/// allows passing the boolean value. The name given to the fallback method adds
/// another method to the builder without where the bool value can be specified.
///
/// - `transform = |param1: Type1, param2: Type2 ...| expr`: this makes the setter accept
/// `param1: Type1, param2: Type2 ...` instead of the field type itself. The parameters are
/// transformed into the field type using the expression `expr`. The transformation is performed
Expand Down Expand Up @@ -401,4 +405,40 @@ impl<T> Optional<T> for (T,) {
/// value: Option<i32>,
/// }
/// ```
///
/// Handling invalid property for `strip_bool`
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// struct Foo {
/// #[builder(setter(strip_bool(invalid_field = should_fail)))]
/// value: bool,
/// }
/// ```
///
/// Handling multiple propertes for `strip_bool`
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// struct Foo {
/// #[builder(setter(strip_bool(fallback = value_bool, fallback = value_bool2)))]
/// value: bool,
/// }
/// ```
///
/// Handling alternative propertes for `strip_bool`
///
/// ```compile_fail
/// use typed_builder::TypedBuilder;
///
/// #[derive(TypedBuilder)]
/// struct Foo {
/// #[builder(setter(strip_bool(invalid = value_bool, fallback = value_bool2)))]
/// value: bool,
/// }
/// ```
fn _compile_fail_tests() {}
13 changes: 13 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,19 @@ fn test_strip_bool() {
assert!(Foo::builder().build() == Foo { x: false });
}

#[test]
fn test_strip_bool_with_fallback() {
#[derive(PartialEq, TypedBuilder)]
struct Foo {
#[builder(setter(into, strip_bool(fallback = x_bool)))]
x: bool,
}

assert!(Foo::builder().x().build() == Foo { x: true });
assert!(Foo::builder().x_bool(false).build() == Foo { x: false });
assert!(Foo::builder().build() == Foo { x: false });
}

#[test]
fn test_default() {
#[derive(PartialEq, TypedBuilder)]
Expand Down
45 changes: 39 additions & 6 deletions typed-builder-macro/src/field_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ impl<'a> FieldInfo<'a> {
}

fn post_process(mut self) -> Result<Self, Error> {
if let Some(ref strip_bool_span) = self.builder_attr.setter.strip_bool {
if let Some(ref strip_bool) = self.builder_attr.setter.strip_bool {
if let Some(default_span) = self.builder_attr.default.as_ref().map(Spanned::span) {
let mut error = Error::new(
*strip_bool_span,
strip_bool.span,
"cannot set both strip_bool and default - default is assumed to be false",
);
error.combine(Error::new(default_span, "default set here"));
Expand All @@ -102,7 +102,7 @@ impl<'a> FieldInfo<'a> {
attrs: Default::default(),
lit: syn::Lit::Bool(syn::LitBool {
value: false,
span: *strip_bool_span,
span: strip_bool.span,
}),
}));
}
Expand All @@ -128,7 +128,7 @@ pub struct SetterSettings {
pub skip: Option<Span>,
pub auto_into: Option<Span>,
pub strip_option: Option<Strip>,
pub strip_bool: Option<Span>,
pub strip_bool: Option<Strip>,
pub transform: Option<Transform>,
pub prefix: Option<String>,
pub suffix: Option<String>,
Expand Down Expand Up @@ -196,7 +196,7 @@ impl<'a> FieldBuilderAttr<'a> {
let conflicting_transformations = [
("transform", self.setter.transform.as_ref().map(|t| &t.span)),
("strip_option", self.setter.strip_option.as_ref().map(|s| &s.span)),
("strip_bool", self.setter.strip_bool.as_ref()),
("strip_bool", self.setter.strip_bool.as_ref().map(|s| &s.span)),
];
let mut conflicting_transformations = conflicting_transformations
.iter()
Expand Down Expand Up @@ -370,7 +370,40 @@ impl ApplyMeta for SetterSettings {
_ => Err(expr.incorrect_type()),
}
}
"strip_bool" => expr.apply_flag_to_field(&mut self.strip_bool, "zero arguments setter, sets the field to true"),
"strip_bool" => {
let caption = "zero arguments setter, sets the field to true";

match expr {
AttrArg::Sub(sub) => {
let span = sub.span();

if self.strip_bool.is_none() {
let mut strip_bool = Strip::new(span);
strip_bool.apply_sub_attr(sub)?;
self.strip_bool = Some(strip_bool);
Ok(())
} else {
Err(Error::new(span, format!("Illegal setting - field is already {caption}")))
}
}
AttrArg::Flag(flag) => {
if self.strip_bool.is_none() {
self.strip_bool = Some(Strip::new(flag.span()));
Ok(())
} else {
Err(Error::new(
flag.span(),
format!("Illegal setting - field is already {caption}"),
))
}
}
AttrArg::Not { .. } => {
self.strip_bool = None;
Ok(())
}
_ => Err(expr.incorrect_type()),
}
}
_ => Err(Error::new_spanned(
expr.name(),
format!("Unknown parameter {:?}", expr.name().to_string()),
Expand Down
31 changes: 28 additions & 3 deletions typed-builder-macro/src/struct_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,14 @@ impl<'a> StructInfo<'a> {
(arg_type.to_token_stream(), field_name.to_token_stream())
};

let mut strip_bool_fallback: Option<(Ident, TokenStream, TokenStream)> = None;
let mut strip_option_fallback: Option<(Ident, TokenStream, TokenStream)> = None;
let (param_list, arg_expr) = if field.builder_attr.setter.strip_bool.is_some() {

let (param_list, arg_expr) = if let Some(ref strip_bool) = field.builder_attr.setter.strip_bool {
if let Some(ref fallback) = strip_bool.fallback {
strip_bool_fallback = Some((fallback.clone(), quote!(#field_name: #field_type), quote!(#arg_expr)));
}

(quote!(), quote!(true))
} else if let Some(transform) = &field.builder_attr.setter.transform {
let params = transform.params.iter().map(|(pat, ty)| quote!(#pat: #ty));
Expand Down Expand Up @@ -305,7 +311,25 @@ impl<'a> StructInfo<'a> {

let method_name = field.setter_method_name();

let fallback_method = if let Some((method_name, param_list, arg_expr)) = strip_option_fallback {
let strip_option_fallback_method = if let Some((method_name, param_list, arg_expr)) = strip_option_fallback {
Some(quote! {
#deprecated
#doc
#[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)]
pub fn #method_name (self, #param_list) -> #builder_name <#target_generics> {
let #field_name = (#arg_expr,);
let ( #(#destructuring,)* ) = self.fields;
#builder_name {
fields: ( #(#reconstructing,)* ),
phantom: self.phantom,
}
}
})
} else {
None
};

let strip_bool_fallback_method = if let Some((method_name, param_list, arg_expr)) = strip_bool_fallback {
Some(quote! {
#deprecated
#doc
Expand Down Expand Up @@ -338,7 +362,8 @@ impl<'a> StructInfo<'a> {
phantom: self.phantom,
}
}
#fallback_method
#strip_option_fallback_method
#strip_bool_fallback_method
}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
Expand Down

0 comments on commit 278c256

Please sign in to comment.