typed-builder-macro-0.20.1/.cargo_vcs_info.json0000644000000001610000000000100147620ustar { "git": { "sha1": "3668822d59ca9f542c463d76420b13bb040e2e91" }, "path_in_vcs": "typed-builder-macro" }typed-builder-macro-0.20.1/Cargo.lock0000644000000021440000000000100127400ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "proc-macro2" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "typed-builder-macro" version = "0.20.1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" typed-builder-macro-0.20.1/Cargo.toml0000644000000023570000000000100127710ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "typed-builder-macro" version = "0.20.1" authors = [ "IdanArye ", "Chris Morgan ", ] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Compile-time type-checked builder derive" documentation = "https://idanarye.github.io/rust-typed-builder/" readme = "README.md" keywords = ["builder"] categories = ["rust-patterns"] license = "MIT OR Apache-2.0" repository = "https://github.com/idanarye/rust-typed-builder" [lib] name = "typed_builder_macro" path = "src/lib.rs" proc-macro = true [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.syn] version = "2" features = [ "full", "extra-traits", ] typed-builder-macro-0.20.1/Cargo.toml.orig000064400000000000000000000006651046102023000164520ustar 00000000000000[package] name = "typed-builder-macro" description.workspace = true version.workspace = true authors.workspace = true edition.workspace = true license.workspace = true repository.workspace = true documentation.workspace = true readme.workspace = true keywords.workspace = true categories.workspace = true [lib] proc-macro = true [dependencies] syn = { version = "2", features = ["full", "extra-traits"] } quote = "1" proc-macro2 = "1" typed-builder-macro-0.20.1/LICENSE-APACHE000064400000000000000000000251371046102023000155100ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. typed-builder-macro-0.20.1/LICENSE-MIT000064400000000000000000000017771046102023000152240ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. typed-builder-macro-0.20.1/README.md000064400000000000000000000075561046102023000150500ustar 00000000000000[![Build Status](https://github.com/idanarye/rust-typed-builder/workflows/CI/badge.svg)](https://github.com/idanarye/rust-typed-builder/actions) [![Latest Version](https://img.shields.io/crates/v/typed-builder.svg)](https://crates.io/crates/typed-builder) [![Rust Documentation](https://img.shields.io/badge/api-rustdoc-blue.svg)](https://idanarye.github.io/rust-typed-builder/) # Rust Typed Builder Creates a compile-time verified builder: ```rust use typed_builder::TypedBuilder; #[derive(TypedBuilder)] struct Foo { // Mandatory Field: x: i32, // #[builder(default)] without parameter - use the type's default // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` #[builder(default, setter(strip_option))] y: Option, // Or you can set the default #[builder(default=20)] z: i32, } ``` Build in any order: ```rust Foo::builder().x(1).y(2).z(3).build(); Foo::builder().z(1).x(2).y(3).build(); ``` Omit optional fields(the one marked with `#[default]`): ```rust Foo::builder().x(1).build() ``` But you can't omit non-optional arguments - or it won't compile: ```rust Foo::builder().build(); // missing x Foo::builder().x(1).y(2).y(3); // y is specified twice ``` ## Features * Custom derive for generating the builder pattern. * Ability to annotate fields with `#[builder(setter(into))]` to make their setters accept `Into` values. * Compile time verification that all fields are set before calling `.build()`. * Compile time verification that no field is set more than once. * Ability to annotate fields with `#[builder(default)]` to make them optional and specify a default value when the user does not set them. * Generates simple documentation for the `.builder()` method. * Customizable method name and visibility of the `.build()` method. ## Limitations * 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. * 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). * 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... * 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. ## Conflicts * `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. ## Alternatives - and why typed-builder is better * [derive-builder](https://crates.io/crates/derive_builder) - does all the checks in runtime, returning a `Result` you need to unwrap. * [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. ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. typed-builder-macro-0.20.1/src/builder_attr.rs000064400000000000000000000147271046102023000174040ustar 00000000000000use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::parse::Error; use crate::field_info::FieldBuilderAttr; use crate::mutator::Mutator; use crate::util::{path_to_single_string, ApplyMeta, AttrArg}; #[derive(Debug, Default, Clone)] pub struct CommonDeclarationSettings { pub vis: Option, pub name: Option, pub doc: Option, } impl ApplyMeta for CommonDeclarationSettings { fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { match expr.name().to_string().as_str() { "vis" => { let expr_str = expr.key_value()?.parse_value::()?.value(); self.vis = Some(syn::parse_str(&expr_str)?); Ok(()) } "name" => { self.name = Some(expr.key_value()?.parse_value()?); Ok(()) } "doc" => { self.doc = Some(expr.key_value()?.parse_value()?); Ok(()) } _ => Err(Error::new_spanned( expr.name(), format!("Unknown parameter {:?}", expr.name().to_string()), )), } } } impl CommonDeclarationSettings { pub fn get_name(&self) -> Option { self.name.as_ref().map(|name| name.to_token_stream()) } pub fn get_doc_or(&self, gen_doc: impl FnOnce() -> String) -> TokenStream { if let Some(ref doc) = self.doc { quote!(#[doc = #doc]) } else { let doc = gen_doc(); quote!(#[doc = #doc]) } } } /// Setting of the `into` argument. #[derive(Debug, Clone)] pub enum IntoSetting { /// Do not run any conversion on the built value. NoConversion, /// Convert the build value into the generic parameter passed to the `build` method. GenericConversion, /// Convert the build value into a specific type specified in the attribute. TypeConversionToSpecificType(syn::TypePath), } impl Default for IntoSetting { fn default() -> Self { Self::NoConversion } } #[derive(Debug, Default, Clone)] pub struct BuildMethodSettings { pub common: CommonDeclarationSettings, /// Whether to convert the built type into another while finishing the build. pub into: IntoSetting, } impl ApplyMeta for BuildMethodSettings { fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { match expr.name().to_string().as_str() { "into" => match expr { AttrArg::Flag(_) => { self.into = IntoSetting::GenericConversion; Ok(()) } AttrArg::KeyValue(key_value) => { let type_path = key_value.parse_value::()?; self.into = IntoSetting::TypeConversionToSpecificType(type_path); Ok(()) } _ => Err(expr.incorrect_type()), }, _ => self.common.apply_meta(expr), } } } #[derive(Debug)] pub struct TypeBuilderAttr<'a> { /// Whether to show docs for the `TypeBuilder` type (rather than hiding them). pub doc: bool, /// Customize builder method, ex. visibility, name pub builder_method: CommonDeclarationSettings, /// Customize builder type, ex. visibility, name pub builder_type: CommonDeclarationSettings, /// Customize build method, ex. visibility, name pub build_method: BuildMethodSettings, pub field_defaults: FieldBuilderAttr<'a>, pub crate_module_path: syn::Path, /// Functions that are able to mutate fields in the builder that are already set pub mutators: Vec, } impl Default for TypeBuilderAttr<'_> { fn default() -> Self { Self { doc: Default::default(), builder_method: Default::default(), builder_type: Default::default(), build_method: Default::default(), field_defaults: Default::default(), crate_module_path: syn::parse_quote!(::typed_builder), mutators: Default::default(), } } } impl<'a> TypeBuilderAttr<'a> { pub fn new(attrs: &[syn::Attribute]) -> Result { let mut result = Self::default(); for attr in attrs { let list = match &attr.meta { syn::Meta::List(list) => { if path_to_single_string(&list.path).as_deref() != Some("builder") { continue; } list } _ => continue, }; result.apply_subsections(list)?; } if result.builder_type.doc.is_some() || result.build_method.common.doc.is_some() { result.doc = true; } Ok(result) } } impl ApplyMeta for TypeBuilderAttr<'_> { fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { match expr.name().to_string().as_str() { "crate_module_path" => { let crate_module_path = expr.key_value()?.parse_value::()?; self.crate_module_path = crate_module_path.path; Ok(()) } "builder_method_doc" => Err(Error::new_spanned( expr.name(), "`builder_method_doc` is deprecated - use `builder_method(doc = \"...\")`", )), "builder_type_doc" => Err(Error::new_spanned( expr.name(), "`builder_typemethod_doc` is deprecated - use `builder_type(doc = \"...\")`", )), "build_method_doc" => Err(Error::new_spanned( expr.name(), "`build_method_doc` is deprecated - use `build_method(doc = \"...\")`", )), "doc" => { expr.flag()?; self.doc = true; Ok(()) } "mutators" => { self.mutators.extend(expr.sub_attr()?.undelimited()?); Ok(()) } "field_defaults" => self.field_defaults.apply_sub_attr(expr.sub_attr()?), "builder_method" => self.builder_method.apply_sub_attr(expr.sub_attr()?), "builder_type" => self.builder_type.apply_sub_attr(expr.sub_attr()?), "build_method" => self.build_method.apply_sub_attr(expr.sub_attr()?), _ => Err(Error::new_spanned( expr.name(), format!("Unknown parameter {:?}", expr.name().to_string()), )), } } } typed-builder-macro-0.20.1/src/field_info.rs000064400000000000000000000374171046102023000170230ustar 00000000000000use proc_macro2::{Ident, Span, TokenStream}; use quote::quote_spanned; use syn::{parse::Error, spanned::Spanned}; use crate::mutator::Mutator; use crate::util::{expr_to_lit_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, ApplyMeta, AttrArg}; #[derive(Debug)] pub struct FieldInfo<'a> { pub ordinal: usize, pub name: &'a syn::Ident, pub generic_ident: syn::Ident, pub ty: &'a syn::Type, pub builder_attr: FieldBuilderAttr<'a>, } impl<'a> FieldInfo<'a> { pub fn new(ordinal: usize, field: &'a syn::Field, field_defaults: FieldBuilderAttr<'a>) -> Result, Error> { if let Some(ref name) = field.ident { FieldInfo { ordinal, name, generic_ident: syn::Ident::new(&format!("__{}", strip_raw_ident_prefix(name.to_string())), Span::call_site()), ty: &field.ty, builder_attr: field_defaults.with(name, &field.attrs)?, } .post_process() } else { Err(Error::new(field.span(), "Nameless field in struct")) } } pub fn generic_ty_param(&self) -> syn::GenericParam { syn::GenericParam::Type(self.generic_ident.clone().into()) } pub fn type_ident(&self) -> syn::Type { ident_to_type(self.generic_ident.clone()) } pub fn tuplized_type_ty_param(&self) -> syn::Type { let mut types = syn::punctuated::Punctuated::default(); types.push(self.ty.clone()); types.push_punct(Default::default()); syn::TypeTuple { paren_token: Default::default(), elems: types, } .into() } pub fn type_from_inside_option(&self) -> Option<&syn::Type> { let path = if let syn::Type::Path(type_path) = self.ty { if type_path.qself.is_some() { return None; } &type_path.path } else { return None; }; let segment = path.segments.last()?; if segment.ident != "Option" { return None; } let generic_params = if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments { generic_params } else { return None; }; if let syn::GenericArgument::Type(ty) = generic_params.args.first()? { Some(ty) } else { None } } pub fn setter_method_name(&self) -> Ident { let name = strip_raw_ident_prefix(self.name.to_string()); if let (Some(prefix), Some(suffix)) = (&self.builder_attr.setter.prefix, &self.builder_attr.setter.suffix) { Ident::new(&format!("{}{}{}", prefix, name, suffix), Span::call_site()) } else if let Some(prefix) = &self.builder_attr.setter.prefix { Ident::new(&format!("{}{}", prefix, name), Span::call_site()) } else if let Some(suffix) = &self.builder_attr.setter.suffix { Ident::new(&format!("{}{}", name, suffix), Span::call_site()) } else { self.name.clone() } } fn post_process(mut self) -> Result { if let Some(ref strip_bool) = self.builder_attr.setter.strip_bool { if let Some(default_span) = self.builder_attr.default.as_ref().map(Spanned::span) { let mut error = Error::new( strip_bool.span, "cannot set both strip_bool and default - default is assumed to be false", ); error.combine(Error::new(default_span, "default set here")); return Err(error); } self.builder_attr.default = Some(syn::Expr::Lit(syn::ExprLit { attrs: Default::default(), lit: syn::Lit::Bool(syn::LitBool { value: false, span: strip_bool.span, }), })); } Ok(self) } } #[derive(Debug, Default, Clone)] pub struct FieldBuilderAttr<'a> { pub default: Option, pub via_mutators: Option, pub deprecated: Option<&'a syn::Attribute>, pub doc_comments: Vec<&'a syn::Expr>, pub setter: SetterSettings, /// Functions that are able to mutate fields in the builder that are already set pub mutators: Vec, pub mutable_during_default_resolution: Option, } #[derive(Debug, Default, Clone)] pub struct SetterSettings { pub doc: Option, pub skip: Option, pub auto_into: Option, pub strip_option: Option, pub strip_bool: Option, pub transform: Option, pub prefix: Option, pub suffix: Option, } impl<'a> FieldBuilderAttr<'a> { pub fn with(mut self, name: &Ident, attrs: &'a [syn::Attribute]) -> Result { for attr in attrs { let list = match &attr.meta { syn::Meta::List(list) => { let Some(path) = path_to_single_string(&list.path) else { continue; }; if path == "deprecated" { self.deprecated = Some(attr); continue; } if path != "builder" { continue; } list } syn::Meta::NameValue(syn::MetaNameValue { path, value, .. }) => { match path_to_single_string(path).as_deref() { Some("deprecated") => self.deprecated = Some(attr), Some("doc") => self.doc_comments.push(value), _ => continue, } continue; } syn::Meta::Path(path) => { match path_to_single_string(path).as_deref() { Some("deprecated") => self.deprecated = Some(attr), _ => continue, } continue; } }; self.apply_subsections(list)?; } for mutator in self.mutators.iter_mut() { mutator.required_fields.insert(name.clone()); } self.inter_fields_conflicts()?; Ok(self) } fn inter_fields_conflicts(&self) -> Result<(), Error> { if let (Some(skip), None) = (&self.setter.skip, &self.default) { return Err(Error::new( *skip, "#[builder(skip)] must be accompanied by default or default_code", )); } let conflicting_transformations = [ ("transform", self.setter.transform.as_ref().map(|t| &t.span)), ("strip_option", self.setter.strip_option.as_ref().map(|s| &s.span)), ("strip_bool", self.setter.strip_bool.as_ref().map(|s| &s.span)), ]; let mut conflicting_transformations = conflicting_transformations .iter() .filter_map(|(caption, span)| span.map(|span| (caption, span))) .collect::>(); if 1 < conflicting_transformations.len() { let (first_caption, first_span) = conflicting_transformations.pop().unwrap(); let conflicting_captions = conflicting_transformations .iter() .map(|(caption, _)| **caption) .collect::>(); let mut error = Error::new( *first_span, format_args!("{} conflicts with {}", first_caption, conflicting_captions.join(", ")), ); for (caption, span) in conflicting_transformations { error.combine(Error::new(*span, format_args!("{} set here", caption))); } return Err(error); } Ok(()) } } impl ApplyMeta for FieldBuilderAttr<'_> { fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { match expr.name().to_string().as_str() { "default" => match expr { AttrArg::Flag(ident) => { self.default = Some(syn::parse2(quote_spanned!(ident.span() => ::core::default::Default::default())).unwrap()); Ok(()) } AttrArg::KeyValue(key_value) => { self.default = Some(key_value.parse_value()?); Ok(()) } AttrArg::Not { .. } => { self.default = None; Ok(()) } AttrArg::Sub(_) => Err(expr.incorrect_type()), }, "default_code" => { use std::str::FromStr; let code = expr.key_value()?.parse_value::()?; let tokenized_code = TokenStream::from_str(&code.value())?; self.default = Some(syn::parse2(tokenized_code).map_err(|e| Error::new_spanned(code, format!("{}", e)))?); Ok(()) } "setter" => self.setter.apply_sub_attr(expr.sub_attr()?), "mutable_during_default_resolution" => expr.apply_flag_to_field( &mut self.mutable_during_default_resolution, "made mutable during default resolution", ), "via_mutators" => { match expr { AttrArg::Flag(ident) => { self.via_mutators = Some(ViaMutators { span: ident.span(), init: syn::parse2(quote_spanned!(ident.span() => ::core::default::Default::default())).unwrap(), }); } AttrArg::KeyValue(key_value) => { self.via_mutators = Some(ViaMutators { span: key_value.span(), init: key_value.parse_value()?, }); } AttrArg::Not { .. } => { self.via_mutators = None; } AttrArg::Sub(sub) => { if let Some(via_mutators) = self.via_mutators.as_mut() { if let Some(joined_span) = via_mutators.span.join(sub.span()) { via_mutators.span = joined_span; } else { // Shouldn't happen, but whatever via_mutators.span = sub.span(); }; via_mutators.apply_sub_attr(sub)?; } else { let mut via_mutators = ViaMutators::empty_spanned(sub.span()); via_mutators.apply_sub_attr(sub)?; self.via_mutators = Some(via_mutators); } } } Ok(()) } "mutators" => { self.mutators.extend(expr.sub_attr()?.undelimited()?); Ok(()) } _ => Err(Error::new_spanned( expr.name(), format!("Unknown parameter {:?}", expr.name().to_string()), )), } } } impl ApplyMeta for SetterSettings { fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { match expr.name().to_string().as_str() { "doc" => { self.doc = expr.key_value_or_not()?.map(|kv| kv.parse_value()).transpose()?; Ok(()) } "transform" => { self.transform = if let Some(key_value) = expr.key_value_or_not()? { Some(parse_transform_closure(key_value.name.span(), key_value.parse_value()?)?) } else { None }; Ok(()) } "prefix" => { self.prefix = if let Some(key_value) = expr.key_value_or_not()? { Some(expr_to_lit_string(&key_value.parse_value()?)?) } else { None }; Ok(()) } "suffix" => { self.suffix = if let Some(key_value) = expr.key_value_or_not()? { Some(expr_to_lit_string(&key_value.parse_value()?)?) } else { None }; Ok(()) } "skip" => expr.apply_flag_to_field(&mut self.skip, "skipped"), "into" => expr.apply_flag_to_field(&mut self.auto_into, "calling into() on the argument"), "strip_option" => { expr.apply_potentialy_empty_sub_to_field(&mut self.strip_option, "putting the argument in Some(...)", Strip::new) } "strip_bool" => expr.apply_potentialy_empty_sub_to_field( &mut self.strip_bool, "zero arguments setter, sets the field to true", Strip::new, ), _ => Err(Error::new_spanned( expr.name(), format!("Unknown parameter {:?}", expr.name().to_string()), )), } } } #[derive(Debug, Clone)] pub struct Strip { pub fallback: Option, span: Span, } impl Strip { fn new(span: Span) -> Self { Self { fallback: None, span } } } impl ApplyMeta for Strip { fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { match expr.name().to_string().as_str() { "fallback" => { if self.fallback.is_some() { return Err(Error::new_spanned( expr.name(), format!("Duplicate fallback parameter {:?}", expr.name().to_string()), )); } let ident: syn::Ident = expr.key_value().map(|kv| kv.parse_value())??; self.fallback = Some(ident); Ok(()) } _ => Err(Error::new_spanned( expr.name(), format!("Invalid parameter used {:?}", expr.name().to_string()), )), } } } #[derive(Debug, Clone)] pub struct Transform { pub params: Vec<(syn::Pat, syn::Type)>, pub body: syn::Expr, span: Span, } fn parse_transform_closure(span: Span, expr: syn::Expr) -> Result { let closure = match expr { syn::Expr::Closure(closure) => closure, _ => return Err(Error::new_spanned(expr, "Expected closure")), }; if let Some(kw) = &closure.asyncness { return Err(Error::new(kw.span, "Transform closure cannot be async")); } if let Some(kw) = &closure.capture { return Err(Error::new(kw.span, "Transform closure cannot be move")); } let params = closure .inputs .into_iter() .map(|input| match input { syn::Pat::Type(pat_type) => Ok((*pat_type.pat, *pat_type.ty)), _ => Err(Error::new_spanned(input, "Transform closure must explicitly declare types")), }) .collect::, _>>()?; Ok(Transform { params, body: *closure.body, span, }) } #[derive(Debug, Clone)] pub struct ViaMutators { pub span: Span, pub init: syn::Expr, } impl ViaMutators { fn empty_spanned(span: Span) -> Self { Self { span, init: syn::parse2(quote_spanned!(span => ::core::default::Default::default())).unwrap(), } } } impl ApplyMeta for ViaMutators { fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { match expr.name().to_string().as_str() { "init" => { self.init = expr.key_value()?.parse_value()?; Ok(()) } _ => Err(Error::new_spanned( expr.name(), format!("Unknown parameter {:?}", expr.name().to_string()), )), } } } typed-builder-macro-0.20.1/src/lib.rs000064400000000000000000000024121046102023000154560ustar 00000000000000use proc_macro2::TokenStream; use syn::{parse::Error, parse_macro_input, spanned::Spanned, DeriveInput}; mod builder_attr; mod field_info; mod mutator; mod struct_info; mod util; #[proc_macro_derive(TypedBuilder, attributes(builder))] pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); match impl_my_derive(&input) { Ok(output) => output.into(), Err(error) => error.to_compile_error().into(), } } fn impl_my_derive(ast: &syn::DeriveInput) -> Result { let data = match &ast.data { syn::Data::Struct(data) => match &data.fields { syn::Fields::Named(fields) => struct_info::StructInfo::new(ast, fields.named.iter())?.derive()?, syn::Fields::Unnamed(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for tuple structs")), syn::Fields::Unit => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unit structs")), }, syn::Data::Enum(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for enums")), syn::Data::Union(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unions")), }; Ok(data) } typed-builder-macro-0.20.1/src/mutator.rs000064400000000000000000000100621046102023000164030ustar 00000000000000use std::collections::HashSet; use proc_macro2::Ident; use syn::{ parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, spanned::Spanned, Error, Expr, FnArg, ItemFn, PatIdent, ReturnType, Signature, Token, Type, }; use crate::util::{pat_to_ident, ApplyMeta, AttrArg}; #[derive(Debug, Clone)] pub struct Mutator { pub fun: ItemFn, pub required_fields: HashSet, } #[derive(Default)] struct MutatorAttribute { requires: HashSet, } impl ApplyMeta for MutatorAttribute { fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error> { if expr.name() != "requires" { return Err(Error::new_spanned(expr.name(), "Only `requires` is supported")); } match expr.key_value()?.parse_value()? { Expr::Array(syn::ExprArray { elems, .. }) => self.requires.extend( elems .into_iter() .map(|expr| match expr { Expr::Path(path) if path.path.get_ident().is_some() => { Ok(path.path.get_ident().cloned().expect("should be ident")) } expr => Err(Error::new_spanned(expr, "Expected field name")), }) .collect::, _>>()?, ), expr => { return Err(Error::new_spanned( expr, "Only list of field names [field1, field2, …] supported", )) } } Ok(()) } } impl Parse for Mutator { fn parse(input: ParseStream) -> syn::Result { let mut fun: ItemFn = input.parse()?; let mut attribute = MutatorAttribute::default(); let mut i = 0; while i < fun.attrs.len() { let attr = &fun.attrs[i]; if attr.path().is_ident("mutator") { attribute.apply_attr(attr)?; fun.attrs.remove(i); } else { i += 1; } } // Ensure `&mut self` receiver if let Some(FnArg::Receiver(receiver)) = fun.sig.inputs.first_mut() { *receiver = parse_quote!(&mut self); } else { // Error either on first argument or `()` return Err(syn::Error::new( fun.sig .inputs .first() .map(Spanned::span) .unwrap_or(fun.sig.paren_token.span.span()), "mutator needs to take a reference to `self`", )); }; Ok(Self { fun, required_fields: attribute.requires, }) } } impl Mutator { /// Signature for Builder:: function pub fn outer_sig(&self, output: Type) -> Signature { let mut sig = self.fun.sig.clone(); sig.output = ReturnType::Type(Default::default(), output.into()); sig.inputs = sig .inputs .into_iter() .enumerate() .map(|(i, input)| match input { FnArg::Receiver(_) => parse_quote!(self), FnArg::Typed(mut input) => { input.pat = Box::new( PatIdent { attrs: Vec::new(), by_ref: None, mutability: None, ident: pat_to_ident(i, &input.pat), subpat: None, } .into(), ); FnArg::Typed(input) } }) .collect(); sig } /// Arguments to call inner mutator function pub fn arguments(&self) -> Punctuated { self.fun .sig .inputs .iter() .enumerate() .filter_map(|(i, input)| match &input { FnArg::Receiver(_) => None, FnArg::Typed(input) => Some(pat_to_ident(i, &input.pat)), }) .collect() } } typed-builder-macro-0.20.1/src/struct_info.rs000064400000000000000000000752651046102023000172670ustar 00000000000000use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{parse::Error, parse_quote, punctuated::Punctuated, GenericArgument, ItemFn, Token}; use crate::builder_attr::{IntoSetting, TypeBuilderAttr}; use crate::field_info::FieldInfo; use crate::mutator::Mutator; use crate::util::{ empty_type, empty_type_tuple, first_visibility, modify_types_generics_hack, phantom_data_for_generics, public_visibility, strip_raw_ident_prefix, type_tuple, }; #[derive(Debug)] pub struct StructInfo<'a> { vis: &'a syn::Visibility, name: &'a syn::Ident, generics: &'a syn::Generics, fields: Vec>, builder_attr: TypeBuilderAttr<'a>, builder_name: syn::Ident, } impl<'a> StructInfo<'a> { fn included_fields(&self) -> impl Iterator> { self.fields.iter().filter(|f| f.builder_attr.setter.skip.is_none()) } fn setter_fields(&self) -> impl Iterator> { self.included_fields().filter(|f| f.builder_attr.via_mutators.is_none()) } fn generic_arguments(&self) -> Punctuated { self.generics .params .iter() .map(|generic_param| match generic_param { syn::GenericParam::Type(type_param) => { let ident = type_param.ident.to_token_stream(); syn::parse2(ident).unwrap() } syn::GenericParam::Lifetime(lifetime_def) => syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone()), syn::GenericParam::Const(const_param) => { let ident = const_param.ident.to_token_stream(); syn::parse2(ident).unwrap() } }) .collect() } pub fn new(ast: &'a syn::DeriveInput, fields: impl Iterator) -> syn::Result> { let builder_attr = TypeBuilderAttr::new(&ast.attrs)?; let builder_name = builder_attr .builder_type .get_name() .map(|name| strip_raw_ident_prefix(name.to_string())) .unwrap_or_else(|| strip_raw_ident_prefix(format!("{}Builder", ast.ident))); Ok(StructInfo { vis: &ast.vis, name: &ast.ident, generics: &ast.generics, fields: fields .enumerate() .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone())) .collect::>()?, builder_attr, builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()), }) } fn builder_creation_impl(&self) -> syn::Result { let StructInfo { vis, ref name, ref builder_name, .. } = *self; let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); let init_fields_type = type_tuple(self.included_fields().map(|f| { if f.builder_attr.via_mutators.is_some() { f.tuplized_type_ty_param() } else { empty_type() } })); let init_fields_expr = self.included_fields().map(|f| { f.builder_attr.via_mutators.as_ref().map_or_else( || quote!(()), |via_mutators| { let init = &via_mutators.init; quote!((#init,)) }, ) }); let mut all_fields_param_type: syn::TypeParam = syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(); let all_fields_param = syn::GenericParam::Type(all_fields_param_type.clone()); all_fields_param_type.default = Some(syn::Type::Tuple(init_fields_type.clone())); let b_generics = { let mut generics = self.generics.clone(); generics.params.push(syn::GenericParam::Type(all_fields_param_type)); generics }; let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| { args.push(syn::GenericArgument::Type(init_fields_type.clone().into())); }); let phantom_data = phantom_data_for_generics(self.generics); let builder_method_name = self.builder_attr.builder_method.get_name().unwrap_or_else(|| quote!(builder)); let builder_method_visibility = first_visibility(&[ self.builder_attr.builder_method.vis.as_ref(), self.builder_attr.builder_type.vis.as_ref(), Some(vis), ]); let builder_method_doc = self.builder_attr.builder_method.get_doc_or(|| { format!( " Create a builder for building `{name}`. On the builder, call {setters} to set the values of the fields. Finally, call `.{build_method_name}()` to create the instance of `{name}`. ", name = self.name, build_method_name = self.build_method_name(), setters = { let mut result = String::new(); let mut is_first = true; for field in self.setter_fields() { use std::fmt::Write; if is_first { is_first = false; } else { write!(&mut result, ", ").unwrap(); } write!(&mut result, "`.{}(...)`", field.name).unwrap(); if field.builder_attr.default.is_some() { write!(&mut result, "(optional)").unwrap(); } } result } ) }); let builder_type_visibility = first_visibility(&[self.builder_attr.builder_type.vis.as_ref(), Some(vis)]); let builder_type_doc = if self.builder_attr.doc { self.builder_attr.builder_type.get_doc_or(|| { format!( " Builder for [`{name}`] instances. See [`{name}::{builder_method_name}()`] for more info. ", name = name, builder_method_name = builder_method_name ) }) } else { quote!(#[doc(hidden)]) }; let (b_generics_impl, b_generics_ty, b_generics_where_extras_predicates) = b_generics.split_for_impl(); let mut b_generics_where: syn::WhereClause = syn::parse2(quote! { where TypedBuilderFields: Clone })?; if let Some(predicates) = b_generics_where_extras_predicates { b_generics_where.predicates.extend(predicates.predicates.clone()); } Ok(quote! { #[automatically_derived] impl #impl_generics #name #ty_generics #where_clause { #builder_method_doc #[allow(dead_code, clippy::default_trait_access)] #builder_method_visibility fn #builder_method_name() -> #builder_name #generics_with_empty { #builder_name { fields: (#(#init_fields_expr,)*), phantom: ::core::default::Default::default(), } } } #[must_use] #builder_type_doc #[allow(dead_code, non_camel_case_types, non_snake_case)] #builder_type_visibility struct #builder_name #b_generics #b_generics_where_extras_predicates { fields: #all_fields_param, phantom: #phantom_data, } #[automatically_derived] impl #b_generics_impl Clone for #builder_name #b_generics_ty #b_generics_where { #[allow(clippy::default_trait_access)] fn clone(&self) -> Self { Self { fields: self.fields.clone(), phantom: ::core::default::Default::default(), } } } }) } fn field_impl(&self, field: &FieldInfo) -> syn::Result { let StructInfo { ref builder_name, .. } = *self; let destructuring = self .included_fields() .map(|f| { if f.ordinal == field.ordinal { quote!(()) } else { let name = f.name; name.to_token_stream() } }) .collect::>(); let reconstructing = self.included_fields().map(|f| f.name).collect::>(); let &FieldInfo { name: field_name, ty: field_type, .. } = field; let mut ty_generics = self.generic_arguments(); let mut target_generics_tuple = empty_type_tuple(); let mut ty_generics_tuple = empty_type_tuple(); let generics = { let mut generics = self.generics.clone(); for f in self.included_fields() { if f.ordinal == field.ordinal { ty_generics_tuple.elems.push_value(empty_type()); target_generics_tuple.elems.push_value(f.tuplized_type_ty_param()); } else { generics.params.push(f.generic_ty_param()); let generic_argument: syn::Type = f.type_ident(); ty_generics_tuple.elems.push_value(generic_argument.clone()); target_generics_tuple.elems.push_value(generic_argument); } ty_generics_tuple.elems.push_punct(Default::default()); target_generics_tuple.elems.push_punct(Default::default()); } generics }; let mut target_generics = ty_generics.clone(); target_generics.push(syn::GenericArgument::Type(target_generics_tuple.into())); ty_generics.push(syn::GenericArgument::Type(ty_generics_tuple.into())); let (impl_generics, _, where_clause) = generics.split_for_impl(); let doc = if let Some(doc) = field.builder_attr.setter.doc.as_ref() { Some(quote!(#[doc = #doc])) } else if !field.builder_attr.doc_comments.is_empty() { Some( field .builder_attr .doc_comments .iter() .map(|&line| quote!(#[doc = #line])) .collect(), ) } else { None }; let deprecated = &field.builder_attr.deprecated; // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of // nesting is different, so we have to do this little dance. let arg_type = if field.builder_attr.setter.strip_option.is_some() && field.builder_attr.setter.transform.is_none() { field .type_from_inside_option() .ok_or_else(|| Error::new_spanned(field_type, "can't `strip_option` - field is not `Option<...>`"))? } else { field_type }; let (arg_type, arg_expr) = if field.builder_attr.setter.auto_into.is_some() { (quote!(impl ::core::convert::Into<#arg_type>), quote!(#field_name.into())) } else { (arg_type.to_token_stream(), field_name.to_token_stream()) }; let strip_bool_fallback = field .builder_attr .setter .strip_bool .as_ref() .and_then(|strip_bool| strip_bool.fallback.as_ref()) .map(|fallback| (fallback.clone(), quote!(#field_name: #field_type), quote!(#arg_expr))); let strip_option_fallback = field .builder_attr .setter .strip_option .as_ref() .and_then(|strip_option| strip_option.fallback.as_ref()) .map(|fallback| (fallback.clone(), quote!(#field_name: #field_type), quote!(#arg_expr))); let (param_list, arg_expr) = if field.builder_attr.setter.strip_bool.is_some() { (quote!(), quote!(true)) } else if let Some(transform) = &field.builder_attr.setter.transform { let params = transform.params.iter().map(|(pat, ty)| quote!(#pat: #ty)); let body = &transform.body; (quote!(#(#params),*), quote!({ #body })) } else if field.builder_attr.setter.strip_option.is_some() { (quote!(#field_name: #arg_type), quote!(Some(#arg_expr))) } else { (quote!(#field_name: #arg_type), arg_expr) }; let repeated_fields_error_type_name = syn::Ident::new( &format!( "{}_Error_Repeated_field_{}", builder_name, strip_raw_ident_prefix(field_name.to_string()) ), proc_macro2::Span::call_site(), ); let repeated_fields_error_message = format!("Repeated field {}", field_name); let method_name = field.setter_method_name(); let strip_option_fallback_method = if let Some((method_name, param_list, arg_expr)) = strip_option_fallback { Some(quote! { #deprecated #doc #[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] pub fn #method_name (self, #param_list) -> #builder_name <#target_generics> { let #field_name = (#arg_expr,); let ( #(#destructuring,)* ) = self.fields; #builder_name { fields: ( #(#reconstructing,)* ), phantom: self.phantom, } } }) } else { None }; let strip_bool_fallback_method = if let Some((method_name, param_list, arg_expr)) = strip_bool_fallback { Some(quote! { #deprecated #doc #[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] pub fn #method_name (self, #param_list) -> #builder_name <#target_generics> { let #field_name = (#arg_expr,); let ( #(#destructuring,)* ) = self.fields; #builder_name { fields: ( #(#reconstructing,)* ), phantom: self.phantom, } } }) } else { None }; Ok(quote! { #[allow(dead_code, non_camel_case_types, missing_docs)] #[automatically_derived] impl #impl_generics #builder_name <#ty_generics> #where_clause { #deprecated #doc #[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] pub fn #method_name (self, #param_list) -> #builder_name <#target_generics> { let #field_name = (#arg_expr,); let ( #(#destructuring,)* ) = self.fields; #builder_name { fields: ( #(#reconstructing,)* ), phantom: self.phantom, } } #strip_option_fallback_method #strip_bool_fallback_method } #[doc(hidden)] #[allow(dead_code, non_camel_case_types, non_snake_case)] #[allow(clippy::exhaustive_enums)] pub enum #repeated_fields_error_type_name {} #[doc(hidden)] #[allow(dead_code, non_camel_case_types, missing_docs)] #[automatically_derived] impl #impl_generics #builder_name <#target_generics> #where_clause { #[deprecated( note = #repeated_fields_error_message )] #doc pub fn #method_name (self, _: #repeated_fields_error_type_name) -> #builder_name <#target_generics> { self } } }) } fn required_field_impl(&self, field: &FieldInfo) -> TokenStream { let StructInfo { ref builder_name, .. } = self; let FieldInfo { name: ref field_name, .. } = field; let mut builder_generics: Vec = self .generics .params .iter() .map(|generic_param| match generic_param { syn::GenericParam::Type(type_param) => { let ident = type_param.ident.to_token_stream(); syn::parse2(ident).unwrap() } syn::GenericParam::Lifetime(lifetime_def) => syn::GenericArgument::Lifetime(lifetime_def.lifetime.clone()), syn::GenericParam::Const(const_param) => { let ident = const_param.ident.to_token_stream(); syn::parse2(ident).unwrap() } }) .collect(); let mut builder_generics_tuple = empty_type_tuple(); let generics = { let mut generics = self.generics.clone(); for f in self.included_fields() { if f.builder_attr.default.is_some() || f.builder_attr.via_mutators.is_some() { // `f` is not mandatory - it does not have its own fake `build` method, so `field` will need // to warn about missing `field` regardless of whether `f` is set. assert!( f.ordinal != field.ordinal, "`required_field_impl` called for optional field {}", field.name ); generics.params.push(f.generic_ty_param()); builder_generics_tuple.elems.push_value(f.type_ident()); } else if f.ordinal < field.ordinal { // Only add a `build` method that warns about missing `field` if `f` is set. If `f` is not set, // `f`'s `build` method will warn, since it appears earlier in the argument list. builder_generics_tuple.elems.push_value(f.tuplized_type_ty_param()); } else if f.ordinal == field.ordinal { builder_generics_tuple.elems.push_value(empty_type()); } else { // `f` appears later in the argument list after `field`, so if they are both missing we will // show a warning for `field` and not for `f` - which means this warning should appear whether // or not `f` is set. generics.params.push(f.generic_ty_param()); builder_generics_tuple.elems.push_value(f.type_ident()); } builder_generics_tuple.elems.push_punct(Default::default()); } generics }; builder_generics.push(syn::GenericArgument::Type(builder_generics_tuple.into())); let (impl_generics, _, where_clause) = generics.split_for_impl(); let early_build_error_type_name = syn::Ident::new( &format!( "{}_Error_Missing_required_field_{}", builder_name, strip_raw_ident_prefix(field_name.to_string()) ), proc_macro2::Span::call_site(), ); let early_build_error_message = format!("Missing required field {}", field_name); let build_method_name = self.build_method_name(); let build_method_visibility = self.build_method_visibility(); quote! { #[doc(hidden)] #[allow(dead_code, non_camel_case_types, non_snake_case)] #[allow(clippy::exhaustive_enums)] pub enum #early_build_error_type_name {} #[doc(hidden)] #[allow(dead_code, non_camel_case_types, missing_docs, clippy::panic)] #[automatically_derived] impl #impl_generics #builder_name < #( #builder_generics ),* > #where_clause { #[deprecated( note = #early_build_error_message )] #build_method_visibility fn #build_method_name(self, _: #early_build_error_type_name) -> ! { panic!() } } } } fn mutator_impl( &self, mutator @ Mutator { fun: mutator_fn, required_fields, }: &Mutator, ) -> syn::Result { let StructInfo { ref builder_name, .. } = *self; let mut required_fields = required_fields.clone(); let mut ty_generics = self.generic_arguments(); let mut destructuring = TokenStream::new(); let mut ty_generics_tuple = empty_type_tuple(); let mut generics = self.generics.clone(); let mut mutator_ty_fields = Punctuated::<_, Token![,]>::new(); let mut mutator_destructure_fields = Punctuated::<_, Token![,]>::new(); for f @ FieldInfo { name, ty, .. } in self.included_fields() { if f.builder_attr.via_mutators.is_some() || required_fields.remove(f.name) { ty_generics_tuple.elems.push(f.tuplized_type_ty_param()); mutator_ty_fields.push(quote!(#name: #ty)); mutator_destructure_fields.push(name); quote!((#name,),).to_tokens(&mut destructuring); } else { generics.params.push(f.generic_ty_param()); let generic_argument: syn::Type = f.type_ident(); ty_generics_tuple.elems.push(generic_argument.clone()); quote!(#name,).to_tokens(&mut destructuring); } } ty_generics.push(syn::GenericArgument::Type(ty_generics_tuple.into())); let (impl_generics, _, where_clause) = generics.split_for_impl(); let mutator_struct_name = format_ident!("TypedBuilderFieldMutator"); let ItemFn { attrs, vis, .. } = mutator_fn; let sig = mutator.outer_sig(parse_quote!(#builder_name <#ty_generics>)); let fn_name = &sig.ident; let (_fn_impl_generics, fn_ty_generics, _fn_where_clause) = &sig.generics.split_for_impl(); let fn_call_turbofish = fn_ty_generics.as_turbofish(); let mutator_args = mutator.arguments(); // Generics for the mutator - should be similar to the struct's generics let m_generics = &self.generics; let (m_impl_generics, m_ty_generics, m_where_clause) = m_generics.split_for_impl(); let m_phantom = phantom_data_for_generics(self.generics); Ok(quote! { #[allow(dead_code, non_camel_case_types, missing_docs)] #[automatically_derived] impl #impl_generics #builder_name <#ty_generics> #where_clause { #(#attrs)* #[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] #vis #sig { struct #mutator_struct_name #m_generics #m_where_clause { __phantom: #m_phantom, #mutator_ty_fields } impl #m_impl_generics #mutator_struct_name #m_ty_generics #m_where_clause { #mutator_fn } let __args = (#mutator_args); let ( #destructuring ) = self.fields; let mut __mutator: #mutator_struct_name #m_ty_generics = #mutator_struct_name { __phantom: ::core::default::Default::default(), #mutator_destructure_fields }; // This dance is required to keep mutator args and destrucutre fields from interfering. { let (#mutator_args) = __args; __mutator.#fn_name #fn_call_turbofish(#mutator_args); } let #mutator_struct_name { __phantom, #mutator_destructure_fields } = __mutator; #builder_name { fields: ( #destructuring ), phantom: self.phantom, } } } }) } fn build_method_name(&self) -> TokenStream { self.builder_attr.build_method.common.get_name().unwrap_or(quote!(build)) } fn build_method_visibility(&self) -> TokenStream { first_visibility(&[self.builder_attr.build_method.common.vis.as_ref(), Some(&public_visibility())]) } fn build_method_impl(&self) -> TokenStream { let StructInfo { ref name, ref builder_name, .. } = *self; let generics = { let mut generics = self.generics.clone(); for field in self.included_fields() { if field.builder_attr.default.is_some() { let trait_ref = syn::TraitBound { paren_token: None, lifetimes: None, modifier: syn::TraitBoundModifier::None, path: { let mut path = self.builder_attr.crate_module_path.clone(); path.segments.push(syn::PathSegment { ident: Ident::new("Optional", Span::call_site()), arguments: syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { colon2_token: None, lt_token: Default::default(), args: [syn::GenericArgument::Type(field.ty.clone())].into_iter().collect(), gt_token: Default::default(), }), }); path }, }; let mut generic_param: syn::TypeParam = field.generic_ident.clone().into(); generic_param.bounds.push(trait_ref.into()); generics.params.push(generic_param.into()); } } generics }; let (impl_generics, _, _) = generics.split_for_impl(); let (_, ty_generics, where_clause) = self.generics.split_for_impl(); let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| { args.push(syn::GenericArgument::Type( type_tuple(self.included_fields().map(|field| { if field.builder_attr.default.is_some() { field.type_ident() } else { field.tuplized_type_ty_param() } })) .into(), )); }); let destructuring = self.included_fields().map(|f| f.name); // The default of a field can refer to earlier-defined fields, which we handle by // writing out a bunch of `let` statements first, which can each refer to earlier ones. // This means that field ordering may actually be significant, which isn't ideal. We could // relax that restriction by calculating a DAG of field default dependencies and // reordering based on that, but for now this much simpler thing is a reasonable approach. let assignments = self.fields.iter().map(|field| { let name = &field.name; let maybe_mut = if let Some(span) = field.builder_attr.mutable_during_default_resolution { quote_spanned!(span => mut) } else { quote!() }; if let Some(ref default) = field.builder_attr.default { if field.builder_attr.setter.skip.is_some() { quote!(let #maybe_mut #name = #default;) } else { let crate_module_path = &self.builder_attr.crate_module_path; quote!(let #maybe_mut #name = #crate_module_path::Optional::into_value(#name, || #default);) } } else { quote!(let #maybe_mut #name = #name.0;) } }); let field_names = self.fields.iter().map(|field| field.name); let build_method_name = self.build_method_name(); let build_method_visibility = self.build_method_visibility(); let build_method_doc = if self.builder_attr.doc { self.builder_attr .build_method .common .get_doc_or(|| format!("Finalise the builder and create its [`{}`] instance", name)) } else { quote!() }; let type_constructor = { let ty_generics = ty_generics.as_turbofish(); quote!(#name #ty_generics) }; let (build_method_generic, output_type, build_method_where_clause) = match &self.builder_attr.build_method.into { IntoSetting::NoConversion => (None, quote!(#name #ty_generics), None), IntoSetting::GenericConversion => ( Some(quote!(<__R>)), quote!(__R), Some(quote!(where #name #ty_generics: Into<__R>)), ), IntoSetting::TypeConversionToSpecificType(into) => (None, into.to_token_stream(), None), }; quote!( #[allow(dead_code, non_camel_case_types, missing_docs)] #[automatically_derived] impl #impl_generics #builder_name #modified_ty_generics #where_clause { #build_method_doc #[allow(clippy::default_trait_access, clippy::used_underscore_binding, clippy::no_effect_underscore_binding)] #build_method_visibility fn #build_method_name #build_method_generic (self) -> #output_type #build_method_where_clause { let ( #(#destructuring,)* ) = self.fields; #( #assignments )* #[allow(deprecated)] #type_constructor { #( #field_names ),* }.into() } } ) } pub fn derive(&self) -> syn::Result { let builder_creation = self.builder_creation_impl()?; let fields = self .setter_fields() .map(|f| self.field_impl(f)) .collect::>()?; let required_fields = self .setter_fields() .filter(|f| f.builder_attr.default.is_none()) .map(|f| self.required_field_impl(f)); let mutators = self .fields .iter() .flat_map(|f| &f.builder_attr.mutators) .chain(&self.builder_attr.mutators) .map(|m| self.mutator_impl(m)) .collect::>()?; let build_method = self.build_method_impl(); Ok(quote! { #builder_creation #fields #(#required_fields)* #mutators #build_method }) } } typed-builder-macro-0.20.1/src/util.rs000064400000000000000000000303201046102023000156640ustar 00000000000000use std::iter; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::{format_ident, quote, ToTokens}; use syn::{ parenthesized, parse::{Parse, ParseStream, Parser}, punctuated::Punctuated, spanned::Spanned, token, Attribute, Error, Pat, PatIdent, Token, }; pub fn path_to_single_string(path: &syn::Path) -> Option { if path.leading_colon.is_some() { return None; } let mut it = path.segments.iter(); let segment = it.next()?; if it.next().is_some() { // Multipart path return None; } if segment.arguments != syn::PathArguments::None { return None; } Some(segment.ident.to_string()) } pub fn ident_to_type(ident: syn::Ident) -> syn::Type { let mut path = syn::Path { leading_colon: None, segments: Default::default(), }; path.segments.push(syn::PathSegment { ident, arguments: Default::default(), }); syn::Type::Path(syn::TypePath { qself: None, path }) } pub fn empty_type() -> syn::Type { syn::TypeTuple { paren_token: Default::default(), elems: Default::default(), } .into() } pub fn type_tuple(elems: impl Iterator) -> syn::TypeTuple { let mut result = syn::TypeTuple { paren_token: Default::default(), elems: elems.collect(), }; if !result.elems.empty_or_trailing() { result.elems.push_punct(Default::default()); } result } pub fn empty_type_tuple() -> syn::TypeTuple { syn::TypeTuple { paren_token: Default::default(), elems: Default::default(), } } pub fn modify_types_generics_hack(ty_generics: &syn::TypeGenerics, mut mutator: F) -> syn::AngleBracketedGenericArguments where F: FnMut(&mut syn::punctuated::Punctuated), { let mut abga: syn::AngleBracketedGenericArguments = syn::parse2(ty_generics.to_token_stream()).unwrap_or_else(|_| syn::AngleBracketedGenericArguments { colon2_token: None, lt_token: Default::default(), args: Default::default(), gt_token: Default::default(), }); mutator(&mut abga.args); abga } pub fn strip_raw_ident_prefix(mut name: String) -> String { if name.starts_with("r#") { name.replace_range(0..2, ""); } name } pub fn first_visibility(visibilities: &[Option<&syn::Visibility>]) -> proc_macro2::TokenStream { let vis = visibilities .iter() .flatten() .next() .expect("need at least one visibility in the list"); vis.to_token_stream() } pub fn public_visibility() -> syn::Visibility { syn::Visibility::Public(syn::token::Pub::default()) } pub fn expr_to_lit_string(expr: &syn::Expr) -> Result { match expr { syn::Expr::Lit(lit) => match &lit.lit { syn::Lit::Str(str) => Ok(str.value()), _ => Err(Error::new_spanned(expr, "attribute only allows str values")), }, _ => Err(Error::new_spanned(expr, "attribute only allows str values")), } } pub enum AttrArg { Flag(Ident), KeyValue(KeyValue), Sub(SubAttr), Not { not: Token![!], name: Ident }, } impl AttrArg { pub fn name(&self) -> &Ident { match self { AttrArg::Flag(name) => name, AttrArg::KeyValue(KeyValue { name, .. }) => name, AttrArg::Sub(SubAttr { name, .. }) => name, AttrArg::Not { name, .. } => name, } } pub fn incorrect_type(&self) -> syn::Error { let message = match self { AttrArg::Flag(name) => format!("{:?} is not supported as a flag", name.to_string()), AttrArg::KeyValue(KeyValue { name, .. }) => format!("{:?} is not supported as key-value", name.to_string()), AttrArg::Sub(SubAttr { name, .. }) => format!("{:?} is not supported as nested attribute", name.to_string()), AttrArg::Not { name, .. } => format!("{:?} cannot be nullified", name.to_string()), }; syn::Error::new_spanned(self, message) } pub fn flag(self) -> syn::Result { if let Self::Flag(name) = self { Ok(name) } else { Err(self.incorrect_type()) } } pub fn key_value(self) -> syn::Result { if let Self::KeyValue(key_value) = self { Ok(key_value) } else { Err(self.incorrect_type()) } } pub fn key_value_or_not(self) -> syn::Result> { match self { Self::KeyValue(key_value) => Ok(Some(key_value)), Self::Not { .. } => Ok(None), _ => Err(self.incorrect_type()), } } pub fn sub_attr(self) -> syn::Result { if let Self::Sub(sub_attr) = self { Ok(sub_attr) } else { Err(self.incorrect_type()) } } pub fn apply_flag_to_field(self, field: &mut Option, caption: &str) -> syn::Result<()> { match self { AttrArg::Flag(flag) => { if field.is_none() { *field = Some(flag.span()); Ok(()) } else { Err(Error::new( flag.span(), format!("Illegal setting - field is already {caption}"), )) } } AttrArg::Not { .. } => { *field = None; Ok(()) } _ => Err(self.incorrect_type()), } } pub fn apply_potentialy_empty_sub_to_field( self, field: &mut Option, caption: &str, init: impl FnOnce(Span) -> T, ) -> syn::Result<()> { match self { AttrArg::Sub(sub) => { if field.is_none() { let mut value = init(sub.span()); value.apply_sub_attr(sub)?; *field = Some(value); Ok(()) } else { Err(Error::new( sub.span(), format!("Illegal setting - field is already {caption}"), )) } } AttrArg::Flag(flag) => { if field.is_none() { *field = Some(init(flag.span())); Ok(()) } else { Err(Error::new( flag.span(), format!("Illegal setting - field is already {caption}"), )) } } AttrArg::Not { .. } => { *field = None; Ok(()) } _ => Err(self.incorrect_type()), } } } pub struct KeyValue { pub name: Ident, pub eq: Token![=], pub value: TokenStream, } impl KeyValue { pub fn parse_value(self) -> syn::Result { syn::parse2(self.value) } } impl ToTokens for KeyValue { fn to_tokens(&self, tokens: &mut TokenStream) { self.name.to_tokens(tokens); self.eq.to_tokens(tokens); self.value.to_tokens(tokens); } } impl Parse for KeyValue { fn parse(input: syn::parse::ParseStream) -> syn::Result { Ok(Self { name: input.parse()?, eq: input.parse()?, value: input.parse()?, }) } } pub struct SubAttr { pub name: Ident, pub paren: token::Paren, pub args: TokenStream, } impl SubAttr { pub fn args(self) -> syn::Result> { Punctuated::::parse_terminated.parse2(self.args) } pub fn undelimited(self) -> syn::Result> { (|p: ParseStream| iter::from_fn(|| (!p.is_empty()).then(|| p.parse())).collect::>>()).parse2(self.args) } } impl ToTokens for SubAttr { fn to_tokens(&self, tokens: &mut TokenStream) { self.name.to_tokens(tokens); self.paren.surround(tokens, |t| self.args.to_tokens(t)); } } fn get_cursor_after_parsing(input: syn::parse::ParseBuffer) -> syn::Result { let parse_attempt: P = input.parse()?; let cursor = input.cursor(); if cursor.eof() || input.peek(Token![,]) { Ok(cursor) } else { Err(syn::Error::new( parse_attempt.span(), "does not end with comma or end of section", )) } } fn get_token_stream_up_to_cursor(input: syn::parse::ParseStream, cursor: syn::buffer::Cursor) -> syn::Result { Ok(core::iter::from_fn(|| { if input.cursor() < cursor { input.parse::().ok() } else { None } }) .collect()) } impl Parse for AttrArg { fn parse(input: syn::parse::ParseStream) -> syn::Result { if input.peek(Token![!]) { Ok(Self::Not { not: input.parse()?, name: input.parse()?, }) } else { let name = input.parse()?; if input.peek(Token![,]) || input.is_empty() { Ok(Self::Flag(name)) } else if input.peek(token::Paren) { let args; Ok(Self::Sub(SubAttr { name, paren: parenthesized!(args in input), args: args.parse()?, })) } else if input.peek(Token![=]) { // Try parsing as a type first, because it _should_ be simpler Ok(Self::KeyValue(KeyValue { name, eq: input.parse()?, value: { let cursor = get_cursor_after_parsing::(input.fork()) .or_else(|_| get_cursor_after_parsing::(input.fork()))?; get_token_stream_up_to_cursor(input, cursor)? }, })) } else { Err(input.error("expected !, = or (…)")) } } } } impl ToTokens for AttrArg { fn to_tokens(&self, tokens: &mut TokenStream) { match self { AttrArg::Flag(flag) => flag.to_tokens(tokens), AttrArg::KeyValue(kv) => kv.to_tokens(tokens), AttrArg::Sub(sub) => sub.to_tokens(tokens), AttrArg::Not { not, name } => { not.to_tokens(tokens); name.to_tokens(tokens); } } } } pub trait ApplyMeta { fn apply_meta(&mut self, expr: AttrArg) -> Result<(), Error>; fn apply_sub_attr(&mut self, sub_attr: SubAttr) -> syn::Result<()> { for arg in sub_attr.args()? { self.apply_meta(arg)?; } Ok(()) } fn apply_subsections(&mut self, list: &syn::MetaList) -> syn::Result<()> { if list.tokens.is_empty() { return Err(syn::Error::new_spanned(list, "Expected builder(…)")); } let parser = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated; let exprs = parser.parse2(list.tokens.clone())?; for expr in exprs { self.apply_meta(expr)?; } Ok(()) } fn apply_attr(&mut self, attr: &Attribute) -> syn::Result<()> { match &attr.meta { syn::Meta::List(list) => self.apply_subsections(list), meta => Err(Error::new_spanned(meta, "Expected builder(…)")), } } } pub fn pat_to_ident(i: usize, pat: &Pat) -> Ident { if let Pat::Ident(PatIdent { ident, .. }) = pat { ident.clone() } else { format_ident!("__{i}", span = pat.span()) } } pub fn phantom_data_for_generics(generics: &syn::Generics) -> proc_macro2::TokenStream { let phantom_generics = generics.params.iter().filter_map(|param| match param { syn::GenericParam::Lifetime(lifetime) => { let lifetime = &lifetime.lifetime; Some(quote!(&#lifetime ())) } syn::GenericParam::Type(ty) => { let ty = &ty.ident; Some(ty.to_token_stream()) } syn::GenericParam::Const(_cnst) => None, }); quote!(::core::marker::PhantomData<(#( ::core::marker::PhantomData<#phantom_generics> ),*)>) }