├── rustfmt.toml ├── typed-builder-macro ├── LICENSE-MIT ├── LICENSE-APACHE ├── Cargo.toml └── src │ ├── lib.rs │ ├── mutator.rs │ ├── builder_attr.rs │ ├── util.rs │ ├── field_info.rs │ └── struct_info.rs ├── run-retrospective-crate-version-tagging.sh ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── tests ├── no_type_leakage.rs ├── no_std.rs └── tests.rs ├── examples ├── example_for_linting.rs ├── example.rs ├── example_prefix_suffix.rs └── complicate_build.rs ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── LICENSE-APACHE ├── CHANGELOG.md └── src └── lib.rs /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 130 2 | -------------------------------------------------------------------------------- /typed-builder-macro/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /typed-builder-macro/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /run-retrospective-crate-version-tagging.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ( 4 | retrospective-crate-version-tagging detect \ 5 | --crate-name typed-builder \ 6 | --changelog-path CHANGELOG.md \ 7 | --tag-prefix v \ 8 | ) | retrospective-crate-version-tagging create-releases 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | /target/ 12 | **/*.rs.bk 13 | Cargo.lock 14 | 15 | /typed-builder-macro/target/ 16 | -------------------------------------------------------------------------------- /typed-builder-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "typed-builder-macro" 3 | description.workspace = true 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | documentation.workspace = true 10 | readme.workspace = true 11 | keywords.workspace = true 12 | categories.workspace = true 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | syn = { version = "2", features = ["full", "extra-traits"] } 19 | quote = "1" 20 | proc-macro2 = "1" 21 | -------------------------------------------------------------------------------- /tests/no_type_leakage.rs: -------------------------------------------------------------------------------- 1 | // As long as this test compiles, it passes (test does not occur at runtime) 2 | 3 | use typed_builder::TypedBuilder; 4 | 5 | #[derive(PartialEq, TypedBuilder)] 6 | #[builder( 7 | build_method(vis="pub", into=Bar), 8 | builder_method(vis=""), 9 | builder_type(vis="pub", name=BarBuilder), 10 | )] 11 | struct Foo { 12 | x: i32, 13 | } 14 | 15 | #[allow(unused)] 16 | pub struct Bar(Foo); 17 | 18 | impl Bar { 19 | pub fn builder() -> BarBuilder { 20 | Foo::builder() 21 | } 22 | } 23 | 24 | impl From for Bar { 25 | fn from(wrapped: Foo) -> Self { 26 | Bar(wrapped) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/example_for_linting.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::all, clippy::pedantic)] 2 | //! This example is mainly to make sure `TypedBuilder` does not do anything that triggers Clippy 3 | //! warnings. It's not for checking behavior. 4 | 5 | use typed_builder::TypedBuilder; 6 | 7 | #[derive(TypedBuilder)] 8 | struct BigStruct<'a> { 9 | option_with_ref: Option<&'a str>, 10 | 11 | #[builder(default = None)] 12 | option_with_default: Option<()>, 13 | } 14 | 15 | fn main() { 16 | #[allow(unused)] 17 | let BigStruct { 18 | option_with_ref: option_with_str, 19 | option_with_default: skipped_option, 20 | } = BigStruct::builder() 21 | .option_with_ref(Some("option with string")) 22 | .option_with_default(None) 23 | .build(); 24 | } 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [".", "./typed-builder-macro"] 3 | 4 | [workspace.package] 5 | description = "Compile-time type-checked builder derive" 6 | version = "0.23.2" 7 | authors = ["IdanArye ", "Chris Morgan "] 8 | edition = "2024" 9 | license = "MIT OR Apache-2.0" 10 | repository = "https://github.com/idanarye/rust-typed-builder" 11 | documentation = "https://idanarye.github.io/rust-typed-builder/" 12 | readme = "README.md" 13 | keywords = ["builder"] 14 | categories = ["rust-patterns"] 15 | 16 | [package] 17 | name = "typed-builder" 18 | description.workspace = true 19 | version.workspace = true 20 | authors.workspace = true 21 | edition.workspace = true 22 | license.workspace = true 23 | repository.workspace = true 24 | documentation.workspace = true 25 | readme.workspace = true 26 | keywords.workspace = true 27 | categories.workspace = true 28 | 29 | [dependencies] 30 | typed-builder-macro = { path = "typed-builder-macro", version = "=0.23.2" } 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | use typed_builder::TypedBuilder; 2 | 3 | #[derive(Debug, PartialEq, TypedBuilder)] 4 | struct Foo { 5 | /// `x` value. 6 | /// 7 | /// This field is mandatory. 8 | x: i32, 9 | 10 | // #[builder(default)] without parameter - use the type's default 11 | // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` 12 | #[builder( 13 | default, 14 | setter(strip_option, doc = "Set `y`. If you don't specify a value it'll default to no value.",) 15 | )] 16 | y: Option, 17 | 18 | // Or you can set the default 19 | #[builder(default = 20)] 20 | z: i32, 21 | } 22 | 23 | fn main() { 24 | assert_eq!(Foo::builder().x(1).y(2).z(3).build(), Foo { x: 1, y: Some(2), z: 3 }); 25 | 26 | // Change the order of construction: 27 | assert_eq!(Foo::builder().z(1).x(2).y(3).build(), Foo { x: 2, y: Some(3), z: 1 }); 28 | 29 | // Optional fields are optional: 30 | assert_eq!(Foo::builder().x(1).build(), Foo { x: 1, y: None, z: 20 }); 31 | 32 | // This will not compile - because we did not set x: 33 | // Foo::builder().build(); 34 | 35 | // This will not compile - because we set y twice: 36 | // Foo::builder().x(1).y(2).y(3); 37 | } 38 | -------------------------------------------------------------------------------- /examples/example_prefix_suffix.rs: -------------------------------------------------------------------------------- 1 | use typed_builder::TypedBuilder; 2 | 3 | #[derive(Debug, PartialEq, TypedBuilder)] 4 | #[builder(field_defaults(setter(prefix = "with_", suffix = "_value")))] 5 | struct Foo { 6 | // Mandatory Field: 7 | x: i32, 8 | 9 | // #[builder(default)] without parameter - use the type's default 10 | // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` 11 | #[builder(default, setter(strip_option))] 12 | y: Option, 13 | 14 | // Or you can set the default 15 | #[builder(default = 20)] 16 | z: i32, 17 | } 18 | 19 | fn main() { 20 | assert_eq!( 21 | Foo::builder().with_x_value(1).with_y_value(2).with_z_value(3).build(), 22 | Foo { x: 1, y: Some(2), z: 3 } 23 | ); 24 | 25 | // Change the order of construction: 26 | assert_eq!( 27 | Foo::builder().with_z_value(1).with_x_value(2).with_y_value(3).build(), 28 | Foo { x: 2, y: Some(3), z: 1 } 29 | ); 30 | 31 | // Optional fields are optional: 32 | assert_eq!(Foo::builder().with_x_value(1).build(), Foo { x: 1, y: None, z: 20 }); 33 | 34 | // This will not compile - because we did not set x: 35 | // Foo::builder().build(); 36 | 37 | // This will not compile - because we set y twice: 38 | // Foo::builder().with_x_value(1).with_y_value(2).with_y_value(3); 39 | } 40 | -------------------------------------------------------------------------------- /typed-builder-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use syn::{DeriveInput, parse::Error, parse_macro_input, spanned::Spanned}; 3 | 4 | mod builder_attr; 5 | mod field_info; 6 | mod mutator; 7 | mod struct_info; 8 | mod util; 9 | 10 | #[proc_macro_derive(TypedBuilder, attributes(builder))] 11 | pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 12 | let input = parse_macro_input!(input as DeriveInput); 13 | match impl_my_derive(&input) { 14 | Ok(output) => output.into(), 15 | Err(error) => error.to_compile_error().into(), 16 | } 17 | } 18 | 19 | fn impl_my_derive(ast: &syn::DeriveInput) -> Result { 20 | let data = match &ast.data { 21 | syn::Data::Struct(data) => match &data.fields { 22 | syn::Fields::Named(fields) => struct_info::StructInfo::new(ast, fields.named.iter())?.derive()?, 23 | syn::Fields::Unnamed(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for tuple structs")), 24 | syn::Fields::Unit => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unit structs")), 25 | }, 26 | syn::Data::Enum(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for enums")), 27 | syn::Data::Union(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unions")), 28 | }; 29 | Ok(data) 30 | } 31 | -------------------------------------------------------------------------------- /examples/complicate_build.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::disallowed_names)] 2 | mod scope { 3 | use typed_builder::TypedBuilder; 4 | 5 | #[derive(Debug, PartialEq, TypedBuilder)] 6 | #[builder(build_method(vis="", name=__build))] 7 | pub struct Foo { 8 | // Mandatory Field: 9 | x: i32, 10 | 11 | // #[builder(default)] without parameter - use the type's default 12 | // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` 13 | #[builder(default, setter(strip_option))] 14 | y: Option, 15 | 16 | // Or you can set the default 17 | #[builder(default = 20)] 18 | z: i32, 19 | } 20 | 21 | // Customize build method to add complicated logic. 22 | // 23 | // The signature might be frightening at first glance, 24 | // but we don't need to infer this whole ourselves. 25 | // 26 | // We can use `cargo expand` to show code expanded by `TypedBuilder`, 27 | // copy the generated `__build` method, and modify the content of the build method. 28 | #[allow(non_camel_case_types)] 29 | impl<__z, __y> FooBuilder<((i32,), __y, __z)> 30 | where 31 | for<'a> Foo: typed_builder::NextFieldDefault<(&'a i32, __y), Output = Option>, 32 | for<'a> Foo: typed_builder::NextFieldDefault<(&'a i32, &'a Option, __z), Output = i32>, 33 | { 34 | pub fn build(self) -> Bar { 35 | let foo = self.__build(); 36 | Bar { 37 | x: foo.x + 1, 38 | y: foo.y.map(|y| y + 1), 39 | z: foo.z + 1, 40 | } 41 | } 42 | } 43 | 44 | #[derive(Debug, PartialEq)] 45 | pub struct Bar { 46 | pub x: i32, 47 | pub y: Option, 48 | pub z: i32, 49 | } 50 | } 51 | 52 | use scope::{Bar, Foo}; 53 | 54 | fn main() { 55 | assert_eq!(Foo::builder().x(1).y(2).z(3).build(), Bar { x: 2, y: Some(3), z: 4 }); 56 | 57 | // This will not compile - because `__build` is a private method 58 | // Foo::builder().x(1).y(2).z(3).__build() 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/idanarye/rust-typed-builder/workflows/CI/badge.svg)](https://github.com/idanarye/rust-typed-builder/actions) 2 | [![Latest Version](https://img.shields.io/crates/v/typed-builder.svg)](https://crates.io/crates/typed-builder) 3 | [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://idanarye.github.io/rust-typed-builder/) 4 | 5 | # Rust Typed Builder 6 | 7 | Creates a compile-time verified builder: 8 | 9 | ```rust 10 | use typed_builder::TypedBuilder; 11 | 12 | #[derive(TypedBuilder)] 13 | struct Foo { 14 | // Mandatory Field: 15 | x: i32, 16 | 17 | // #[builder(default)] without parameter - use the type's default 18 | // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` 19 | #[builder(default, setter(strip_option))] 20 | y: Option, 21 | 22 | // Or you can set the default 23 | #[builder(default=20)] 24 | z: i32, 25 | } 26 | ``` 27 | 28 | Build in any order: 29 | ```rust 30 | Foo::builder().x(1).y(2).z(3).build(); 31 | Foo::builder().z(1).x(2).y(3).build(); 32 | ``` 33 | 34 | Omit optional fields(the one marked with `#[default]`): 35 | ```rust 36 | Foo::builder().x(1).build() 37 | ``` 38 | 39 | But you can't omit non-optional arguments - or it won't compile: 40 | ```rust 41 | Foo::builder().build(); // missing x 42 | Foo::builder().x(1).y(2).y(3); // y is specified twice 43 | ``` 44 | 45 | ## Features 46 | 47 | * Custom derive for generating the builder pattern. 48 | * Ability to annotate fields with `#[builder(setter(into))]` to make their setters accept `Into` values. 49 | * Compile time verification that all fields are set before calling `.build()`. 50 | * Compile time verification that no field is set more than once. 51 | * Ability to annotate fields with `#[builder(default)]` to make them optional and specify a default value when the user does not set them. 52 | * Generates simple documentation for the `.builder()` method. 53 | * Customizable method name and visibility of the `.build()` method. 54 | 55 | ## Limitations 56 | 57 | * The build errors when you neglect to set a field or set a field describe the actual problem as a deprecation warning, not as the main error. 58 | * The generated builder type has ugly internal name and many generic parameters. It is not meant for passing around and doing fancy builder tricks - only for nicer object creation syntax(constructor with named arguments and optional arguments). 59 | * For the that reason, all builder methods are call-by-move and the builder is not cloneable. Saves the trouble of determining if the fields are cloneable... 60 | * If you want a builder you can pass around, check out [derive-builder](https://crates.io/crates/derive_builder). It's API does not conflict with typed-builder's so you can be able to implement them both on the same type. 61 | 62 | ## Conflicts 63 | 64 | * `TypedBuilder` accepts arbitrary Rust code for `#[builder(default = ...)]`, but other custom derive proc-macro crates may try to parse them using the older restrictions that allow only literals. To solve this, use `#[builder(default_code = "...")]` instead. 65 | 66 | ## Alternatives - and why typed-builder is better 67 | 68 | * [derive-builder](https://crates.io/crates/derive_builder) - does all the checks in runtime, returning a `Result` you need to unwrap. 69 | * [safe-builder-derive](https://crates.io/crates/safe-builder-derive) - this one does compile-time checks - by generating a type for each possible state of the builder. Rust can remove the dead code, but your build time will still be exponential. typed-builder is encoding the builder's state in the generics arguments - so Rust will only generate the path you actually use. 70 | 71 | ## License 72 | 73 | Licensed under either of 74 | 75 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 76 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 77 | 78 | at your option. 79 | 80 | ### Contribution 81 | 82 | Unless you explicitly state otherwise, any contribution intentionally submitted 83 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 84 | additional terms or conditions. 85 | -------------------------------------------------------------------------------- /typed-builder-macro/src/mutator.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use proc_macro2::Ident; 4 | use syn::{ 5 | Error, Expr, FnArg, ItemFn, PatIdent, ReturnType, Signature, Token, Type, 6 | parse::{Parse, ParseStream}, 7 | parse_quote, 8 | punctuated::Punctuated, 9 | spanned::Spanned, 10 | }; 11 | 12 | use crate::util::{ApplyMeta, AttrArg, pat_to_ident}; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct Mutator { 16 | pub fun: ItemFn, 17 | pub required_fields: HashSet, 18 | } 19 | 20 | #[derive(Default)] 21 | struct MutatorAttribute { 22 | requires: HashSet, 23 | } 24 | 25 | impl ApplyMeta for MutatorAttribute { 26 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { 27 | if expr.name() != "requires" { 28 | return Err(Error::new_spanned(expr.name(), "Only `requires` is supported")); 29 | } 30 | 31 | match expr.key_value()?.parse_value()? { 32 | Expr::Array(syn::ExprArray { elems, .. }) => self.requires.extend( 33 | elems 34 | .into_iter() 35 | .map(|expr| match expr { 36 | Expr::Path(path) if path.path.get_ident().is_some() => { 37 | Ok(path.path.get_ident().cloned().expect("should be ident")) 38 | } 39 | expr => Err(Error::new_spanned(expr, "Expected field name")), 40 | }) 41 | .collect::, _>>()?, 42 | ), 43 | expr => { 44 | return Err(Error::new_spanned( 45 | expr, 46 | "Only list of field names [field1, field2, …] supported", 47 | )); 48 | } 49 | } 50 | Ok(()) 51 | } 52 | } 53 | 54 | impl Parse for Mutator { 55 | fn parse(input: ParseStream) -> syn::Result { 56 | let mut fun: ItemFn = input.parse()?; 57 | 58 | let mut attribute = MutatorAttribute::default(); 59 | 60 | let mut i = 0; 61 | while i < fun.attrs.len() { 62 | let attr = &fun.attrs[i]; 63 | if attr.path().is_ident("mutator") { 64 | attribute.apply_attr(attr)?; 65 | fun.attrs.remove(i); 66 | } else { 67 | i += 1; 68 | } 69 | } 70 | 71 | // Ensure `&mut self` receiver 72 | if let Some(FnArg::Receiver(receiver)) = fun.sig.inputs.first_mut() { 73 | *receiver = parse_quote!(&mut self); 74 | } else { 75 | // Error either on first argument or `()` 76 | return Err(syn::Error::new( 77 | fun.sig 78 | .inputs 79 | .first() 80 | .map(Spanned::span) 81 | .unwrap_or(fun.sig.paren_token.span.span()), 82 | "mutator needs to take a reference to `self`", 83 | )); 84 | }; 85 | 86 | Ok(Self { 87 | fun, 88 | required_fields: attribute.requires, 89 | }) 90 | } 91 | } 92 | 93 | impl Mutator { 94 | /// Signature for Builder:: function 95 | pub fn outer_sig(&self, output: Type) -> Signature { 96 | let mut sig = self.fun.sig.clone(); 97 | sig.output = ReturnType::Type(Default::default(), output.into()); 98 | 99 | sig.inputs = sig 100 | .inputs 101 | .into_iter() 102 | .enumerate() 103 | .map(|(i, input)| match input { 104 | FnArg::Receiver(_) => parse_quote!(self), 105 | FnArg::Typed(mut input) => { 106 | input.pat = Box::new( 107 | PatIdent { 108 | attrs: Vec::new(), 109 | by_ref: None, 110 | mutability: None, 111 | ident: pat_to_ident(i, &input.pat), 112 | subpat: None, 113 | } 114 | .into(), 115 | ); 116 | FnArg::Typed(input) 117 | } 118 | }) 119 | .collect(); 120 | sig 121 | } 122 | 123 | /// Arguments to call inner mutator function 124 | pub fn arguments(&self) -> Punctuated { 125 | self.fun 126 | .sig 127 | .inputs 128 | .iter() 129 | .enumerate() 130 | .filter_map(|(i, input)| match &input { 131 | FnArg::Receiver(_) => None, 132 | FnArg::Typed(input) => Some(pat_to_ident(i, &input.pat)), 133 | }) 134 | .collect() 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/no_std.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | #![no_std] 3 | 4 | use typed_builder::TypedBuilder; 5 | 6 | #[test] 7 | fn test_simple() { 8 | #[derive(PartialEq, TypedBuilder)] 9 | struct Foo { 10 | x: i32, 11 | y: i32, 12 | } 13 | 14 | assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); 15 | assert!(Foo::builder().y(1).x(2).build() == Foo { x: 2, y: 1 }); 16 | } 17 | 18 | #[test] 19 | fn test_lifetime() { 20 | #[derive(PartialEq, TypedBuilder)] 21 | struct Foo<'a, 'b> { 22 | x: &'a i32, 23 | y: &'b i32, 24 | } 25 | 26 | assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); 27 | } 28 | 29 | #[test] 30 | fn test_generics() { 31 | #[derive(PartialEq, TypedBuilder)] 32 | struct Foo { 33 | x: S, 34 | y: T, 35 | } 36 | 37 | assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); 38 | } 39 | 40 | #[test] 41 | fn test_into() { 42 | #[derive(PartialEq, TypedBuilder)] 43 | struct Foo { 44 | #[builder(setter(into))] 45 | x: i32, 46 | } 47 | 48 | assert!(Foo::builder().x(1_u8).build() == Foo { x: 1 }); 49 | } 50 | 51 | #[test] 52 | fn test_default() { 53 | #[derive(PartialEq, TypedBuilder)] 54 | struct Foo { 55 | /// x value. 56 | #[builder(default, setter(strip_option))] 57 | x: Option, 58 | #[builder(default = 10)] 59 | /// y value. 60 | y: i32, 61 | /// z value. 62 | #[builder(default = [20, 30, 40])] 63 | z: [i32; 3], 64 | } 65 | 66 | assert!( 67 | Foo::builder().build() 68 | == Foo { 69 | x: None, 70 | y: 10, 71 | z: [20, 30, 40] 72 | } 73 | ); 74 | assert!( 75 | Foo::builder().x(1).build() 76 | == Foo { 77 | x: Some(1), 78 | y: 10, 79 | z: [20, 30, 40] 80 | } 81 | ); 82 | assert!( 83 | Foo::builder().y(2).build() 84 | == Foo { 85 | x: None, 86 | y: 2, 87 | z: [20, 30, 40] 88 | } 89 | ); 90 | assert!( 91 | Foo::builder().x(1).y(2).build() 92 | == Foo { 93 | x: Some(1), 94 | y: 2, 95 | z: [20, 30, 40] 96 | } 97 | ); 98 | assert!( 99 | Foo::builder().z([1, 2, 3]).build() 100 | == Foo { 101 | x: None, 102 | y: 10, 103 | z: [1, 2, 3] 104 | } 105 | ); 106 | } 107 | 108 | #[test] 109 | fn test_field_dependencies_in_build() { 110 | #[derive(PartialEq, TypedBuilder)] 111 | struct Foo { 112 | #[builder(default, setter(strip_option))] 113 | x: Option, 114 | #[builder(default = 10)] 115 | y: i32, 116 | #[builder(default = [*y, 30, 40])] 117 | z: [i32; 3], 118 | } 119 | 120 | assert!( 121 | Foo::builder().build() 122 | == Foo { 123 | x: None, 124 | y: 10, 125 | z: [10, 30, 40] 126 | } 127 | ); 128 | assert!( 129 | Foo::builder().x(1).build() 130 | == Foo { 131 | x: Some(1), 132 | y: 10, 133 | z: [10, 30, 40] 134 | } 135 | ); 136 | assert!( 137 | Foo::builder().y(2).build() 138 | == Foo { 139 | x: None, 140 | y: 2, 141 | z: [2, 30, 40] 142 | } 143 | ); 144 | assert!( 145 | Foo::builder().x(1).y(2).build() 146 | == Foo { 147 | x: Some(1), 148 | y: 2, 149 | z: [2, 30, 40] 150 | } 151 | ); 152 | assert!( 153 | Foo::builder().z([1, 2, 3]).build() 154 | == Foo { 155 | x: None, 156 | y: 10, 157 | z: [1, 2, 3] 158 | } 159 | ); 160 | } 161 | 162 | #[test] 163 | fn test_default_with_generic_bounds() { 164 | #[derive(Debug, PartialEq, TypedBuilder)] 165 | struct Foo { 166 | #[builder(default, default_where(T: Default))] 167 | x: T, 168 | } 169 | 170 | #[derive(Debug, PartialEq)] 171 | struct HasNoDefault { 172 | y: i32, 173 | } 174 | 175 | assert_eq!(Foo::builder().build(), Foo { x: 0 }); 176 | 177 | assert_eq!( 178 | Foo::builder().x(HasNoDefault { y: 7 }).build(), 179 | Foo { 180 | x: HasNoDefault { y: 7 } 181 | } 182 | ); 183 | } 184 | 185 | #[test] 186 | fn test_custom_default_with_generic_bounds() { 187 | use core::fmt::Debug; 188 | use core::str::FromStr; 189 | 190 | #[derive(Debug, PartialEq, TypedBuilder)] 191 | struct Foo { 192 | x: &'static str, 193 | #[builder(default = x.parse().unwrap(), default_where(T: FromStr, ::Err : Debug))] 194 | y: T, 195 | } 196 | 197 | assert_eq!(Foo::builder().x("42").build(), Foo { x: "42", y: 42 }); 198 | } 199 | 200 | #[test] 201 | fn test_builder_type_with_derive_attribute() { 202 | #[derive(TypedBuilder)] 203 | #[builder(builder_type(attributes(#[derive(PartialEq, Debug)])))] 204 | #[allow(dead_code)] 205 | struct Foo { 206 | x: i32, 207 | y: i32, 208 | } 209 | 210 | assert_eq!(Foo::builder().x(1), Foo::builder().x(1)); 211 | assert_ne!(Foo::builder().x(1), Foo::builder().x(2)); 212 | } 213 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: [master] 6 | 7 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | checks: write 13 | 14 | jobs: 15 | ci: 16 | name: CI 17 | needs: [test, clippy, docs] 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Done 21 | run: exit 0 22 | test: 23 | name: Tests 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | os: [ubuntu-latest] 28 | rust: [1.90.0, nightly] 29 | runs-on: ${{ matrix.os }} 30 | steps: 31 | - uses: actions/checkout@v5 32 | - name: Install rust 33 | uses: actions-rs/toolchain@v1 34 | with: 35 | toolchain: ${{ matrix.rust }} 36 | override: true 37 | - name: Ready cache 38 | if: matrix.os == 'ubuntu-latest' 39 | run: sudo chown -R $(whoami):$(id -ng) ~/.cargo/ 40 | - name: Cache cargo 41 | uses: actions/cache@v4 42 | id: cache 43 | with: 44 | path: ~/.cargo 45 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 46 | - name: Test typed-builder 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: test 50 | args: --all-targets 51 | nostd-build: 52 | name: nostd Build 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | os: [ubuntu-latest] 57 | rust: [nightly] 58 | runs-on: ${{ matrix.os }} 59 | steps: 60 | - uses: actions/checkout@v5 61 | - name: Install rust 62 | uses: actions-rs/toolchain@v1 63 | with: 64 | toolchain: ${{ matrix.rust }} 65 | override: true 66 | target: aarch64-unknown-none 67 | - name: Ready cache 68 | if: matrix.os == 'ubuntu-latest' 69 | run: sudo chown -R $(whoami):$(id -ng) ~/.cargo/ 70 | - name: Cache cargo 71 | uses: actions/cache@v4 72 | id: cache 73 | with: 74 | path: ~/.cargo 75 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 76 | - name: Add rustup components 77 | run: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu 78 | - name: Build with nostd 79 | uses: actions-rs/cargo@v1 80 | with: 81 | command: build 82 | args: -Zbuild-std=core --target aarch64-unknown-none 83 | fmt: 84 | name: Rustfmt 85 | runs-on: ubuntu-latest 86 | steps: 87 | - uses: actions/checkout@v5 88 | - uses: actions-rs/toolchain@v1 89 | with: 90 | profile: minimal 91 | toolchain: nightly 92 | override: true 93 | components: rustfmt 94 | - name: Run fmt --all -- --check 95 | uses: actions-rs/cargo@v1 96 | with: 97 | command: fmt 98 | args: --all -- --check 99 | 100 | clippy: 101 | name: Clippy 102 | runs-on: ubuntu-latest 103 | steps: 104 | - uses: actions/checkout@v5 105 | - uses: actions-rs/toolchain@v1 106 | with: 107 | profile: minimal 108 | toolchain: nightly 109 | override: true 110 | components: clippy 111 | - name: Cache cargo 112 | uses: actions/cache@v4 113 | id: cache 114 | with: 115 | path: ~/.cargo 116 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 117 | - name: Run clippy --all-targets -- 118 | uses: actions-rs/clippy-check@v1 119 | with: 120 | token: ${{ secrets.GITHUB_TOKEN }} 121 | args: --all-targets -- 122 | docs: 123 | name: Docs 124 | runs-on: ubuntu-latest 125 | steps: 126 | - uses: actions/checkout@v5 127 | - uses: actions-rs/toolchain@v1 128 | with: 129 | profile: minimal 130 | toolchain: nightly 131 | override: true 132 | - name: Cache cargo 133 | uses: actions/cache@v4 134 | id: cache 135 | with: 136 | path: ~/.cargo 137 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 138 | - name: Run doc tests 139 | uses: actions-rs/cargo@v1 140 | with: 141 | command: test 142 | args: --doc 143 | - name: Check typed-builder docs 144 | uses: actions-rs/cargo@v1 145 | with: 146 | command: doc 147 | args: --no-deps 148 | docs-ghpages: 149 | name: Update Docs in GitHub Pages 150 | runs-on: ubuntu-latest 151 | if: github.ref == 'refs/heads/master' 152 | steps: 153 | - uses: actions/checkout@v5 154 | - name: Build docs 155 | env: 156 | GITHUB_REPO: ${{ github.repository }} 157 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 158 | run: |- 159 | cargo doc --verbose && 160 | echo "" > target/doc/index.html 161 | - name: Add read permissions 162 | run: |- 163 | chmod --recursive +r target/doc 164 | - name: Upload artifact 165 | uses: actions/upload-pages-artifact@v4 166 | with: 167 | path: target/doc 168 | deploy-ghpages: 169 | environment: 170 | name: github-pages 171 | url: ${{ steps.deployment.outputs.page_url }} 172 | runs-on: ubuntu-latest 173 | needs: docs-ghpages 174 | if: github.ref == 'refs/heads/master' 175 | steps: 176 | - name: Deploy to GitHub Pages 177 | id: deployment 178 | uses: actions/deploy-pages@v4 179 | -------------------------------------------------------------------------------- /typed-builder-macro/src/builder_attr.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{ToTokens, quote}; 3 | use syn::parse::Error; 4 | 5 | use crate::field_info::FieldBuilderAttr; 6 | use crate::mutator::Mutator; 7 | use crate::util::{ApplyMeta, AttrArg, path_to_single_string}; 8 | 9 | #[derive(Debug, Default, Clone)] 10 | pub struct CommonDeclarationSettings { 11 | pub vis: Option, 12 | pub name: Option, 13 | pub doc: Option, 14 | } 15 | 16 | impl ApplyMeta for CommonDeclarationSettings { 17 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { 18 | match expr.name().to_string().as_str() { 19 | "vis" => { 20 | let expr_str = expr.key_value()?.parse_value::()?.value(); 21 | self.vis = Some(syn::parse_str(&expr_str)?); 22 | Ok(()) 23 | } 24 | "name" => { 25 | self.name = Some(expr.key_value()?.parse_value()?); 26 | Ok(()) 27 | } 28 | "doc" => { 29 | self.doc = Some(expr.key_value()?.parse_value()?); 30 | Ok(()) 31 | } 32 | _ => Err(Error::new_spanned( 33 | expr.name(), 34 | format!("Unknown parameter {:?}", expr.name().to_string()), 35 | )), 36 | } 37 | } 38 | } 39 | 40 | impl CommonDeclarationSettings { 41 | pub fn get_name(&self) -> Option { 42 | self.name.as_ref().map(|name| name.to_token_stream()) 43 | } 44 | 45 | pub fn get_doc_or(&self, gen_doc: impl FnOnce() -> String) -> TokenStream { 46 | if let Some(ref doc) = self.doc { 47 | quote!(#[doc = #doc]) 48 | } else { 49 | let doc = gen_doc(); 50 | quote!(#[doc = #doc]) 51 | } 52 | } 53 | } 54 | 55 | #[derive(Debug, Default, Clone)] 56 | pub struct BuilderTypeSettings { 57 | pub common: CommonDeclarationSettings, 58 | pub attributes: Vec, 59 | } 60 | 61 | struct InlineAttributes(Vec); 62 | 63 | impl syn::parse::Parse for InlineAttributes { 64 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 65 | Ok(Self(input.call(syn::Attribute::parse_outer)?)) 66 | } 67 | } 68 | 69 | impl ApplyMeta for BuilderTypeSettings { 70 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { 71 | match expr.name().to_string().as_str() { 72 | "attributes" => { 73 | let InlineAttributes(attributes) = syn::parse2(expr.sub_attr()?.args)?; 74 | self.attributes = attributes; 75 | Ok(()) 76 | } 77 | _ => self.common.apply_meta(expr), 78 | } 79 | } 80 | } 81 | 82 | /// Setting of the `into` argument. 83 | #[derive(Debug, Clone, Default)] 84 | pub enum IntoSetting { 85 | /// Do not run any conversion on the built value. 86 | #[default] 87 | NoConversion, 88 | /// Convert the build value into the generic parameter passed to the `build` method. 89 | GenericConversion, 90 | /// Convert the build value into a specific type specified in the attribute. 91 | TypeConversionToSpecificType(syn::TypePath), 92 | } 93 | 94 | #[derive(Debug, Default, Clone)] 95 | pub struct BuildMethodSettings { 96 | pub common: CommonDeclarationSettings, 97 | 98 | /// Whether to convert the built type into another while finishing the build. 99 | pub into: IntoSetting, 100 | } 101 | 102 | impl ApplyMeta for BuildMethodSettings { 103 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { 104 | match expr.name().to_string().as_str() { 105 | "into" => match expr { 106 | AttrArg::Flag(_) => { 107 | self.into = IntoSetting::GenericConversion; 108 | Ok(()) 109 | } 110 | AttrArg::KeyValue(key_value) => { 111 | let type_path = key_value.parse_value::()?; 112 | self.into = IntoSetting::TypeConversionToSpecificType(type_path); 113 | Ok(()) 114 | } 115 | _ => Err(expr.incorrect_type()), 116 | }, 117 | _ => self.common.apply_meta(expr), 118 | } 119 | } 120 | } 121 | 122 | #[derive(Debug)] 123 | pub struct TypeBuilderAttr<'a> { 124 | /// Whether to show docs for the `TypeBuilder` type (rather than hiding them). 125 | pub doc: bool, 126 | 127 | /// Customize builder method, ex. visibility, name 128 | pub builder_method: CommonDeclarationSettings, 129 | 130 | /// Customize builder type, ex. visibility, name 131 | pub builder_type: BuilderTypeSettings, 132 | 133 | /// Customize build method, ex. visibility, name 134 | pub build_method: BuildMethodSettings, 135 | 136 | pub field_defaults: FieldBuilderAttr<'a>, 137 | 138 | pub crate_module_path: syn::Path, 139 | 140 | /// Functions that are able to mutate fields in the builder that are already set 141 | pub mutators: Vec, 142 | } 143 | 144 | impl Default for TypeBuilderAttr<'_> { 145 | fn default() -> Self { 146 | Self { 147 | doc: Default::default(), 148 | builder_method: Default::default(), 149 | builder_type: Default::default(), 150 | build_method: Default::default(), 151 | field_defaults: Default::default(), 152 | crate_module_path: syn::parse_quote!(::typed_builder), 153 | mutators: Default::default(), 154 | } 155 | } 156 | } 157 | 158 | impl<'a> TypeBuilderAttr<'a> { 159 | pub fn new(attrs: &[syn::Attribute]) -> Result { 160 | let mut result = Self::default(); 161 | 162 | for attr in attrs { 163 | let list = match &attr.meta { 164 | syn::Meta::List(list) => { 165 | if path_to_single_string(&list.path).as_deref() != Some("builder") { 166 | continue; 167 | } 168 | 169 | list 170 | } 171 | _ => continue, 172 | }; 173 | 174 | result.apply_subsections(list)?; 175 | } 176 | 177 | if result.builder_type.common.doc.is_some() || result.build_method.common.doc.is_some() { 178 | result.doc = true; 179 | } 180 | 181 | Ok(result) 182 | } 183 | } 184 | 185 | impl ApplyMeta for TypeBuilderAttr<'_> { 186 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { 187 | match expr.name().to_string().as_str() { 188 | "crate_module_path" => { 189 | let crate_module_path = expr.key_value()?.parse_value::()?; 190 | self.crate_module_path = crate_module_path.path; 191 | Ok(()) 192 | } 193 | "builder_method_doc" => Err(Error::new_spanned( 194 | expr.name(), 195 | "`builder_method_doc` is deprecated - use `builder_method(doc = \"...\")`", 196 | )), 197 | "builder_type_doc" => Err(Error::new_spanned( 198 | expr.name(), 199 | "`builder_typemethod_doc` is deprecated - use `builder_type(doc = \"...\")`", 200 | )), 201 | "build_method_doc" => Err(Error::new_spanned( 202 | expr.name(), 203 | "`build_method_doc` is deprecated - use `build_method(doc = \"...\")`", 204 | )), 205 | "doc" => { 206 | expr.flag()?; 207 | self.doc = true; 208 | Ok(()) 209 | } 210 | "mutators" => { 211 | self.mutators.extend(expr.sub_attr()?.undelimited()?); 212 | Ok(()) 213 | } 214 | "field_defaults" => self.field_defaults.apply_sub_attr(expr.sub_attr()?), 215 | "builder_method" => self.builder_method.apply_sub_attr(expr.sub_attr()?), 216 | "builder_type" => self.builder_type.apply_sub_attr(expr.sub_attr()?), 217 | "build_method" => self.build_method.apply_sub_attr(expr.sub_attr()?), 218 | _ => Err(Error::new_spanned( 219 | expr.name(), 220 | format!("Unknown parameter {:?}", expr.name().to_string()), 221 | )), 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /typed-builder-macro/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | 3 | use proc_macro2::{Ident, Span, TokenStream, TokenTree}; 4 | use quote::{ToTokens, format_ident, quote}; 5 | use syn::{ 6 | Attribute, Error, Pat, PatIdent, Token, parenthesized, 7 | parse::{Parse, ParseStream, Parser}, 8 | punctuated::Punctuated, 9 | spanned::Spanned, 10 | token, 11 | }; 12 | 13 | pub fn path_to_single_string(path: &syn::Path) -> Option { 14 | if path.leading_colon.is_some() { 15 | return None; 16 | } 17 | let mut it = path.segments.iter(); 18 | let segment = it.next()?; 19 | if it.next().is_some() { 20 | // Multipart path 21 | return None; 22 | } 23 | if segment.arguments != syn::PathArguments::None { 24 | return None; 25 | } 26 | Some(segment.ident.to_string()) 27 | } 28 | 29 | pub fn ident_to_type(ident: syn::Ident) -> syn::Type { 30 | let mut path = syn::Path { 31 | leading_colon: None, 32 | segments: Default::default(), 33 | }; 34 | path.segments.push(syn::PathSegment { 35 | ident, 36 | arguments: Default::default(), 37 | }); 38 | syn::Type::Path(syn::TypePath { qself: None, path }) 39 | } 40 | 41 | pub fn empty_type() -> syn::Type { 42 | syn::TypeTuple { 43 | paren_token: Default::default(), 44 | elems: Default::default(), 45 | } 46 | .into() 47 | } 48 | 49 | pub fn type_tuple(elems: impl Iterator) -> syn::TypeTuple { 50 | let mut result = syn::TypeTuple { 51 | paren_token: Default::default(), 52 | elems: elems.collect(), 53 | }; 54 | if !result.elems.empty_or_trailing() { 55 | result.elems.push_punct(Default::default()); 56 | } 57 | result 58 | } 59 | 60 | pub fn empty_type_tuple() -> syn::TypeTuple { 61 | syn::TypeTuple { 62 | paren_token: Default::default(), 63 | elems: Default::default(), 64 | } 65 | } 66 | 67 | pub fn modify_types_generics_hack(ty_generics: &syn::TypeGenerics, mut mutator: F) -> syn::AngleBracketedGenericArguments 68 | where 69 | F: FnMut(&mut syn::punctuated::Punctuated), 70 | { 71 | let mut abga: syn::AngleBracketedGenericArguments = 72 | syn::parse2(ty_generics.to_token_stream()).unwrap_or_else(|_| syn::AngleBracketedGenericArguments { 73 | colon2_token: None, 74 | lt_token: Default::default(), 75 | args: Default::default(), 76 | gt_token: Default::default(), 77 | }); 78 | mutator(&mut abga.args); 79 | abga 80 | } 81 | 82 | pub fn strip_raw_ident_prefix(mut name: String) -> String { 83 | if name.starts_with("r#") { 84 | name.replace_range(0..2, ""); 85 | } 86 | name 87 | } 88 | 89 | pub fn first_visibility(visibilities: &[Option<&syn::Visibility>]) -> proc_macro2::TokenStream { 90 | let vis = visibilities 91 | .iter() 92 | .flatten() 93 | .next() 94 | .expect("need at least one visibility in the list"); 95 | 96 | vis.to_token_stream() 97 | } 98 | 99 | pub fn public_visibility() -> syn::Visibility { 100 | syn::Visibility::Public(syn::token::Pub::default()) 101 | } 102 | 103 | pub fn expr_to_lit_string(expr: &syn::Expr) -> Result { 104 | match expr { 105 | syn::Expr::Lit(lit) => match &lit.lit { 106 | syn::Lit::Str(str) => Ok(str.value()), 107 | _ => Err(Error::new_spanned(expr, "attribute only allows str values")), 108 | }, 109 | _ => Err(Error::new_spanned(expr, "attribute only allows str values")), 110 | } 111 | } 112 | 113 | #[allow(clippy::large_enum_variant)] 114 | pub enum AttrArg { 115 | Flag(Ident), 116 | KeyValue(KeyValue), 117 | Sub(SubAttr), 118 | Not { not: Token![!], name: Ident }, 119 | Fn(syn::ItemFn), 120 | } 121 | 122 | impl AttrArg { 123 | pub fn name(&self) -> &Ident { 124 | match self { 125 | AttrArg::Flag(name) => name, 126 | AttrArg::KeyValue(KeyValue { name, .. }) => name, 127 | AttrArg::Sub(SubAttr { name, .. }) => name, 128 | AttrArg::Not { name, .. } => name, 129 | AttrArg::Fn(func) => &func.sig.ident, 130 | } 131 | } 132 | 133 | pub fn incorrect_type(&self) -> syn::Error { 134 | let message = match self { 135 | AttrArg::Flag(name) => format!("{:?} is not supported as a flag", name.to_string()), 136 | AttrArg::KeyValue(KeyValue { name, .. }) => format!("{:?} is not supported as key-value", name.to_string()), 137 | AttrArg::Sub(SubAttr { name, .. }) => format!("{:?} is not supported as nested attribute", name.to_string()), 138 | AttrArg::Not { name, .. } => format!("{:?} cannot be nullified", name.to_string()), 139 | AttrArg::Fn(func) => format!("{:?} is not supported as a function", func.sig.ident.to_string()), 140 | }; 141 | syn::Error::new_spanned(self, message) 142 | } 143 | 144 | pub fn flag(self) -> syn::Result { 145 | if let Self::Flag(name) = self { 146 | Ok(name) 147 | } else { 148 | Err(self.incorrect_type()) 149 | } 150 | } 151 | 152 | pub fn key_value(self) -> syn::Result { 153 | if let Self::KeyValue(key_value) = self { 154 | Ok(key_value) 155 | } else { 156 | Err(self.incorrect_type()) 157 | } 158 | } 159 | 160 | pub fn key_value_or_not(self) -> syn::Result> { 161 | match self { 162 | Self::KeyValue(key_value) => Ok(Some(key_value)), 163 | Self::Not { .. } => Ok(None), 164 | _ => Err(self.incorrect_type()), 165 | } 166 | } 167 | 168 | pub fn sub_attr(self) -> syn::Result { 169 | if let Self::Sub(sub_attr) = self { 170 | Ok(sub_attr) 171 | } else { 172 | Err(self.incorrect_type()) 173 | } 174 | } 175 | 176 | pub fn apply_flag_to_field(self, field: &mut Option, caption: &str) -> syn::Result<()> { 177 | match self { 178 | AttrArg::Flag(flag) => { 179 | if field.is_none() { 180 | *field = Some(flag.span()); 181 | Ok(()) 182 | } else { 183 | Err(Error::new( 184 | flag.span(), 185 | format!("Illegal setting - field is already {caption}"), 186 | )) 187 | } 188 | } 189 | AttrArg::Not { .. } => { 190 | *field = None; 191 | Ok(()) 192 | } 193 | _ => Err(self.incorrect_type()), 194 | } 195 | } 196 | 197 | pub fn apply_potentialy_empty_sub_to_field( 198 | self, 199 | field: &mut Option, 200 | caption: &str, 201 | init: impl FnOnce(Span) -> T, 202 | ) -> syn::Result<()> { 203 | match self { 204 | AttrArg::Sub(sub) => { 205 | if field.is_none() { 206 | let mut value = init(sub.span()); 207 | value.apply_sub_attr(sub)?; 208 | *field = Some(value); 209 | Ok(()) 210 | } else { 211 | Err(Error::new( 212 | sub.span(), 213 | format!("Illegal setting - field is already {caption}"), 214 | )) 215 | } 216 | } 217 | AttrArg::Flag(flag) => { 218 | if field.is_none() { 219 | *field = Some(init(flag.span())); 220 | Ok(()) 221 | } else { 222 | Err(Error::new( 223 | flag.span(), 224 | format!("Illegal setting - field is already {caption}"), 225 | )) 226 | } 227 | } 228 | AttrArg::Not { .. } => { 229 | *field = None; 230 | Ok(()) 231 | } 232 | _ => Err(self.incorrect_type()), 233 | } 234 | } 235 | } 236 | 237 | pub struct KeyValue { 238 | pub name: Ident, 239 | pub eq: Token![=], 240 | pub value: TokenStream, 241 | } 242 | 243 | impl KeyValue { 244 | pub fn parse_value(self) -> syn::Result { 245 | syn::parse2(self.value) 246 | } 247 | } 248 | 249 | impl ToTokens for KeyValue { 250 | fn to_tokens(&self, tokens: &mut TokenStream) { 251 | self.name.to_tokens(tokens); 252 | self.eq.to_tokens(tokens); 253 | self.value.to_tokens(tokens); 254 | } 255 | } 256 | 257 | impl Parse for KeyValue { 258 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 259 | Ok(Self { 260 | name: input.parse()?, 261 | eq: input.parse()?, 262 | value: input.parse()?, 263 | }) 264 | } 265 | } 266 | 267 | pub struct SubAttr { 268 | pub name: Ident, 269 | pub paren: token::Paren, 270 | pub args: TokenStream, 271 | } 272 | 273 | impl SubAttr { 274 | pub fn args(self) -> syn::Result> { 275 | Punctuated::::parse_terminated.parse2(self.args) 276 | } 277 | pub fn undelimited(self) -> syn::Result> { 278 | (|p: ParseStream| iter::from_fn(|| (!p.is_empty()).then(|| p.parse())).collect::>>()).parse2(self.args) 279 | } 280 | } 281 | 282 | impl ToTokens for SubAttr { 283 | fn to_tokens(&self, tokens: &mut TokenStream) { 284 | self.name.to_tokens(tokens); 285 | self.paren.surround(tokens, |t| self.args.to_tokens(t)); 286 | } 287 | } 288 | 289 | fn get_cursor_after_parsing(input: syn::parse::ParseBuffer) -> syn::Result { 290 | let parse_attempt: P = input.parse()?; 291 | let cursor = input.cursor(); 292 | if cursor.eof() || input.peek(Token![,]) { 293 | Ok(cursor) 294 | } else { 295 | Err(syn::Error::new( 296 | parse_attempt.span(), 297 | "does not end with comma or end of section", 298 | )) 299 | } 300 | } 301 | 302 | fn get_token_stream_up_to_cursor(input: syn::parse::ParseStream, cursor: syn::buffer::Cursor) -> syn::Result { 303 | Ok(core::iter::from_fn(|| { 304 | if input.cursor() < cursor { 305 | input.parse::().ok() 306 | } else { 307 | None 308 | } 309 | }) 310 | .collect()) 311 | } 312 | 313 | impl Parse for AttrArg { 314 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 315 | // Check for standalone function first 316 | if input.peek(Token![fn]) { 317 | return Ok(Self::Fn(input.parse()?)); 318 | } 319 | 320 | if input.peek(Token![!]) { 321 | Ok(Self::Not { 322 | not: input.parse()?, 323 | name: input.parse()?, 324 | }) 325 | } else { 326 | let name = input.parse()?; 327 | if input.peek(Token![,]) || input.is_empty() { 328 | Ok(Self::Flag(name)) 329 | } else if input.peek(token::Paren) { 330 | let args; 331 | Ok(Self::Sub(SubAttr { 332 | name, 333 | paren: parenthesized!(args in input), 334 | args: args.parse()?, 335 | })) 336 | } else if input.peek(Token![=]) { 337 | Ok(Self::KeyValue(KeyValue { 338 | name, 339 | eq: input.parse()?, 340 | value: { 341 | let cursor = get_cursor_after_parsing::(input.fork()) 342 | .or_else(|_| get_cursor_after_parsing::(input.fork()))?; 343 | get_token_stream_up_to_cursor(input, cursor)? 344 | }, 345 | })) 346 | } else { 347 | Err(input.error("expected !, =, (…), or fn")) 348 | } 349 | } 350 | } 351 | } 352 | 353 | impl ToTokens for AttrArg { 354 | fn to_tokens(&self, tokens: &mut TokenStream) { 355 | match self { 356 | AttrArg::Flag(flag) => flag.to_tokens(tokens), 357 | AttrArg::KeyValue(kv) => kv.to_tokens(tokens), 358 | AttrArg::Sub(sub) => sub.to_tokens(tokens), 359 | AttrArg::Not { not, name } => { 360 | not.to_tokens(tokens); 361 | name.to_tokens(tokens); 362 | } 363 | AttrArg::Fn(func) => func.to_tokens(tokens), 364 | } 365 | } 366 | } 367 | 368 | pub trait ApplyMeta { 369 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error>; 370 | 371 | fn apply_sub_attr(&mut self, sub_attr: SubAttr) -> syn::Result<()> { 372 | for arg in sub_attr.args()? { 373 | self.apply_meta(arg)?; 374 | } 375 | Ok(()) 376 | } 377 | 378 | fn apply_subsections(&mut self, list: &syn::MetaList) -> syn::Result<()> { 379 | if list.tokens.is_empty() { 380 | return Err(syn::Error::new_spanned(list, "Expected builder(…)")); 381 | } 382 | 383 | let parser = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated; 384 | let exprs = parser.parse2(list.tokens.clone())?; 385 | for expr in exprs { 386 | self.apply_meta(expr)?; 387 | } 388 | 389 | Ok(()) 390 | } 391 | 392 | fn apply_attr(&mut self, attr: &Attribute) -> syn::Result<()> { 393 | match &attr.meta { 394 | syn::Meta::List(list) => self.apply_subsections(list), 395 | meta => Err(Error::new_spanned(meta, "Expected builder(…)")), 396 | } 397 | } 398 | } 399 | 400 | pub fn pat_to_ident(i: usize, pat: &Pat) -> Ident { 401 | if let Pat::Ident(PatIdent { ident, .. }) = pat { 402 | ident.clone() 403 | } else { 404 | format_ident!("__{i}", span = pat.span()) 405 | } 406 | } 407 | 408 | pub fn phantom_data_for_generics(generics: &syn::Generics) -> proc_macro2::TokenStream { 409 | let phantom_generics = generics.params.iter().filter_map(|param| match param { 410 | syn::GenericParam::Lifetime(lifetime) => { 411 | let lifetime = &lifetime.lifetime; 412 | Some(quote!(&#lifetime ())) 413 | } 414 | syn::GenericParam::Type(ty) => { 415 | let ty = &ty.ident; 416 | Some(ty.to_token_stream()) 417 | } 418 | syn::GenericParam::Const(_cnst) => None, 419 | }); 420 | quote!(::core::marker::PhantomData<(#( ::core::marker::PhantomData<#phantom_generics> ),*)>) 421 | } 422 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## 0.23.2 - 2025-11-19 10 | ### Fixed 11 | - Clippy warnings for using `&Option<&T>` instead of `Option<&T>`. 12 | 13 | ## 0.23.1 - 2025-11-16 14 | ### Fixed 15 | - Clippy warnings for unused variables in the new default implementation. 16 | 17 | ## 0.23.0 - 2025-10-20 18 | ### Changed 19 | - Upgrae Rust edition to 2024. 20 | - [**BREAKING**] When `default` is an expression that uses previous fields, 21 | it'll receive them as reference. 22 | 23 | ### Added 24 | - `default_where` option to add generic bounds on `default`. 25 | - `builder_type(attributes(...))` for adding attributes to the builder type 26 | (mainly for `derive`) 27 | 28 | ### Removed 29 | - [**BREAKING**] The `Optional` trait. This is an internal implementation 30 | detail, but some users may have relied on it for customized flows. 31 | 32 | ## 0.22.0 - 2025-09-08 33 | ### Added 34 | - New optional alternate `transform` syntax using a full fn, to allow support for custom lifetimes, generics and a where clause to custom builder method. 35 | 36 | Example: 37 | ```rust 38 | #[derive(TypedBuilder)] 39 | struct Foo { 40 | #[builder( 41 | setter( 42 | fn transform<'a, M>(value: impl IntoValue<'a, String, M>) -> String 43 | where 44 | M: std::fmt::Display 45 | { 46 | value.into_value() 47 | }, 48 | ) 49 | )] 50 | s: String, 51 | } 52 | ``` 53 | 54 | ## 0.21.2 - 2025-08-21 55 | ### Fixed 56 | - Recognize `TypeGroup` when checking for `Option`. 57 | 58 | ## 0.21.1 - 2025-08-12 59 | ### Fixed 60 | - Strip raw ident prefix from base method name before assembling prefixed/suffixed fallback method names 61 | 62 | ## 0.21.0 - 2025-03-20 63 | ### Added 64 | - Added `ignore_invalid` option to `strip_option` to skip stripping for non-Option fields 65 | - Added `fallback_prefix` and `fallback_suffix` options to `strip_option` for customizing fallback method names 66 | - Added support for field defaults with `strip_option` and its fallback options 67 | 68 | ### Changed 69 | - Improved handling of `strip_option` to work better with field defaults 70 | - Made `strip_option` more flexible with non-Option fields when `ignore_invalid` is set 71 | 72 | ## 0.20.1 - 2025-03-14 73 | ### Fixed 74 | - Fix mutator with type parameter using associated type (see issue #157) 75 | 76 | ## 0.20.0 - 2024-08-22 77 | ### Added 78 | - Add `#[builder(setter(strip_option(fallback = field_opt)))]` to add a fallback unstripped method to the builder struct. 79 | - Add `#[builder(setter(strip_bool(fallback = field_bool)))]` to add a fallback setter that takes the `bool` value to the builder struct. 80 | 81 | ## 0.19.1 - 2024-07-14 82 | ### Fixed 83 | - Fix mutators for generic fields (see issue #149) 84 | 85 | ## 0.19.0 - 2024-06-15 86 | ### Added 87 | - Use fields' doc comments for the setters. 88 | 89 | ## 0.18.2 - 2024-04-16 90 | ### Fixed 91 | - Also add the licenses to the macro crate. 92 | 93 | ## 0.18.1 - 2024-01-17 94 | ### Fixed 95 | - Add `#[allow(clippy::no_effect_underscore_binding)]` to generated methods 96 | that need to destructure intermediate builder state. 97 | - Use a proper `OR` syntax for the dual license. 98 | 99 | ## 0.18.0 - 2023-10-19 100 | ### Fixed 101 | - `?Sized` generic parameters are now supported. 102 | 103 | ## 0.17.0 - 2023-10-15 104 | ### Changed 105 | - Internal refactor of attribute parsing - results in better error messages and 106 | easier proces for adding new settings. 107 | 108 | ### Added 109 | - `#[builder(mutators(...))]` to generate functions on builder to mutate fields 110 | - `#[builder(via_mutator)]` on fields to allow defining fields initialized 111 | during `::builder()` for use with `mutators` 112 | - `mutable_during_default_resolution` to allow `default` expression mutate 113 | previous fields. 114 | 115 | ### Fixed 116 | - Add support for paths with angle brackets (see PR #122 ) 117 | 118 | ## 0.16.2 - 2023-09-22 119 | ### Fixed 120 | - Use generics with the constructor in `build` method (see issue #118) 121 | 122 | ## 0.16.1 - 2023-09-18 123 | ### Fixed 124 | - Add `#[allow(clippy::exhaustive_enums)]` to generated empty enums used for 125 | error "reporting" (see issue #112) 126 | - Add `#[automatically_derived]` to generated `impl`s (see issue #114) 127 | - Add `#[allow(clippy::used_underscore_binding)]` to build method and setter 128 | methods (see issue #113) 129 | 130 | ## 0.16.0 - 2023-08-26 131 | ### Added 132 | - `#[builder(crate_module_path = ...)]` for overcoming cases where the derive 133 | macro is used in another crate's macro (see issue #109) 134 | 135 | ## 0.15.2 - 2023-08-03 136 | ### Fixed 137 | - Fix const generics generating "empty" entries in some lists, resulting in 138 | consecutive commas (see issue #106) 139 | 140 | ## 0.15.1 - 2023-07-10 141 | ### Fixed 142 | - no-std build. 143 | 144 | ## 0.15.0 - 2023-07-06 145 | ### Changed 146 | - [**BREAKING**] Split the derive macro out to [a separate procmacro 147 | crate](https://crates.io/crates/typed-builder-macro). This is considered a 148 | breaking change because reexporting and/or renmaing the crate can now prevent 149 | the generated code from finding the types it needs (see issue #101) 150 | 151 | ### Fixed 152 | - Marking a field as `#[deprecated]` now behaves properly - `TypedBuilder` 153 | generated code itself does trigger the deprecation warning, and instead the 154 | setter for that field now does. 155 | - The "fake" `build` method when required fields are not provided now returns 156 | the never type ("`!`"). Refer to PR #97 for more thorough explanation. 157 | 158 | ### Added 159 | - Support for setter method prefixes and suffixes `#[builder(field_defaults(setter(prefix = "...", suffix = "...")))]`. 160 | This either prepends or appends the provided string to the setter method. This allows method names like: `set_x()`, 161 | `with_y()`, or `set_z_value()`. 162 | 163 | ## 0.14.0 - 2023-03-08 164 | ### Added 165 | - `build_method(into)` and `build_method(into = ...)`. 166 | 167 | ## 0.13.0 - 2023-03-05 168 | ### Changed 169 | y 170 | - [**BREAKING**] Builder state parameter moved to the end of the generated builder type's parameters list. 171 | - Generated builder type's builder state parameter now defaults to tuple of 172 | empty tuples. This means the empty builder, where no parameter is yet set. 173 | 174 | ### Fixed 175 | - `#[builder(build_method(...))]` now affects the fake `build` method that's 176 | generated to add information to the compiler error. 177 | 178 | ## 0.12.0 - 2023-01-29 179 | ### Removed 180 | - [**BREAKING**] `builder_method_doc = "..."`, `builder_type_doc = "..."` and 181 | `build_method_doc = "..."` are replaced with `builder_method(doc = "...")`, 182 | `builder_type(doc = "...")` and `build_method(doc = "...")`. 183 | 184 | ### Added 185 | - `build_method(...)` now has a `doc` field. 186 | - `builder_method(...)` and `builder_type(...)`, which are structured similarly to `build_method(...)`. 187 | 188 | ## 0.11.0 - 2022-10-29 189 | ### Added 190 | - `#[builder(build_method(vis="pub", name=build))]` for customizing visibility and fn name of the final build method 191 | (the default visibility is `pub`, and default build name is `build`) 192 | 193 | ## 0.10.0 - 2022-02-13 194 | ### Added 195 | - `#[builder(setter(strip_bool))]` for making zero arguments setters for `bool` fields that just 196 | set them to `true` (the `default` automatically becomes `false`) 197 | 198 | ## 0.9.1 - 2021-09-04 199 | ### Fixed 200 | - Add `extern crate proc_macro;` to solve some weird problem (https://github.com/idanarye/rust-typed-builder/issues/57) 201 | - Use unambiguous `::` prefixed absolute paths in generated code. 202 | 203 | ## 0.9.0 - 2021-01-31 204 | ### Added 205 | - Builder type implements `Clone` when all set fields support clone. 206 | - `#[builder(setter(transform = ...))]` attribute for running a transform on a 207 | setter's argument to convert them to the field's type. 208 | 209 | ### Fixed 210 | - Fix code generation for raw identifiers. 211 | 212 | ## 0.8.0 - 2020-12-06 213 | ### Changed 214 | - Upgraded the Rust edition to 2018. 215 | 216 | ### Added 217 | - `#[field_defaults(...)]` attribute for settings default attributes for all 218 | the fields. 219 | 220 | ## 0.7.1 - 2020-11-20 221 | ### Fixed 222 | - Fix lifetime bounds erroneously preserved in phantom generics. 223 | 224 | ## 0.7.0 - 2020-07-23 225 | ### Added 226 | - Brought back `default_code`, because it needed to resolve conflict with other 227 | custom derive proc-macro crates that try to parse `[#builder(default = ...)]` 228 | attribute in order to decide if they are relevant to them - and fail because 229 | the expect them to be simple literals. 230 | 231 | ## 0.6.0 - 2020-05-18 232 | ### Added 233 | - Ability to use `into` and `strip_option` simultaneously for a field. 234 | 235 | ### Changed 236 | - [**BREAKING**] Specifying `skip` twice in the same `builder(setter(...))` is 237 | no longer supported. Then again, if you were doing that you probably deserve 238 | having your code broken. 239 | 240 | ## 0.5.1 - 2020-01-26 241 | ### Fixed 242 | - Prevent Clippy from warning about the `panic!()` in the faux build method. 243 | 244 | ## 0.5.0 - 2020-01-25 245 | ### Changed 246 | - [**BREAKING**] Move `doc` and `skip` into a subsetting named `setter(...)`. 247 | This means that `#[builder(doc = "...")]`, for example, should now be written 248 | as `#[builder(setter(doc = "..."))]`. 249 | - [**BREAKING**] Setter arguments by default are no longer automatically 250 | converted to the target type with `into()`. If you want to automatically 251 | convert them, use `#[builder(setter(into))]`. This new default enables rustc 252 | inference for generic types and proper integer literal type detection. 253 | - Improve build errors for incomplete `.build()` and repeated setters, by 254 | creating faux methods with deprecation warnings. 255 | 256 | ### Added 257 | - `#[builder(setter(strip_option))]` for making setters for `Option` fields 258 | automatically wrap the argument with `Some(...)`. Note that this is a weaker 259 | conversion than `#[builder(setter(into))]`, and thus can still support type 260 | inference and integer literal type detection. 261 | 262 | ### Removed 263 | - [**BREAKING**] Removed the `default_code` setting (`#[builder(default_code = 264 | "...")]`) because it is no longer required now that Rust and `syn` support 265 | arbitrary expressions in attributes. 266 | 267 | ## 0.4.1 - 2020-01-17 268 | ### Fixed 269 | - [**BREAKING**] now state types are placed before original generic types. 270 | Previously, all state types are appended to generic arguments. For example, 271 | `Foo<'a, X, Y>` yields `FooBuilder<'a, X, Y, ((), ())>` **previously**, and 272 | now it becomes `FooBuilder<'a, ((), ()), X, Y, >.`. This change fix compiler error 273 | for struct with default type like `Foo<'a, X, Y=Bar>`. Rust only allow type 274 | parameters with a default to be trailing. 275 | 276 | ## 0.4.0 - 2019-12-13 277 | ### Added 278 | - `#![no_std]` is now supported out of the box. (You don't need to opt into any 279 | features, it just works.) 280 | - [**BREAKING**] a `default_code` expression can now refer to the values of 281 | earlier fields by name (This is extremely unlikely to break your code, but 282 | could in theory due to shadowing) 283 | - `#[builder(skip)]` on fields, to not provide a method to set that field. 284 | - Control of documentation: 285 | - `#[builder(doc = "…")]` on fields, to document the field's method on the 286 | builder. Unlike `#[doc]`, you can currently only have one value rather than 287 | one attribute per line; but that's not a big deal since you don't get to 288 | use the `///` sugar anyway. Just use a multiline string. 289 | - `#[builder(doc, builder_method_doc = "…", builder_type_doc = "…", 290 | build_method_doc = "…")]` on structs: 291 | - `doc` unhides the builder type from the documentation. 292 | - `builder_method_doc = "…"` replaces the default documentation that 293 | will be generated for the builder() method of the type for which the 294 | builder is being generated. 295 | - `builder_type_doc = "…"` replaces the default documentation that will 296 | be generated for the builder type. Implies `doc`. 297 | - `build_method_doc = "…"` replaces the default documentation that will 298 | be generated for the build() method of the builder type. Implies 299 | `doc`. 300 | 301 | ### Changed 302 | - [**BREAKING**] Renamed the generated builder type from 303 | `TypedBuilder_BuilderFor_Foo` to `FooBuilder`, for improved ergonomics, 304 | especially when you enable documentation of the builder type. 305 | - Generic identifiers were also changed, from `TypedBuilder_genericType_x` to 306 | `__x`. This is still expected to avoid all name collisions, but is easier 307 | to read in the builder type docs if you enable them. 308 | - Renamed the conversion helper trait for documentation purposes 309 | (`TypedBuilder_conversionHelperTrait_Foo` to `FooBuilder_Optional`), and 310 | its method name for simpler code. 311 | - [**BREAKING**] `default_code` is now lazily evaluated instead of eagerly; any 312 | side-effects that there might have been will no longer occur. As is usual in 313 | this release, this is very unlikely to affect you. 314 | - The restriction that there be only one `#[builder]` attribute per field has 315 | been lifted. You can now write `#[builder(skip)] #[builder(default)]` instead 316 | of `#[builder(skip, default)]` if you want to. As was already the case, 317 | latest definition wins. 318 | - [**BREAKING**] Use a single generic parameter to represent the builder type's 319 | state (see issue #21). Previously we would use a parameter for each field. 320 | 321 | ### Changed 322 | - Move to dual license - MIT/Apache-2.0. Previously this project was just MIT. 323 | 324 | ## 0.3.0 - 2019-02-19 325 | ### Added 326 | - `#[builder(default_code = "...")]` syntax for defaults that cannot be parsed 327 | as attributes no matter what. 328 | 329 | ### Changed 330 | - Move the docs from the crate to the custom derive proc macro. 331 | 332 | ## 0.2.0 - 2019-02-06 333 | ### Changed 334 | - Upgraded `syn` version to support Rust 2018. 335 | - [**BREAKING**] Changed attribute style to `#[builder(...)]`: 336 | - `#[default]` -> `#[builder(default)]` 337 | - `#[default=...]` -> `#[builder(default=...)]` 338 | - [**BREAKING**] `default` no longer needs to be a string. 339 | - But you need to change your code anyways because the attribute style was changed. 340 | 341 | ## 0.1.1 - 2018-07-24 342 | ### Fixed 343 | - Allow missing docs in structs that derive `TypedBuilder`. 344 | 345 | ## 0.1.0 - 2017-10-05 346 | ### Added 347 | - Custom derive for generating the builder pattern. 348 | - All setters are accepting `Into` values. 349 | - Compile time verification that all fields are set before calling `.build()`. 350 | - Compile time verification that no field is set more than once. 351 | - Ability to annotate fields with `#[default]` to make them optional and specify a default value when the user does not set them. 352 | - Generates simple documentation for the `.builder()` method. 353 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | /// `TypedBuilder` is not a real type - deriving it will generate a `::builder()` method on your 4 | /// struct that will return a compile-time checked builder. Set the fields using setters with the 5 | /// same name as the struct's fields and call `.build()` when you are done to create your object. 6 | /// 7 | /// Trying to set the same fields twice will generate a compile-time error. Trying to build without 8 | /// setting one of the fields will also generate a compile-time error - unless that field is marked 9 | /// as `#[builder(default)]`, in which case the `::default()` value of it's type will be picked. If 10 | /// you want to set a different default, use `#[builder(default=...)]`. 11 | /// 12 | /// # Examples 13 | /// 14 | /// ``` 15 | /// use typed_builder::TypedBuilder; 16 | /// 17 | /// #[derive(PartialEq, TypedBuilder)] 18 | /// struct Foo { 19 | /// // Mandatory Field: 20 | /// x: i32, 21 | /// 22 | /// // #[builder(default)] without parameter - use the type's default 23 | /// // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` 24 | /// #[builder(default, setter(strip_option))] 25 | /// y: Option, 26 | /// 27 | /// // Or you can set the default 28 | /// #[builder(default=20)] 29 | /// z: i32, 30 | /// } 31 | /// 32 | /// assert!( 33 | /// Foo::builder().x(1).y(2).z(3).build() 34 | /// == Foo { x: 1, y: Some(2), z: 3, }); 35 | /// 36 | /// // Change the order of construction: 37 | /// assert!( 38 | /// Foo::builder().z(1).x(2).y(3).build() 39 | /// == Foo { x: 2, y: Some(3), z: 1, }); 40 | /// 41 | /// // Optional fields are optional: 42 | /// assert!( 43 | /// Foo::builder().x(1).build() 44 | /// == Foo { x: 1, y: None, z: 20, }); 45 | /// 46 | /// // This will not compile - because we did not set x: 47 | /// // Foo::builder().build(); 48 | /// 49 | /// // This will not compile - because we set y twice: 50 | /// // Foo::builder().x(1).y(2).y(3); 51 | /// ``` 52 | /// 53 | /// # Customization with attributes 54 | /// 55 | /// In addition to putting `#[derive(TypedBuilder)]` on a type, you can specify a `#[builder(...)]` 56 | /// attribute on the type, and on any fields in it. 57 | /// 58 | /// On the **type**, the following values are permitted: 59 | /// 60 | /// - `doc`: enable documentation of the builder type. By default, the builder type is given 61 | /// `#[doc(hidden)]`, so that the `builder()` method will show `FooBuilder` as its return type, 62 | /// but it won't be a link. If you turn this on, the builder type and its `build` method will get 63 | /// sane defaults. The field methods on the builder will be undocumented by default. 64 | /// 65 | /// - `crate_module_path`: This is only needed when `typed_builder` is reexported from another 66 | /// crate - which usually happens when another macro uses it. In that case, it is the 67 | /// reponsibility of that macro to set the `crate_module_path` to the _unquoted_ module path from 68 | /// which the `typed_builder` crate can be accessed, so that the `TypedBuilder` macro will be 69 | /// able to access the typed declared in it. 70 | /// 71 | /// Defaults to `#[builder(crate_module_path=::typed_builder)]`. 72 | /// 73 | /// - The following subsections: 74 | /// - `builder_method(...)`: customize the builder method that creates the builder type 75 | /// - `builder_type(...)`: customize the builder type 76 | /// - `build_method(...)`: customize the final build method 77 | /// 78 | /// All have the same fields: 79 | /// - `vis = "..."`: sets the visibility of the build method, default is `pub` 80 | /// - `name = ...`: sets the fn name of the build method, default is `build` 81 | /// - `doc = "..."` replaces the default documentation that will be generated for the 82 | /// `build()` method of the builder type. Setting this implies `doc`. 83 | /// 84 | /// 85 | /// - The `build_method(...)` subsection also has: 86 | /// - `into` or `into = ...`: change the output type of the builder. When a specific value/type 87 | /// is set via the assignment, this will be the output type of the builder. If no specific 88 | /// type is set, but `into` is specified, the return type will be generic and the user can 89 | /// decide which type shall be constructed. In both cases an [`Into`] conversion is required to 90 | /// be defined from the original type to the target type. 91 | /// 92 | /// - The `builder_type(...)` subsection also has: 93 | /// - `attributes` - for adding attributes to the builder type. Note that the full attribute 94 | /// syntax is required **inside** this section - 95 | /// ```ignore 96 | /// #[builder(builder_type(attributes( 97 | /// #[derive(...)] 98 | /// #[some_other_attribute] 99 | /// )))] 100 | /// ``` 101 | /// 102 | /// Example: 103 | /// 104 | /// ``` 105 | /// use typed_builder::TypedBuilder; 106 | /// 107 | /// #[derive(TypedBuilder)] 108 | /// #[builder(builder_type(attributes(#[derive(Debug)])))] 109 | /// struct Foo { 110 | /// x: i32, 111 | /// } 112 | /// ``` 113 | /// 114 | /// - `field_defaults(...)` is structured like the `#[builder(...)]` attribute you can put on the 115 | /// fields and sets default options for fields of the type. If specific field need to revert some 116 | /// options to the default defaults they can prepend `!` to the option they need to revert, and 117 | /// it would ignore the field defaults for that option in that field. 118 | /// 119 | /// ``` 120 | /// use typed_builder::TypedBuilder; 121 | /// 122 | /// #[derive(TypedBuilder)] 123 | /// #[builder(field_defaults(default, setter(strip_option)))] 124 | /// struct Foo { 125 | /// // Defaults to None, options-stripping is performed: 126 | /// x: Option, 127 | /// 128 | /// // Defaults to 0, option-stripping is not performed: 129 | /// #[builder(setter(!strip_option))] 130 | /// y: i32, 131 | /// 132 | /// // Defaults to Some(13), option-stripping is performed: 133 | /// #[builder(default = Some(13))] 134 | /// z: Option, 135 | /// 136 | /// // Accepts params `(x: f32, y: f32)` 137 | /// #[builder(setter(!strip_option, transform = |x: f32, y: f32| Point { x, y }))] 138 | /// w: Point, 139 | /// } 140 | /// 141 | /// #[derive(Default)] 142 | /// struct Point { x: f32, y: f32 } 143 | /// ``` 144 | /// 145 | /// - `mutators(...)` takes functions, that can mutate fields inside of the builder. 146 | /// See [mutators](#mutators) for details. 147 | /// 148 | /// On each **field**, the following values are permitted: 149 | /// 150 | /// - `default`: make the field optional, defaulting to `Default::default()`. This requires that 151 | /// the field type implement `Default`. Mutually exclusive with any other form of default. 152 | /// 153 | /// - `default = ...`: make the field optional, defaulting to the expression `...`. 154 | /// 155 | /// - `default_where(...)`: add trait bounds to the default. This means that the `default` 156 | /// expression (or type default) is allowed to rely on these bounds, but the field will not have 157 | /// a default if these trait bounds are not fulfilled. 158 | /// 159 | /// Example: 160 | /// 161 | /// ``` 162 | /// use typed_builder::TypedBuilder; 163 | /// 164 | /// #[derive(TypedBuilder)] 165 | /// struct Foo { 166 | /// #[builder(default, default_where(T: Default))] 167 | /// bar: T, 168 | /// } 169 | /// ``` 170 | /// 171 | /// - `default_code = "..."`: make the field optional, defaulting to the expression `...`. Note that 172 | /// you need to enclose it in quotes, which allows you to use it together with other custom 173 | /// derive proc-macro crates that complain about "expected literal". Note that if `...` contains 174 | /// a string, you can use raw string literals to avoid escaping the double quotes - e.g. 175 | /// `#[builder(default_code = r#""default text".to_owned()"#)]`. 176 | /// 177 | /// - `via_mutators`: initialize the field when constructing the builder, useful in combination 178 | /// with [mutators](#mutators). 179 | /// 180 | /// - `via_mutators = ...` or `via_mutators(init = ...)`: initialies the field with the expression `...` 181 | /// when constructing the builder, useful in combination with [mutators](#mutators). 182 | /// 183 | /// - `mutators(...)` takes functions, that can mutate fields inside of the builder. 184 | /// Mutators specified on a field, mark this field as required, see [mutators](#mutators) for details. 185 | /// 186 | /// - `setter(...)`: settings for the field setters. The following values are permitted inside: 187 | /// 188 | /// - `doc = "..."`: sets the documentation for the field's setter on the builder type. This will be 189 | /// of no value unless you enable docs for the builder type with `#[builder(doc)]` or similar on 190 | /// the type. 191 | /// 192 | /// - `skip`: do not define a method on the builder for this field. This requires that a default 193 | /// be set. 194 | /// 195 | /// - `into`: automatically convert the argument of the setter method to the type of the field. 196 | /// Note that this conversion interferes with Rust's type inference and integer literal 197 | /// detection, so this may reduce ergonomics if the field type is generic or an unsigned integer. 198 | /// 199 | /// - `strip_option`: for `Option<...>` fields only, this makes the setter wrap its argument with 200 | /// `Some(...)`, relieving the caller from having to do this. Note that with this setting on 201 | /// one cannot set the field to `None` with the setter - so the only way to get it to be `None` 202 | /// is by using `#[builder(default)]` and not calling the field's setter. 203 | /// 204 | /// - `strip_option(fallback = field_opt)`: for `Option<...>` fields only. As above this 205 | /// still wraps the argument with `Some(...)`. The name given to the fallback method adds 206 | /// another method to the builder without wrapping the argument in `Some`. You can now call 207 | /// `field_opt(Some(...))` instead of `field(...)`. 208 | /// 209 | /// The `setter(strip_option)` attribute supports several `field_defaults` features: 210 | /// 211 | /// - `ignore_invalid`: Skip stripping for non-Option fields instead of causing a compile error 212 | /// - `fallback_prefix`: Add a prefix to every fallback method name 213 | /// - `fallback_suffix`: Add a suffix to every fallback method name 214 | /// 215 | /// Example: 216 | /// 217 | /// ``` 218 | /// use typed_builder::TypedBuilder; 219 | /// 220 | /// #[derive(TypedBuilder)] 221 | /// #[builder(field_defaults(setter(strip_option( 222 | /// ignore_invalid, 223 | /// fallback_prefix = "opt_", 224 | /// fallback_suffix = "_val" 225 | /// ))))] 226 | /// struct Foo { 227 | /// x: Option, // Can use .x(42) or .opt_x_val(None) 228 | /// y: i32, // Uses .y(42) only since it's not an Option 229 | /// } 230 | /// ``` 231 | /// 232 | /// - `strip_bool`: for `bool` fields only, this makes the setter receive no arguments and simply 233 | /// set the field's value to `true`. When used, the `default` is automatically set to `false`. 234 | /// 235 | /// - `strip_bool(fallback = field_bool)`: for `bool` fields only. As above this allows passing 236 | /// the boolean value. The name given to the fallback method adds another method to the builder 237 | /// without where the bool value can be specified. 238 | /// 239 | /// - `transform = |param1: Type1, param2: Type2 ...| expr`: this makes the setter accept 240 | /// `param1: Type1, param2: Type2 ...` instead of the field type itself. The parameters are 241 | /// transformed into the field type using the expression `expr`. The transformation is performed 242 | /// when the setter is called. `transform` can also be provided in full `fn` syntax, 243 | /// to allow custom lifetimes, a generic and a where clause. 244 | /// Example: 245 | /// ```rust 246 | /// #[builder( 247 | /// setter( 248 | /// fn transform<'a, M>(value: impl IntoValue<'a, String, M>) -> String 249 | /// where 250 | /// M: 'a, 251 | /// { 252 | /// value.into_value() 253 | /// }, 254 | /// ) 255 | /// )] 256 | /// ``` 257 | /// 258 | /// - `prefix = "..."` prepends the setter method with the specified prefix. For example, setting 259 | /// `prefix = "with_"` results in setters like `with_x` or `with_y`. This option is combinable 260 | /// with `suffix = "..."`. 261 | /// 262 | /// - `suffix = "..."` appends the setter method with the specified suffix. For example, setting 263 | /// `suffix = "_value"` results in setters like `x_value` or `y_value`. This option is combinable 264 | /// with `prefix = "..."`. 265 | /// 266 | /// - `mutable_during_default_resolution`: when expressions in `default = ...` field attributes 267 | /// are evaluated, this field will be mutable, allowing earlier-defined fields to be mutated by 268 | /// later-defined fields. 269 | /// **Warning** - Use this feature with care! If the field that mutates the previous field in 270 | /// its `default` expression is set via a setter, that mutation will not happen. 271 | /// 272 | /// # Mutators 273 | /// Set fields can be mutated using mutators, these can be defind via `mutators(...)`. 274 | /// 275 | /// Fields annotated with `#[builder(via_mutators)]` are always available to mutators. Additional fields, 276 | /// that the mutator accesses need to be delcared using `#[mutator(requires = [field1, field2, ...])]`. 277 | /// The mutator will only be availible to call when they are set. 278 | /// 279 | /// Mutators on a field, result in them automatically making the field required, i.e., it needs to be 280 | /// marked as `via_mutators`, or its setter be called. Appart from that, they behave identically. 281 | /// 282 | /// ``` 283 | /// use typed_builder::TypedBuilder; 284 | /// 285 | /// #[derive(PartialEq, Debug, TypedBuilder)] 286 | /// #[builder(mutators( 287 | /// // Mutator has only acces to fields marked as `via_mutators`. 288 | /// fn inc_a(&mut self, a: i32){ 289 | /// self.a += a; 290 | /// } 291 | /// // Mutator has access to `x` additionally. 292 | /// #[mutator(requires = [x])] 293 | /// fn x_into_b(&mut self) { 294 | /// self.b.push(self.x) 295 | /// } 296 | /// ))] 297 | /// struct Struct { 298 | /// // Does not require explicit `requires = [x]`, as the field 299 | /// // the mutator is specifed on, is required implicitly. 300 | /// #[builder(mutators( 301 | /// fn x_into_b_field(self) { 302 | /// self.b.push(self.x) 303 | /// } 304 | /// ))] 305 | /// x: i32, 306 | /// #[builder(via_mutators(init = 1))] 307 | /// a: i32, 308 | /// #[builder(via_mutators)] 309 | /// b: Vec 310 | /// } 311 | /// 312 | /// // Mutators do not enforce only being called once 313 | /// assert_eq!( 314 | /// Struct::builder().x(2).x_into_b().x_into_b().x_into_b_field().inc_a(2).build(), 315 | /// Struct {x: 2, a: 3, b: vec![2, 2, 2]}); 316 | /// ``` 317 | pub use typed_builder_macro::TypedBuilder; 318 | 319 | #[doc(hidden)] 320 | pub trait NextFieldDefault { 321 | type Output; 322 | 323 | fn resolve(input: TypedBuilderExistingFields) -> Self::Output; 324 | } 325 | 326 | // It'd be nice for the compilation tests to live in tests/ with the rest, but short of pulling in 327 | // some other test runner for that purpose (e.g. compiletest_rs), rustdoc compile_fail in this 328 | // crate is all we can use. 329 | 330 | #[doc(hidden)] 331 | /// When a property is non-default, you can't ignore it: 332 | /// 333 | /// ```compile_fail 334 | /// use typed_builder::TypedBuilder; 335 | /// 336 | /// #[derive(TypedBuilder)] 337 | /// struct Foo { 338 | /// x: i8, 339 | /// } 340 | /// 341 | /// let _ = Foo::builder().build(); 342 | /// ``` 343 | /// 344 | /// When a property is skipped, you can't set it: 345 | /// (“method `y` not found for this”) 346 | /// 347 | /// ```compile_fail 348 | /// use typed_builder::TypedBuilder; 349 | /// 350 | /// #[derive(PartialEq, TypedBuilder)] 351 | /// struct Foo { 352 | /// #[builder(default, setter(skip))] 353 | /// y: i8, 354 | /// } 355 | /// 356 | /// let _ = Foo::builder().y(1i8).build(); 357 | /// ``` 358 | /// 359 | /// But you can build a record: 360 | /// 361 | /// ``` 362 | /// use typed_builder::TypedBuilder; 363 | /// 364 | /// #[derive(PartialEq, TypedBuilder)] 365 | /// struct Foo { 366 | /// #[builder(default, setter(skip))] 367 | /// y: i8, 368 | /// } 369 | /// 370 | /// let _ = Foo::builder().build(); 371 | /// ``` 372 | /// 373 | /// `skip` without `default` is disallowed: 374 | /// (“error: #[builder(skip)] must be accompanied by default”) 375 | /// 376 | /// ```compile_fail 377 | /// use typed_builder::TypedBuilder; 378 | /// 379 | /// #[derive(PartialEq, TypedBuilder)] 380 | /// struct Foo { 381 | /// #[builder(setter(skip))] 382 | /// y: i8, 383 | /// } 384 | /// ``` 385 | /// 386 | /// `clone` does not work if non-Clone fields have already been set 387 | /// 388 | /// ```compile_fail 389 | /// use typed_builder::TypedBuilder; 390 | /// 391 | /// #[derive(Default)] 392 | /// struct Uncloneable; 393 | /// 394 | /// #[derive(TypedBuilder)] 395 | /// struct Foo { 396 | /// x: Uncloneable, 397 | /// y: i32, 398 | /// } 399 | /// 400 | /// let _ = Foo::builder().x(Uncloneable).clone(); 401 | /// ``` 402 | /// 403 | /// Same, but with generics 404 | /// 405 | /// ```compile_fail 406 | /// use typed_builder::TypedBuilder; 407 | /// 408 | /// #[derive(Default)] 409 | /// struct Uncloneable; 410 | /// 411 | /// #[derive(TypedBuilder)] 412 | /// struct Foo { 413 | /// x: T, 414 | /// y: i32, 415 | /// } 416 | /// 417 | /// let _ = Foo::builder().x(Uncloneable).clone(); 418 | /// ``` 419 | /// 420 | /// Handling deprecated fields: 421 | /// 422 | /// ```compile_fail 423 | /// use typed_builder::TypedBuilder; 424 | /// 425 | /// #[derive(TypedBuilder)] 426 | /// struct Foo { 427 | /// #[deprecated = "Don't use this!"] 428 | /// #[allow(dead_code)] 429 | /// value: i32, 430 | /// } 431 | /// 432 | /// #[deny(deprecated)] 433 | /// Foo::builder().value(42).build(); 434 | /// ``` 435 | /// 436 | /// Handling invalid property for `strip_option` 437 | /// 438 | /// ```compile_fail 439 | /// use typed_builder::TypedBuilder; 440 | /// 441 | /// #[derive(TypedBuilder)] 442 | /// struct Foo { 443 | /// #[builder(setter(strip_option(invalid_field = "should_fail")))] 444 | /// value: Option, 445 | /// } 446 | /// ``` 447 | /// 448 | /// Handling multiple properties for `strip_option` 449 | /// 450 | /// ```compile_fail 451 | /// use typed_builder::TypedBuilder; 452 | /// 453 | /// #[derive(TypedBuilder)] 454 | /// struct Foo { 455 | /// #[builder(setter(strip_option(fallback = value_opt, fallback = value_opt2)))] 456 | /// value: Option, 457 | /// } 458 | /// ``` 459 | /// 460 | /// Handling alternative properties for `strip_option` 461 | /// 462 | /// ```compile_fail 463 | /// use typed_builder::TypedBuilder; 464 | /// 465 | /// #[derive(TypedBuilder)] 466 | /// struct Foo { 467 | /// #[builder(setter(strip_option(type = value_opt, fallback = value_opt2)))] 468 | /// value: Option, 469 | /// } 470 | /// ``` 471 | /// 472 | /// Handling invalid property for `strip_bool` 473 | /// 474 | /// ```compile_fail 475 | /// use typed_builder::TypedBuilder; 476 | /// 477 | /// #[derive(TypedBuilder)] 478 | /// struct Foo { 479 | /// #[builder(setter(strip_bool(invalid_field = should_fail)))] 480 | /// value: bool, 481 | /// } 482 | /// ``` 483 | /// 484 | /// Handling multiple propertes for `strip_bool` 485 | /// 486 | /// ```compile_fail 487 | /// use typed_builder::TypedBuilder; 488 | /// 489 | /// #[derive(TypedBuilder)] 490 | /// struct Foo { 491 | /// #[builder(setter(strip_bool(fallback = value_bool, fallback = value_bool2)))] 492 | /// value: bool, 493 | /// } 494 | /// ``` 495 | /// 496 | /// Handling alternative propertes for `strip_bool` 497 | /// 498 | /// ```compile_fail 499 | /// use typed_builder::TypedBuilder; 500 | /// 501 | /// #[derive(TypedBuilder)] 502 | /// struct Foo { 503 | /// #[builder(setter(strip_bool(invalid = value_bool, fallback = value_bool2)))] 504 | /// value: bool, 505 | /// } 506 | /// ``` 507 | fn _compile_fail_tests() {} 508 | -------------------------------------------------------------------------------- /typed-builder-macro/src/field_info.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use proc_macro2::{Ident, Span, TokenStream}; 4 | use quote::{quote, quote_spanned}; 5 | use syn::parse::Parser; 6 | use syn::punctuated::Punctuated; 7 | use syn::{Expr, ExprBlock}; 8 | use syn::{parse::Error, spanned::Spanned}; 9 | 10 | use crate::mutator::Mutator; 11 | use crate::struct_info::StructInfo; 12 | use crate::util::{ApplyMeta, AttrArg, expr_to_lit_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix}; 13 | 14 | #[derive(Debug)] 15 | pub struct FieldInfo<'a> { 16 | pub ordinal: usize, 17 | pub name: &'a syn::Ident, 18 | pub generic_ident: syn::Ident, 19 | pub ty: &'a syn::Type, 20 | pub builder_attr: FieldBuilderAttr<'a>, 21 | } 22 | 23 | impl<'a> FieldInfo<'a> { 24 | pub fn new(ordinal: usize, field: &'a syn::Field, field_defaults: FieldBuilderAttr<'a>) -> Result, Error> { 25 | if let Some(ref name) = field.ident { 26 | FieldInfo { 27 | ordinal, 28 | name, 29 | generic_ident: syn::Ident::new(&format!("__{}", strip_raw_ident_prefix(name.to_string())), Span::call_site()), 30 | ty: &field.ty, 31 | builder_attr: field_defaults.with(name, &field.attrs)?, 32 | } 33 | .post_process() 34 | } else { 35 | Err(Error::new(field.span(), "Nameless field in struct")) 36 | } 37 | } 38 | 39 | pub fn generic_ty_param(&self) -> syn::GenericParam { 40 | syn::GenericParam::Type(self.generic_ident.clone().into()) 41 | } 42 | 43 | pub fn type_ident(&self) -> syn::Type { 44 | ident_to_type(self.generic_ident.clone()) 45 | } 46 | 47 | pub fn tuplized_type_ty_param(&self) -> syn::Type { 48 | let mut types = Punctuated::default(); 49 | types.push(self.ty.clone()); 50 | types.push_punct(Default::default()); 51 | syn::TypeTuple { 52 | paren_token: Default::default(), 53 | elems: types, 54 | } 55 | .into() 56 | } 57 | 58 | pub fn type_from_inside_option(&self) -> Option<&syn::Type> { 59 | let typ = if let syn::Type::Group(type_group) = self.ty { 60 | type_group.elem.deref() 61 | } else { 62 | self.ty 63 | }; 64 | 65 | let path = if let syn::Type::Path(type_path) = typ { 66 | if type_path.qself.is_some() { 67 | return None; 68 | } 69 | &type_path.path 70 | } else { 71 | return None; 72 | }; 73 | let segment = path.segments.last()?; 74 | if segment.ident != "Option" { 75 | return None; 76 | } 77 | let generic_params = if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments { 78 | generic_params 79 | } else { 80 | return None; 81 | }; 82 | if let syn::GenericArgument::Type(ty) = generic_params.args.first()? { 83 | Some(ty) 84 | } else { 85 | None 86 | } 87 | } 88 | 89 | pub fn setter_method_name(&self) -> Ident { 90 | let name = strip_raw_ident_prefix(self.name.to_string()); 91 | 92 | if let (Some(prefix), Some(suffix)) = (&self.builder_attr.setter.prefix, &self.builder_attr.setter.suffix) { 93 | Ident::new(&format!("{}{}{}", prefix, name, suffix), Span::call_site()) 94 | } else if let Some(prefix) = &self.builder_attr.setter.prefix { 95 | Ident::new(&format!("{}{}", prefix, name), Span::call_site()) 96 | } else if let Some(suffix) = &self.builder_attr.setter.suffix { 97 | Ident::new(&format!("{}{}", name, suffix), Span::call_site()) 98 | } else { 99 | self.name.clone() 100 | } 101 | } 102 | 103 | fn post_process(mut self) -> Result { 104 | if let Some(ref strip_bool) = self.builder_attr.setter.strip_bool { 105 | if let Some(default_span) = self.builder_attr.default.as_ref().map(Spanned::span) { 106 | let mut error = Error::new( 107 | strip_bool.span, 108 | "cannot set both strip_bool and default - default is assumed to be false", 109 | ); 110 | error.combine(Error::new(default_span, "default set here")); 111 | return Err(error); 112 | } 113 | self.builder_attr.default = Some(syn::Expr::Lit(syn::ExprLit { 114 | attrs: Default::default(), 115 | lit: syn::Lit::Bool(syn::LitBool { 116 | value: false, 117 | span: strip_bool.span, 118 | }), 119 | })); 120 | } 121 | if self.builder_attr.default.is_none() 122 | && let Some(default_where) = self.builder_attr.default_where.as_ref() 123 | { 124 | return Err(Error::new( 125 | default_where.span(), 126 | "default_where is not allowed without a default", 127 | )); 128 | } 129 | Ok(self) 130 | } 131 | 132 | pub fn maybe_mut(&self) -> TokenStream { 133 | if let Some(span) = self.builder_attr.mutable_during_default_resolution { 134 | quote_spanned!(span => mut) 135 | } else { 136 | quote!() 137 | } 138 | } 139 | 140 | pub fn gen_next_field_default_trait_impl(&self, struct_info: &StructInfo) -> syn::Result> { 141 | if self.builder_attr.setter.skip.is_some() { 142 | return Ok(None); 143 | } 144 | let Some(default_expr) = self.builder_attr.default.as_ref() else { 145 | return Ok(None); 146 | }; 147 | 148 | let crate_module_path = &struct_info.builder_attr.crate_module_path; 149 | let struct_name = struct_info.name; 150 | let (impl_generics, ty_generics, where_clause) = struct_info.generics.split_for_impl(); 151 | let field_type = self.ty; 152 | 153 | let (dep_types, dep_names): (Vec<_>, Vec<_>) = struct_info 154 | .fields 155 | .iter() 156 | .take(self.ordinal) 157 | .map(|dep_field| { 158 | let dep_type = dep_field.ty; 159 | let dep_mut = dep_field.maybe_mut(); 160 | (quote!(&#dep_mut #dep_type), dep_field.name) 161 | }) 162 | .unzip(); 163 | 164 | let where_clause_storage; 165 | let where_clause_for_default = if let Some(default_where) = self.builder_attr.default_where.as_ref() { 166 | let mut predicates: Punctuated<_, _> = Default::default(); 167 | if let Some(where_clause) = where_clause { 168 | predicates.extend(where_clause.predicates.iter().cloned()); 169 | } 170 | predicates.extend(default_where.iter().cloned()); 171 | where_clause_storage = syn::WhereClause { 172 | where_token: Default::default(), 173 | predicates, 174 | }; 175 | Some(&where_clause_storage) 176 | } else { 177 | where_clause 178 | }; 179 | 180 | Ok(Some(quote! { 181 | #[allow(clippy::ref_option_ref)] 182 | #[automatically_derived] 183 | impl #impl_generics #crate_module_path::NextFieldDefault<(#(#dep_types,)* (#field_type,),)> for #struct_name #ty_generics #where_clause { 184 | type Output = #field_type; 185 | 186 | fn resolve((.., (input,),): (#(#dep_types,)* (#field_type,),)) -> Self::Output { 187 | input 188 | } 189 | } 190 | 191 | #[allow(clippy::ref_option_ref)] 192 | #[automatically_derived] 193 | impl #impl_generics #crate_module_path::NextFieldDefault<(#(#dep_types,)* (),)> for #struct_name #ty_generics #where_clause_for_default { 194 | type Output = #field_type; 195 | 196 | fn resolve((#(#dep_names,)* (),): (#(#dep_types,)* (),)) -> Self::Output { 197 | #default_expr 198 | } 199 | } 200 | })) 201 | } 202 | } 203 | 204 | #[derive(Debug, Default, Clone)] 205 | pub struct FieldBuilderAttr<'a> { 206 | pub default: Option, 207 | pub default_where: Option>, 208 | pub via_mutators: Option, 209 | pub deprecated: Option<&'a syn::Attribute>, 210 | pub doc_comments: Vec<&'a syn::Expr>, 211 | pub setter: SetterSettings, 212 | /// Functions that are able to mutate fields in the builder that are already set 213 | pub mutators: Vec, 214 | pub mutable_during_default_resolution: Option, 215 | } 216 | 217 | #[derive(Debug, Default, Clone)] 218 | pub struct SetterSettings { 219 | pub doc: Option, 220 | pub skip: Option, 221 | pub auto_into: Option, 222 | pub strip_option: Option, 223 | pub strip_bool: Option, 224 | pub transform: Option, 225 | pub prefix: Option, 226 | pub suffix: Option, 227 | } 228 | 229 | impl<'a> FieldBuilderAttr<'a> { 230 | pub fn with(mut self, name: &Ident, attrs: &'a [syn::Attribute]) -> Result { 231 | for attr in attrs { 232 | let list = match &attr.meta { 233 | syn::Meta::List(list) => { 234 | let Some(path) = path_to_single_string(&list.path) else { 235 | continue; 236 | }; 237 | 238 | if path == "deprecated" { 239 | self.deprecated = Some(attr); 240 | continue; 241 | } 242 | 243 | if path != "builder" { 244 | continue; 245 | } 246 | 247 | list 248 | } 249 | syn::Meta::NameValue(syn::MetaNameValue { path, value, .. }) => { 250 | match path_to_single_string(path).as_deref() { 251 | Some("deprecated") => self.deprecated = Some(attr), 252 | Some("doc") => self.doc_comments.push(value), 253 | _ => continue, 254 | } 255 | 256 | continue; 257 | } 258 | syn::Meta::Path(path) => { 259 | match path_to_single_string(path).as_deref() { 260 | Some("deprecated") => self.deprecated = Some(attr), 261 | _ => continue, 262 | } 263 | 264 | continue; 265 | } 266 | }; 267 | 268 | self.apply_subsections(list)?; 269 | } 270 | 271 | for mutator in self.mutators.iter_mut() { 272 | mutator.required_fields.insert(name.clone()); 273 | } 274 | 275 | self.inter_fields_conflicts()?; 276 | 277 | Ok(self) 278 | } 279 | 280 | fn inter_fields_conflicts(&self) -> Result<(), Error> { 281 | if let (Some(skip), None) = (&self.setter.skip, &self.default) { 282 | return Err(Error::new( 283 | *skip, 284 | "#[builder(skip)] must be accompanied by default or default_code", 285 | )); 286 | } 287 | 288 | let conflicting_transformations = [ 289 | ("transform", self.setter.transform.as_ref().map(|t| &t.span)), 290 | ("strip_option", self.setter.strip_option.as_ref().map(|s| &s.span)), 291 | ("strip_bool", self.setter.strip_bool.as_ref().map(|s| &s.span)), 292 | ]; 293 | let mut conflicting_transformations = conflicting_transformations 294 | .iter() 295 | .filter_map(|(caption, span)| span.map(|span| (caption, span))) 296 | .collect::>(); 297 | 298 | if 1 < conflicting_transformations.len() { 299 | let (first_caption, first_span) = conflicting_transformations.pop().unwrap(); 300 | let conflicting_captions = conflicting_transformations 301 | .iter() 302 | .map(|(caption, _)| **caption) 303 | .collect::>(); 304 | let mut error = Error::new( 305 | *first_span, 306 | format_args!("{} conflicts with {}", first_caption, conflicting_captions.join(", ")), 307 | ); 308 | for (caption, span) in conflicting_transformations { 309 | error.combine(Error::new(*span, format_args!("{} set here", caption))); 310 | } 311 | return Err(error); 312 | } 313 | Ok(()) 314 | } 315 | } 316 | 317 | impl ApplyMeta for FieldBuilderAttr<'_> { 318 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { 319 | match expr.name().to_string().as_str() { 320 | "default" => match expr { 321 | AttrArg::Flag(ident) => { 322 | self.default = 323 | Some(syn::parse2(quote_spanned!(ident.span() => ::core::default::Default::default())).unwrap()); 324 | Ok(()) 325 | } 326 | AttrArg::KeyValue(key_value) => { 327 | self.default = Some(key_value.parse_value()?); 328 | Ok(()) 329 | } 330 | AttrArg::Not { .. } => { 331 | self.default = None; 332 | Ok(()) 333 | } 334 | AttrArg::Sub(_) => Err(expr.incorrect_type()), 335 | AttrArg::Fn(_) => Err(expr.incorrect_type()), 336 | }, 337 | "default_where" => { 338 | let sub_attr = expr.sub_attr()?; 339 | let span = sub_attr.args.span(); 340 | self.default_where = Some( 341 | Punctuated::parse_terminated 342 | .parse2(sub_attr.args) 343 | .map_err(|e| Error::new(span, format!("{e}")))?, 344 | ); 345 | Ok(()) 346 | } 347 | "default_code" => { 348 | use std::str::FromStr; 349 | 350 | let code = expr.key_value()?.parse_value::()?; 351 | let tokenized_code = TokenStream::from_str(&code.value())?; 352 | self.default = Some(syn::parse2(tokenized_code).map_err(|e| Error::new_spanned(code, format!("{}", e)))?); 353 | 354 | Ok(()) 355 | } 356 | "setter" => self.setter.apply_sub_attr(expr.sub_attr()?), 357 | "mutable_during_default_resolution" => expr.apply_flag_to_field( 358 | &mut self.mutable_during_default_resolution, 359 | "made mutable during default resolution", 360 | ), 361 | "via_mutators" => { 362 | match expr { 363 | AttrArg::Flag(ident) => { 364 | self.via_mutators = Some(ViaMutators { 365 | span: ident.span(), 366 | init: syn::parse2(quote_spanned!(ident.span() => ::core::default::Default::default())).unwrap(), 367 | }); 368 | } 369 | AttrArg::KeyValue(key_value) => { 370 | self.via_mutators = Some(ViaMutators { 371 | span: key_value.span(), 372 | init: key_value.parse_value()?, 373 | }); 374 | } 375 | AttrArg::Not { .. } => { 376 | self.via_mutators = None; 377 | } 378 | AttrArg::Sub(sub) => { 379 | if let Some(via_mutators) = self.via_mutators.as_mut() { 380 | if let Some(joined_span) = via_mutators.span.join(sub.span()) { 381 | via_mutators.span = joined_span; 382 | } else { 383 | // Shouldn't happen, but whatever 384 | via_mutators.span = sub.span(); 385 | }; 386 | via_mutators.apply_sub_attr(sub)?; 387 | } else { 388 | let mut via_mutators = ViaMutators::empty_spanned(sub.span()); 389 | via_mutators.apply_sub_attr(sub)?; 390 | self.via_mutators = Some(via_mutators); 391 | } 392 | } 393 | AttrArg::Fn(_) => return Err(expr.incorrect_type()), 394 | } 395 | Ok(()) 396 | } 397 | "mutators" => { 398 | self.mutators.extend(expr.sub_attr()?.undelimited()?); 399 | Ok(()) 400 | } 401 | _ => Err(Error::new_spanned( 402 | expr.name(), 403 | format!("Unknown parameter {:?}", expr.name().to_string()), 404 | )), 405 | } 406 | } 407 | } 408 | 409 | impl ApplyMeta for SetterSettings { 410 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { 411 | match expr.name().to_string().as_str() { 412 | "doc" => { 413 | self.doc = expr.key_value_or_not()?.map(|kv| kv.parse_value()).transpose()?; 414 | Ok(()) 415 | } 416 | "transform" => { 417 | self.transform = match expr { 418 | AttrArg::Fn(func) => Some(parse_transform_fn(func.span(), func)?), 419 | AttrArg::KeyValue(key_value) => { 420 | Some(parse_transform_closure(key_value.name.span(), key_value.parse_value()?)?) 421 | } 422 | AttrArg::Not { .. } => None, 423 | _ => return Err(expr.incorrect_type()), 424 | }; 425 | Ok(()) 426 | } 427 | "prefix" => { 428 | self.prefix = if let Some(key_value) = expr.key_value_or_not()? { 429 | Some(expr_to_lit_string(&key_value.parse_value()?)?) 430 | } else { 431 | None 432 | }; 433 | Ok(()) 434 | } 435 | "suffix" => { 436 | self.suffix = if let Some(key_value) = expr.key_value_or_not()? { 437 | Some(expr_to_lit_string(&key_value.parse_value()?)?) 438 | } else { 439 | None 440 | }; 441 | Ok(()) 442 | } 443 | "skip" => expr.apply_flag_to_field(&mut self.skip, "skipped"), 444 | "into" => expr.apply_flag_to_field(&mut self.auto_into, "calling into() on the argument"), 445 | "strip_option" => { 446 | expr.apply_potentialy_empty_sub_to_field(&mut self.strip_option, "putting the argument in Some(...)", Strip::new) 447 | } 448 | "strip_bool" => expr.apply_potentialy_empty_sub_to_field( 449 | &mut self.strip_bool, 450 | "zero arguments setter, sets the field to true", 451 | Strip::new, 452 | ), 453 | _ => Err(Error::new_spanned( 454 | expr.name(), 455 | format!("Unknown parameter {:?}", expr.name().to_string()), 456 | )), 457 | } 458 | } 459 | } 460 | 461 | #[derive(Debug, Clone)] 462 | pub struct Strip { 463 | pub fallback: Option, 464 | pub fallback_prefix: Option, 465 | pub fallback_suffix: Option, 466 | pub ignore_invalid: bool, 467 | span: Span, 468 | } 469 | 470 | impl Strip { 471 | fn new(span: Span) -> Self { 472 | Self { 473 | fallback: None, 474 | fallback_prefix: None, 475 | fallback_suffix: None, 476 | ignore_invalid: false, 477 | span, 478 | } 479 | } 480 | } 481 | 482 | impl ApplyMeta for Strip { 483 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { 484 | match expr.name().to_string().as_str() { 485 | "fallback" => { 486 | if self.fallback.is_some() { 487 | return Err(Error::new_spanned( 488 | expr.name(), 489 | format!("Duplicate fallback parameter {:?}", expr.name().to_string()), 490 | )); 491 | } 492 | 493 | let ident: syn::Ident = expr.key_value().map(|kv| kv.parse_value())??; 494 | self.fallback = Some(ident); 495 | Ok(()) 496 | } 497 | "fallback_prefix" => { 498 | if self.fallback_prefix.is_some() { 499 | return Err(Error::new_spanned( 500 | expr.name(), 501 | format!("Duplicate fallback_prefix parameter {:?}", expr.name().to_string()), 502 | )); 503 | } 504 | 505 | self.fallback_prefix = Some(expr.key_value()?.parse_value::()?.value()); 506 | Ok(()) 507 | } 508 | "fallback_suffix" => { 509 | if self.fallback_suffix.is_some() { 510 | return Err(Error::new_spanned( 511 | expr.name(), 512 | format!("Duplicate fallback_suffix parameter {:?}", expr.name().to_string()), 513 | )); 514 | } 515 | 516 | self.fallback_suffix = Some(expr.key_value()?.parse_value::()?.value()); 517 | Ok(()) 518 | } 519 | "ignore_invalid" => { 520 | if self.ignore_invalid { 521 | return Err(Error::new_spanned( 522 | expr.name(), 523 | format!("Duplicate ignore_invalid parameter {:?}", expr.name().to_string()), 524 | )); 525 | } 526 | 527 | expr.flag()?; 528 | self.ignore_invalid = true; 529 | Ok(()) 530 | } 531 | _ => Err(Error::new_spanned( 532 | expr.name(), 533 | format!("Unknown parameter {:?}", expr.name().to_string()), 534 | )), 535 | } 536 | } 537 | } 538 | 539 | #[derive(Debug, Clone)] 540 | pub struct Transform { 541 | pub params: Vec<(syn::Pat, syn::Type)>, 542 | pub body: syn::Expr, 543 | pub generics: Option, 544 | pub return_type: syn::ReturnType, 545 | span: Span, 546 | } 547 | 548 | fn parse_transform_fn(span: Span, func: syn::ItemFn) -> Result { 549 | if let Some(kw) = &func.sig.asyncness { 550 | return Err(Error::new(kw.span, "Transform function cannot be async")); 551 | } 552 | 553 | let params = func 554 | .sig 555 | .inputs 556 | .into_iter() 557 | .map(|input| match input { 558 | syn::FnArg::Typed(pat_type) => Ok((*pat_type.pat, *pat_type.ty)), 559 | syn::FnArg::Receiver(_) => Err(Error::new_spanned(input, "Transform function cannot have self parameter")), 560 | }) 561 | .collect::, _>>()?; 562 | 563 | let body = Expr::Block(ExprBlock { 564 | attrs: Vec::new(), 565 | label: None, 566 | block: *func.block, 567 | }); 568 | 569 | Ok(Transform { 570 | params, 571 | body, 572 | span, 573 | generics: Some(func.sig.generics), 574 | return_type: func.sig.output, 575 | }) 576 | } 577 | 578 | fn parse_transform_closure(span: Span, expr: syn::Expr) -> Result { 579 | let closure = match expr { 580 | syn::Expr::Closure(closure) => closure, 581 | _ => return Err(Error::new_spanned(expr, "Expected closure")), 582 | }; 583 | 584 | if let Some(kw) = &closure.asyncness { 585 | return Err(Error::new(kw.span, "Transform closure cannot be async")); 586 | } 587 | if let Some(kw) = &closure.capture { 588 | return Err(Error::new(kw.span, "Transform closure cannot be move")); 589 | } 590 | 591 | let params = closure 592 | .inputs 593 | .into_iter() 594 | .map(|input| match input { 595 | syn::Pat::Type(pat_type) => Ok((*pat_type.pat, *pat_type.ty)), 596 | _ => Err(Error::new_spanned(input, "Transform closure must explicitly declare types")), 597 | }) 598 | .collect::, _>>()?; 599 | 600 | Ok(Transform { 601 | params, 602 | body: *closure.body, 603 | span, 604 | generics: None, 605 | return_type: closure.output, 606 | }) 607 | } 608 | 609 | #[derive(Debug, Clone)] 610 | pub struct ViaMutators { 611 | pub span: Span, 612 | pub init: syn::Expr, 613 | } 614 | 615 | impl ViaMutators { 616 | fn empty_spanned(span: Span) -> Self { 617 | Self { 618 | span, 619 | init: syn::parse2(quote_spanned!(span => ::core::default::Default::default())).unwrap(), 620 | } 621 | } 622 | } 623 | 624 | impl ApplyMeta for ViaMutators { 625 | fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { 626 | match expr.name().to_string().as_str() { 627 | "init" => { 628 | self.init = expr.key_value()?.parse_value()?; 629 | Ok(()) 630 | } 631 | _ => Err(Error::new_spanned( 632 | expr.name(), 633 | format!("Unknown parameter {:?}", expr.name().to_string()), 634 | )), 635 | } 636 | } 637 | } 638 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::pedantic)] 2 | #![allow(clippy::disallowed_names, clippy::type_complexity)] 3 | 4 | use typed_builder::TypedBuilder; 5 | 6 | #[test] 7 | fn test_simple() { 8 | #[derive(PartialEq, TypedBuilder)] 9 | struct Foo { 10 | x: i32, 11 | y: i32, 12 | } 13 | 14 | assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); 15 | assert!(Foo::builder().y(1).x(2).build() == Foo { x: 2, y: 1 }); 16 | } 17 | 18 | #[test] 19 | fn test_lifetime() { 20 | #[derive(PartialEq, TypedBuilder)] 21 | struct Foo<'a, 'b> { 22 | x: &'a i32, 23 | y: &'b i32, 24 | } 25 | 26 | assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); 27 | } 28 | 29 | #[test] 30 | fn test_lifetime_bounded() { 31 | #[derive(PartialEq, TypedBuilder)] 32 | struct Foo<'a, 'b: 'a> { 33 | x: &'a i32, 34 | y: &'b i32, 35 | } 36 | 37 | assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); 38 | } 39 | 40 | #[test] 41 | fn test_mutable_borrows() { 42 | #[derive(PartialEq, TypedBuilder)] 43 | struct Foo<'a, 'b> { 44 | x: &'a mut i32, 45 | y: &'b mut i32, 46 | } 47 | 48 | let mut a = 1; 49 | let mut b = 2; 50 | { 51 | let foo = Foo::builder().x(&mut a).y(&mut b).build(); 52 | *foo.x *= 10; 53 | *foo.y *= 100; 54 | } 55 | assert!(a == 10); 56 | assert!(b == 200); 57 | } 58 | 59 | #[test] 60 | fn test_generics() { 61 | #[derive(PartialEq, TypedBuilder)] 62 | struct Foo { 63 | x: S, 64 | y: T, 65 | } 66 | 67 | assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); 68 | } 69 | 70 | #[test] 71 | fn test_2d_const_generics() { 72 | #[derive(PartialEq, TypedBuilder)] 73 | struct Foo { 74 | data: [[u32; NUM_ROWS]; NUM_COLS], 75 | } 76 | assert!(Foo::builder().data([[]]).build() == Foo { data: [[]] }); 77 | } 78 | 79 | #[test] 80 | fn test_multiple_const_generics() { 81 | #[derive(PartialEq, TypedBuilder)] 82 | struct Foo { 83 | data: [u32; A], 84 | data2: [u32; B], 85 | data3: [u32; C], 86 | } 87 | assert!( 88 | Foo::builder().data([1]).data2([2, 3]).data3([]).build() 89 | == Foo { 90 | data: [1], 91 | data2: [2, 3], 92 | data3: [] 93 | } 94 | ); 95 | } 96 | 97 | #[test] 98 | fn test_const_generics_with_other_generics() { 99 | #[derive(PartialEq, TypedBuilder)] 100 | struct Foo { 101 | data: [B; A], 102 | data2: [B; 3], 103 | } 104 | assert!( 105 | Foo::builder().data([3]).data2([0, 1, 2]).build() 106 | == Foo { 107 | data: [3], 108 | data2: [0, 1, 2] 109 | } 110 | ); 111 | } 112 | 113 | #[test] 114 | fn test_into() { 115 | #[derive(PartialEq, TypedBuilder)] 116 | struct Foo { 117 | #[builder(setter(into))] 118 | x: i32, 119 | } 120 | 121 | assert!(Foo::builder().x(1_u8).build() == Foo { x: 1 }); 122 | } 123 | 124 | #[test] 125 | fn test_strip_option_with_into() { 126 | #[derive(PartialEq, TypedBuilder)] 127 | struct Foo { 128 | #[builder(setter(strip_option, into))] 129 | x: Option, 130 | } 131 | 132 | assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); 133 | } 134 | 135 | #[test] 136 | fn test_into_with_strip_option() { 137 | #[derive(PartialEq, TypedBuilder)] 138 | struct Foo { 139 | #[builder(setter(into, strip_option))] 140 | x: Option, 141 | } 142 | 143 | assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); 144 | } 145 | 146 | #[test] 147 | fn test_strip_option_with_fallback() { 148 | #[derive(PartialEq, TypedBuilder)] 149 | struct Foo { 150 | #[builder(setter(strip_option(fallback = x_opt)))] 151 | x: Option, 152 | } 153 | 154 | assert!(Foo::builder().x(1).build() == Foo { x: Some(1) }); 155 | assert!(Foo::builder().x_opt(Some(1)).build() == Foo { x: Some(1) }); 156 | } 157 | 158 | #[test] 159 | fn test_into_with_strip_option_with_fallback() { 160 | #[derive(PartialEq, TypedBuilder)] 161 | struct Foo { 162 | #[builder(setter(into, strip_option(fallback = x_opt)))] 163 | x: Option, 164 | } 165 | 166 | assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); 167 | assert!(Foo::builder().x_opt(Some(1)).build() == Foo { x: Some(1) }); 168 | } 169 | 170 | #[test] 171 | fn test_strip_bool() { 172 | #[derive(PartialEq, TypedBuilder)] 173 | struct Foo { 174 | #[builder(setter(into, strip_bool))] 175 | x: bool, 176 | } 177 | 178 | assert!(Foo::builder().x().build() == Foo { x: true }); 179 | assert!(Foo::builder().build() == Foo { x: false }); 180 | } 181 | 182 | #[test] 183 | fn test_strip_bool_with_fallback() { 184 | #[derive(PartialEq, TypedBuilder)] 185 | struct Foo { 186 | #[builder(setter(into, strip_bool(fallback = x_bool)))] 187 | x: bool, 188 | } 189 | 190 | assert!(Foo::builder().x().build() == Foo { x: true }); 191 | assert!(Foo::builder().x_bool(false).build() == Foo { x: false }); 192 | assert!(Foo::builder().build() == Foo { x: false }); 193 | } 194 | 195 | #[test] 196 | fn test_default() { 197 | #[derive(PartialEq, TypedBuilder)] 198 | struct Foo { 199 | #[builder(default, setter(strip_option))] 200 | x: Option, 201 | #[builder(default = 10)] 202 | y: i32, 203 | #[builder(default = vec![20, 30, 40])] 204 | z: Vec, 205 | } 206 | 207 | assert!( 208 | Foo::builder().build() 209 | == Foo { 210 | x: None, 211 | y: 10, 212 | z: vec![20, 30, 40] 213 | } 214 | ); 215 | assert!( 216 | Foo::builder().x(1).build() 217 | == Foo { 218 | x: Some(1), 219 | y: 10, 220 | z: vec![20, 30, 40] 221 | } 222 | ); 223 | assert!( 224 | Foo::builder().y(2).build() 225 | == Foo { 226 | x: None, 227 | y: 2, 228 | z: vec![20, 30, 40] 229 | } 230 | ); 231 | assert!( 232 | Foo::builder().x(1).y(2).build() 233 | == Foo { 234 | x: Some(1), 235 | y: 2, 236 | z: vec![20, 30, 40] 237 | } 238 | ); 239 | assert!( 240 | Foo::builder().z(vec![1, 2, 3]).build() 241 | == Foo { 242 | x: None, 243 | y: 10, 244 | z: vec![1, 2, 3] 245 | } 246 | ); 247 | } 248 | 249 | #[test] 250 | fn test_field_dependencies_in_build() { 251 | #[derive(PartialEq, TypedBuilder)] 252 | struct Foo { 253 | #[builder(default, setter(strip_option))] 254 | x: Option, 255 | #[builder(default = 10)] 256 | y: i32, 257 | #[builder(default = vec![*y, 30, 40])] 258 | z: Vec, 259 | } 260 | 261 | assert!( 262 | Foo::builder().build() 263 | == Foo { 264 | x: None, 265 | y: 10, 266 | z: vec![10, 30, 40] 267 | } 268 | ); 269 | assert!( 270 | Foo::builder().x(1).build() 271 | == Foo { 272 | x: Some(1), 273 | y: 10, 274 | z: vec![10, 30, 40] 275 | } 276 | ); 277 | assert!( 278 | Foo::builder().y(2).build() 279 | == Foo { 280 | x: None, 281 | y: 2, 282 | z: vec![2, 30, 40] 283 | } 284 | ); 285 | assert!( 286 | Foo::builder().x(1).y(2).build() 287 | == Foo { 288 | x: Some(1), 289 | y: 2, 290 | z: vec![2, 30, 40] 291 | } 292 | ); 293 | assert!( 294 | Foo::builder().z(vec![1, 2, 3]).build() 295 | == Foo { 296 | x: None, 297 | y: 10, 298 | z: vec![1, 2, 3] 299 | } 300 | ); 301 | } 302 | 303 | // compile-fail tests for skip are in src/lib.rs out of necessity. These are just the bland 304 | // successful cases. 305 | #[test] 306 | fn test_skip() { 307 | #[derive(PartialEq, TypedBuilder)] 308 | struct Foo { 309 | #[builder(default, setter(skip))] 310 | x: i32, 311 | #[builder(setter(into))] 312 | y: i32, 313 | #[builder(default = y + 1, setter(skip))] 314 | z: i32, 315 | } 316 | 317 | assert!(Foo::builder().y(1_u8).build() == Foo { x: 0, y: 1, z: 2 }); 318 | } 319 | 320 | #[test] 321 | fn test_docs() { 322 | #[derive(TypedBuilder)] 323 | #[builder( 324 | builder_method(doc = "Point::builder() method docs"), 325 | builder_type(doc = "PointBuilder type docs"), 326 | build_method(doc = "PointBuilder.build() method docs") 327 | )] 328 | struct Point { 329 | #[allow(dead_code)] 330 | x: i32, 331 | #[builder( 332 | default = *x, 333 | setter( 334 | doc = "Set `z`. If you don't specify a value it'll default to the value specified for `x`.", 335 | ), 336 | )] 337 | #[allow(dead_code)] 338 | y: i32, 339 | } 340 | 341 | let _ = Point::builder(); 342 | } 343 | 344 | #[test] 345 | fn test_builder_name() { 346 | #[derive(TypedBuilder)] 347 | struct Foo {} 348 | 349 | let _: FooBuilder<_> = Foo::builder(); 350 | } 351 | 352 | // NOTE: `test_builder_type_stability` and `test_builder_type_stability_with_other_generics` are 353 | // meant to ensure we don't break things for people that use custom `impl`s on the builder 354 | // type before the tuple field generic param transformation traits are in. 355 | // See: 356 | // - https://github.com/idanarye/rust-typed-builder/issues/22 357 | // - https://github.com/idanarye/rust-typed-builder/issues/23 358 | #[test] 359 | fn test_builder_type_stability() { 360 | #[derive(PartialEq, TypedBuilder)] 361 | struct Foo { 362 | x: i32, 363 | y: i32, 364 | z: i32, 365 | } 366 | 367 | impl FooBuilder<((), Y, ())> { 368 | fn xz(self, x: i32, z: i32) -> FooBuilder<((i32,), Y, (i32,))> { 369 | self.x(x).z(z) 370 | } 371 | } 372 | 373 | assert!(Foo::builder().xz(1, 2).y(3).build() == Foo { x: 1, y: 3, z: 2 }); 374 | assert!(Foo::builder().xz(1, 2).y(3).build() == Foo::builder().x(1).z(2).y(3).build()); 375 | 376 | assert!(Foo::builder().y(1).xz(2, 3).build() == Foo { x: 2, y: 1, z: 3 }); 377 | assert!(Foo::builder().y(1).xz(2, 3).build() == Foo::builder().y(1).x(2).z(3).build()); 378 | } 379 | 380 | #[test] 381 | fn test_builder_type_stability_with_other_generics() { 382 | #[derive(PartialEq, TypedBuilder)] 383 | struct Foo { 384 | x: X, 385 | y: Y, 386 | } 387 | 388 | impl FooBuilder { 389 | fn x_default(self) -> FooBuilder { 390 | self.x(X::default()) 391 | } 392 | } 393 | 394 | assert!(Foo::builder().x_default().y(1.0).build() == Foo { x: 0, y: 1.0 }); 395 | assert!( 396 | Foo::builder().y("hello".to_owned()).x_default().build() 397 | == Foo { 398 | x: "", 399 | y: "hello".to_owned() 400 | } 401 | ); 402 | } 403 | 404 | #[test] 405 | #[allow(clippy::items_after_statements)] 406 | fn test_builder_type_with_default_on_generic_type() { 407 | #[derive(PartialEq, TypedBuilder)] 408 | struct Types { 409 | x: X, 410 | y: Y, 411 | } 412 | assert!(Types::builder().x(()).y(()).build() == Types { x: (), y: () }); 413 | 414 | #[derive(PartialEq, TypedBuilder)] 415 | struct TypeAndLifetime<'a, X, Y: Default, Z = usize> { 416 | x: X, 417 | y: Y, 418 | z: &'a Z, 419 | } 420 | let a = 0; 421 | assert!(TypeAndLifetime::builder().x(()).y(0).z(&a).build() == TypeAndLifetime { x: (), y: 0, z: &0 }); 422 | 423 | #[derive(PartialEq, TypedBuilder)] 424 | struct Foo<'a, X, Y: Default, Z: Default = usize, M = ()> { 425 | x: X, 426 | y: &'a Y, 427 | z: Z, 428 | m: M, 429 | } 430 | 431 | impl<'a, X, Y: Default, M, X_, Y_, M_> FooBuilder<'a, X, Y, usize, M, (X_, Y_, (), M_)> { 432 | fn z_default(self) -> FooBuilder<'a, X, Y, usize, M, (X_, Y_, (usize,), M_)> { 433 | self.z(usize::default()) 434 | } 435 | } 436 | 437 | impl<'a, X, Y: Default, Z: Default, X_, Y_, Z_> FooBuilder<'a, X, Y, Z, (), (X_, Y_, Z_, ())> { 438 | fn m_default(self) -> FooBuilder<'a, X, Y, Z, (), (X_, Y_, Z_, ((),))> { 439 | self.m(()) 440 | } 441 | } 442 | 443 | // compile test if rustc can infer type for `z` and `m` 444 | Foo::<(), _, _, f64>::builder().x(()).y(&a).z_default().m(1.0).build(); 445 | Foo::<(), _, _, _>::builder().x(()).y(&a).z_default().m_default().build(); 446 | 447 | assert!( 448 | Foo::builder().x(()).y(&a).z_default().m(1.0).build() 449 | == Foo { 450 | x: (), 451 | y: &0, 452 | z: 0, 453 | m: 1.0 454 | } 455 | ); 456 | assert!( 457 | Foo::builder().x(()).y(&a).z(9).m(1.0).build() 458 | == Foo { 459 | x: (), 460 | y: &0, 461 | z: 9, 462 | m: 1.0 463 | } 464 | ); 465 | } 466 | 467 | #[test] 468 | fn test_builder_type_skip_into() { 469 | #[derive(PartialEq, TypedBuilder)] 470 | struct Foo { 471 | x: X, 472 | } 473 | 474 | // compile test if rustc can infer type for `x` 475 | Foo::builder().x(()).build(); 476 | 477 | assert!(Foo::builder().x(()).build() == Foo { x: () }); 478 | } 479 | 480 | #[test] 481 | fn test_default_code() { 482 | #[derive(PartialEq, TypedBuilder)] 483 | struct Foo { 484 | #[builder(default_code = "\"text1\".to_owned()")] 485 | x: String, 486 | 487 | #[builder(default_code = r#""text2".to_owned()"#)] 488 | y: String, 489 | } 490 | 491 | assert!( 492 | Foo::builder().build() 493 | == Foo { 494 | x: "text1".to_owned(), 495 | y: "text2".to_owned() 496 | } 497 | ); 498 | } 499 | 500 | #[test] 501 | fn test_field_defaults_default_value() { 502 | #[derive(PartialEq, TypedBuilder)] 503 | #[builder(field_defaults(default = 12))] 504 | struct Foo { 505 | x: i32, 506 | #[builder(!default)] 507 | y: String, 508 | #[builder(default = 13)] 509 | z: i32, 510 | } 511 | 512 | assert!( 513 | Foo::builder().y("bla".to_owned()).build() 514 | == Foo { 515 | x: 12, 516 | y: "bla".to_owned(), 517 | z: 13 518 | } 519 | ); 520 | } 521 | 522 | #[test] 523 | fn test_field_defaults_setter_options() { 524 | #[derive(PartialEq, TypedBuilder)] 525 | #[builder(field_defaults(setter(strip_option)))] 526 | struct Foo { 527 | x: Option, 528 | #[builder(setter(!strip_option))] 529 | y: i32, 530 | } 531 | 532 | assert!(Foo::builder().x(1).y(2).build() == Foo { x: Some(1), y: 2 }); 533 | } 534 | 535 | #[test] 536 | fn test_field_defaults_strip_option_ignore_invalid() { 537 | #[derive(TypedBuilder)] 538 | #[builder(field_defaults(setter(strip_option(ignore_invalid))))] 539 | struct Foo { 540 | x: Option, 541 | y: i32, 542 | #[builder(default = Some(13))] 543 | z: Option, 544 | } 545 | 546 | let foo = Foo::builder().x(42).y(10).build(); 547 | 548 | assert_eq!(foo.x, Some(42)); 549 | assert_eq!(foo.y, 10); 550 | assert_eq!(foo.z, Some(13)); 551 | } 552 | 553 | #[test] 554 | fn test_field_defaults_strip_option_fallback_suffix() { 555 | #[derive(TypedBuilder)] 556 | #[builder(field_defaults(setter(strip_option(fallback_suffix = "_opt"))))] 557 | struct Foo { 558 | x: Option, 559 | #[builder(default = Some(13))] 560 | z: Option, 561 | } 562 | 563 | let foo1 = Foo::builder().x(42).build(); 564 | 565 | let foo2 = Foo::builder().x_opt(None).build(); 566 | 567 | assert_eq!(foo1.x, Some(42)); 568 | assert_eq!(foo1.z, Some(13)); 569 | assert_eq!(foo2.x, None); 570 | assert_eq!(foo2.z, Some(13)); 571 | } 572 | 573 | #[test] 574 | fn test_field_defaults_strip_option_fallback_prefix() { 575 | #[derive(TypedBuilder)] 576 | #[builder(field_defaults(setter(strip_option(fallback_prefix = "opt_"))))] 577 | struct Foo { 578 | x: Option, 579 | #[builder(default = Some(13))] 580 | z: Option, 581 | } 582 | 583 | let foo1 = Foo::builder().x(42).build(); 584 | 585 | let foo2 = Foo::builder().opt_x(None).build(); 586 | 587 | assert_eq!(foo1.x, Some(42)); 588 | assert_eq!(foo1.z, Some(13)); 589 | assert_eq!(foo2.x, None); 590 | assert_eq!(foo2.z, Some(13)); 591 | } 592 | 593 | #[test] 594 | fn test_field_defaults_strip_option_fallback_prefix_and_suffix() { 595 | #[derive(TypedBuilder)] 596 | #[builder(field_defaults(setter(strip_option(fallback_prefix = "opt_", fallback_suffix = "_val"))))] 597 | struct Foo { 598 | x: Option, 599 | #[builder(default = Some(13))] 600 | z: Option, 601 | } 602 | 603 | let foo1 = Foo::builder().x(42).build(); 604 | 605 | let foo2 = Foo::builder().opt_x_val(None).build(); 606 | 607 | assert_eq!(foo1.x, Some(42)); 608 | assert_eq!(foo1.z, Some(13)); 609 | assert_eq!(foo2.x, None); 610 | assert_eq!(foo2.z, Some(13)); 611 | } 612 | 613 | #[test] 614 | fn test_clone_builder() { 615 | #[derive(PartialEq, Default)] 616 | struct Uncloneable; 617 | 618 | #[derive(PartialEq, TypedBuilder)] 619 | struct Foo { 620 | x: i32, 621 | y: i32, 622 | #[builder(default)] 623 | z: Uncloneable, 624 | } 625 | 626 | let semi_built = Foo::builder().x(1); 627 | 628 | assert!( 629 | semi_built.clone().y(2).build() 630 | == Foo { 631 | x: 1, 632 | y: 2, 633 | z: Uncloneable 634 | } 635 | ); 636 | assert!( 637 | semi_built.y(3).build() 638 | == Foo { 639 | x: 1, 640 | y: 3, 641 | z: Uncloneable 642 | } 643 | ); 644 | } 645 | 646 | #[test] 647 | #[allow(clippy::items_after_statements)] 648 | fn test_clone_builder_with_generics() { 649 | #[allow(dead_code)] 650 | #[derive(PartialEq, Default)] 651 | struct Uncloneable; 652 | 653 | #[derive(PartialEq, TypedBuilder)] 654 | struct Foo { 655 | x: T, 656 | y: i32, 657 | } 658 | 659 | let semi_built1 = Foo::builder().x(1); 660 | 661 | assert!(semi_built1.clone().y(2).build() == Foo { x: 1, y: 2 }); 662 | assert!(semi_built1.y(3).build() == Foo { x: 1, y: 3 }); 663 | 664 | let semi_built2 = Foo::builder().x("four"); 665 | 666 | assert!(semi_built2.clone().y(5).build() == Foo { x: "four", y: 5 }); 667 | assert!(semi_built2.y(6).build() == Foo { x: "four", y: 6 }); 668 | 669 | // Just to make sure it can build with generic bounds 670 | #[allow(dead_code)] 671 | #[derive(TypedBuilder)] 672 | struct Bar 673 | where 674 | T: std::fmt::Display, 675 | { 676 | x: T, 677 | } 678 | } 679 | 680 | #[test] 681 | fn test_builder_on_struct_with_keywords() { 682 | #[allow(non_camel_case_types)] 683 | #[derive(PartialEq, TypedBuilder)] 684 | struct r#struct { 685 | r#fn: u32, 686 | #[builder(default, setter(strip_option))] 687 | r#type: Option, 688 | #[builder(default = Some(()), setter(skip))] 689 | r#enum: Option<()>, 690 | #[builder(setter(into))] 691 | r#union: String, 692 | } 693 | 694 | assert!( 695 | r#struct::builder().r#fn(1).r#union("two").build() 696 | == r#struct { 697 | r#fn: 1, 698 | r#type: None, 699 | r#enum: Some(()), 700 | r#union: "two".to_owned(), 701 | } 702 | ); 703 | } 704 | 705 | #[test] 706 | fn test_builder_on_struct_with_keywords_prefix_suffix() { 707 | #[allow(non_camel_case_types)] 708 | #[derive(PartialEq, TypedBuilder)] 709 | #[builder(field_defaults(setter(prefix = "set_", suffix = "_value")))] 710 | struct r#struct { 711 | r#fn: u32, 712 | #[builder(default, setter(strip_option))] 713 | r#type: Option, 714 | #[builder(default = Some(()), setter(skip))] 715 | r#enum: Option<()>, 716 | #[builder(setter(into))] 717 | r#union: String, 718 | } 719 | 720 | assert!( 721 | r#struct::builder().r#set_fn_value(1).r#set_union_value("two").build() 722 | == r#struct { 723 | r#fn: 1, 724 | r#type: None, 725 | r#enum: Some(()), 726 | r#union: "two".to_owned(), 727 | } 728 | ); 729 | } 730 | 731 | #[test] 732 | fn test_unsized_generic_params() { 733 | use std::marker::PhantomData; 734 | 735 | #[derive(TypedBuilder)] 736 | struct GenericStructure 737 | where 738 | K: ?Sized, 739 | { 740 | key: PhantomData, 741 | value: PhantomData, 742 | } 743 | } 744 | 745 | #[test] 746 | fn test_field_setter_transform_closure() { 747 | #[derive(PartialEq)] 748 | struct Point { 749 | x: i32, 750 | y: i32, 751 | } 752 | 753 | #[derive(PartialEq, TypedBuilder)] 754 | struct Foo { 755 | #[builder(setter(transform = |x: i32, y: i32| Point { x, y }))] 756 | point: Point, 757 | } 758 | 759 | assert!( 760 | Foo::builder().point(1, 2).build() 761 | == Foo { 762 | point: Point { x: 1, y: 2 } 763 | } 764 | ); 765 | } 766 | 767 | #[test] 768 | fn test_field_setter_transform_fn() { 769 | struct MBaseCase; 770 | 771 | struct MClosure; 772 | 773 | // Lifetime is not needed, just added to test. 774 | trait IntoValue<'a, T, M> { 775 | fn into_value(self) -> T; 776 | } 777 | 778 | impl IntoValue<'_, T, MBaseCase> for I 779 | where 780 | I: Into, 781 | { 782 | fn into_value(self) -> T { 783 | self.into() 784 | } 785 | } 786 | 787 | impl IntoValue<'_, T, MClosure> for F 788 | where 789 | F: FnOnce() -> T, 790 | { 791 | fn into_value(self) -> T { 792 | self() 793 | } 794 | } 795 | 796 | #[derive(TypedBuilder)] 797 | struct Foo { 798 | #[builder( 799 | setter( 800 | fn transform<'a, M>(value: impl IntoValue<'a, String, M>) 801 | where 802 | M: 'a, 803 | { 804 | value.into_value() 805 | }, 806 | ) 807 | )] 808 | s: String, 809 | } 810 | 811 | // Check where clause and return type 812 | #[derive(TypedBuilder)] 813 | struct Bar { 814 | #[builder( 815 | setter( 816 | fn transform(value: A) -> String 817 | where 818 | A: std::fmt::Display, 819 | { 820 | value.to_string() 821 | }, 822 | ) 823 | )] 824 | s: String, 825 | } 826 | 827 | assert_eq!(Foo::builder().s("foo").build().s, "foo".to_owned()); 828 | assert_eq!(Foo::builder().s(|| "foo".to_owned()).build().s, "foo".to_owned()); 829 | 830 | assert_eq!(Bar::builder().s(42).build().s, "42".to_owned()); 831 | } 832 | 833 | #[test] 834 | fn test_build_method() { 835 | #[derive(PartialEq, TypedBuilder)] 836 | #[builder(build_method(vis="", name=__build))] 837 | struct Foo { 838 | x: i32, 839 | } 840 | 841 | assert!(Foo::builder().x(1).__build() == Foo { x: 1 }); 842 | } 843 | 844 | #[test] 845 | fn test_builder_method() { 846 | #[derive(PartialEq, TypedBuilder)] 847 | #[builder(builder_method(vis="", name=__builder))] 848 | struct Foo { 849 | x: i32, 850 | } 851 | 852 | assert!(Foo::__builder().x(1).build() == Foo { x: 1 }); 853 | } 854 | 855 | #[test] 856 | fn test_builder_type() { 857 | #[derive(PartialEq, TypedBuilder)] 858 | #[builder(builder_type(vis="", name=__FooBuilder))] 859 | struct Foo { 860 | x: i32, 861 | } 862 | 863 | let builder: __FooBuilder<_> = Foo::builder(); 864 | assert!(builder.x(1).build() == Foo { x: 1 }); 865 | } 866 | 867 | #[test] 868 | fn test_default_builder_type() { 869 | #[derive(Debug, PartialEq, TypedBuilder)] 870 | #[builder(builder_method(vis = ""), builder_type(name = InnerBuilder), build_method(into = Outer))] 871 | struct Inner { 872 | a: i32, 873 | b: i32, 874 | } 875 | 876 | #[derive(Debug, PartialEq)] 877 | struct Outer(Inner); 878 | 879 | impl Outer { 880 | pub fn builder() -> InnerBuilder { 881 | Inner::builder() 882 | } 883 | } 884 | 885 | impl From for Outer { 886 | fn from(value: Inner) -> Self { 887 | Self(value) 888 | } 889 | } 890 | 891 | let outer = Outer::builder().a(3).b(5).build(); 892 | assert_eq!(outer, Outer(Inner { a: 3, b: 5 })); 893 | } 894 | 895 | #[test] 896 | fn test_into_set_generic_impl_from() { 897 | #[derive(TypedBuilder)] 898 | #[builder(build_method(into))] 899 | struct Foo { 900 | value: i32, 901 | } 902 | 903 | #[derive(Debug, PartialEq)] 904 | struct Bar { 905 | value: i32, 906 | } 907 | 908 | impl From for Bar { 909 | fn from(value: Foo) -> Self { 910 | Self { value: value.value } 911 | } 912 | } 913 | 914 | let bar: Bar = Foo::builder().value(42).build(); 915 | assert_eq!(bar, Bar { value: 42 }); 916 | } 917 | 918 | #[test] 919 | fn test_into_angle_bracket_type() { 920 | #[derive(Debug, PartialEq, TypedBuilder)] 921 | #[builder(build_method(into = std::sync::Arc))] 922 | struct Foo { 923 | value: i32, 924 | } 925 | 926 | let foo: std::sync::Arc = Foo::builder().value(42).build(); 927 | assert_eq!(*foo, Foo { value: 42 }); 928 | } 929 | 930 | #[test] 931 | fn test_into_set_generic_impl_into() { 932 | #[derive(TypedBuilder)] 933 | #[builder(build_method(into))] 934 | struct Foo { 935 | value: i32, 936 | } 937 | 938 | #[derive(Debug, PartialEq)] 939 | struct Bar { 940 | value: i32, 941 | } 942 | 943 | impl From for Bar { 944 | fn from(val: Foo) -> Self { 945 | Self { value: val.value } 946 | } 947 | } 948 | 949 | let bar: Bar = Foo::builder().value(42).build(); 950 | assert_eq!(bar, Bar { value: 42 }); 951 | } 952 | 953 | #[test] 954 | fn test_prefix() { 955 | #[derive(Debug, PartialEq, TypedBuilder)] 956 | #[builder(field_defaults(setter(prefix = "with_")))] 957 | struct Foo { 958 | x: i32, 959 | y: i32, 960 | } 961 | 962 | let foo = Foo::builder().with_x(1).with_y(2).build(); 963 | assert_eq!(foo, Foo { x: 1, y: 2 }); 964 | } 965 | 966 | #[test] 967 | fn test_suffix() { 968 | #[derive(Debug, PartialEq, TypedBuilder)] 969 | #[builder(field_defaults(setter(suffix = "_value")))] 970 | struct Foo { 971 | x: i32, 972 | y: i32, 973 | } 974 | 975 | let foo = Foo::builder().x_value(1).y_value(2).build(); 976 | assert_eq!(foo, Foo { x: 1, y: 2 }); 977 | } 978 | 979 | #[test] 980 | fn test_prefix_and_suffix() { 981 | #[derive(Debug, PartialEq, TypedBuilder)] 982 | #[builder(field_defaults(setter(prefix = "with_", suffix = "_value")))] 983 | struct Foo { 984 | x: i32, 985 | y: i32, 986 | } 987 | 988 | let foo = Foo::builder().with_x_value(1).with_y_value(2).build(); 989 | assert_eq!(foo, Foo { x: 1, y: 2 }); 990 | } 991 | 992 | #[test] 993 | fn test_issue_118() { 994 | #[derive(TypedBuilder)] 995 | #[builder(build_method(into=Bar))] 996 | struct Foo { 997 | #[builder(default, setter(skip))] 998 | #[allow(dead_code)] 999 | foo: Option, 1000 | } 1001 | 1002 | struct Bar; 1003 | 1004 | impl From> for Bar { 1005 | fn from(_value: Foo) -> Self { 1006 | Self 1007 | } 1008 | } 1009 | 1010 | let _ = Foo::::builder().build(); 1011 | } 1012 | 1013 | #[test] 1014 | fn test_mutable_defaults() { 1015 | #[derive(TypedBuilder, PartialEq, Debug)] 1016 | struct Foo { 1017 | #[builder(default, mutable_during_default_resolution, setter(strip_option))] 1018 | x: Option, 1019 | #[builder(default = if let Some(x) = x.as_mut() { 1020 | *x *= 2; 1021 | *x 1022 | } else { 1023 | Default::default() 1024 | })] 1025 | y: i32, 1026 | } 1027 | 1028 | let foo = Foo::builder().x(5).build(); 1029 | 1030 | assert_eq!(foo, Foo { x: Some(10), y: 10 }); 1031 | } 1032 | 1033 | #[test] 1034 | fn test_preinitialized_fields() { 1035 | #[derive(Debug, PartialEq, TypedBuilder)] 1036 | struct Foo { 1037 | x: i32, 1038 | #[builder(via_mutators)] 1039 | y: i32, 1040 | #[builder(via_mutators = 2)] 1041 | z: i32, 1042 | #[builder(via_mutators(init = 2))] 1043 | w: i32, 1044 | } 1045 | 1046 | let foo = Foo::builder().x(1).build(); 1047 | assert_eq!(foo, Foo { x: 1, y: 0, z: 2, w: 2 }); 1048 | } 1049 | 1050 | #[test] 1051 | fn test_mutators_item() { 1052 | #[derive(Debug, PartialEq, TypedBuilder)] 1053 | #[builder(mutators( 1054 | #[mutator(requires = [x])] 1055 | fn inc_x(self) { 1056 | self.x += 1; 1057 | } 1058 | #[mutator(requires = [x])] 1059 | fn inc_x_by(self, x: i32) { 1060 | self.x += x; 1061 | } 1062 | fn inc_preset(self) { 1063 | self.y += 1; 1064 | self.z += 1; 1065 | self.w += 1; 1066 | } 1067 | #[mutator(requires = [x])] 1068 | fn inc_y_by_x(self) { 1069 | self.y += self.x; 1070 | } 1071 | ))] 1072 | struct Foo { 1073 | x: i32, 1074 | #[builder(via_mutators)] 1075 | y: i32, 1076 | #[builder(via_mutators = 2)] 1077 | z: i32, 1078 | #[builder(via_mutators(init = 2))] 1079 | w: i32, 1080 | } 1081 | 1082 | let foo = Foo::builder().x(1).inc_x().inc_preset().build(); 1083 | assert_eq!(foo, Foo { x: 2, y: 1, z: 3, w: 3 }); 1084 | let foo = Foo::builder().x(1).inc_x_by(4).inc_y_by_x().build(); 1085 | assert_eq!(foo, Foo { x: 5, y: 5, z: 2, w: 2 }); 1086 | } 1087 | 1088 | #[test] 1089 | fn test_mutators_field() { 1090 | #[derive(Debug, PartialEq, TypedBuilder)] 1091 | #[builder(mutators())] 1092 | struct Foo { 1093 | #[builder(mutators( 1094 | fn inc_x(self) { 1095 | self.x += 1; 1096 | } 1097 | #[mutator(requires = [y])] 1098 | fn inc_y_by_x(self) { 1099 | self.y += self.x; 1100 | } 1101 | ))] 1102 | x: i32, 1103 | #[builder(default)] 1104 | y: i32, 1105 | #[builder(via_mutators = 2, mutators( 1106 | fn inc_preset(self) { 1107 | self.z += 1; 1108 | self.w += 1; 1109 | } 1110 | ))] 1111 | z: i32, 1112 | #[builder(via_mutators(init = 2))] 1113 | w: i32, 1114 | } 1115 | 1116 | let foo = Foo::builder().x(1).inc_x().inc_preset().build(); 1117 | assert_eq!(foo, Foo { x: 2, y: 0, z: 3, w: 3 }); 1118 | let foo = Foo::builder().x(1).y(1).inc_y_by_x().build(); 1119 | assert_eq!(foo, Foo { x: 1, y: 2, z: 2, w: 2 }); 1120 | } 1121 | 1122 | #[test] 1123 | fn test_mutators_for_generic_fields() { 1124 | use core::ops::AddAssign; 1125 | 1126 | #[derive(Debug, PartialEq, TypedBuilder)] 1127 | struct Foo { 1128 | #[builder(via_mutators, mutators( 1129 | fn x_plus(self, plus: S) { 1130 | self.x += plus; 1131 | } 1132 | ))] 1133 | x: S, 1134 | y: T, 1135 | } 1136 | 1137 | assert_eq!(Foo::builder().x_plus(1).y(2).build(), Foo { x: 1, y: 2 }); 1138 | } 1139 | 1140 | #[test] 1141 | fn test_mutators_with_type_param() { 1142 | use core::ops::AddAssign; 1143 | 1144 | trait HasS { 1145 | type MyS: Default + AddAssign; 1146 | } 1147 | 1148 | #[derive(Debug, PartialEq, TypedBuilder)] 1149 | struct Foo { 1150 | #[builder(via_mutators, mutators( 1151 | fn x_plus>(self, s: ::MyS) { 1152 | self.x += s; 1153 | } 1154 | ))] 1155 | x: S, 1156 | } 1157 | 1158 | struct HasSImpl; 1159 | 1160 | impl HasS for HasSImpl { 1161 | type MyS = u32; 1162 | } 1163 | 1164 | assert_eq!(Foo::builder().x_plus::(1).build(), Foo { x: 1 }); 1165 | } 1166 | 1167 | #[test] 1168 | fn test_default_with_generic_bounds() { 1169 | #[derive(Debug, PartialEq, TypedBuilder)] 1170 | struct Foo { 1171 | #[builder(default, default_where(T: Default))] 1172 | x: T, 1173 | } 1174 | 1175 | #[derive(Debug, PartialEq)] 1176 | struct HasNoDefault { 1177 | y: i32, 1178 | } 1179 | 1180 | assert_eq!(Foo::builder().build(), Foo { x: 0 }); 1181 | 1182 | assert_eq!( 1183 | Foo::builder().x(HasNoDefault { y: 7 }).build(), 1184 | Foo { 1185 | x: HasNoDefault { y: 7 } 1186 | } 1187 | ); 1188 | } 1189 | 1190 | #[test] 1191 | fn test_custom_default_with_generic_bounds() { 1192 | use core::fmt::Debug; 1193 | use core::str::FromStr; 1194 | 1195 | #[derive(Debug, PartialEq, TypedBuilder)] 1196 | struct Foo { 1197 | x: &'static str, 1198 | #[builder(default = x.parse().unwrap(), default_where(T: FromStr, ::Err : Debug))] 1199 | y: T, 1200 | } 1201 | 1202 | assert_eq!(Foo::builder().x("42").build(), Foo { x: "42", y: 42 }); 1203 | } 1204 | 1205 | #[test] 1206 | fn test_builder_type_with_derive_attribute() { 1207 | #[derive(TypedBuilder)] 1208 | #[builder(builder_type(attributes(#[derive(PartialEq, Debug)])))] 1209 | #[allow(dead_code)] 1210 | struct Foo { 1211 | x: i32, 1212 | y: i32, 1213 | } 1214 | 1215 | assert_eq!(Foo::builder().x(1), Foo::builder().x(1)); 1216 | assert_ne!(Foo::builder().x(1), Foo::builder().x(2)); 1217 | } 1218 | -------------------------------------------------------------------------------- /typed-builder-macro/src/struct_info.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{ToTokens, format_ident, quote}; 3 | use syn::{GenericArgument, ItemFn, Token, parse::Error, parse_quote, punctuated::Punctuated}; 4 | 5 | use crate::{ 6 | builder_attr::{IntoSetting, TypeBuilderAttr}, 7 | field_info::FieldInfo, 8 | mutator::Mutator, 9 | util::{ 10 | empty_type, empty_type_tuple, first_visibility, modify_types_generics_hack, phantom_data_for_generics, public_visibility, 11 | strip_raw_ident_prefix, type_tuple, 12 | }, 13 | }; 14 | 15 | #[derive(Debug)] 16 | pub struct StructInfo<'a> { 17 | vis: &'a syn::Visibility, 18 | pub name: &'a syn::Ident, 19 | pub generics: &'a syn::Generics, 20 | pub fields: Vec>, 21 | 22 | pub builder_attr: TypeBuilderAttr<'a>, 23 | builder_name: syn::Ident, 24 | } 25 | 26 | impl<'a> StructInfo<'a> { 27 | fn included_fields(&self) -> impl Iterator> { 28 | self.fields.iter().filter(|f| f.builder_attr.setter.skip.is_none()) 29 | } 30 | fn setter_fields(&self) -> impl Iterator> { 31 | self.included_fields().filter(|f| f.builder_attr.via_mutators.is_none()) 32 | } 33 | 34 | fn generic_arguments(&self) -> Punctuated { 35 | self.generics 36 | .params 37 | .iter() 38 | .map(|generic_param| match generic_param { 39 | syn::GenericParam::Type(type_param) => { 40 | let ident = type_param.ident.to_token_stream(); 41 | syn::parse2(ident).unwrap() 42 | } 43 | syn::GenericParam::Lifetime(lifetime_def) => syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone()), 44 | syn::GenericParam::Const(const_param) => { 45 | let ident = const_param.ident.to_token_stream(); 46 | syn::parse2(ident).unwrap() 47 | } 48 | }) 49 | .collect() 50 | } 51 | 52 | pub fn new(ast: &'a syn::DeriveInput, fields: impl Iterator) -> syn::Result> { 53 | let builder_attr = TypeBuilderAttr::new(&ast.attrs)?; 54 | let builder_name = builder_attr 55 | .builder_type 56 | .common 57 | .get_name() 58 | .map(|name| strip_raw_ident_prefix(name.to_string())) 59 | .unwrap_or_else(|| strip_raw_ident_prefix(format!("{}Builder", ast.ident))); 60 | Ok(StructInfo { 61 | vis: &ast.vis, 62 | name: &ast.ident, 63 | generics: &ast.generics, 64 | fields: fields 65 | .enumerate() 66 | .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone())) 67 | .collect::>()?, 68 | builder_attr, 69 | builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()), 70 | }) 71 | } 72 | 73 | fn builder_creation_impl(&self) -> syn::Result { 74 | let StructInfo { 75 | vis, 76 | ref name, 77 | ref builder_name, 78 | .. 79 | } = *self; 80 | let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); 81 | let init_fields_type = type_tuple(self.included_fields().map(|f| { 82 | if f.builder_attr.via_mutators.is_some() { 83 | f.tuplized_type_ty_param() 84 | } else { 85 | empty_type() 86 | } 87 | })); 88 | let init_fields_expr = self.included_fields().map(|f| { 89 | f.builder_attr.via_mutators.as_ref().map_or_else( 90 | || quote!(()), 91 | |via_mutators| { 92 | let init = &via_mutators.init; 93 | quote!((#init,)) 94 | }, 95 | ) 96 | }); 97 | let mut all_fields_param_type: syn::TypeParam = 98 | syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(); 99 | let all_fields_param = syn::GenericParam::Type(all_fields_param_type.clone()); 100 | all_fields_param_type.default = Some(syn::Type::Tuple(init_fields_type.clone())); 101 | let b_generics = { 102 | let mut generics = self.generics.clone(); 103 | generics.params.push(syn::GenericParam::Type(all_fields_param_type)); 104 | generics 105 | }; 106 | let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| { 107 | args.push(syn::GenericArgument::Type(init_fields_type.clone().into())); 108 | }); 109 | let phantom_data = phantom_data_for_generics(self.generics); 110 | 111 | let builder_method_name = self.builder_attr.builder_method.get_name().unwrap_or_else(|| quote!(builder)); 112 | let builder_method_visibility = first_visibility(&[ 113 | self.builder_attr.builder_method.vis.as_ref(), 114 | self.builder_attr.builder_type.common.vis.as_ref(), 115 | Some(vis), 116 | ]); 117 | let builder_method_doc = self.builder_attr.builder_method.get_doc_or(|| { 118 | format!( 119 | " 120 | Create a builder for building `{name}`. 121 | On the builder, call {setters} to set the values of the fields. 122 | Finally, call `.{build_method_name}()` to create the instance of `{name}`. 123 | ", 124 | name = self.name, 125 | build_method_name = self.build_method_name(), 126 | setters = { 127 | let mut result = String::new(); 128 | let mut is_first = true; 129 | for field in self.setter_fields() { 130 | use std::fmt::Write; 131 | if is_first { 132 | is_first = false; 133 | } else { 134 | write!(&mut result, ", ").unwrap(); 135 | } 136 | write!(&mut result, "`.{}(...)`", field.name).unwrap(); 137 | if field.builder_attr.default.is_some() { 138 | write!(&mut result, "(optional)").unwrap(); 139 | } 140 | } 141 | result 142 | } 143 | ) 144 | }); 145 | 146 | let builder_type_visibility = first_visibility(&[self.builder_attr.builder_type.common.vis.as_ref(), Some(vis)]); 147 | let builder_type_doc = if self.builder_attr.doc { 148 | self.builder_attr.builder_type.common.get_doc_or(|| { 149 | format!( 150 | " 151 | Builder for [`{name}`] instances. 152 | 153 | See [`{name}::{builder_method_name}()`] for more info. 154 | ", 155 | name = name, 156 | builder_method_name = builder_method_name 157 | ) 158 | }) 159 | } else { 160 | quote!(#[doc(hidden)]) 161 | }; 162 | 163 | let (b_generics_impl, b_generics_ty, b_generics_where_extras_predicates) = b_generics.split_for_impl(); 164 | let mut b_generics_where: syn::WhereClause = syn::parse2(quote! { 165 | where TypedBuilderFields: Clone 166 | })?; 167 | if let Some(predicates) = b_generics_where_extras_predicates { 168 | b_generics_where.predicates.extend(predicates.predicates.clone()); 169 | } 170 | 171 | let builder_type_attributes = &self.builder_attr.builder_type.attributes; 172 | 173 | Ok(quote! { 174 | #[automatically_derived] 175 | impl #impl_generics #name #ty_generics #where_clause { 176 | #builder_method_doc 177 | #[allow(dead_code, clippy::default_trait_access)] 178 | #builder_method_visibility fn #builder_method_name() -> #builder_name #generics_with_empty { 179 | #builder_name { 180 | fields: (#(#init_fields_expr,)*), 181 | phantom: ::core::default::Default::default(), 182 | } 183 | } 184 | } 185 | 186 | #[must_use] 187 | #builder_type_doc 188 | #[allow(dead_code, non_camel_case_types, non_snake_case)] 189 | #(#builder_type_attributes)* 190 | #builder_type_visibility struct #builder_name #b_generics #b_generics_where_extras_predicates { 191 | fields: #all_fields_param, 192 | phantom: #phantom_data, 193 | } 194 | 195 | #[automatically_derived] 196 | impl #b_generics_impl Clone for #builder_name #b_generics_ty #b_generics_where { 197 | #[allow(clippy::default_trait_access)] 198 | fn clone(&self) -> Self { 199 | Self { 200 | fields: self.fields.clone(), 201 | phantom: ::core::default::Default::default(), 202 | } 203 | } 204 | } 205 | }) 206 | } 207 | 208 | fn field_impl(&self, field: &FieldInfo) -> syn::Result { 209 | let StructInfo { ref builder_name, .. } = *self; 210 | 211 | let destructuring = self 212 | .included_fields() 213 | .map(|f| { 214 | if f.ordinal == field.ordinal { 215 | quote!(()) 216 | } else { 217 | let name = f.name; 218 | name.to_token_stream() 219 | } 220 | }) 221 | .collect::>(); 222 | let reconstructing = self.included_fields().map(|f| f.name).collect::>(); 223 | 224 | let &FieldInfo { 225 | name: field_name, 226 | ty: field_type, 227 | .. 228 | } = field; 229 | let mut ty_generics = self.generic_arguments(); 230 | let mut target_generics_tuple = empty_type_tuple(); 231 | let mut ty_generics_tuple = empty_type_tuple(); 232 | let generics = { 233 | let mut generics = self.generics.clone(); 234 | for f in self.included_fields() { 235 | if f.ordinal == field.ordinal { 236 | ty_generics_tuple.elems.push_value(empty_type()); 237 | target_generics_tuple.elems.push_value(f.tuplized_type_ty_param()); 238 | } else { 239 | generics.params.push(f.generic_ty_param()); 240 | let generic_argument: syn::Type = f.type_ident(); 241 | ty_generics_tuple.elems.push_value(generic_argument.clone()); 242 | target_generics_tuple.elems.push_value(generic_argument); 243 | } 244 | ty_generics_tuple.elems.push_punct(Default::default()); 245 | target_generics_tuple.elems.push_punct(Default::default()); 246 | } 247 | generics 248 | }; 249 | let mut target_generics = ty_generics.clone(); 250 | target_generics.push(syn::GenericArgument::Type(target_generics_tuple.into())); 251 | ty_generics.push(syn::GenericArgument::Type(ty_generics_tuple.into())); 252 | let (impl_generics, _, where_clause) = generics.split_for_impl(); 253 | let doc = if let Some(doc) = field.builder_attr.setter.doc.as_ref() { 254 | Some(quote!(#[doc = #doc])) 255 | } else if !field.builder_attr.doc_comments.is_empty() { 256 | Some( 257 | field 258 | .builder_attr 259 | .doc_comments 260 | .iter() 261 | .map(|&line| quote!(#[doc = #line])) 262 | .collect(), 263 | ) 264 | } else { 265 | None 266 | }; 267 | 268 | let deprecated = &field.builder_attr.deprecated; 269 | 270 | let option_was_stripped; 271 | let arg_type = if field.builder_attr.setter.strip_option.is_some() && field.builder_attr.setter.transform.is_none() { 272 | if let Some(inner_type) = field.type_from_inside_option() { 273 | option_was_stripped = true; 274 | inner_type 275 | } else if field 276 | .builder_attr 277 | .setter 278 | .strip_option 279 | .as_ref() 280 | .is_some_and(|s| s.ignore_invalid) 281 | { 282 | option_was_stripped = false; 283 | field_type 284 | } else { 285 | return Err(Error::new_spanned( 286 | field_type, 287 | "can't `strip_option` - field is not `Option<...>`", 288 | )); 289 | } 290 | } else { 291 | option_was_stripped = false; 292 | field_type 293 | }; 294 | let (arg_type, arg_expr) = if field.builder_attr.setter.auto_into.is_some() { 295 | (quote!(impl ::core::convert::Into<#arg_type>), quote!(#field_name.into())) 296 | } else { 297 | (arg_type.to_token_stream(), field_name.to_token_stream()) 298 | }; 299 | 300 | let strip_bool_fallback = field 301 | .builder_attr 302 | .setter 303 | .strip_bool 304 | .as_ref() 305 | .and_then(|strip_bool| strip_bool.fallback.as_ref()) 306 | .map(|fallback| (fallback.clone(), quote!(#field_name: #field_type), quote!(#arg_expr))); 307 | 308 | let strip_option_fallback = field.builder_attr.setter.strip_option.as_ref().and_then(|strip_option| { 309 | if let Some(ref fallback) = strip_option.fallback { 310 | Some((fallback.clone(), quote!(#field_name: #field_type), quote!(#arg_expr))) 311 | } else if strip_option.fallback_prefix.is_none() && strip_option.fallback_suffix.is_none() { 312 | None 313 | } else { 314 | let method = strip_raw_ident_prefix(field_name.to_string()); 315 | let prefix = strip_option.fallback_prefix.as_deref().unwrap_or_default(); 316 | let suffix = strip_option.fallback_suffix.as_deref().unwrap_or_default(); 317 | let fallback_name = syn::Ident::new(&format!("{}{}{}", prefix, method, suffix), field_name.span()); 318 | Some((fallback_name, quote!(#field_name: #field_type), quote!(#arg_expr))) 319 | } 320 | }); 321 | 322 | let (method_generics, param_list, arg_expr, method_where_clause) = if field.builder_attr.setter.strip_bool.is_some() { 323 | (quote!(), quote!(), quote!(true), quote!()) 324 | } else if let Some(transform) = &field.builder_attr.setter.transform { 325 | let params = transform.params.iter().map(|(pat, ty)| quote!(#pat: #ty)); 326 | let body = &transform.body; 327 | let method_generics = transform.generics.as_ref().map_or(quote!(), |g| g.to_token_stream()); 328 | let method_where_clause = transform 329 | .generics 330 | .as_ref() 331 | .and_then(|g| g.where_clause.as_ref()) 332 | .map_or(quote!(), |w| w.to_token_stream()); 333 | 334 | let body = match &transform.return_type { 335 | syn::ReturnType::Default => quote!({ #body }), 336 | syn::ReturnType::Type(_, ty) => quote!({ 337 | let value: #ty = { #body }; 338 | value 339 | }), 340 | }; 341 | 342 | (method_generics, quote!(#(#params),*), body, method_where_clause) 343 | } else if option_was_stripped { 344 | (quote!(), quote!(#field_name: #arg_type), quote!(Some(#arg_expr)), quote!()) 345 | } else { 346 | (quote!(), quote!(#field_name: #arg_type), arg_expr, quote!()) 347 | }; 348 | 349 | let repeated_fields_error_type_name = syn::Ident::new( 350 | &format!( 351 | "{}_Error_Repeated_field_{}", 352 | builder_name, 353 | strip_raw_ident_prefix(field_name.to_string()) 354 | ), 355 | proc_macro2::Span::call_site(), 356 | ); 357 | let repeated_fields_error_message = format!("Repeated field {}", field_name); 358 | 359 | let method_name = field.setter_method_name(); 360 | 361 | let strip_option_fallback_method = if let Some((method_name, param_list, arg_expr)) = strip_option_fallback { 362 | Some(quote! { 363 | #deprecated 364 | #doc 365 | #[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] 366 | pub fn #method_name #method_generics (self, #param_list) -> #builder_name <#target_generics> 367 | #method_where_clause 368 | { 369 | let #field_name = (#arg_expr,); 370 | let ( #(#destructuring,)* ) = self.fields; 371 | #builder_name { 372 | fields: ( #(#reconstructing,)* ), 373 | phantom: self.phantom, 374 | } 375 | } 376 | }) 377 | } else { 378 | None 379 | }; 380 | 381 | let strip_bool_fallback_method = if let Some((method_name, param_list, arg_expr)) = strip_bool_fallback { 382 | Some(quote! { 383 | #deprecated 384 | #doc 385 | #[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] 386 | pub fn #method_name #method_generics (self, #param_list) -> #builder_name <#target_generics> 387 | #method_where_clause 388 | { 389 | let #field_name = (#arg_expr,); 390 | let ( #(#destructuring,)* ) = self.fields; 391 | #builder_name { 392 | fields: ( #(#reconstructing,)* ), 393 | phantom: self.phantom, 394 | } 395 | } 396 | }) 397 | } else { 398 | None 399 | }; 400 | 401 | Ok(quote! { 402 | #[allow(dead_code, non_camel_case_types, missing_docs)] 403 | #[automatically_derived] 404 | impl #impl_generics #builder_name <#ty_generics> #where_clause { 405 | #deprecated 406 | #doc 407 | #[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] 408 | pub fn #method_name #method_generics (self, #param_list) -> #builder_name <#target_generics> 409 | #method_where_clause 410 | { 411 | let #field_name = (#arg_expr,); 412 | let ( #(#destructuring,)* ) = self.fields; 413 | #builder_name { 414 | fields: ( #(#reconstructing,)* ), 415 | phantom: self.phantom, 416 | } 417 | } 418 | #strip_option_fallback_method 419 | #strip_bool_fallback_method 420 | } 421 | #[doc(hidden)] 422 | #[allow(dead_code, non_camel_case_types, non_snake_case)] 423 | #[allow(clippy::exhaustive_enums)] 424 | pub enum #repeated_fields_error_type_name {} 425 | #[doc(hidden)] 426 | #[allow(dead_code, non_camel_case_types, missing_docs)] 427 | #[automatically_derived] 428 | impl #impl_generics #builder_name <#target_generics> #where_clause { 429 | #[deprecated( 430 | note = #repeated_fields_error_message 431 | )] 432 | #doc 433 | pub fn #method_name #method_generics (self, _: #repeated_fields_error_type_name) -> #builder_name <#target_generics> 434 | #method_where_clause 435 | { 436 | self 437 | } 438 | } 439 | }) 440 | } 441 | 442 | fn required_field_impl(&self, field: &FieldInfo) -> TokenStream { 443 | let StructInfo { builder_name, .. } = &self; 444 | 445 | let FieldInfo { name: field_name, .. } = field; 446 | let mut builder_generics: Vec = self 447 | .generics 448 | .params 449 | .iter() 450 | .map(|generic_param| match generic_param { 451 | syn::GenericParam::Type(type_param) => { 452 | let ident = type_param.ident.to_token_stream(); 453 | syn::parse2(ident).unwrap() 454 | } 455 | syn::GenericParam::Lifetime(lifetime_def) => syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone()), 456 | syn::GenericParam::Const(const_param) => { 457 | let ident = const_param.ident.to_token_stream(); 458 | syn::parse2(ident).unwrap() 459 | } 460 | }) 461 | .collect(); 462 | let mut builder_generics_tuple = empty_type_tuple(); 463 | let generics = { 464 | let mut generics = self.generics.clone(); 465 | for f in self.included_fields() { 466 | if f.builder_attr.default.is_some() || f.builder_attr.via_mutators.is_some() { 467 | // `f` is not mandatory - it does not have its own fake `build` method, so `field` will need 468 | // to warn about missing `field` regardless of whether `f` is set. 469 | assert!( 470 | f.ordinal != field.ordinal, 471 | "`required_field_impl` called for optional field {}", 472 | field.name 473 | ); 474 | generics.params.push(f.generic_ty_param()); 475 | builder_generics_tuple.elems.push_value(f.type_ident()); 476 | } else if f.ordinal < field.ordinal { 477 | // Only add a `build` method that warns about missing `field` if `f` is set. If `f` is not set, 478 | // `f`'s `build` method will warn, since it appears earlier in the argument list. 479 | builder_generics_tuple.elems.push_value(f.tuplized_type_ty_param()); 480 | } else if f.ordinal == field.ordinal { 481 | builder_generics_tuple.elems.push_value(empty_type()); 482 | } else { 483 | // `f` appears later in the argument list after `field`, so if they are both missing we will 484 | // show a warning for `field` and not for `f` - which means this warning should appear whether 485 | // or not `f` is set. 486 | generics.params.push(f.generic_ty_param()); 487 | builder_generics_tuple.elems.push_value(f.type_ident()); 488 | } 489 | 490 | builder_generics_tuple.elems.push_punct(Default::default()); 491 | } 492 | generics 493 | }; 494 | 495 | builder_generics.push(syn::GenericArgument::Type(builder_generics_tuple.into())); 496 | let (impl_generics, _, where_clause) = generics.split_for_impl(); 497 | 498 | let early_build_error_type_name = syn::Ident::new( 499 | &format!( 500 | "{}_Error_Missing_required_field_{}", 501 | builder_name, 502 | strip_raw_ident_prefix(field_name.to_string()) 503 | ), 504 | proc_macro2::Span::call_site(), 505 | ); 506 | let early_build_error_message = format!("Missing required field {}", field_name); 507 | 508 | let build_method_name = self.build_method_name(); 509 | let build_method_visibility = self.build_method_visibility(); 510 | 511 | quote! { 512 | #[doc(hidden)] 513 | #[allow(dead_code, non_camel_case_types, non_snake_case)] 514 | #[allow(clippy::exhaustive_enums)] 515 | pub enum #early_build_error_type_name {} 516 | #[doc(hidden)] 517 | #[allow(dead_code, non_camel_case_types, missing_docs, clippy::panic)] 518 | #[automatically_derived] 519 | impl #impl_generics #builder_name < #( #builder_generics ),* > #where_clause { 520 | #[deprecated( 521 | note = #early_build_error_message 522 | )] 523 | #build_method_visibility fn #build_method_name(self, _: #early_build_error_type_name) -> ! { 524 | panic!() 525 | } 526 | } 527 | } 528 | } 529 | 530 | fn mutator_impl( 531 | &self, 532 | mutator @ Mutator { 533 | fun: mutator_fn, 534 | required_fields, 535 | }: &Mutator, 536 | ) -> syn::Result { 537 | let StructInfo { ref builder_name, .. } = *self; 538 | 539 | let mut required_fields = required_fields.clone(); 540 | 541 | let mut ty_generics = self.generic_arguments(); 542 | let mut destructuring = TokenStream::new(); 543 | let mut ty_generics_tuple = empty_type_tuple(); 544 | let mut generics = self.generics.clone(); 545 | let mut mutator_ty_fields = Punctuated::<_, Token![,]>::new(); 546 | let mut mutator_destructure_fields = Punctuated::<_, Token![,]>::new(); 547 | for f @ FieldInfo { name, ty, .. } in self.included_fields() { 548 | if f.builder_attr.via_mutators.is_some() || required_fields.remove(f.name) { 549 | ty_generics_tuple.elems.push(f.tuplized_type_ty_param()); 550 | mutator_ty_fields.push(quote!(#name: #ty)); 551 | mutator_destructure_fields.push(name); 552 | quote!((#name,),).to_tokens(&mut destructuring); 553 | } else { 554 | generics.params.push(f.generic_ty_param()); 555 | let generic_argument: syn::Type = f.type_ident(); 556 | ty_generics_tuple.elems.push(generic_argument.clone()); 557 | quote!(#name,).to_tokens(&mut destructuring); 558 | } 559 | } 560 | ty_generics.push(syn::GenericArgument::Type(ty_generics_tuple.into())); 561 | let (impl_generics, _, where_clause) = generics.split_for_impl(); 562 | 563 | let mutator_struct_name = format_ident!("TypedBuilderFieldMutator"); 564 | 565 | let ItemFn { attrs, vis, .. } = mutator_fn; 566 | let sig = mutator.outer_sig(parse_quote!(#builder_name <#ty_generics>)); 567 | let fn_name = &sig.ident; 568 | let (_fn_impl_generics, fn_ty_generics, _fn_where_clause) = &sig.generics.split_for_impl(); 569 | let fn_call_turbofish = fn_ty_generics.as_turbofish(); 570 | let mutator_args = mutator.arguments(); 571 | 572 | // Generics for the mutator - should be similar to the struct's generics 573 | let m_generics = &self.generics; 574 | let (m_impl_generics, m_ty_generics, m_where_clause) = m_generics.split_for_impl(); 575 | let m_phantom = phantom_data_for_generics(self.generics); 576 | 577 | Ok(quote! { 578 | #[allow(dead_code, non_camel_case_types, missing_docs)] 579 | #[automatically_derived] 580 | impl #impl_generics #builder_name <#ty_generics> #where_clause { 581 | #(#attrs)* 582 | #[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] 583 | #vis #sig { 584 | struct #mutator_struct_name #m_generics #m_where_clause { 585 | __phantom: #m_phantom, 586 | #mutator_ty_fields 587 | } 588 | impl #m_impl_generics #mutator_struct_name #m_ty_generics #m_where_clause { 589 | #mutator_fn 590 | } 591 | 592 | let __args = (#mutator_args); 593 | 594 | let ( #destructuring ) = self.fields; 595 | let mut __mutator: #mutator_struct_name #m_ty_generics = #mutator_struct_name { 596 | __phantom: ::core::default::Default::default(), 597 | #mutator_destructure_fields 598 | }; 599 | 600 | // This dance is required to keep mutator args and destrucutre fields from interfering. 601 | { 602 | let (#mutator_args) = __args; 603 | __mutator.#fn_name #fn_call_turbofish(#mutator_args); 604 | } 605 | 606 | let #mutator_struct_name { 607 | __phantom, 608 | #mutator_destructure_fields 609 | } = __mutator; 610 | 611 | #builder_name { 612 | fields: ( #destructuring ), 613 | phantom: self.phantom, 614 | } 615 | } 616 | } 617 | }) 618 | } 619 | 620 | fn build_method_name(&self) -> TokenStream { 621 | self.builder_attr.build_method.common.get_name().unwrap_or(quote!(build)) 622 | } 623 | 624 | fn build_method_visibility(&self) -> TokenStream { 625 | first_visibility(&[self.builder_attr.build_method.common.vis.as_ref(), Some(&public_visibility())]) 626 | } 627 | 628 | fn build_method_impl(&self) -> TokenStream { 629 | let StructInfo { 630 | ref name, 631 | ref builder_name, 632 | .. 633 | } = *self; 634 | 635 | let generics = { 636 | let mut generics = self.generics.clone(); 637 | for field in self.included_fields() { 638 | if field.builder_attr.default.is_some() { 639 | let generic_param: syn::TypeParam = field.generic_ident.clone().into(); 640 | generics.params.push(generic_param.into()); 641 | } 642 | } 643 | generics 644 | }; 645 | let (impl_generics, _, _) = generics.split_for_impl(); 646 | 647 | let (_, ty_generics, where_clause) = self.generics.split_for_impl(); 648 | 649 | let name_with_generics = quote!(#name #ty_generics); 650 | 651 | let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| { 652 | args.push(syn::GenericArgument::Type( 653 | type_tuple(self.included_fields().map(|field| { 654 | if field.builder_attr.default.is_some() { 655 | field.type_ident() 656 | } else { 657 | field.tuplized_type_ty_param() 658 | } 659 | })) 660 | .into(), 661 | )); 662 | }); 663 | 664 | let destructuring = self.included_fields().map(|f| f.name); 665 | 666 | let crate_module_path = &self.builder_attr.crate_module_path; 667 | 668 | let where_predicates_for_defaults: Vec = self.fields.iter().enumerate() 669 | .filter(|(_, field)| field.builder_attr.default.is_some() && field.builder_attr.setter.skip.is_none()) 670 | .map(|(field_index, field)| { 671 | let types = self.fields.iter().take(field_index).map(|dep_field| { 672 | let dep_type = dep_field.ty; 673 | let dep_mut = dep_field.maybe_mut(); 674 | quote!(&'__typed_builder_lifetime_for_default #dep_mut #dep_type) 675 | }).chain(core::iter::once({ 676 | let generic_argument: syn::Type = field.type_ident(); 677 | quote!(#generic_argument) 678 | })); 679 | let field_type = field.ty; 680 | parse_quote!{ 681 | #name_with_generics: for<'__typed_builder_lifetime_for_default> #crate_module_path::NextFieldDefault<(#(#types,)*), Output = #field_type> 682 | } 683 | 684 | }).collect::>(); 685 | let where_clause_storage; 686 | let where_clause = if where_predicates_for_defaults.is_empty() { 687 | where_clause 688 | } else { 689 | let mut predicates: Punctuated<_, _> = Default::default(); 690 | if let Some(where_clause) = where_clause { 691 | predicates.extend(where_clause.predicates.iter().cloned()); 692 | } 693 | predicates.extend(where_predicates_for_defaults); 694 | where_clause_storage = syn::WhereClause { 695 | where_token: Default::default(), 696 | predicates, 697 | }; 698 | Some(&where_clause_storage) 699 | }; 700 | 701 | // The default of a field can refer to earlier-defined fields, which we handle by 702 | // writing out a bunch of `let` statements first, which can each refer to earlier ones. 703 | // This means that field ordering may actually be significant, which isn't ideal. We could 704 | // relax that restriction by calculating a DAG of field default dependencies and 705 | // reordering based on that, but for now this much simpler thing is a reasonable approach. 706 | let assignments = self 707 | .fields 708 | .iter() 709 | .enumerate() 710 | .map(|(field_index, field)| { 711 | let name = &field.name; 712 | let maybe_mut = field.maybe_mut(); 713 | 714 | if let Some(ref default) = field.builder_attr.default { 715 | if field.builder_attr.setter.skip.is_some() { 716 | let make_fields_refs = self.fields.iter().take(field_index).map(|dep_field| { 717 | let dep_name = dep_field.name; 718 | let dep_mut = dep_field.maybe_mut(); 719 | quote! { 720 | #[allow(unused_variables)] 721 | let #dep_name = &#dep_mut #dep_name; 722 | } 723 | }); 724 | quote! { 725 | let #maybe_mut #name = { 726 | #(#make_fields_refs)* 727 | #default 728 | }; 729 | } 730 | } else { 731 | let (types, values): (Vec<_>, Vec<_>) = self 732 | .fields 733 | .iter() 734 | .take(field_index) 735 | .map(|dep_field| { 736 | let dep_type = dep_field.ty; 737 | let dep_name = dep_field.name; 738 | let dep_mut = dep_field.maybe_mut(); 739 | (quote!(&#dep_mut #dep_type), quote!(&#dep_mut #dep_name)) 740 | }) 741 | .chain(core::iter::once({ 742 | let generic_argument: syn::Type = field.type_ident(); 743 | (quote!(#generic_argument), quote!(#name)) 744 | })) 745 | .unzip(); 746 | 747 | quote! { 748 | let #maybe_mut #name = < 749 | #name_with_generics 750 | as 751 | #crate_module_path::NextFieldDefault<(#(#types,)*)>>::resolve((#(#values,)*)); 752 | } 753 | } 754 | } else { 755 | quote!(let #maybe_mut #name = #name.0;) 756 | } 757 | }) 758 | .collect::>(); 759 | let field_names = self.fields.iter().map(|field| field.name); 760 | 761 | let build_method_name = self.build_method_name(); 762 | let build_method_visibility = self.build_method_visibility(); 763 | let build_method_doc = if self.builder_attr.doc { 764 | self.builder_attr 765 | .build_method 766 | .common 767 | .get_doc_or(|| format!("Finalise the builder and create its [`{}`] instance", name)) 768 | } else { 769 | quote!() 770 | }; 771 | 772 | let type_constructor = { 773 | let ty_generics = ty_generics.as_turbofish(); 774 | quote!(#name #ty_generics) 775 | }; 776 | 777 | let (build_method_generic, output_type, build_method_where_clause) = match &self.builder_attr.build_method.into { 778 | IntoSetting::NoConversion => (None, quote!(#name #ty_generics), None), 779 | IntoSetting::GenericConversion => ( 780 | Some(quote!(<__R>)), 781 | quote!(__R), 782 | Some(quote!(where #name #ty_generics: Into<__R>)), 783 | ), 784 | IntoSetting::TypeConversionToSpecificType(into) => (None, into.to_token_stream(), None), 785 | }; 786 | 787 | quote!( 788 | #[allow(dead_code, non_camel_case_types, missing_docs, clippy::ref_option_ref)] 789 | #[automatically_derived] 790 | impl #impl_generics #builder_name #modified_ty_generics #where_clause { 791 | #build_method_doc 792 | #[allow(clippy::default_trait_access, clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] 793 | #build_method_visibility fn #build_method_name #build_method_generic (self) -> #output_type #build_method_where_clause { 794 | let ( #(#destructuring,)* ) = self.fields; 795 | #( #assignments )* 796 | 797 | #[allow(deprecated)] 798 | #type_constructor { 799 | #( #field_names ),* 800 | }.into() 801 | } 802 | } 803 | ) 804 | } 805 | 806 | pub fn derive(&self) -> syn::Result { 807 | let builder_creation = self.builder_creation_impl()?; 808 | 809 | let fields = self 810 | .setter_fields() 811 | .map(|f| self.field_impl(f)) 812 | .collect::>()?; 813 | 814 | let next_field_default_impls = self 815 | .fields 816 | .iter() 817 | .filter_map(|field| field.gen_next_field_default_trait_impl(self).transpose()) 818 | .collect::>()?; 819 | 820 | let required_fields = self 821 | .setter_fields() 822 | .filter(|f| f.builder_attr.default.is_none()) 823 | .map(|f| self.required_field_impl(f)); 824 | 825 | let mutators = self 826 | .fields 827 | .iter() 828 | .flat_map(|f| &f.builder_attr.mutators) 829 | .chain(&self.builder_attr.mutators) 830 | .map(|m| self.mutator_impl(m)) 831 | .collect::>()?; 832 | 833 | let build_method = self.build_method_impl(); 834 | 835 | Ok(quote! { 836 | #builder_creation 837 | #fields 838 | #next_field_default_impls 839 | #(#required_fields)* 840 | #mutators 841 | #build_method 842 | }) 843 | } 844 | } 845 | --------------------------------------------------------------------------------