typed-builder-macro-0.18.1/.cargo_vcs_info.json0000644000000001610000000000100147710ustar { "git": { "sha1": "bdf07dc5df79a01fda83f67281579368c693c9fa" }, "path_in_vcs": "typed-builder-macro" }typed-builder-macro-0.18.1/Cargo.toml0000644000000021240000000000100127700ustar # 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.18.1" authors = [ "IdanArye ", "Chris Morgan ", ] 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] proc-macro = true [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.syn] version = "2" features = [ "full", "extra-traits", ] typed-builder-macro-0.18.1/Cargo.toml.orig000064400000000000000000000006651046102023000164610ustar 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.18.1/README.md000064400000000000000000000075561046102023000150570ustar 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.18.1/src/builder_attr.rs000064400000000000000000000147271046102023000174130ustar 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.18.1/src/field_info.rs000064400000000000000000000343161046102023000170250ustar 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_span) = 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 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::Path(path) | syn::Meta::NameValue(syn::MetaNameValue { path, .. }) => { if path_to_single_string(path).as_deref() == Some("deprecated") { self.deprecated = Some(attr); }; 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()), ("strip_bool", self.setter.strip_bool.as_ref()), ]; 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_flag_to_field(&mut self.strip_option, "putting the argument in Some(...)"), "strip_bool" => expr.apply_flag_to_field(&mut self.strip_bool, "zero arguments setter, sets the field to true"), _ => Err(Error::new_spanned( expr.name(), format!("Unknown parameter {:?}", 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.18.1/src/lib.rs000064400000000000000000000024121046102023000154650ustar 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.18.1/src/mutator.rs000064400000000000000000000100621046102023000164120ustar 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.18.1/src/struct_info.rs000064400000000000000000000673441046102023000172750ustar 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, 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_generics = self.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, }); 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: ::core::marker::PhantomData<(#( ::core::marker::PhantomData<#phantom_generics> ),*)>, } #[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 descructuring = self.included_fields().map(|f| { if f.ordinal == field.ordinal { quote!(()) } else { let name = f.name; name.to_token_stream() } }); let reconstructing = self.included_fields().map(|f| f.name); 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 = field.builder_attr.setter.doc.as_ref().map(|doc| quote!(#[doc = #doc])); 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 (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(); 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 ( #(#descructuring,)* ) = self.fields; #builder_name { fields: ( #(#reconstructing,)* ), phantom: self.phantom, } } } #[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 )] 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 it's own fake `build` method, so `field` will need // to warn about missing `field` whether or not `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 mutator_args = mutator.arguments(); 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 { #mutator_ty_fields } impl #mutator_struct_name { #mutator_fn } let __args = (#mutator_args); let ( #destructuring ) = self.fields; let mut __mutator = #mutator_struct_name{ #mutator_destructure_fields }; // This dance is required to keep mutator args and destrucutre fields from interfering. { let (#mutator_args) = __args; __mutator.#fn_name(#mutator_args); } let #mutator_struct_name { #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 descructuring = 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 ( #(#descructuring,)* ) = 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.18.1/src/util.rs000064400000000000000000000246221046102023000157030ustar 00000000000000use std::iter; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; use quote::{format_ident, 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 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()) } }