├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── resources ├── better_error.png └── state_machine.jpg ├── rust-toolchain.toml ├── src ├── attribute.rs ├── err.rs ├── generator │ ├── impl_constraint.rs │ ├── impl_default.rs │ ├── impl_init.rs │ ├── impl_setter.rs │ └── mod.rs ├── generics.rs ├── lib.rs └── wrap.rs └── tests ├── nightly_ui.rs ├── nightly_ui ├── better_error.rs └── better_error.stderr ├── stable_all_optional.rs ├── stable_default_values.rs ├── stable_default_values_for_optionals.rs ├── stable_generics_with_bounds.rs ├── stable_generics_with_bounds_and_where_clause.rs ├── stable_generics_with_const_generics.rs ├── stable_generics_with_lifetimes.rs ├── stable_generics_with_where_clause.rs ├── stable_generics_without_bounds.rs ├── stable_impl_default_no_required_fields.rs ├── stable_multiple_initialization.rs ├── stable_no_generics.rs ├── stable_no_generics_reorder.rs ├── stable_repeated_setters.rs ├── stable_repeated_setters_for_default_fields.rs ├── stable_repeated_setters_for_optionals.rs ├── stable_repeated_setters_overrided_for_optionals.rs ├── stable_repeated_setters_overrided_with_setter.rs ├── stable_skip_optional_and_defaults.rs ├── stable_ui.rs └── ui ├── call_skipped_setter_of_default.rs ├── call_skipped_setter_of_default.stderr ├── call_skipped_setter_of_optional.rs ├── call_skipped_setter_of_optional.stderr ├── default_value_for_non_default_field.rs ├── default_value_for_non_default_field.stderr ├── error ├── builder_does_not_support_enums.rs ├── builder_does_not_support_enums.stderr ├── builder_does_not_support_unions.rs ├── builder_does_not_support_unions.stderr ├── builder_does_not_support_unit_structs.rs ├── builder_does_not_support_unit_structs.stderr ├── builder_does_not_support_unnamed_fields.rs ├── builder_does_not_support_unnamed_fields.stderr ├── nested_meta_list.rs ├── nested_meta_list.stderr ├── not_expected_literal.rs ├── not_expected_literal.stderr ├── not_meta_list.rs ├── not_meta_list.stderr ├── not_string_value.rs ├── not_string_value.stderr ├── unexpected_name.rs ├── unexpected_name.stderr ├── unexpected_name_for_path_attrs.rs └── unexpected_name_for_path_attrs.stderr ├── generics_with_bounds.rs ├── generics_with_bounds.stderr ├── generics_with_bounds_and_where_clause.rs ├── generics_with_bounds_and_where_clause.stderr ├── generics_with_const_generics.rs ├── generics_with_const_generics.stderr ├── generics_with_lifetimes.rs ├── generics_with_lifetimes.stderr ├── generics_with_where_clause.rs ├── generics_with_where_clause.stderr ├── generics_without_bounds.rs ├── generics_without_bounds.stderr ├── multiple_initialization.rs ├── multiple_initialization.stderr ├── no_generics.rs ├── no_generics.stderr ├── no_generics_reorder.rs ├── no_generics_reorder.stderr ├── repeated_setters_with_the_same_name.rs ├── repeated_setters_with_the_same_name.stderr ├── repeated_setters_with_the_same_name_for_optionals.rs ├── repeated_setters_with_the_same_name_for_optionals.stderr ├── skip_required_field.rs └── skip_required_field.stderr /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tidy-builder" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "tidy-builder is a builder generator that is compile-time correct" 6 | authors = ["m.amin.rayej "] 7 | repository = "https://github.com/maminrayej/tidy-builder" 8 | license = "MIT" 9 | categories = ["rust-patterns"] 10 | keywords = ["builder", "builder_pattern"] 11 | 12 | [features] 13 | better_error = [] 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [dependencies] 19 | proc-macro2 = "1.0.42" 20 | quote = "1.0.20" 21 | syn = { version = "1.0.98", features = ["extra-traits"] } 22 | convert_case = "0.5.0" 23 | 24 | [dev-dependencies] 25 | trybuild = "1.0" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mohamad Amin Rayej 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: test_stable test_nightly test_doc 2 | 3 | test_stable: 4 | cargo test --test 'stable_*' 5 | 6 | test_nightly: 7 | cargo +nightly test --features better_error --test 'nightly_*' 8 | 9 | test_doc: 10 | cargo test --doc 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The `Builder` derive macro creates a compile-time correct builder 2 | which means that it only allows you to build the given struct if and only if you provide a 3 | value for all of its required fields. 4 | 5 | From the perspective of the builder there are three types of fields: 6 | * **Optional Fields** which are fields wrapped in an `Option`. 7 | * **Default Fields** which are given a default value through the `#[builder(default)]` attribute. 8 | * **Required Fields** which are fields that do not fall into the previous categories. 9 | 10 | Example below depicts these three types of fields: 11 | ```rust 12 | use tidy_builder::Builder; 13 | 14 | #[derive(Builder)] 15 | struct Person { 16 | first_name: String, 17 | last_name: String, 18 | 19 | age: Option, 20 | 21 | #[builder(default = false)] 22 | employed: bool, 23 | } 24 | 25 | fn main() { 26 | let person = Person::builder() 27 | .first_name("Foo".to_string()) 28 | .last_name("Bar".to_string()) 29 | .age(18) 30 | .build(); 31 | 32 | assert_eq!(person.first_name, "Foo".to_string()); 33 | assert_eq!(person.last_name, "Bar".to_string()); 34 | assert_eq!(person.age, Some(18)); 35 | assert_eq!(person.employed, false); 36 | } 37 | ``` 38 | As you can see, `first_name` and `last_name` are required fields, `age` is optional, and `employed` takes a default value of `false`. 39 | As we mentioned, in order to call `build`, you have to at least provide values for `first_name` and `last_name`. tidy-builder enforces this rule by creating a state machine and guarding the `build` function with special traits in order to make sure `build` is called only in the final state. Picture below shows the state machine created by tidy-builder: 40 | 41 |
42 | 43 | ![](resources/state_machine.jpg) 44 | 45 |
46 | 47 | For more info see [What if I try to call the build function early?](#what_if) and [How it Works](#how_it_works). 48 | 49 | # Features 50 | ## Repeated Setters 51 | For fields that are of form `Vec`, you can instruct the builder to create a repeated setter for you. 52 | This repeated setter gets a single value of type `T` and appends to the `Vec`. For example: 53 | ```rust 54 | use tidy_builder::Builder; 55 | 56 | #[derive(Builder)] 57 | struct Input<'a> { 58 | #[builder(each = "arg")] 59 | args: Vec<&'a str> 60 | } 61 | 62 | fn main() { 63 | let input1 = Input::builder().arg("arg1").arg("arg2").build(); 64 | let input2 = Input::builder().args(vec!["arg1", "arg2"]).build(); 65 | 66 | assert_eq!(input1.args, vec!["arg1", "arg2"]); 67 | assert_eq!(input2.args, vec!["arg1", "arg2"]); 68 | } 69 | ``` 70 | The builder will create another setter function named `arg` alongside the `args` function that was going to be generated anyway. 71 | **Note** that if the name provided for the repeated setter is the same name as the field itself, 72 | only the repeated setter will be provided by the builder since Rust does not support function overloading. 73 | For example if in the example above the repeated setter was named `args`, the setter that takes a `Vec` wouldn't be provided. 74 | 75 | ## Default Values 76 | You can provide default values for fields and make them non-required. If the field is a primitive or a `String`, 77 | you can specify the default value in the `#[builder(default)]` attribute, but if the field is not a primitive, it must implement the `Default` trait. For example: 78 | ```rust 79 | use tidy_builder::Builder; 80 | 81 | #[derive(Debug, PartialEq)] 82 | pub struct Point { 83 | x: usize, 84 | y: usize, 85 | } 86 | 87 | impl Default for Point { 88 | fn default() -> Self { 89 | Point { 90 | x: 0, 91 | y: 0, 92 | } 93 | } 94 | } 95 | 96 | #[derive(Builder)] 97 | struct PlayerPosition { 98 | #[builder(default)] 99 | start: Point, 100 | 101 | #[builder(default = 0)] 102 | offset: usize, 103 | } 104 | 105 | fn main() { 106 | let position = PlayerPosition::builder().build(); 107 | 108 | assert_eq!(position.start, Point { x: 0, y: 0}); 109 | assert_eq!(position.offset, 0); 110 | } 111 | ``` 112 | 113 | ## Skipping Fields 114 | You can prevent the builder from providing setters for **optional** and **default** fields. For example: 115 | ```rust compile_fail 116 | use tidy_builder::Builder; 117 | 118 | #[derive(Builder)] 119 | struct Vote { 120 | submit_url: String, 121 | 122 | #[builder(skip)] 123 | name: Option, 124 | 125 | #[builder(skip)] 126 | #[builder(default = false)] 127 | vote: bool 128 | } 129 | 130 | fn main() { 131 | let vote = Vote::builder().submit_url("fake_submit_url.com").name("Foo".to_string()); // Fails since there is no `name` setter 132 | } 133 | ``` 134 | # What if I try to call the `build` function early? 135 | tidy-builder uses special traits to hint at the missing required fields. For example: 136 | ```rust compile_fail 137 | use tidy_builder::Builder; 138 | 139 | #[derive(Builder)] 140 | struct Foo { 141 | bar: usize, 142 | baz: usize, 143 | } 144 | 145 | fn main() { 146 | let foo = Foo::builder().bar(0).build(); 147 | } 148 | ``` 149 | On stable Rust you'll get a **compile-time** error that the trait `HasBaz` is not implemented for the struct `FooBuilder<...>`. 150 | The trait `HasBaz` indicates that `FooBuilder` **has** a value for the **`baz`** field. 151 | So this trait not being implemented for `FooBuilder` means that a value is not specified for the `baz` field and that's why you cannot call the `build` function. 152 | 153 | On nightly Rust and with the help of `rustc_on_unimplemented`, the `Builder` can hint at the compiler to 154 | show the message `missing baz` to inform the user that in order to call `build`, they should set the value of the `baz` field. 155 | **Note** that this is behind the `better_error` feature gate. 156 | 157 |
158 | 159 | ![better_error](resources/better_error.png) 160 | 161 |
162 | 163 | 164 | # How it works 165 | tidy-builder creates a state machine in order to model the behavior of the builder. The generated builder has a const generic parameter of type `bool` 166 | for each required field to encode whether a value has been set for the field or not. For example: 167 | ```rust 168 | use tidy_builder::Builder; 169 | 170 | #[derive(Builder)] 171 | struct Foo { 172 | bar: usize, 173 | baz: usize, 174 | } 175 | ``` 176 | The struct above will cause this builder to get generated: 177 | ```rust 178 | struct FooBuilder { 179 | bar: Option, 180 | baz: Option, 181 | } 182 | ``` 183 | The builder will start in the `FooBuilder` state when you call the `builder` function of `Foo`: 184 | ```rust 185 | let builder: FooBuilder = Foo::builder(); 186 | let builder: FooBuilder = Foo::builder().bar(0); 187 | let builder: FooBuilder = Foo::builder().bar(0).baz(1); 188 | 189 | let foo = builder.build(); 190 | 191 | assert_eq!(foo.bar, 0); 192 | assert_eq!(foo.baz, 1); 193 | ``` 194 | When you call the `bar` function to set the value of the `bar` field, you cause the builder to transition to the `FooBuilder` state: 195 | Similarly, when you call the `baz` function, you cause the builder to transition to the `FooBuilder` state. 196 | So when you set the value for both fields, you end up at the `FooBuilder` state, 197 | and it's in this state that you can call the build function(the state that all const generic paramters are `true`): 198 | 199 | The error reporting discussed in the previous section leverages these states to inform the user of the missing fields. 200 | For example `HasBar` trait will be implemented for `FooBuilder` , and `HasBaz` will be implemented for `FooBuilder`. 201 | The `build` function is guarded with a where clause to make sure the builder implements all these traits: 202 | ```rust 203 | impl FooBuilder { 204 | fn build(self) -> Foo 205 | where 206 | Self: HasBar + HasBaz 207 | { 208 | // Safety: 209 | // 210 | // It's safe since HasBar and HasBaz are implemented 211 | // hence self.bar and self.baz both contain valid values. 212 | unsafe { 213 | Foo { 214 | bar: self.bar.unwrap_unchecked(), 215 | baz: self.baz.unwrap_unchecked(), 216 | } 217 | } 218 | } 219 | } 220 | ``` 221 | So if you set the value of `bar` and not `baz`, since `HasBaz` won't be implemented for `FooBuilder`, 222 | you'll get a compile-time error that calling `build` is not possible. 223 | -------------------------------------------------------------------------------- /resources/better_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maminrayej/tidy-builder/0f5bdfa927fc78d0b665ff7277a70c210e518d0f/resources/better_error.png -------------------------------------------------------------------------------- /resources/state_machine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maminrayej/tidy-builder/0f5bdfa927fc78d0b665ff7277a70c210e518d0f/resources/state_machine.jpg -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | profile = "default" 3 | channel = "1.63.0" 4 | -------------------------------------------------------------------------------- /src/attribute.rs: -------------------------------------------------------------------------------- 1 | use crate::err::Error; 2 | 3 | // Different attributes that a field can have. 4 | pub enum FieldAttr { 5 | // Represents the repeated setter attribute: `#[builder(each = "name")]` 6 | // `String` will be the name specified by the user. 7 | Repeat(String), 8 | 9 | // Represents the default attribute: 10 | // If the `Option` is `None`: `#[builder(default)]` 11 | // If the `Option` is `Some`: `#[builder(default = lit)]` 12 | Default(Option), 13 | 14 | // Represents the `#[builder(skip)]` attribute. 15 | Skip, 16 | } 17 | 18 | fn parse_attr( 19 | nested: &syn::punctuated::Punctuated, 20 | ) -> Result { 21 | match &nested[0] { 22 | syn::NestedMeta::Meta(meta) => match meta { 23 | // Single word attributes: 24 | // * `#[builder(default)]` 25 | // * `#[builder(skip)]` 26 | syn::Meta::Path(path) => { 27 | let name = &path.segments[0].ident; 28 | 29 | match name.to_string().as_str() { 30 | "default" => Ok(FieldAttr::Default(None)), 31 | "skip" => Ok(FieldAttr::Skip), 32 | _ => Err(Error::UnknownAttr(meta.clone())), 33 | } 34 | } 35 | // Name value attributes: 36 | // * `#[builder(each = "name")]` 37 | // * `#[builder(default = lit)]` 38 | syn::Meta::NameValue(name_value) => { 39 | let name = &name_value.path.segments[0].ident; 40 | 41 | match name.to_string().as_str() { 42 | "each" => { 43 | let each = extract_value(name_value)?; 44 | 45 | Ok(FieldAttr::Repeat(each)) 46 | } 47 | "default" => Ok(FieldAttr::Default(Some(name_value.lit.clone()))), 48 | _ => Err(Error::UnknownAttr(meta.clone())), 49 | } 50 | } 51 | syn::Meta::List(list) => Err(Error::NestedMetaList(list.clone())), 52 | }, 53 | syn::NestedMeta::Lit(lit) => Err(Error::UnexpectedLit(lit.clone())), 54 | } 55 | } 56 | 57 | fn extract_value(name_value: &syn::MetaNameValue) -> Result { 58 | if let syn::Lit::Str(lit_str) = &name_value.lit { 59 | Ok(lit_str.value()) 60 | } else { 61 | Err(Error::NotStrValue(name_value.lit.clone())) 62 | } 63 | } 64 | 65 | // Parses and returns the attributes of the `field`. 66 | pub fn parse_attrs(field: &syn::Field) -> Result { 67 | let mut parsed_attrs = vec![]; 68 | 69 | for raw_attr in &field.attrs { 70 | if let Ok(syn::Meta::List(syn::MetaList { nested, .. })) = raw_attr.parse_meta() { 71 | parsed_attrs.push(parse_attr(&nested)?); 72 | } else { 73 | return Err(Error::NotMetaList(raw_attr.clone())); 74 | } 75 | } 76 | 77 | Ok(FieldAttrs(parsed_attrs)) 78 | } 79 | 80 | pub struct FieldAttrs(Vec); 81 | 82 | impl FieldAttrs { 83 | pub fn should_skip(&self) -> bool { 84 | self.0.iter().any(|attr| matches!(&attr, FieldAttr::Skip)) 85 | } 86 | 87 | #[allow(clippy::option_option)] 88 | pub fn is_default(&self) -> Option> { 89 | self.0.iter().find_map(|attr| { 90 | if let FieldAttr::Default(default) = attr { 91 | Some(default.clone()) 92 | } else { 93 | None 94 | } 95 | }) 96 | } 97 | 98 | pub fn repeated(&self) -> Option<&String> { 99 | self.0.iter().find_map(|attr| { 100 | if let FieldAttr::Repeat(each) = attr { 101 | Some(each) 102 | } else { 103 | None 104 | } 105 | }) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/err.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Error { 3 | Enum(syn::DataEnum), 4 | Union(syn::DataUnion), 5 | UnnamedFields(syn::Fields), 6 | UnitStruct(syn::Fields), 7 | NotMetaList(syn::Attribute), 8 | NotStrValue(syn::Lit), 9 | UnexpectedLit(syn::Lit), 10 | NestedMetaList(syn::MetaList), 11 | UnknownAttr(syn::Meta), 12 | UnsupportedType(syn::Type), 13 | SkipRequired(syn::Field), 14 | } 15 | 16 | impl From for proc_macro::TokenStream { 17 | fn from(error: Error) -> proc_macro::TokenStream { 18 | match error { 19 | Error::Enum(enum_t) => { 20 | syn::Error::new_spanned(enum_t.enum_token, "Builder does not support enums") 21 | .into_compile_error() 22 | .into() 23 | } 24 | Error::Union(union_t) => { 25 | syn::Error::new_spanned(union_t.union_token, "Builder does not support unions") 26 | .into_compile_error() 27 | .into() 28 | } 29 | Error::UnnamedFields(fields) => { 30 | syn::Error::new_spanned(fields, "Builder does not support unnamed fields") 31 | .into_compile_error() 32 | .into() 33 | } 34 | Error::UnitStruct(fields) => { 35 | syn::Error::new_spanned(fields, "Builder does not support unit structs") 36 | .into_compile_error() 37 | .into() 38 | } 39 | Error::NotMetaList(attr) => { 40 | syn::Error::new_spanned(attr, "Provided attribute is malformed") 41 | .into_compile_error() 42 | .into() 43 | } 44 | Error::NotStrValue(lit) => syn::Error::new_spanned(lit, "Value must be a string") 45 | .into_compile_error() 46 | .into(), 47 | Error::UnexpectedLit(lit) => syn::Error::new_spanned(lit, "Not expected a literal") 48 | .into_compile_error() 49 | .into(), 50 | Error::NestedMetaList(meta_list) => { 51 | syn::Error::new_spanned(meta_list, "Nested attributes are not supported") 52 | .into_compile_error() 53 | .into() 54 | } 55 | Error::UnknownAttr(name_value) => { 56 | syn::Error::new_spanned(name_value, "Unknown attribute") 57 | .into_compile_error() 58 | .into() 59 | } 60 | Error::UnsupportedType(ty) => { 61 | syn::Error::new_spanned(ty, "Only segmented paths are supported") 62 | .into_compile_error() 63 | .into() 64 | } 65 | Error::SkipRequired(field) => { 66 | syn::Error::new_spanned(field, "Cannot skip a required field") 67 | .into_compile_error() 68 | .into() 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/generator/impl_constraint.rs: -------------------------------------------------------------------------------- 1 | use convert_case::{Case, Casing}; 2 | use quote::{format_ident, quote}; 3 | 4 | use super::Generator; 5 | 6 | impl<'a> Generator<'a> { 7 | // Returns the traits guarding the `build` function. 8 | pub fn guards(&self) -> (Vec, Vec) { 9 | let mut guard_traits = vec![]; 10 | let mut guard_trait_idents = vec![]; 11 | 12 | // Generate a trait guard for each required field. 13 | for (field_idx, field) in self.req_fields.iter().enumerate() { 14 | let field_name = field.ident.as_ref().unwrap().to_string(); 15 | let field_camel = field_name.to_case(Case::UpperCamel); 16 | let trait_ident = format_ident!("Has{}", field_camel); 17 | 18 | let before_ct_pn = &self.b_const_pn[0..field_idx]; 19 | let after_ct_pn = &self.b_const_pn[field_idx + 1..]; 20 | 21 | let before_ct_p = &self.b_const_p[0..field_idx]; 22 | let after_ct_p = &self.b_const_p[field_idx + 1..]; 23 | 24 | // This feature uses `#[rustc_on_unimplemented]` which is only available 25 | // in a nightly compiler. 26 | let mut error_message = None; 27 | if cfg!(feature = "better_error") { 28 | let message = format!("missing `{}`", &field_name); 29 | let label = format!("provide `{}` before calling `.build()`", &field_name); 30 | error_message = Some(quote! { 31 | #[rustc_on_unimplemented( 32 | message=#message, 33 | label=#label, 34 | )] 35 | }); 36 | } 37 | 38 | // Define these to be able to interpolate in quote. 39 | let b_ident = &self.b_ident; 40 | let where_clause = &self.where_clause; 41 | let st_lifetime_pn = &self.st_lifetime_pn; 42 | let st_const_pn = &self.st_const_pn; 43 | let st_type_pn = &self.st_type_pn; 44 | let st_lifetime_p = &self.st_lifetime_p; 45 | let st_const_p = &self.st_const_p; 46 | let st_type_p = &self.st_type_p; 47 | 48 | guard_traits.push(quote! { 49 | #error_message 50 | trait #trait_ident {} 51 | impl<#(#st_lifetime_p,)* #(#st_const_p,)* #(#before_ct_p,)* #(#after_ct_p,)* #(#st_type_p,)* > 52 | #trait_ident for 53 | #b_ident<#(#st_lifetime_pn,)* #(#st_const_pn,)* #(#before_ct_pn,)* true, #(#after_ct_pn,)* #(#st_type_pn,)* > 54 | #where_clause { } 55 | }); 56 | 57 | guard_trait_idents.push(trait_ident); 58 | } 59 | 60 | (guard_traits, guard_trait_idents) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/generator/impl_default.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | 3 | use super::Generator; 4 | 5 | impl<'a> Generator<'a> { 6 | /// Generate Default trait impl if there are no required fields 7 | pub fn default_trait(&self) -> Vec { 8 | if self.req_fields.len() > 0 { 9 | return vec![]; 10 | } 11 | 12 | let impl_generics = &self.impl_generics; 13 | let s_ident = &self.s_ident; 14 | let ty_generics = &self.ty_generics; 15 | 16 | vec![quote! { 17 | impl #impl_generics Default for #s_ident #ty_generics { 18 | fn default() -> Self { 19 | Self::builder().build() 20 | } 21 | } 22 | }] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/generator/impl_init.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::spanned::Spanned; 3 | 4 | use super::Generator; 5 | 6 | impl<'a> Generator<'a> { 7 | // Iterates over required fields and initializes the generator. 8 | pub fn req_init(&mut self) { 9 | for (index, field) in self.req_fields.iter().enumerate() { 10 | let field_ident = &field.ident; 11 | let field_ty = &field.ty; 12 | let ct_param_ident = syn::Ident::new(&format!("P{}", index), field.span()); 13 | 14 | // Wrap the type of the field in an `Option` to be able to set it to `None` at the beginning. 15 | self.b_fields 16 | .push(quote! { #field_ident: ::std::option::Option<#field_ty> }); 17 | self.b_inits.push(quote! { #field_ident: None }); 18 | 19 | // Create a const generic parameter for each required field in order to track whether it's been initialized or not. 20 | self.b_const_p.push(quote! { const #ct_param_ident: bool }); 21 | self.b_const_pn.push(quote! { #ct_param_ident }); 22 | 23 | self.all_false.push(quote! { false }); 24 | 25 | self.req_moves 26 | .push(quote! { #field_ident: self.#field_ident }); 27 | self.req_unwraps 28 | .push(quote! { #field_ident: self.#field_ident.unwrap_unchecked() }); 29 | } 30 | } 31 | 32 | // Iterates over optional fields and initializes the generator. 33 | pub fn opt_init(&mut self) { 34 | for opt_field in &self.opt_fields { 35 | let field_ident = &opt_field.ident; 36 | let field_ty = &opt_field.ty; 37 | 38 | // Since this field is already `Option`, we don't wrap it in `Option` again. 39 | self.b_fields.push(quote! { #field_ident: #field_ty }); 40 | self.b_inits.push(quote! { #field_ident: None }); 41 | 42 | self.opt_moves 43 | .push(quote! { #field_ident: self.#field_ident }); 44 | } 45 | } 46 | 47 | // Iterates over default fields and initializes the generator. 48 | pub fn def_init(&mut self) { 49 | for field in &self.def_fields { 50 | let field_ident = &field.ident; 51 | let field_ty = &field.ty; 52 | 53 | let default_value = match self.f_attrs[field].is_default().unwrap() { 54 | Some(value) => quote! { #value }, 55 | None => quote! { ::std::default::Default::default() }, 56 | }; 57 | 58 | // No need to wrap a default field in an `Option` since we have its initialization value. 59 | self.b_fields.push(quote! { #field_ident: #field_ty }); 60 | self.b_inits.push(quote! { #field_ident: #default_value }); 61 | 62 | self.def_moves 63 | .push(quote! { #field_ident: self.#field_ident }); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/generator/impl_setter.rs: -------------------------------------------------------------------------------- 1 | use quote::quote; 2 | use syn::spanned::Spanned; 3 | 4 | use super::Generator; 5 | use crate::err::Error; 6 | use crate::wrap::{is_option, type_ident, wrapped_in}; 7 | 8 | impl<'a> Generator<'a> { 9 | // Iterates over required fields and generate their corrosponding setters. 10 | pub fn req_setters(&self) -> Result, Error> { 11 | let mut req_setters = vec![]; 12 | 13 | for (index, &req_field) in self.req_fields.iter().enumerate() { 14 | let field_ident = &req_field.ident; 15 | let field_ty = &req_field.ty; 16 | 17 | if self.f_attrs[req_field].should_skip() { 18 | return Err(Error::SkipRequired(req_field.clone())); 19 | } 20 | 21 | let repeated_attr = self.f_attrs[req_field].repeated(); 22 | 23 | // When setting a required field, we need to move the other required fields 24 | // into the new state. So we pick the moves before and after this field. 25 | let before_req_moves = &self.req_moves[..index]; 26 | let after_req_moves = &self.req_moves[index + 1..]; 27 | 28 | // When setting a parameter to `true`, we need to copy the other parameter 29 | // names. So we pick the parameter names before and after the parameter that 30 | // corresponds to this required field. 31 | let before_pn = &self.b_const_pn[..index]; 32 | let after_pn = &self.b_const_pn[index + 1..]; 33 | 34 | // Define these to be able to interpolate in quote. 35 | let b_ident = &self.b_ident; 36 | let st_lifetime_pn = &self.st_lifetime_pn; 37 | let st_const_pn = &self.st_const_pn; 38 | let st_type_pn = &self.st_type_pn; 39 | let req_moves = &self.req_moves; 40 | let opt_moves = &self.opt_moves; 41 | let def_moves = &self.def_moves; 42 | 43 | // When we set the value of a required field, we must change to a state in 44 | // which the parameter corresponding to that field is set to `true`. 45 | // This is the non-repeated setter. 46 | let req_setter = quote! { 47 | pub fn #field_ident(self, #field_ident: #field_ty) -> 48 | #b_ident<#(#st_lifetime_pn,)* #(#st_const_pn,)* #(#before_pn,)* true, #(#after_pn,)* #(#st_type_pn,)*> 49 | { 50 | #b_ident { 51 | #(#before_req_moves,)* 52 | #field_ident: Some(#field_ident), 53 | #(#after_req_moves,)* 54 | #(#opt_moves,)* 55 | #(#def_moves,)* 56 | } 57 | } 58 | }; 59 | 60 | if let Some(each) = repeated_attr { 61 | let container_ident = type_ident(field_ty)?; 62 | let item_type = wrapped_in(field_ty, Some("Vec")); 63 | let each_ident = syn::Ident::new(each.as_str(), req_field.span()); 64 | 65 | req_setters.push( 66 | quote! { 67 | pub fn #each_ident(mut self, #each_ident: #item_type) -> 68 | #b_ident<#(#st_lifetime_pn,)* #(#st_const_pn,)* #(#before_pn,)* true, #(#after_pn,)* #(#st_type_pn,)*> 69 | { 70 | match self.#field_ident.as_mut() { 71 | // If the vector is already created, just extend it using the newly provided value. 72 | Some(c) => c.extend(Some(#each_ident)), 73 | // If not, create an empty `Vec`, extend it using the provided value, and set it. 74 | None => { 75 | let mut c = #container_ident::new(); 76 | c.extend(Some(#each_ident)); 77 | self.#field_ident = Some(c); 78 | } 79 | } 80 | #b_ident { 81 | #(#req_moves,)* 82 | #(#opt_moves,)* 83 | #(#def_moves,)* 84 | } 85 | } 86 | } 87 | ); 88 | 89 | // Rust doesn't support function overloading so we can't have two setter functions with the same name. 90 | // Prefer the repeated setter over the other setter since the user was explicit about wanting a repeated setter. 91 | if field_ident.clone().unwrap() != each { 92 | req_setters.push(req_setter); 93 | } 94 | } else { 95 | req_setters.push(req_setter); 96 | } 97 | } 98 | 99 | Ok(req_setters) 100 | } 101 | 102 | pub fn opt_setters(&self) -> Result, Error> { 103 | let mut opt_setters = vec![]; 104 | 105 | for opt_field in &self.opt_fields { 106 | let field_ident = &opt_field.ident; 107 | let field_ty = &opt_field.ty; 108 | let inner_ty = is_option(field_ty).unwrap(); 109 | 110 | if self.f_attrs[opt_field].should_skip() { 111 | continue; 112 | } 113 | 114 | let repeated_attr = self.f_attrs[opt_field].repeated(); 115 | 116 | // Define these to be able to interpolate in quote. 117 | let b_ident = &self.b_ident; 118 | let b_const_pn = &self.b_const_pn; 119 | let st_lifetime_pn = &self.st_lifetime_pn; 120 | let st_const_pn = &self.st_const_pn; 121 | let st_type_pn = &self.st_type_pn; 122 | 123 | // No need to create a new state, so just set the value. 124 | // This setter is the non-repeated setter. 125 | let opt_setter = quote! { 126 | pub fn #field_ident(mut self, #field_ident: #inner_ty) -> 127 | #b_ident<#(#st_lifetime_pn,)* #(#st_const_pn,)* #(#b_const_pn,)* #(#st_type_pn,)*> 128 | { 129 | self.#field_ident = Some(#field_ident); 130 | self 131 | } 132 | }; 133 | 134 | if let Some(each) = repeated_attr { 135 | let container_ident = type_ident(inner_ty)?; 136 | let item_type = wrapped_in(inner_ty, Some("Vec")); 137 | let each_ident = syn::Ident::new(each.as_str(), opt_field.span()); 138 | 139 | // Repeated setter 140 | // No need to create a new state, so just set the value. 141 | opt_setters.push(quote! { 142 | pub fn #each_ident(mut self, #each_ident: #item_type) -> 143 | #b_ident<#(#st_lifetime_pn,)* #(#st_const_pn,)* #(#b_const_pn,)* #(#st_type_pn,)*> 144 | { 145 | match self.#field_ident.as_mut() { 146 | // If the vector is already created, just extend it using the newly provided value. 147 | Some(c) => c.extend(Some(#each_ident)), 148 | // If not, create an empty `Vec`, extend it using the provided value, and set it. 149 | None => { 150 | let mut c = #container_ident::new(); 151 | c.extend(Some(#each_ident)); 152 | self.#field_ident = Some(c); 153 | } 154 | } 155 | 156 | self 157 | } 158 | }); 159 | 160 | // Rust doesn't support function overloading so we can't have two setter functions with the same name. 161 | // Prefer the repeated setter over the other setter since the user was explicit about wanting a repeated setter. 162 | if field_ident.clone().unwrap() != each { 163 | opt_setters.push(opt_setter); 164 | } 165 | } else { 166 | opt_setters.push(opt_setter); 167 | } 168 | } 169 | 170 | Ok(opt_setters) 171 | } 172 | 173 | pub fn def_setters(&self) -> Result, Error> { 174 | let mut def_setters = vec![]; 175 | 176 | for def_field in &self.def_fields { 177 | let field_ident = &def_field.ident; 178 | let field_ty = &def_field.ty; 179 | 180 | if self.f_attrs[def_field].should_skip() { 181 | continue; 182 | } 183 | 184 | let repeated_attr = self.f_attrs[def_field].repeated(); 185 | 186 | // Define these to be able to interpolate in quote. 187 | let b_ident = &self.b_ident; 188 | let b_const_pn = &self.b_const_pn; 189 | let st_lifetime_pn = &self.st_lifetime_pn; 190 | let st_const_pn = &self.st_const_pn; 191 | let st_type_pn = &self.st_type_pn; 192 | 193 | // No need to create a new state, so just set the value. 194 | let def_setter = quote! { 195 | pub fn #field_ident(mut self, #field_ident: #field_ty) -> 196 | #b_ident<#(#st_lifetime_pn,)* #(#st_const_pn,)* #(#b_const_pn,)* #(#st_type_pn,)*> 197 | { 198 | self.#field_ident = #field_ident; 199 | self 200 | } 201 | }; 202 | 203 | if let Some(each) = repeated_attr { 204 | let item_type = wrapped_in(field_ty, Some("Vec")); 205 | let each_ident = syn::Ident::new(each.as_str(), field_ty.span()); 206 | 207 | // Repeated setter 208 | // No need to create a new state, so just set the value. 209 | def_setters.push(quote! { 210 | pub fn #each_ident(mut self, #each_ident: #item_type) -> 211 | #b_ident<#(#st_lifetime_pn,)* #(#st_const_pn,)* #(#b_const_pn,)* #(#st_type_pn,)*> 212 | { 213 | self.#field_ident.extend(Some(#each_ident)); 214 | 215 | self 216 | } 217 | }); 218 | 219 | // Rust doesn't support function overloading so we can't have two setter functions with the same name. 220 | // Prefer the repeated setter over the other setter since the user was explicit about wanting a repeated setter. 221 | if field_ident.clone().unwrap() != each { 222 | def_setters.push(def_setter); 223 | } 224 | } else { 225 | def_setters.push(def_setter); 226 | } 227 | } 228 | 229 | Ok(def_setters) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/generator/mod.rs: -------------------------------------------------------------------------------- 1 | mod impl_constraint; 2 | mod impl_default; 3 | mod impl_init; 4 | mod impl_setter; 5 | 6 | use std::collections::HashMap; 7 | 8 | use quote::{format_ident, quote}; 9 | 10 | use crate::attribute::{parse_attrs, FieldAttrs}; 11 | use crate::err::Error; 12 | use crate::generics::{param_to_name, split_param_names, split_params, GenericParamName}; 13 | use crate::wrap::is_option; 14 | 15 | pub struct Generator<'a> { 16 | // Map from a field to its parsed attributes 17 | f_attrs: HashMap<&'a syn::Field, FieldAttrs>, 18 | 19 | // Builder name 20 | b_ident: syn::Ident, 21 | 22 | // Struct name 23 | s_ident: syn::Ident, 24 | 25 | // Different pieces of a type’s generics required for impl’ing a trait for that type. 26 | // 27 | // impl Foo where T: std::fmt::Display 28 | // ----------------- ---- -------------------------- 29 | // 0 1 2 30 | // 31 | // 0: impl_generics 32 | // 1: ty_generics 33 | // 2: where_clause 34 | impl_generics: syn::ImplGenerics<'a>, 35 | ty_generics: syn::TypeGenerics<'a>, 36 | where_clause: Option<&'a syn::WhereClause>, 37 | 38 | // st_lifetime_pn: struct's lifetime parameter names 39 | // st_const_pn: struct's const parameter names 40 | // st_type_pn: struct's type parameter names 41 | // 42 | // struct Foo<'a: 'b, const N: usize, T: std::fmt::Display> 43 | // -- - - 44 | // 0 1 2 45 | // 46 | // 0: st_lifetime_pn 47 | // 1: st_const_pn 48 | // 2: st_type_pn 49 | st_lifetime_pn: Vec, 50 | st_const_pn: Vec, 51 | st_type_pn: Vec, 52 | 53 | // st_lt_p: struct's lifetime parameters 54 | // st_ct_p: struct's const parameters 55 | // st_ty_p: struct's type parameters 56 | // 57 | // struct Foo<'a: 'b, const N: usize, T: std::fmt::Display> 58 | // ------ -------------- -------------------- 59 | // 0 1 2 60 | // 61 | // 0: st_lt_p 62 | // 1: st_ct_p 63 | // 2: st_ty_p 64 | st_lifetime_p: Vec, 65 | st_const_p: Vec, 66 | st_type_p: Vec, 67 | 68 | // Different kinds of fields of the struct 69 | // 70 | // struct Foo { 71 | // req_filed: usize, 72 | // 73 | // opt_field: Option, 79 | opt_fields: Vec<&'a syn::Field>, 80 | def_fields: Vec<&'a syn::Field>, 81 | 82 | // All builder const generics set to false. 83 | // Represents the initial state of the state machine. 84 | all_false: Vec, 85 | 86 | // b_ct_pn: builder const param names 87 | // b_ct_p: builder const params 88 | // 89 | // Have similar semantics to `s_ct_pn` and `s_ct_p`. 90 | b_const_pn: Vec, 91 | b_const_p: Vec, 92 | 93 | // b_fields: Contains fields of the builder 94 | // b_inits: Contains initializtion code for fields of the builder 95 | b_fields: Vec, 96 | b_inits: Vec, 97 | 98 | // When we set the value of a required field, we must create the next state in the state machine. 99 | // For that matter, we need to move the fields from the previous state(previous struct) to the new one(new struct). 100 | // These variables contain the code to move the fields to the new state. 101 | req_moves: Vec, 102 | opt_moves: Vec, 103 | def_moves: Vec, 104 | 105 | // When we reach the final state of the state machine and want to build the struct, 106 | // we will call `unwrap` on the required fields because we know they are not `None`. 107 | // This variable contains the code to unwrap the required fields. 108 | req_unwraps: Vec, 109 | } 110 | 111 | impl<'a> Generator<'a> { 112 | pub fn new(ast: &'a syn::DeriveInput) -> Result { 113 | match ast.data { 114 | syn::Data::Struct(ref struct_t) => match &struct_t.fields { 115 | syn::Fields::Named(syn::FieldsNamed { named, .. }) => { 116 | let fields = named; 117 | let s_ident = ast.ident.clone(); 118 | 119 | // Map each field to its parsed attributes. 120 | let mut f_attrs = HashMap::with_capacity(fields.len()); 121 | for field in fields { 122 | let attrs = parse_attrs(field)?; 123 | 124 | f_attrs.insert(field, attrs); 125 | } 126 | 127 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 128 | 129 | let b_ident = format_ident!("{}Builder", s_ident); 130 | 131 | //--- Struct generic Parameters ---// 132 | let st_param_names = param_to_name(&ast.generics); 133 | 134 | // st_lifetime_pn: struct lifetime param names 135 | // st_const_pn: struct const param names 136 | // st_type_pn: struct type param names 137 | let (st_lifetime_pn, st_const_pn, st_type_pn) = 138 | split_param_names(st_param_names); 139 | 140 | // st_lifetime_p: struct lifetime params 141 | // st_const_p: struct const params 142 | // st_type_p: struct type params 143 | let (st_lifetime_p, st_const_p, st_type_p) = 144 | split_params(ast.generics.params.iter()); 145 | 146 | // Split the struct fields since handling required, optional, and default fields is different. 147 | let mut req_fields = vec![]; 148 | let mut opt_fields = vec![]; 149 | let mut def_fields = vec![]; 150 | for field in fields { 151 | let is_default = f_attrs[field].is_default().is_some(); 152 | let is_option = is_option(&field.ty).is_some(); 153 | 154 | if is_option { 155 | opt_fields.push(field); 156 | } else if is_default { 157 | def_fields.push(field); 158 | } else { 159 | req_fields.push(field); 160 | } 161 | } 162 | 163 | let mut generator = Generator { 164 | f_attrs, 165 | b_ident, 166 | s_ident, 167 | 168 | impl_generics, 169 | ty_generics, 170 | where_clause, 171 | 172 | st_lifetime_pn, 173 | st_const_pn, 174 | st_type_pn, 175 | st_lifetime_p, 176 | st_const_p, 177 | st_type_p, 178 | 179 | req_fields, 180 | opt_fields, 181 | def_fields, 182 | 183 | all_false: vec![], 184 | 185 | b_const_pn: vec![], 186 | b_const_p: vec![], 187 | b_fields: vec![], 188 | b_inits: vec![], 189 | 190 | req_moves: vec![], 191 | opt_moves: vec![], 192 | def_moves: vec![], 193 | 194 | req_unwraps: vec![], 195 | }; 196 | 197 | generator.req_init(); 198 | generator.opt_init(); 199 | generator.def_init(); 200 | 201 | Ok(generator) 202 | } 203 | syn::Fields::Unnamed(_) => Err(Error::UnnamedFields(struct_t.fields.clone())), 204 | syn::Fields::Unit => Err(Error::UnitStruct(struct_t.fields.clone())), 205 | }, 206 | syn::Data::Enum(ref enum_t) => Err(Error::Enum(enum_t.clone())), 207 | syn::Data::Union(ref union_t) => Err(Error::Union(union_t.clone())), 208 | } 209 | } 210 | 211 | pub fn generate(self) -> Result { 212 | let req_setters = self.req_setters()?; 213 | let opt_setters = self.opt_setters()?; 214 | let def_setters = self.def_setters()?; 215 | 216 | let (guard_traits, guard_trait_idents) = self.guards(); 217 | let default_trait = self.default_trait(); 218 | 219 | let ( 220 | b_ident, 221 | s_ident, 222 | all_false, 223 | impl_generics, 224 | ty_generics, 225 | where_clause, 226 | st_lifetime_pn, 227 | st_const_pn, 228 | st_type_pn, 229 | st_lifetime_p, 230 | st_const_p, 231 | st_type_p, 232 | _req_fields, 233 | _opt_fields, 234 | _def_fields, 235 | b_const_pn, 236 | b_const_p, 237 | b_fields, 238 | b_inits, 239 | _req_moves, 240 | opt_moves, 241 | def_moves, 242 | req_unwraps, 243 | ) = ( 244 | self.b_ident, 245 | self.s_ident, 246 | self.all_false, 247 | self.impl_generics, 248 | self.ty_generics, 249 | self.where_clause, 250 | self.st_lifetime_pn, 251 | self.st_const_pn, 252 | self.st_type_pn, 253 | self.st_lifetime_p, 254 | self.st_const_p, 255 | self.st_type_p, 256 | self.req_fields, 257 | self.opt_fields, 258 | self.def_fields, 259 | self.b_const_pn, 260 | self.b_const_p, 261 | self.b_fields, 262 | self.b_inits, 263 | self.req_moves, 264 | self.opt_moves, 265 | self.def_moves, 266 | self.req_unwraps, 267 | ); 268 | 269 | Ok(quote! { 270 | pub struct #b_ident<#(#st_lifetime_p,)* #(#st_const_p,)* #(#b_const_p,)* #(#st_type_p,)*> #where_clause { 271 | #(#b_fields),* 272 | } 273 | 274 | impl #impl_generics #s_ident #ty_generics #where_clause { 275 | pub fn builder() -> #b_ident<#(#st_lifetime_pn,)* #(#st_const_pn,)* #(#all_false,)* #(#st_type_pn,)*> { 276 | #b_ident { 277 | #(#b_inits),* 278 | } 279 | } 280 | } 281 | 282 | impl<#(#st_lifetime_p,)* #(#st_const_p,)* #(#b_const_p,)* #(#st_type_p,)*> 283 | #b_ident<#(#st_lifetime_pn,)* #(#st_const_pn,)* #(#b_const_pn,)* #(#st_type_pn,)* > 284 | #where_clause 285 | { 286 | #(#req_setters)* 287 | #(#opt_setters)* 288 | #(#def_setters)* 289 | 290 | fn build(self) -> #s_ident #ty_generics 291 | where Self: #(#guard_trait_idents)+* 292 | { 293 | unsafe { 294 | #s_ident { 295 | #(#opt_moves,)* 296 | #(#def_moves,)* 297 | #(#req_unwraps,)* 298 | } 299 | } 300 | } 301 | } 302 | 303 | #(#guard_traits)* 304 | #(#default_trait)* 305 | }) 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/generics.rs: -------------------------------------------------------------------------------- 1 | // Sometimes we only need the name of a generic parameter. 2 | // For example in `T: std::fmt::Display`, the whole thing is 3 | // a generic parameter but we want to extract the `T` from it. 4 | // Since we have three types of generic parameters, we need to 5 | // distinguish between their names too. 6 | // * A `Type` is like `T: std::fmt::Display` from which we want the `T` which is the `Ident`. 7 | // * A `Lifetime` is like `'a: 'b` from which we want the `'a` which is the `Lifetime`. 8 | // * A `Const` is like `const N: usize` from which we want the `N` which is the `Ident`. 9 | #[derive(Clone)] 10 | pub enum GenericParamName { 11 | Type(syn::Ident), 12 | Lifetime(syn::Lifetime), 13 | Const(syn::Ident), 14 | } 15 | 16 | // We need this trait to be able to interpolate on a vector of `GenericParamName`. 17 | impl quote::ToTokens for GenericParamName { 18 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 19 | match self { 20 | GenericParamName::Type(ty) => ty.to_tokens(tokens), 21 | GenericParamName::Lifetime(lt) => lt.to_tokens(tokens), 22 | GenericParamName::Const(ct) => ct.to_tokens(tokens), 23 | } 24 | } 25 | } 26 | 27 | // Extracts the name of each generic parameter in `generics`. 28 | pub fn param_to_name(generics: &syn::Generics) -> Vec { 29 | generics 30 | .params 31 | .iter() 32 | .map(|param| match param { 33 | syn::GenericParam::Type(ty) => GenericParamName::Type(ty.ident.clone()), 34 | syn::GenericParam::Lifetime(lt) => GenericParamName::Lifetime(lt.lifetime.clone()), 35 | syn::GenericParam::Const(c) => GenericParamName::Const(c.ident.clone()), 36 | }) 37 | .collect() 38 | } 39 | 40 | // Splits the generic parameter names into three categories. 41 | pub fn split_param_names( 42 | param_names: Vec, 43 | ) -> ( 44 | Vec, // Lifetime generic parameter names 45 | Vec, // Const generic parameter names 46 | Vec, // Type generic parameter names 47 | ) { 48 | let mut lifetimes = vec![]; 49 | let mut consts = vec![]; 50 | let mut types = vec![]; 51 | 52 | for param_name in param_names { 53 | match param_name { 54 | GenericParamName::Lifetime(_) => lifetimes.push(param_name.clone()), 55 | GenericParamName::Const(_) => consts.push(param_name.clone()), 56 | GenericParamName::Type(_) => types.push(param_name.clone()), 57 | } 58 | } 59 | 60 | (lifetimes, consts, types) 61 | } 62 | 63 | // Splits generic parameters into three categories. 64 | pub fn split_params<'a>( 65 | params: impl Iterator, 66 | ) -> ( 67 | Vec, // Lifetime generic parameters 68 | Vec, // Const generic parameters 69 | Vec, // Type generic parameters 70 | ) { 71 | let mut lifetimes = vec![]; 72 | let mut consts = vec![]; 73 | let mut types = vec![]; 74 | 75 | for param in params { 76 | match param { 77 | syn::GenericParam::Lifetime(_) => lifetimes.push(param.clone()), 78 | syn::GenericParam::Const(_) => consts.push(param.clone()), 79 | syn::GenericParam::Type(_) => types.push(param.clone()), 80 | } 81 | } 82 | 83 | (lifetimes, consts, types) 84 | } 85 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The `Builder` derive macro creates a compile-time correct builder 2 | //! which means that it only allows you to build the given struct if and only if you provide a 3 | //! value for all of its required fields. 4 | //! 5 | //! From the perspective of the builder there are three types of fields: 6 | //! * **Optional Fields** which are fields wrapped in an `Option`. 7 | //! * **Default Fields** which are given a default value through the `#[builder(default)]` attribute. 8 | //! * **Required Fields** which are fields that do not fall into the previous categories. 9 | //! 10 | //! Example below depicts these three types of fields: 11 | //! ```rust 12 | //! use tidy_builder::Builder; 13 | //! 14 | //! #[derive(Builder)] 15 | //! struct Person { 16 | //! first_name: String, 17 | //! last_name: String, 18 | //! 19 | //! age: Option, 20 | //! 21 | //! #[builder(default = false)] 22 | //! employed: bool, 23 | //! } 24 | //! 25 | //! let person = Person::builder() 26 | //! .first_name("Foo".to_string()) 27 | //! .last_name("Bar".to_string()) 28 | //! .age(18) 29 | //! .build(); 30 | //! 31 | //! assert_eq!(person.first_name, "Foo".to_string()); 32 | //! assert_eq!(person.last_name, "Bar".to_string()); 33 | //! assert_eq!(person.age, Some(18)); 34 | //! assert_eq!(person.employed, false); 35 | //! ``` 36 | //! As you can see, `first_name` and `last_name` are required fields, `age` is optional, and `employed` takes a default value of `false`. 37 | //! As we mentioned, in order to call `build`, you have to at least provide values for `first_name` and `last_name`. 38 | //! 39 | //! # Features 40 | //! ## Repeated setters 41 | //! For fields that are of form `Vec`, you can instruct the builder to create a repeated setter for you. 42 | //! This repeated setter gets a single value of type `T` and appends to the `Vec`. For example: 43 | //! ```rust 44 | //! use tidy_builder::Builder; 45 | //! 46 | //! #[derive(Builder)] 47 | //! struct Input<'a> { 48 | //! #[builder(each = "arg")] 49 | //! args: Vec<&'a str> 50 | //! } 51 | //! 52 | //! let input1 = Input::builder().arg("arg1").arg("arg2").build(); 53 | //! let input2 = Input::builder().args(vec!["arg1", "arg2"]).build(); 54 | //! 55 | //! assert_eq!(input1.args, vec!["arg1", "arg2"]); 56 | //! assert_eq!(input2.args, vec!["arg1", "arg2"]); 57 | //! ``` 58 | //! The builder will create another setter function named `arg` alongside the `args` function that was going to be generated anyway. 59 | //! **Note** that if the name provided for the repeated setter is the same name as the field itself, 60 | //! only the repeated setter will be provided by the builder since Rust does not support function overloading. 61 | //! For example if in the example above the repeated setter was named `args`, the setter that takes a `Vec` wouldn't be provided. 62 | //! 63 | //! ## Default values 64 | //! You can provide default values for fields and make them non-required. If the field is a primitive or a `String`, 65 | //! you can specify the default value in the `#[builder(default)]` attribute, but if the field is not a primitive, it must implement the `Default` trait. For example: 66 | //! ```rust 67 | //! use tidy_builder::Builder; 68 | //! 69 | //! #[derive(Debug, PartialEq)] 70 | //! pub struct Point { 71 | //! x: usize, 72 | //! y: usize, 73 | //! } 74 | //! 75 | //! impl Default for Point { 76 | //! fn default() -> Self { 77 | //! Point { 78 | //! x: 0, 79 | //! y: 0, 80 | //! } 81 | //! } 82 | //! } 83 | //! 84 | //! #[derive(Builder)] 85 | //! struct PlayerPosition { 86 | //! #[builder(default)] 87 | //! start: Point, 88 | //! 89 | //! #[builder(default = 0)] 90 | //! offset: usize, 91 | //! } 92 | //! 93 | //! let position = PlayerPosition::builder().build(); 94 | //! 95 | //! assert_eq!(position.start, Point { x: 0, y: 0}); 96 | //! assert_eq!(position.offset, 0); 97 | //! ``` 98 | //! 99 | //! ## Skipping Fields 100 | //! You can prevent the builder from providing setters for **optional** and **default** fields. For example: 101 | //! ```rust compile_fail 102 | //! use tidy_builder::Builder; 103 | //! 104 | //! #[derive(Builder)] 105 | //! struct Vote { 106 | //! submit_url: String, 107 | //! 108 | //! #[builder(skip)] 109 | //! name: Option, 110 | //! 111 | //! #[builder(skip)] 112 | //! #[builder(default = false)] 113 | //! vote: bool 114 | //! } 115 | //! 116 | //! fn main() { 117 | //! let vote = Vote::builder().submit_url("fake_submit_url.com").name("Foo".to_string()); // Fails since there is no `name` setter 118 | //! } 119 | //! ``` 120 | //! 121 | //! # What if I try to call the `build` function early? 122 | //! tidy-builder uses special traits to hint at the missing required fields. For example: 123 | //! ```rust compile_fail 124 | //! use tidy_builder::Builder; 125 | //! 126 | //! #[derive(Builder)] 127 | //! struct Foo { 128 | //! bar: usize, 129 | //! baz: usize, 130 | //! } 131 | //! 132 | //! fn main() { 133 | //! let foo = Foo::builder().bar(0).build(); 134 | //! } 135 | //! ``` 136 | //! On stable Rust you'll get a **compile-time** error that the trait `HasBaz` is not implemented for the struct `FooBuilder<...>`. 137 | //! The trait `HasBaz` indicates that `FooBuilder` **has** a value for the **`baz`** field. 138 | //! So this trait not being implemented for `FooBuilder` means that a value is not specified for the `baz` field and that's why you cannot call the `build` function. 139 | //! 140 | //! On nightly Rust and with the help of `rustc_on_unimplemented`, the `Builder` can hint at the compiler to 141 | //! show the message `missing baz` to inform the user that in order to call `build`, they should set the value of the `baz` field. 142 | //! **Note** that this is behind the `better_error` feature gate. 143 | //! 144 | //! # How it works 145 | //! tidy-builder creates a state machine in order to model the behavior of the builder. The generated builder has a const generic parameter of type `bool` 146 | //! for each required field to encode whether a value has been set for the field or not. For example: 147 | //! ```rust 148 | //! use tidy_builder::Builder; 149 | //! 150 | //! #[derive(Builder)] 151 | //! struct Foo { 152 | //! bar: usize, 153 | //! baz: usize, 154 | //! } 155 | //! ``` 156 | //! The struct above will cause this builder to get generated: 157 | //! ```rust 158 | //! struct FooBuilder { 159 | //! bar: Option, 160 | //! baz: Option, 161 | //! } 162 | //! ``` 163 | //! The builder will start in the `FooBuilder` state when you call the `builder` function of `Foo`: 164 | //! ```rust 165 | //! # use tidy_builder::Builder; 166 | //! # 167 | //! # #[derive(Builder)] 168 | //! # struct Foo { 169 | //! # bar: usize, 170 | //! # baz: usize, 171 | //! # } 172 | //! 173 | //! let builder: FooBuilder = Foo::builder(); 174 | //! let builder: FooBuilder = Foo::builder().bar(0); 175 | //! let builder: FooBuilder = Foo::builder().bar(0).baz(1); 176 | //! 177 | //! let foo = builder.build(); 178 | //! 179 | //! assert_eq!(foo.bar, 0); 180 | //! assert_eq!(foo.baz, 1); 181 | //! ``` 182 | //! When you call the `bar` function to set the value of the `bar` field, you cause the builder to transition to the `FooBuilder` state: 183 | //! Similarly, when you call the `baz` function, you cause the builder to transition to the `FooBuilder` state. 184 | //! So when you set the value for both fields, you end up at the `FooBuilder` state, 185 | //! and it's in this state that you can call the build function(the state that all const generic paramters are `true`): 186 | //! 187 | //! The error reporting discussed in the previous section leverages these states to inform the user of the missing fields. 188 | //! For example `HasBar` trait will be implemented for `FooBuilder` , and `HasBaz` will be implemented for `FooBuilder`. 189 | //! The `build` function is guarded with a where clause to make sure the builder implements all these traits: 190 | //! ```rust 191 | //! # struct Foo { 192 | //! # bar: usize, 193 | //! # baz: usize, 194 | //! # } 195 | //! # 196 | //! # struct FooBuilder { 197 | //! # bar: Option, 198 | //! # baz: Option, 199 | //! # } 200 | //! # 201 | //! # trait HasBar {} 202 | //! # impl HasBar for FooBuilder {} 203 | //! # 204 | //! # trait HasBaz {} 205 | //! # impl HasBaz for FooBuilder {} 206 | //! # 207 | //! impl FooBuilder { 208 | //! fn build(self) -> Foo 209 | //! where 210 | //! Self: HasBar + HasBaz 211 | //! { 212 | //! // Safety: 213 | //! // 214 | //! // It's safe since HasBar and HasBaz are implemented 215 | //! // hence self.bar and self.baz both contain valid values. 216 | //! unsafe { 217 | //! Foo { 218 | //! bar: self.bar.unwrap_unchecked(), 219 | //! baz: self.baz.unwrap_unchecked(), 220 | //! } 221 | //! } 222 | //! } 223 | //! } 224 | //! ``` 225 | //! So if you set the value of `bar` and not `baz`, since `HasBaz` won't be implemented for `FooBuilder`, 226 | //! you'll get a compile-time error that calling `build` is not possible. 227 | //! 228 | 229 | mod attribute; 230 | mod err; 231 | mod generator; 232 | mod generics; 233 | mod wrap; 234 | 235 | macro_rules! ret_on_err { 236 | ($e: expr) => { 237 | match $e { 238 | Ok(value) => value, 239 | Err(err) => { 240 | return err.into(); 241 | } 242 | } 243 | }; 244 | } 245 | 246 | #[proc_macro_derive(Builder, attributes(builder))] 247 | pub fn builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 248 | let ast = syn::parse_macro_input!(input as syn::DeriveInput); 249 | 250 | let generator = ret_on_err!(generator::Generator::new(&ast)); 251 | 252 | ret_on_err!(generator.generate()).into() 253 | } 254 | -------------------------------------------------------------------------------- /src/wrap.rs: -------------------------------------------------------------------------------- 1 | use crate::err::Error; 2 | 3 | // Some types wrap around another type(their inner type). For example `Vec` wraps around `T` so does `Option`. 4 | // This function returns the inner type of a wrapper type, if its name is equal to the provided `wrapper_name`. 5 | // 6 | // For example calling: 7 | // wrapped_in(Vec, Some("Vec")) 8 | // will return `T`. But, calling 9 | // wrapped_in(Vec, Some("Option")) 10 | // will return `None` since the name does not match. 11 | // 12 | // If we only care about the inner type, we can set `wrapper_name` to `None`. 13 | #[rustfmt::skip] 14 | pub fn wrapped_in<'a>(wrapper: &'a syn::Type, wrapper_name: Option<&str>) -> Option<&'a syn::Type> { 15 | if let syn::Type::Path(syn::TypePath { path ,.. }) = wrapper { 16 | if let Some(wrapper_name) = wrapper_name { 17 | if path.segments[0].ident != wrapper_name { 18 | return None; 19 | } 20 | } 21 | 22 | return match &path.segments[0].arguments { 23 | syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => None, 24 | syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }) => { 25 | if let syn::GenericArgument::Type(inner_ty) = &args[0] { 26 | Some(inner_ty) 27 | } else { 28 | None 29 | } 30 | } 31 | }; 32 | } 33 | 34 | None 35 | } 36 | 37 | pub fn type_ident(wrapper: &syn::Type) -> Result<&syn::Ident, Error> { 38 | if let syn::Type::Path(type_path) = wrapper { 39 | Ok(&type_path.path.segments[0].ident) 40 | } else { 41 | Err(Error::UnsupportedType(wrapper.clone())) 42 | } 43 | } 44 | 45 | // Returns inner type of an `Option` and `None` if type is not an `Option`. 46 | #[rustfmt::skip] 47 | pub fn is_option(ty: &syn::Type) -> Option<&syn::Type> { 48 | wrapped_in(ty, Some("Option")) 49 | } 50 | -------------------------------------------------------------------------------- /tests/nightly_ui.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn ui_tests() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/nightly_ui/**/*.rs"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/nightly_ui/better_error.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_attrs)] 2 | 3 | #[derive(tidy_builder::Builder)] 4 | struct Item { 5 | field1: Option, 6 | field2: usize, 7 | } 8 | 9 | 10 | fn main() { 11 | let item = Item::builder() 12 | .field1(10) 13 | .build(); 14 | } -------------------------------------------------------------------------------- /tests/nightly_ui/better_error.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: missing `field2` 2 | --> tests/nightly_ui/better_error.rs:13:10 3 | | 4 | 13 | .build(); 5 | | ^^^^^ provide `field2` before calling `.build()` 6 | | 7 | = help: the trait `HasField2` is not implemented for `ItemBuilder` 8 | = help: the trait `HasField2` is implemented for `ItemBuilder` 9 | note: required by a bound in `ItemBuilder::::build` 10 | --> tests/nightly_ui/better_error.rs:3:10 11 | | 12 | 3 | #[derive(tidy_builder::Builder)] 13 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ItemBuilder::::build` 14 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 15 | -------------------------------------------------------------------------------- /tests/stable_all_optional.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | opt1: Option, 4 | opt2: Option, 5 | } 6 | 7 | #[test] 8 | fn default_values() { 9 | let my_struct = MyStruct::builder().build(); 10 | 11 | assert!(my_struct.opt1.is_none()); 12 | assert!(my_struct.opt2.is_none()); 13 | } 14 | -------------------------------------------------------------------------------- /tests/stable_default_values.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct MyParam { 3 | param: usize, 4 | } 5 | 6 | #[derive(tidy_builder::Builder)] 7 | struct MyStruct<'a> { 8 | #[builder(default)] 9 | my_param: MyParam, 10 | 11 | #[builder(default = 3)] 12 | my_usize: usize, 13 | 14 | #[builder(default = 1.2)] 15 | my_float: f64, 16 | 17 | #[builder(default = "Name")] 18 | my_name: &'a str, 19 | 20 | #[builder(default = false)] 21 | my_flag: bool, 22 | 23 | req1: usize, 24 | req2: usize, 25 | opt1: Option, 26 | opt2: Option, 27 | } 28 | 29 | #[test] 30 | fn default_values() { 31 | let my_struct = MyStruct::builder().req1(1).req2(2).build(); 32 | 33 | assert_eq!(my_struct.my_param.param, 0); 34 | assert_eq!(my_struct.my_usize, 3); 35 | assert_eq!(my_struct.my_float, 1.2); 36 | assert_eq!(my_struct.my_name, "Name"); 37 | assert!(!my_struct.my_flag); 38 | 39 | assert_eq!(my_struct.req1, 1); 40 | assert_eq!(my_struct.req2, 2); 41 | 42 | assert_eq!(my_struct.opt1, None); 43 | assert_eq!(my_struct.opt2, None); 44 | } 45 | -------------------------------------------------------------------------------- /tests/stable_default_values_for_optionals.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct MyParam { 3 | param: usize, 4 | } 5 | 6 | #[derive(tidy_builder::Builder)] 7 | struct MyStruct<'a> { 8 | #[builder(default)] 9 | my_param: MyParam, 10 | 11 | #[builder(default = 3)] 12 | my_usize: usize, 13 | 14 | #[builder(default = 1.2)] 15 | my_float: f64, 16 | 17 | #[builder(default = "Name")] 18 | my_name: &'a str, 19 | 20 | #[builder(default = false)] 21 | my_flag: bool, 22 | 23 | #[builder(default)] 24 | opt2: Option, 25 | 26 | req1: usize, 27 | req2: usize, 28 | opt1: Option, 29 | } 30 | 31 | #[test] 32 | fn default_values() { 33 | let my_struct = MyStruct::builder().req1(1).req2(2).build(); 34 | 35 | assert_eq!(my_struct.my_param.param, 0); 36 | assert_eq!(my_struct.my_usize, 3); 37 | assert_eq!(my_struct.my_float, 1.2); 38 | assert_eq!(my_struct.my_name, "Name"); 39 | assert!(!my_struct.my_flag); 40 | 41 | assert_eq!(my_struct.req1, 1); 42 | assert_eq!(my_struct.req2, 2); 43 | 44 | assert_eq!(my_struct.opt1, None); 45 | assert_eq!(my_struct.opt2, None); 46 | } 47 | -------------------------------------------------------------------------------- /tests/stable_generics_with_bounds.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | req1: T, 4 | req2: T, 5 | opt1: Option, 6 | } 7 | 8 | #[test] 9 | fn generics_without_bounds() { 10 | let my_struct = MyStruct::builder() 11 | .opt1("opt1".to_string()) 12 | .req2("req2".to_string()) 13 | .req1("req1".to_string()) 14 | .build(); 15 | 16 | assert_eq!(my_struct.req1, "req1".to_string()); 17 | assert_eq!(my_struct.req2, "req2".to_string()); 18 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 19 | } 20 | -------------------------------------------------------------------------------- /tests/stable_generics_with_bounds_and_where_clause.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct 3 | where 4 | T: std::fmt::Display, 5 | { 6 | req1: T, 7 | req2: T, 8 | opt1: Option, 9 | } 10 | 11 | #[test] 12 | fn generics_without_bounds() { 13 | let my_struct = MyStruct::builder() 14 | .opt1("opt1".to_string()) 15 | .req2("req2".to_string()) 16 | .req1("req1".to_string()) 17 | .build(); 18 | 19 | assert_eq!(my_struct.req1, "req1".to_string()); 20 | assert_eq!(my_struct.req2, "req2".to_string()); 21 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 22 | } 23 | -------------------------------------------------------------------------------- /tests/stable_generics_with_const_generics.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct<'a, 'b: 'a, 'c, const N: usize, const FLG: bool, T: std::fmt::Debug> 3 | where 4 | T: std::fmt::Display, 5 | 'c: 'a, 6 | { 7 | req1: &'a T, 8 | req2: &'b T, 9 | opt1: Option<&'c T>, 10 | } 11 | 12 | #[test] 13 | fn generics_with_lifetimes() { 14 | let req1 = "req1".to_string(); 15 | let req2 = "req2".to_string(); 16 | let opt1 = "opt1".to_string(); 17 | 18 | let my_struct: MyStruct<0, false, String> = MyStruct::builder() 19 | .opt1(&opt1) 20 | .req2(&req2) 21 | .req1(&req1) 22 | .build(); 23 | 24 | assert_eq!(my_struct.req1, &req1); 25 | assert_eq!(my_struct.req2, &req2); 26 | assert_eq!(my_struct.opt1, Some(&opt1)); 27 | } 28 | -------------------------------------------------------------------------------- /tests/stable_generics_with_lifetimes.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct<'a, 'b: 'a, 'c, T: std::fmt::Debug> 3 | where 4 | T: std::fmt::Display, 5 | 'c: 'a, 6 | { 7 | req1: &'a T, 8 | req2: &'b T, 9 | opt1: Option<&'c T>, 10 | } 11 | 12 | #[test] 13 | fn generics_with_lifetimes() { 14 | let req1 = "req1".to_string(); 15 | let req2 = "req2".to_string(); 16 | let opt1 = "opt1".to_string(); 17 | 18 | let my_struct = MyStruct::builder() 19 | .opt1(&opt1) 20 | .req2(&req2) 21 | .req1(&req1) 22 | .build(); 23 | 24 | assert_eq!(my_struct.req1, &req1); 25 | assert_eq!(my_struct.req2, &req2); 26 | assert_eq!(my_struct.opt1, Some(&opt1)); 27 | } 28 | -------------------------------------------------------------------------------- /tests/stable_generics_with_where_clause.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct 3 | where 4 | T: std::fmt::Display, 5 | { 6 | req1: T, 7 | req2: T, 8 | opt1: Option, 9 | } 10 | 11 | #[test] 12 | fn generics_without_bounds() { 13 | let my_struct = MyStruct::builder() 14 | .opt1("opt1".to_string()) 15 | .req2("req2".to_string()) 16 | .req1("req1".to_string()) 17 | .build(); 18 | 19 | assert_eq!(my_struct.req1, "req1".to_string()); 20 | assert_eq!(my_struct.req2, "req2".to_string()); 21 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 22 | } 23 | -------------------------------------------------------------------------------- /tests/stable_generics_without_bounds.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | req1: T, 4 | req2: T, 5 | opt1: Option, 6 | } 7 | 8 | #[test] 9 | fn generics_without_bounds() { 10 | let my_struct = MyStruct::builder() 11 | .opt1("opt1".to_string()) 12 | .req2("req2".to_string()) 13 | .req1("req1".to_string()) 14 | .build(); 15 | 16 | assert_eq!(my_struct.req1, "req1".to_string()); 17 | assert_eq!(my_struct.req2, "req2".to_string()); 18 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 19 | } 20 | -------------------------------------------------------------------------------- /tests/stable_impl_default_no_required_fields.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder(skip)] 4 | a: Option, 5 | 6 | #[builder(default = 5)] 7 | b: usize, 8 | } 9 | 10 | #[test] 11 | fn main() { 12 | let my_struct = MyStruct::default(); 13 | 14 | assert_eq!(my_struct.a, None); 15 | assert_eq!(my_struct.b, 5); 16 | } 17 | -------------------------------------------------------------------------------- /tests/stable_multiple_initialization.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct<'a, 'b: 'a, 'c, const N: usize, const FLG: bool, T: std::fmt::Debug> 3 | where 4 | T: std::fmt::Display, 5 | 'c: 'a, 6 | { 7 | req1: &'a T, 8 | req2: &'b T, 9 | opt1: Option<&'c T>, 10 | } 11 | 12 | #[test] 13 | fn generics_with_lifetimes() { 14 | let req1 = "req1".to_string(); 15 | let req2 = "req2".to_string(); 16 | let opt1 = "opt1".to_string(); 17 | 18 | let req1_new = "req1_new".to_string(); 19 | let req2_new = "req2_new".to_string(); 20 | let opt1_new = "opt1_new".to_string(); 21 | 22 | let my_struct: MyStruct<0, false, String> = MyStruct::builder() 23 | .opt1(&opt1) 24 | .req2(&req2) 25 | .req1(&req1) 26 | .req1(&req1_new) 27 | .opt1(&opt1_new) 28 | .req2(&req2_new) 29 | .build(); 30 | 31 | assert_eq!(my_struct.req1, &req1_new); 32 | assert_eq!(my_struct.req2, &req2_new); 33 | assert_eq!(my_struct.opt1, Some(&opt1_new)); 34 | } 35 | -------------------------------------------------------------------------------- /tests/stable_no_generics.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | req1: String, 4 | req2: String, 5 | opt1: Option, 6 | } 7 | 8 | #[test] 9 | fn no_generics() { 10 | let my_struct = MyStruct::builder() 11 | .req1("req1".to_string()) 12 | .req2("req2".to_string()) 13 | .opt1("opt1".to_string()) 14 | .build(); 15 | 16 | assert_eq!(my_struct.req1, "req1".to_string()); 17 | assert_eq!(my_struct.req2, "req2".to_string()); 18 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 19 | } 20 | -------------------------------------------------------------------------------- /tests/stable_no_generics_reorder.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | req1: String, 4 | req2: String, 5 | opt1: Option, 6 | } 7 | 8 | #[test] 9 | fn no_generics_reorder() { 10 | let my_struct = MyStruct::builder() 11 | .opt1("opt1".to_string()) 12 | .req2("req2".to_string()) 13 | .req1("req1".to_string()) 14 | .build(); 15 | 16 | assert_eq!(my_struct.req1, "req1".to_string()); 17 | assert_eq!(my_struct.req2, "req2".to_string()); 18 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 19 | } 20 | -------------------------------------------------------------------------------- /tests/stable_repeated_setters.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | pub struct MyStruct { 3 | #[builder(each = "arg")] 4 | args: Vec, 5 | } 6 | 7 | #[test] 8 | fn repeated_setters() { 9 | let my_struct = MyStruct::builder() 10 | .arg("arg1".to_string()) 11 | .arg("arg2".to_string()) 12 | .build(); 13 | 14 | assert_eq!(my_struct.args.len(), 2); 15 | assert!(my_struct.args.contains(&"arg1".to_string())); 16 | assert!(my_struct.args.contains(&"arg2".to_string())); 17 | } 18 | -------------------------------------------------------------------------------- /tests/stable_repeated_setters_for_default_fields.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | pub struct MyStruct { 3 | #[builder(default)] 4 | #[builder(each = "arg")] 5 | args: Vec, 6 | } 7 | 8 | #[test] 9 | fn repeated_setters() { 10 | let my_struct = MyStruct::builder() 11 | .arg("arg1".to_string()) 12 | .arg("arg2".to_string()) 13 | .build(); 14 | let my_struct_empty = MyStruct::builder().build(); 15 | 16 | assert_eq!(my_struct_empty.args.len(), 0); 17 | 18 | assert_eq!(my_struct.args.len(), 2); 19 | assert!(my_struct.args.contains(&"arg1".to_string())); 20 | assert!(my_struct.args.contains(&"arg2".to_string())); 21 | } 22 | -------------------------------------------------------------------------------- /tests/stable_repeated_setters_for_optionals.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | pub struct MyStruct { 3 | #[builder(each = "arg")] 4 | args: Vec, 5 | 6 | #[builder(each = "opt_args")] 7 | optional_args: Option>, 8 | } 9 | 10 | #[test] 11 | fn repeated_setters() { 12 | let my_struct = MyStruct::builder() 13 | .arg("arg1".to_string()) 14 | .arg("arg2".to_string()) 15 | .opt_args("opt_arg1".to_string()) 16 | .opt_args("opt_arg2".to_string()) 17 | .build(); 18 | 19 | assert_eq!(my_struct.args.len(), 2); 20 | assert!(my_struct.args.contains(&"arg1".to_string())); 21 | assert!(my_struct.args.contains(&"arg1".to_string())); 22 | 23 | assert_eq!(my_struct.optional_args.as_ref().unwrap().len(), 2); 24 | assert!(my_struct 25 | .optional_args 26 | .as_ref() 27 | .unwrap() 28 | .contains(&"opt_arg1".to_string())); 29 | assert!(my_struct 30 | .optional_args 31 | .as_ref() 32 | .unwrap() 33 | .contains(&"opt_arg2".to_string())); 34 | } 35 | -------------------------------------------------------------------------------- /tests/stable_repeated_setters_overrided_for_optionals.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | pub struct MyStruct { 3 | #[builder(each = "arg")] 4 | args: Vec, 5 | 6 | #[builder(each = "opt_args")] 7 | optional_args: Option>, 8 | } 9 | 10 | #[test] 11 | fn repeated_setters() { 12 | let my_struct = MyStruct::builder() 13 | .arg("arg1".to_string()) 14 | .arg("arg2".to_string()) 15 | .opt_args("opt_arg1".to_string()) 16 | .opt_args("opt_arg2".to_string()) 17 | .optional_args(vec!["opt_arg3".to_string(), "opt_arg4".to_string()]) 18 | .build(); 19 | 20 | assert_eq!(my_struct.args.len(), 2); 21 | assert!(my_struct.args.contains(&"arg1".to_string())); 22 | assert!(my_struct.args.contains(&"arg1".to_string())); 23 | 24 | assert_eq!(my_struct.optional_args.as_ref().unwrap().len(), 2); 25 | assert!(my_struct 26 | .optional_args 27 | .as_ref() 28 | .unwrap() 29 | .contains(&"opt_arg3".to_string())); 30 | assert!(my_struct 31 | .optional_args 32 | .as_ref() 33 | .unwrap() 34 | .contains(&"opt_arg4".to_string())); 35 | } 36 | -------------------------------------------------------------------------------- /tests/stable_repeated_setters_overrided_with_setter.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | pub struct MyStruct { 3 | #[builder(each = "arg")] 4 | args: Vec, 5 | } 6 | 7 | #[test] 8 | fn repeated_setters() { 9 | let my_struct = MyStruct::builder() 10 | .arg("arg1".to_string()) 11 | .arg("arg2".to_string()) 12 | .args(vec!["arg3".to_string(), "arg4".to_string()]) 13 | .build(); 14 | 15 | assert_eq!(my_struct.args.len(), 2); 16 | assert!(my_struct.args.contains(&"arg3".to_string())); 17 | assert!(my_struct.args.contains(&"arg4".to_string())); 18 | } 19 | -------------------------------------------------------------------------------- /tests/stable_skip_optional_and_defaults.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder(skip)] 4 | set_later_opt: Option, 5 | 6 | #[builder(skip)] 7 | #[builder(default = 0)] 8 | set_later_def: usize, 9 | 10 | req1: usize, 11 | } 12 | 13 | fn main() { 14 | let my_struct = MyStruct::builder().req1(1).build(); 15 | 16 | assert_eq!(my_struct.set_later_opt, None); 17 | assert_eq!(my_struct.set_later_def, 0); 18 | assert_eq!(my_struct.req1, 1); 19 | } 20 | -------------------------------------------------------------------------------- /tests/stable_ui.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn ui_tests() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/ui/**/*.rs"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/ui/call_skipped_setter_of_default.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder(skip)] 4 | set_later_opt: Option, 5 | 6 | #[builder(skip)] 7 | #[builder(default = 0)] 8 | set_later_def: usize, 9 | } 10 | 11 | fn main() { 12 | let _ = MyStruct::builder().set_later_def(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/call_skipped_setter_of_default.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: no method named `set_later_def` found for struct `MyStructBuilder` in the current scope 2 | --> tests/ui/call_skipped_setter_of_default.rs:12:33 3 | | 4 | 1 | #[derive(tidy_builder::Builder)] 5 | | --------------------- method `set_later_def` not found for this 6 | ... 7 | 12 | let _ = MyStruct::builder().set_later_def(); 8 | | ^^^^^^^^^^^^^-- help: remove the arguments 9 | | | 10 | | field, not a method 11 | -------------------------------------------------------------------------------- /tests/ui/call_skipped_setter_of_optional.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder(skip)] 4 | set_later_opt: Option, 5 | 6 | #[builder(skip)] 7 | #[builder(default = 0)] 8 | set_later_def: usize, 9 | } 10 | 11 | fn main() { 12 | let _ = MyStruct::builder().set_later_opt(); 13 | } 14 | -------------------------------------------------------------------------------- /tests/ui/call_skipped_setter_of_optional.stderr: -------------------------------------------------------------------------------- 1 | error[E0599]: no method named `set_later_opt` found for struct `MyStructBuilder` in the current scope 2 | --> tests/ui/call_skipped_setter_of_optional.rs:12:33 3 | | 4 | 1 | #[derive(tidy_builder::Builder)] 5 | | --------------------- method `set_later_opt` not found for this 6 | ... 7 | 12 | let _ = MyStruct::builder().set_later_opt(); 8 | | ^^^^^^^^^^^^^-- help: remove the arguments 9 | | | 10 | | field, not a method 11 | -------------------------------------------------------------------------------- /tests/ui/default_value_for_non_default_field.rs: -------------------------------------------------------------------------------- 1 | pub struct MyParam { 2 | param: usize, 3 | } 4 | 5 | #[derive(tidy_builder::Builder)] 6 | struct MyStruct<'a> { 7 | #[builder(default)] 8 | my_param: MyParam, 9 | 10 | #[builder(default = 3)] 11 | my_usize: usize, 12 | 13 | #[builder(default = 1.2)] 14 | my_float: f64, 15 | 16 | #[builder(default = "Name")] 17 | my_name: &'a str, 18 | 19 | #[builder(default = false)] 20 | my_flag: bool, 21 | 22 | req1: usize, 23 | req2: usize, 24 | opt1: Option, 25 | opt2: Option, 26 | } 27 | 28 | fn main() { 29 | let my_struct = MyStruct::builder().req1(1).req2(2).build(); 30 | 31 | assert_eq!(my_struct.my_param.param, 0); 32 | assert_eq!(my_struct.my_usize, 3); 33 | assert_eq!(my_struct.my_float, 1.2); 34 | assert_eq!(my_struct.my_name, "Name"); 35 | assert!(!my_struct.my_flag); 36 | 37 | assert_eq!(my_struct.req1, 1); 38 | assert_eq!(my_struct.req2, 2); 39 | 40 | assert_eq!(my_struct.opt1, None); 41 | assert_eq!(my_struct.opt2, None); 42 | } 43 | -------------------------------------------------------------------------------- /tests/ui/default_value_for_non_default_field.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyParam: Default` is not satisfied 2 | --> tests/ui/default_value_for_non_default_field.rs:5:10 3 | | 4 | 5 | #[derive(tidy_builder::Builder)] 5 | | ^^^^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `MyParam` 6 | | 7 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | help: consider annotating `MyParam` with `#[derive(Default)]` 9 | | 10 | 1 | #[derive(Default)] 11 | | 12 | -------------------------------------------------------------------------------- /tests/ui/error/builder_does_not_support_enums.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | enum MyEnum { 3 | A, 4 | B, 5 | } 6 | 7 | fn main() {} -------------------------------------------------------------------------------- /tests/ui/error/builder_does_not_support_enums.stderr: -------------------------------------------------------------------------------- 1 | error: Builder does not support enums 2 | --> tests/ui/error/builder_does_not_support_enums.rs:2:1 3 | | 4 | 2 | enum MyEnum { 5 | | ^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/error/builder_does_not_support_unions.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | union MyUnion { 3 | f1: u32, 4 | f2: f32, 5 | } 6 | 7 | fn main() {} -------------------------------------------------------------------------------- /tests/ui/error/builder_does_not_support_unions.stderr: -------------------------------------------------------------------------------- 1 | error: Builder does not support unions 2 | --> tests/ui/error/builder_does_not_support_unions.rs:2:1 3 | | 4 | 2 | union MyUnion { 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/error/builder_does_not_support_unit_structs.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct; 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/error/builder_does_not_support_unit_structs.stderr: -------------------------------------------------------------------------------- 1 | error: Builder does not support unit structs 2 | --> tests/ui/error/builder_does_not_support_unit_structs.rs:1:10 3 | | 4 | 1 | #[derive(tidy_builder::Builder)] 5 | | ^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/ui/error/builder_does_not_support_unnamed_fields.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct(usize, usize); 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /tests/ui/error/builder_does_not_support_unnamed_fields.stderr: -------------------------------------------------------------------------------- 1 | error: Builder does not support unnamed fields 2 | --> tests/ui/error/builder_does_not_support_unnamed_fields.rs:2:16 3 | | 4 | 2 | struct MyStruct(usize, usize); 5 | | ^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/error/nested_meta_list.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder(builder(each = "each"))] 4 | args: Vec, 5 | } 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/error/nested_meta_list.stderr: -------------------------------------------------------------------------------- 1 | error: Nested attributes are not supported 2 | --> tests/ui/error/nested_meta_list.rs:3:15 3 | | 4 | 3 | #[builder(builder(each = "each"))] 5 | | ^^^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/error/not_expected_literal.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder("arg")] 4 | args: Vec, 5 | } 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/error/not_expected_literal.stderr: -------------------------------------------------------------------------------- 1 | error: Not expected a literal 2 | --> tests/ui/error/not_expected_literal.rs:3:15 3 | | 4 | 3 | #[builder("arg")] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/error/not_meta_list.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder] 4 | args: Vec, 5 | } 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/error/not_meta_list.stderr: -------------------------------------------------------------------------------- 1 | error: Provided attribute is malformed 2 | --> tests/ui/error/not_meta_list.rs:3:5 3 | | 4 | 3 | #[builder] 5 | | ^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/error/not_string_value.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder(each = 1)] 4 | args: Vec, 5 | } 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/error/not_string_value.stderr: -------------------------------------------------------------------------------- 1 | error: Value must be a string 2 | --> tests/ui/error/not_string_value.rs:3:22 3 | | 4 | 3 | #[builder(each = 1)] 5 | | ^ 6 | -------------------------------------------------------------------------------- /tests/ui/error/unexpected_name.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder(eac = "arg")] 4 | args: Vec, 5 | } 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/error/unexpected_name.stderr: -------------------------------------------------------------------------------- 1 | error: Unknown attribute 2 | --> tests/ui/error/unexpected_name.rs:3:15 3 | | 4 | 3 | #[builder(eac = "arg")] 5 | | ^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/error/unexpected_name_for_path_attrs.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder(unknown)] 4 | args: Vec, 5 | } 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /tests/ui/error/unexpected_name_for_path_attrs.stderr: -------------------------------------------------------------------------------- 1 | error: Unknown attribute 2 | --> tests/ui/error/unexpected_name_for_path_attrs.rs:3:15 3 | | 4 | 3 | #[builder(unknown)] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /tests/ui/generics_with_bounds.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | req1: T, 4 | req2: T, 5 | opt1: Option, 6 | } 7 | 8 | fn main() { 9 | let my_struct = MyStruct::builder() 10 | .opt1("opt1".to_string()) 11 | .req2("req2".to_string()) 12 | .build(); 13 | 14 | assert_eq!(my_struct.req1, "req1".to_string()); 15 | assert_eq!(my_struct.req2, "req2".to_string()); 16 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 17 | } 18 | -------------------------------------------------------------------------------- /tests/ui/generics_with_bounds.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyStructBuilder: HasReq1` is not satisfied 2 | --> tests/ui/generics_with_bounds.rs:12:10 3 | | 4 | 12 | .build(); 5 | | ^^^^^ the trait `HasReq1` is not implemented for `MyStructBuilder` 6 | | 7 | = help: the trait `HasReq1` is implemented for `MyStructBuilder` 8 | note: required by a bound in `MyStructBuilder::::build` 9 | --> tests/ui/generics_with_bounds.rs:1:10 10 | | 11 | 1 | #[derive(tidy_builder::Builder)] 12 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::::build` 13 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | -------------------------------------------------------------------------------- /tests/ui/generics_with_bounds_and_where_clause.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct 3 | where 4 | T: std::fmt::Display, 5 | { 6 | req1: T, 7 | req2: T, 8 | opt1: Option, 9 | } 10 | 11 | fn main() { 12 | let my_struct = MyStruct::builder() 13 | .opt1("opt1".to_string()) 14 | .req2("req2".to_string()) 15 | .build(); 16 | 17 | assert_eq!(my_struct.req1, "req1".to_string()); 18 | assert_eq!(my_struct.req2, "req2".to_string()); 19 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 20 | } 21 | -------------------------------------------------------------------------------- /tests/ui/generics_with_bounds_and_where_clause.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyStructBuilder: HasReq1` is not satisfied 2 | --> tests/ui/generics_with_bounds_and_where_clause.rs:15:10 3 | | 4 | 15 | .build(); 5 | | ^^^^^ the trait `HasReq1` is not implemented for `MyStructBuilder` 6 | | 7 | = help: the trait `HasReq1` is implemented for `MyStructBuilder` 8 | note: required by a bound in `MyStructBuilder::::build` 9 | --> tests/ui/generics_with_bounds_and_where_clause.rs:1:10 10 | | 11 | 1 | #[derive(tidy_builder::Builder)] 12 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::::build` 13 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | -------------------------------------------------------------------------------- /tests/ui/generics_with_const_generics.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct<'a, 'b: 'a, 'c, const N: usize, const FLG: bool, T: std::fmt::Debug> 3 | where 4 | T: std::fmt::Display, 5 | 'c: 'a, 6 | { 7 | req1: &'a T, 8 | req2: &'b T, 9 | opt1: Option<&'c T>, 10 | } 11 | 12 | fn main() { 13 | let req1 = "req1".to_string(); 14 | let req2 = "req2".to_string(); 15 | let opt1 = "opt1".to_string(); 16 | 17 | let my_struct: MyStruct<0, false, String> = MyStruct::builder() 18 | .opt1(&opt1) 19 | .build(); 20 | 21 | assert_eq!(my_struct.req1, &req1); 22 | assert_eq!(my_struct.req2, &req2); 23 | assert_eq!(my_struct.opt1, Some(&opt1)); 24 | } 25 | -------------------------------------------------------------------------------- /tests/ui/generics_with_const_generics.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyStructBuilder<'_, '_, '_, {_: usize}, {_: bool}, false, false, String>: HasReq1` is not satisfied 2 | --> tests/ui/generics_with_const_generics.rs:19:10 3 | | 4 | 19 | .build(); 5 | | ^^^^^ the trait `HasReq1` is not implemented for `MyStructBuilder<'_, '_, '_, {_: usize}, {_: bool}, false, false, String>` 6 | | 7 | = help: the trait `HasReq1` is implemented for `MyStructBuilder<'a, 'b, 'c, N, FLG, true, P1, T>` 8 | note: required by a bound in `MyStructBuilder::<'a, 'b, 'c, N, FLG, P0, P1, T>::build` 9 | --> tests/ui/generics_with_const_generics.rs:1:10 10 | | 11 | 1 | #[derive(tidy_builder::Builder)] 12 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::<'a, 'b, 'c, N, FLG, P0, P1, T>::build` 13 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | 15 | error[E0277]: the trait bound `MyStructBuilder<'_, '_, '_, {_: usize}, {_: bool}, false, false, String>: HasReq2` is not satisfied 16 | --> tests/ui/generics_with_const_generics.rs:19:10 17 | | 18 | 19 | .build(); 19 | | ^^^^^ the trait `HasReq2` is not implemented for `MyStructBuilder<'_, '_, '_, {_: usize}, {_: bool}, false, false, String>` 20 | | 21 | = help: the trait `HasReq2` is implemented for `MyStructBuilder<'a, 'b, 'c, N, FLG, P0, true, T>` 22 | note: required by a bound in `MyStructBuilder::<'a, 'b, 'c, N, FLG, P0, P1, T>::build` 23 | --> tests/ui/generics_with_const_generics.rs:1:10 24 | | 25 | 1 | #[derive(tidy_builder::Builder)] 26 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::<'a, 'b, 'c, N, FLG, P0, P1, T>::build` 27 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 28 | -------------------------------------------------------------------------------- /tests/ui/generics_with_lifetimes.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct<'a, 'b: 'a, 'c, T: std::fmt::Debug> 3 | where 4 | T: std::fmt::Display, 5 | 'c: 'a, 6 | { 7 | req1: &'a T, 8 | req2: &'b T, 9 | opt1: Option<&'c T>, 10 | } 11 | 12 | fn main() { 13 | let req1 = "req1".to_string(); 14 | let req2 = "req2".to_string(); 15 | let opt1 = "opt1".to_string(); 16 | 17 | let my_struct = MyStruct::builder() 18 | .opt1(&opt1) 19 | .req2(&req2) 20 | .build(); 21 | 22 | assert_eq!(my_struct.req1, &req1); 23 | assert_eq!(my_struct.req2, &req2); 24 | assert_eq!(my_struct.opt1, Some(&opt1)); 25 | } 26 | -------------------------------------------------------------------------------- /tests/ui/generics_with_lifetimes.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyStructBuilder<'_, '_, '_, false, true, String>: HasReq1` is not satisfied 2 | --> tests/ui/generics_with_lifetimes.rs:20:10 3 | | 4 | 20 | .build(); 5 | | ^^^^^ the trait `HasReq1` is not implemented for `MyStructBuilder<'_, '_, '_, false, true, String>` 6 | | 7 | = help: the trait `HasReq1` is implemented for `MyStructBuilder<'a, 'b, 'c, true, P1, T>` 8 | note: required by a bound in `MyStructBuilder::<'a, 'b, 'c, P0, P1, T>::build` 9 | --> tests/ui/generics_with_lifetimes.rs:1:10 10 | | 11 | 1 | #[derive(tidy_builder::Builder)] 12 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::<'a, 'b, 'c, P0, P1, T>::build` 13 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | -------------------------------------------------------------------------------- /tests/ui/generics_with_where_clause.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct 3 | where 4 | T: std::fmt::Display, 5 | { 6 | req1: T, 7 | req2: T, 8 | opt1: Option, 9 | } 10 | 11 | fn main() { 12 | let my_struct = MyStruct::builder() 13 | .opt1("opt1".to_string()) 14 | .req1("req1".to_string()) 15 | .build(); 16 | 17 | assert_eq!(my_struct.req1, "req1".to_string()); 18 | assert_eq!(my_struct.req2, "req2".to_string()); 19 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 20 | } 21 | -------------------------------------------------------------------------------- /tests/ui/generics_with_where_clause.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyStructBuilder: HasReq2` is not satisfied 2 | --> tests/ui/generics_with_where_clause.rs:15:10 3 | | 4 | 15 | .build(); 5 | | ^^^^^ the trait `HasReq2` is not implemented for `MyStructBuilder` 6 | | 7 | = help: the trait `HasReq2` is implemented for `MyStructBuilder` 8 | note: required by a bound in `MyStructBuilder::::build` 9 | --> tests/ui/generics_with_where_clause.rs:1:10 10 | | 11 | 1 | #[derive(tidy_builder::Builder)] 12 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::::build` 13 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | -------------------------------------------------------------------------------- /tests/ui/generics_without_bounds.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | req1: T, 4 | req2: T, 5 | opt1: Option, 6 | } 7 | 8 | fn main() { 9 | let my_struct = MyStruct::builder() 10 | .opt1("opt1".to_string()) 11 | .build(); 12 | 13 | assert_eq!(my_struct.req1, "req1".to_string()); 14 | assert_eq!(my_struct.req2, "req2".to_string()); 15 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 16 | } 17 | -------------------------------------------------------------------------------- /tests/ui/generics_without_bounds.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyStructBuilder: HasReq1` is not satisfied 2 | --> tests/ui/generics_without_bounds.rs:11:10 3 | | 4 | 11 | .build(); 5 | | ^^^^^ the trait `HasReq1` is not implemented for `MyStructBuilder` 6 | | 7 | = help: the trait `HasReq1` is implemented for `MyStructBuilder` 8 | note: required by a bound in `MyStructBuilder::::build` 9 | --> tests/ui/generics_without_bounds.rs:1:10 10 | | 11 | 1 | #[derive(tidy_builder::Builder)] 12 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::::build` 13 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | 15 | error[E0277]: the trait bound `MyStructBuilder: HasReq2` is not satisfied 16 | --> tests/ui/generics_without_bounds.rs:11:10 17 | | 18 | 11 | .build(); 19 | | ^^^^^ the trait `HasReq2` is not implemented for `MyStructBuilder` 20 | | 21 | = help: the trait `HasReq2` is implemented for `MyStructBuilder` 22 | note: required by a bound in `MyStructBuilder::::build` 23 | --> tests/ui/generics_without_bounds.rs:1:10 24 | | 25 | 1 | #[derive(tidy_builder::Builder)] 26 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::::build` 27 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 28 | -------------------------------------------------------------------------------- /tests/ui/multiple_initialization.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct<'a, 'b: 'a, 'c, const N: usize, const FLG: bool, T: std::fmt::Debug> 3 | where 4 | T: std::fmt::Display, 5 | 'c: 'a, 6 | { 7 | req1: &'a T, 8 | req2: &'b T, 9 | opt1: Option<&'c T>, 10 | } 11 | 12 | fn main() { 13 | let req2 = "req2".to_string(); 14 | let opt1 = "opt1".to_string(); 15 | 16 | let req1_new = "req1_new".to_string(); 17 | let req2_new = "req2_new".to_string(); 18 | let opt1_new = "opt1_new".to_string(); 19 | 20 | let my_struct: MyStruct<0, false, String> = MyStruct::builder() 21 | .opt1(&opt1) 22 | .req2(&req2) 23 | .opt1(&opt1_new) 24 | .req2(&req2_new) 25 | .build(); 26 | 27 | assert_eq!(my_struct.req1, &req1_new); 28 | assert_eq!(my_struct.req2, &req2_new); 29 | assert_eq!(my_struct.opt1, Some(&opt1_new)); 30 | } 31 | -------------------------------------------------------------------------------- /tests/ui/multiple_initialization.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyStructBuilder<'_, '_, '_, {_: usize}, {_: bool}, false, true, String>: HasReq1` is not satisfied 2 | --> tests/ui/multiple_initialization.rs:25:10 3 | | 4 | 25 | .build(); 5 | | ^^^^^ the trait `HasReq1` is not implemented for `MyStructBuilder<'_, '_, '_, {_: usize}, {_: bool}, false, true, String>` 6 | | 7 | = help: the trait `HasReq1` is implemented for `MyStructBuilder<'a, 'b, 'c, N, FLG, true, P1, T>` 8 | note: required by a bound in `MyStructBuilder::<'a, 'b, 'c, N, FLG, P0, P1, T>::build` 9 | --> tests/ui/multiple_initialization.rs:1:10 10 | | 11 | 1 | #[derive(tidy_builder::Builder)] 12 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::<'a, 'b, 'c, N, FLG, P0, P1, T>::build` 13 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | -------------------------------------------------------------------------------- /tests/ui/no_generics.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | req1: String, 4 | req2: String, 5 | opt1: Option, 6 | } 7 | 8 | fn main() { 9 | let my_struct = MyStruct::builder() 10 | .req1("req1".to_string()) 11 | .opt1("opt1".to_string()) 12 | .build(); 13 | 14 | assert_eq!(my_struct.req1, "req1".to_string()); 15 | assert_eq!(my_struct.req2, "req2".to_string()); 16 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 17 | } 18 | -------------------------------------------------------------------------------- /tests/ui/no_generics.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyStructBuilder: HasReq2` is not satisfied 2 | --> tests/ui/no_generics.rs:12:10 3 | | 4 | 12 | .build(); 5 | | ^^^^^ the trait `HasReq2` is not implemented for `MyStructBuilder` 6 | | 7 | = help: the trait `HasReq2` is implemented for `MyStructBuilder` 8 | note: required by a bound in `MyStructBuilder::::build` 9 | --> tests/ui/no_generics.rs:1:10 10 | | 11 | 1 | #[derive(tidy_builder::Builder)] 12 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::::build` 13 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | -------------------------------------------------------------------------------- /tests/ui/no_generics_reorder.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | req1: String, 4 | req2: String, 5 | opt1: Option, 6 | } 7 | 8 | fn main() { 9 | let my_struct = MyStruct::builder() 10 | .req1("req1".to_string()) 11 | .build(); 12 | 13 | assert_eq!(my_struct.req1, "req1".to_string()); 14 | assert_eq!(my_struct.req2, "req2".to_string()); 15 | assert_eq!(my_struct.opt1, Some("opt1".to_string())); 16 | } 17 | -------------------------------------------------------------------------------- /tests/ui/no_generics_reorder.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `MyStructBuilder: HasReq2` is not satisfied 2 | --> tests/ui/no_generics_reorder.rs:11:10 3 | | 4 | 11 | .build(); 5 | | ^^^^^ the trait `HasReq2` is not implemented for `MyStructBuilder` 6 | | 7 | = help: the trait `HasReq2` is implemented for `MyStructBuilder` 8 | note: required by a bound in `MyStructBuilder::::build` 9 | --> tests/ui/no_generics_reorder.rs:1:10 10 | | 11 | 1 | #[derive(tidy_builder::Builder)] 12 | | ^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MyStructBuilder::::build` 13 | = note: this error originates in the derive macro `tidy_builder::Builder` (in Nightly builds, run with -Z macro-backtrace for more info) 14 | -------------------------------------------------------------------------------- /tests/ui/repeated_setters_with_the_same_name.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | pub struct MyStruct { 3 | #[builder(each = "args")] 4 | args: Vec, 5 | } 6 | 7 | fn main() { 8 | let my_struct = MyStruct::builder() 9 | .args(vec![]) 10 | .build(); 11 | } 12 | -------------------------------------------------------------------------------- /tests/ui/repeated_setters_with_the_same_name.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/ui/repeated_setters_with_the_same_name.rs:9:15 3 | | 4 | 9 | .args(vec![]) 5 | | ---- ^^^^^^ expected struct `String`, found struct `Vec` 6 | | | 7 | | arguments to this function are incorrect 8 | | 9 | = note: expected struct `String` 10 | found struct `Vec<_>` 11 | note: associated function defined here 12 | --> tests/ui/repeated_setters_with_the_same_name.rs:3:5 13 | | 14 | 1 | #[derive(tidy_builder::Builder)] 15 | | --------------------- 16 | 2 | pub struct MyStruct { 17 | 3 | #[builder(each = "args")] 18 | | ^ 19 | = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) 20 | -------------------------------------------------------------------------------- /tests/ui/repeated_setters_with_the_same_name_for_optionals.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | pub struct MyStruct { 3 | #[builder(each = "optional_args")] 4 | optional_args: Option>, 5 | } 6 | 7 | fn main() { 8 | let my_struct = MyStruct::builder().optional_args(vec![]).build(); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/repeated_setters_with_the_same_name_for_optionals.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/ui/repeated_setters_with_the_same_name_for_optionals.rs:8:55 3 | | 4 | 8 | let my_struct = MyStruct::builder().optional_args(vec![]).build(); 5 | | ------------- ^^^^^^ expected struct `String`, found struct `Vec` 6 | | | 7 | | arguments to this function are incorrect 8 | | 9 | = note: expected struct `String` 10 | found struct `Vec<_>` 11 | note: associated function defined here 12 | --> tests/ui/repeated_setters_with_the_same_name_for_optionals.rs:3:5 13 | | 14 | 1 | #[derive(tidy_builder::Builder)] 15 | | --------------------- 16 | 2 | pub struct MyStruct { 17 | 3 | #[builder(each = "optional_args")] 18 | | ^ 19 | = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) 20 | -------------------------------------------------------------------------------- /tests/ui/skip_required_field.rs: -------------------------------------------------------------------------------- 1 | #[derive(tidy_builder::Builder)] 2 | struct MyStruct { 3 | #[builder(skip)] 4 | set_later_opt: Option, 5 | 6 | #[builder(skip)] 7 | #[builder(default = 0)] 8 | set_later_def: usize, 9 | 10 | #[builder(skip)] 11 | req1: usize, 12 | } 13 | 14 | fn main() {} 15 | -------------------------------------------------------------------------------- /tests/ui/skip_required_field.stderr: -------------------------------------------------------------------------------- 1 | error: Cannot skip a required field 2 | --> tests/ui/skip_required_field.rs:10:5 3 | | 4 | 10 | / #[builder(skip)] 5 | 11 | | req1: usize, 6 | | |_______________^ 7 | --------------------------------------------------------------------------------