-
-
Notifications
You must be signed in to change notification settings - Fork 35
Adding support for build_from and build_from_clone builder methods.
#313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,133 @@ | ||||
| use crate::builder::builder_gen::{BuilderGenCtx, member::Member}; | ||||
| use crate::util::prelude::*; | ||||
| use proc_macro2::Span; | ||||
| use quote::quote; | ||||
| use syn::{Type, spanned::Spanned}; | ||||
|
|
||||
| pub(super) fn emit(ctx: &BuilderGenCtx, target_ty: &Type) -> Result<TokenStream> { | ||||
| let mut tokens = TokenStream::new(); | ||||
|
|
||||
| let field_vars: Vec<_> = ctx | ||||
| .members | ||||
| .iter() | ||||
| .map(|member| { | ||||
| let ident = member.orig_ident(); | ||||
| let ty = member.norm_ty(); | ||||
| let default_expr = quote! { ::core::default::Default::default() }; | ||||
|
|
||||
| match member { | ||||
| Member::Field(_) | Member::StartFn(_) => quote! { | ||||
| let #ident: #ty = self.#ident; | ||||
| }, | ||||
| Member::Named(member) => { | ||||
| let index = &member.index; | ||||
| quote! { | ||||
| let #ident: #ty = match self.__unsafe_private_named.#index { | ||||
| Some(value) => value, | ||||
| None => from.#ident.clone(), | ||||
| }; | ||||
| } | ||||
| } | ||||
| Member::FinishFn(_) => quote! { | ||||
| let #ident: #ty = from.#ident.clone(); | ||||
| }, | ||||
| Member::Skip(_) => quote! { | ||||
| let #ident: #ty = #default_expr; | ||||
| }, | ||||
| } | ||||
| }) | ||||
| .collect(); | ||||
|
|
||||
| let ctor_args: Vec<_> = ctx | ||||
| .members | ||||
| .iter() | ||||
| .map(|m| { | ||||
| let ident = m.orig_ident(); | ||||
| quote! { #ident } | ||||
| }) | ||||
| .collect(); | ||||
|
|
||||
| if ctx.build_from { | ||||
| tokens.extend(emit_build_from_method( | ||||
| false, | ||||
| target_ty, | ||||
| &field_vars, | ||||
| &ctor_args, | ||||
| )); | ||||
| } | ||||
|
|
||||
| if ctx.build_from_clone { | ||||
| tokens.extend(emit_build_from_method( | ||||
| true, | ||||
| target_ty, | ||||
| &field_vars, | ||||
| &ctor_args, | ||||
| )?); | ||||
| } | ||||
|
|
||||
| Ok(tokens) | ||||
| } | ||||
|
|
||||
| fn emit_build_from_method( | ||||
| clone: bool, | ||||
| target_ty: &Type, | ||||
| field_vars: &[TokenStream], | ||||
| ctor_args: &[TokenStream], | ||||
| ) -> Result<TokenStream> { | ||||
| let doc = if clone { | ||||
| "Fills unset builder fields from an owned value of the target type and builds it." | ||||
| } else { | ||||
| "Fills unset builder fields from a reference to the target type and builds it." | ||||
| }; | ||||
|
|
||||
| let method_name = if clone { | ||||
| quote!(build_from_clone) | ||||
| } else { | ||||
| quote!(build_from) | ||||
| }; | ||||
|
|
||||
| let arg_type = if clone { | ||||
| quote!(#target_ty) | ||||
| } else { | ||||
| quote!(&#target_ty) | ||||
| }; | ||||
|
|
||||
| let arg_pat = if clone { | ||||
| quote!(mut from) | ||||
| } else { | ||||
| quote!(from) | ||||
| }; | ||||
|
|
||||
| let ctor_path = extract_ctor_ident_path(target_ty, target_ty.span())?; | ||||
|
|
||||
| Ok(quote! { | ||||
| #[inline(always)] | ||||
| #[doc = #doc] | ||||
| pub fn #method_name(self, #arg_pat: #arg_type) -> #target_ty { | ||||
| #( #field_vars )* | ||||
| #ctor_path { | ||||
| #( #ctor_args, )* | ||||
| } | ||||
|
Comment on lines
+82
to
+84
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunatelly, this approach won't work in case if the builder is generated with the
It's only requirement is that members are assigned to variables of the same name in scope. Plus we need to inherit the potential |
||||
| } | ||||
| }) | ||||
| } | ||||
|
|
||||
| pub(crate) fn extract_ctor_ident_path(ty: &Type, span: Span) -> Result<TokenStream> { | ||||
| use quote::quote_spanned; | ||||
|
Check failure on line 116 in bon-macros/src/builder/builder_gen/build_from.rs
|
||||
|
|
||||
| let path = ty.as_path_no_qself().ok_or_else(|| { | ||||
| err!( | ||||
| &span, | ||||
| "expected a concrete type path (like `MyStruct`) for constructor" | ||||
| ) | ||||
| })?; | ||||
|
|
||||
| let ident = path | ||||
| .segments | ||||
| .last() | ||||
| .ok_or_else(|| err!(&span, "expected a named type, but found an empty path"))? | ||||
| .ident | ||||
| .clone(); | ||||
|
|
||||
| Ok(quote_spanned! { span => #ident }) | ||||
|
||||
| } | ||||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -1,14 +1,14 @@ | ||||
| mod on; | ||||
|
|
||||
| pub(crate) use on::OnConfig; | ||||
|
|
||||
| use crate::parsing::{BonCratePath, ItemSigConfig, ItemSigConfigParsing, SpannedKey}; | ||||
| use crate::util::prelude::*; | ||||
| use darling::ast::NestedMeta; | ||||
| use darling::FromMeta; | ||||
| use darling::ast::NestedMeta; | ||||
| use syn::ItemFn; | ||||
| use syn::parse::Parser; | ||||
| use syn::punctuated::Punctuated; | ||||
| use syn::ItemFn; | ||||
|
|
||||
| fn parse_finish_fn(meta: &syn::Meta) -> Result<ItemSigConfig> { | ||||
| ItemSigConfigParsing { | ||||
|
|
@@ -75,6 +75,12 @@ | |||
| /// Specifies the derives to apply to the builder. | ||||
| #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)] | ||||
| pub(crate) derive: DerivesConfig, | ||||
|
|
||||
| #[darling(default)] | ||||
| pub(crate) build_from: bool, | ||||
|
||||
| vis: finish_fn.vis.unwrap_or_else(|| builder_type.vis.clone()), |
(^ this is the place where a bunch of complex inter-field-dependent defaults are resolved)
Otherwise the docs would be incorrectly claiming that these attributes support vis, name, doc attributes.
I think this may require writing a custom with parser function that handles a syn::Meta::Path (in which case it returns None (i.e. Some(None))), and otherwise delegates to ItemSigConfig.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| use crate::prelude::*; | ||
|
|
||
| #[derive(Builder, Clone)] | ||
| #[builder(build_from, build_from_clone)] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should support method/function syntax too, unless you'd like to keep it supported only on struct derives initially. For that, we should have tests for methods/functions too. In fact, |
||
| struct User { | ||
| name: String, | ||
|
Check failure on line 6 in bon/tests/integration/builder/build_from.rs
|
||
| age: u8, | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_build_from_works() { | ||
| let jon = User::builder().name("Jon".into()).age(25).build(); | ||
| let alice = User::builder().name("Alice".into()).build_from(&jon); | ||
| assert_eq!(alice.age, jon.age); | ||
| assert_eq!(alice.name, "Alice"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_build_from_clone_works() { | ||
| let jon = User::builder().name("Jon".into()).age(25).build(); | ||
| let alice = User::builder() | ||
| .name("Alice".into()) | ||
| .build_from_clone(jon.clone()); | ||
| assert_eq!(alice.age, jon.age); | ||
| assert_eq!(alice.name, "Alice"); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that
build_fromacceptsTby value and does not callclone()at all (that's why this method's name is a bit shorter since it is less expensive and should be the preferred thing), butbuild_from_cloneaccepts&Tand calls.clone()if needed (that's why it has "clone" in its name).