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

feat: add strip_option(fallback = field_bool) #151

Merged
merged 1 commit into from
Aug 22, 2024
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
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 @@
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 Expand Up @@ -556,7 +569,7 @@
#[allow(clippy::items_after_statements)]
fn test_clone_builder_with_generics() {
#[derive(PartialEq, Default)]
struct Uncloneable;

Check warning on line 572 in tests/tests.rs

View workflow job for this annotation

GitHub Actions / Tests (ubuntu-latest, nightly)

struct `Uncloneable` is never constructed

#[derive(PartialEq, TypedBuilder)]
struct Foo<T> {
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
Loading