diff --git a/bon-macros/src/builder/builder_gen/input_func.rs b/bon-macros/src/builder/builder_gen/input_func.rs index f34fc378..72bf103e 100644 --- a/bon-macros/src/builder/builder_gen/input_func.rs +++ b/bon-macros/src/builder/builder_gen/input_func.rs @@ -1,6 +1,7 @@ use super::{ - generic_param_to_arg, BuilderGenCtx, FinishFunc, FinishFuncBody, Generics, Member, MemberExpr, - MemberOrigin, ReceiverCtx, StartFunc, + generic_param_to_arg, AssocFreeMethodCtx, AssocMethodCtx, AssocMethodReceiverCtx, + BuilderGenCtx, FinishFunc, FinishFuncBody, Generics, Member, MemberExpr, MemberOrigin, + StartFunc, }; use crate::builder::params::BuilderParams; use crate::normalization::NormalizeSelfTy; @@ -89,17 +90,23 @@ impl FuncInputCtx { Some(prefix) } - fn receiver_ctx(&self) -> Option { - let receiver = self.norm_func.sig.receiver()?; - let mut without_self_ty = receiver.ty.clone(); + fn assoc_method_ctx(&self) -> Option { let self_ty = &self.impl_ctx.as_deref()?.self_ty; + let Some(receiver) = self.norm_func.sig.receiver() else { + return Some(AssocMethodCtx::Free(AssocFreeMethodCtx { + self_ty: self_ty.clone(), + })); + }; + + let mut without_self_ty = receiver.ty.clone(); + NormalizeSelfTy { self_ty }.visit_type_mut(&mut without_self_ty); - Some(ReceiverCtx { - with_self_ty: receiver.clone(), - without_self_ty, - }) + Some(AssocMethodCtx::Receiver(AssocMethodReceiverCtx { + with_self_keyword: receiver.clone(), + without_self_keyword: without_self_ty, + })) } fn generics(&self) -> Generics { @@ -231,7 +238,7 @@ impl FuncInputCtx { } pub(crate) fn into_builder_gen_ctx(self) -> Result { - let receiver = self.receiver_ctx(); + let receiver = self.assoc_method_ctx(); if self.impl_ctx.is_none() { let explanation = "\ @@ -330,7 +337,7 @@ impl FuncInputCtx { builder_private_impl_ident, builder_state_trait_ident, - receiver, + assoc_method_ctx: receiver, generics, vis: self.norm_func.vis, diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 59d8203c..9101ef03 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -146,7 +146,7 @@ impl StructInputCtx { builder_private_impl_ident, builder_state_trait_ident, - receiver: None, + assoc_method_ctx: None, generics, vis: self.norm_struct.vis, diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index ecca4c13..c161a3f3 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -10,9 +10,35 @@ use crate::util::prelude::*; use itertools::Itertools; use quote::quote; -pub(crate) struct ReceiverCtx { - pub(crate) with_self_ty: syn::Receiver, - pub(crate) without_self_ty: Box, +pub(crate) struct AssocMethodReceiverCtx { + pub(crate) with_self_keyword: syn::Receiver, + pub(crate) without_self_keyword: Box, +} +pub(crate) struct AssocFreeMethodCtx { + /// The `Self` type of the impl block. It doesn't contain any nested + /// `Self` keywords in it. This is prohibited by Rust's syntax itself. + pub(crate) self_ty: Box, +} + +pub(crate) enum AssocMethodCtx { + Receiver(AssocMethodReceiverCtx), + Free(AssocFreeMethodCtx), +} + +impl AssocMethodCtx { + fn as_receiver(&self) -> Option<&AssocMethodReceiverCtx> { + match self { + AssocMethodCtx::Receiver(receiver) => Some(receiver), + AssocMethodCtx::Free(_) => None, + } + } + + fn ty_without_self_keyword(&self) -> &syn::Type { + match self { + AssocMethodCtx::Receiver(receiver) => &receiver.without_self_keyword, + AssocMethodCtx::Free(parent) => &parent.self_ty, + } + } } pub(crate) struct BuilderGenCtx { @@ -20,7 +46,7 @@ pub(crate) struct BuilderGenCtx { pub(crate) generics: Generics, pub(crate) vis: syn::Visibility, - pub(crate) receiver: Option, + pub(crate) assoc_method_ctx: Option, pub(crate) start_func: StartFunc, pub(crate) finish_func: FinishFunc, @@ -137,16 +163,19 @@ impl BuilderGenCtx { let member_idents = self.member_idents(); let receiver = self - .receiver + .assoc_method_ctx .as_ref() - .map(|receiver| &receiver.with_self_ty); - let receiver_field_init = self.receiver.as_ref().map(|receiver| { - let self_token = &receiver.with_self_ty.self_token; + .and_then(AssocMethodCtx::as_receiver); + + let receiver_field_init = receiver.map(|receiver| { + let self_token = &receiver.with_self_keyword.self_token; quote! { receiver: #self_token, } }); + let receiver = receiver.map(|receiver| &receiver.with_self_keyword); + let func = quote! { #(#docs)* #vis fn #start_func_ident<#(#generics_decl),*>( @@ -170,7 +199,13 @@ impl BuilderGenCtx { } fn phantom_data(&self) -> TokenStream2 { - let member_types = self.members.iter().map(|member| &member.ty); + let member_types = self.members.iter().map(|member| member.ty.as_ref()); + let receiver_ty = self + .assoc_method_ctx + .as_ref() + .map(AssocMethodCtx::ty_without_self_keyword); + + let types = receiver_ty.into_iter().chain(member_types); quote! { ::core::marker::PhantomData<( @@ -179,7 +214,7 @@ impl BuilderGenCtx { // in phantom data here. // // Suppose a function was defined with an argument of type `&'a T` - // and we then generate the an impl block (simplified): + // and we then generate an impl block (simplified): // // ``` // impl<'a, T, U> for Foo @@ -195,7 +230,7 @@ impl BuilderGenCtx { // // That's a weird implicit behavior in Rust, I suppose there is a reasonable // explanation for it, I just didn't care to research it yet ¯\_(ツ)_/¯. - #(#member_types,)* + #(#types,)* // A special case of zero members requires storing `__State` in phantom data // otherwise it would be reported as an unused type parameter. @@ -232,11 +267,11 @@ impl BuilderGenCtx { let unset_state_types = self.unset_state_types(); let phantom_data = self.phantom_data(); - let receiver_field = self.receiver.as_ref().map(|receiver| { - let ty = &receiver.without_self_ty; - quote! { + let receiver_field = self.assoc_method_ctx.as_ref().and_then(|receiver| { + let ty = &receiver.as_receiver()?.without_self_keyword; + Some(quote! { receiver: #ty, - } + }) }); let members = self.members.iter().map(|member| { diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index d13a4612..e8af11fe 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -1,4 +1,5 @@ use super::{member::Member, BuilderGenCtx}; +use crate::builder::builder_gen::AssocMethodCtx; use crate::util::prelude::*; use darling::ast::GenericParamExt; use itertools::Itertools; @@ -324,7 +325,9 @@ impl<'a> MemberSettersCtx<'a> { let member_idents = self.builder_gen.member_idents(); let maybe_receiver_field = self .builder_gen - .receiver + .assoc_method_ctx + .as_ref() + .and_then(AssocMethodCtx::as_receiver) .is_some() .then(|| quote!(receiver: self.__private_impl.receiver,)); diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 415c1254..8dae4eb5 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -60,7 +60,7 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result { + buf: Vec, + bar: Option, + str: &'a str, + other_ref: &'b (), + } + + #[bon::bon] + impl Sut<'_, '_, T, U> { + #[builder] + fn new() -> Self { + Self { + buf: vec![], + bar: None, + str: "littlepip", + other_ref: &(), + } + } + } + + let actual = Sut::::builder().build(); + + let empty: [u32; 0] = []; + + assert_eq!(actual.buf, empty); + assert!(actual.bar.is_none()); + assert_eq!(actual.str, "littlepip"); + let () = actual.other_ref; +} diff --git a/website/changelog.md b/website/changelog.md index 9c7c2175..e35190ca 100644 --- a/website/changelog.md +++ b/website/changelog.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix a bug of `Default` trait requirement for types under an `Option` ([#13](https://github.com/elastio/bon/pull/13)) -- Fix the link to docs.rs to use reference the latest version ([#11](https://github.com/elastio/bon/pull/11)) +- Fix the link to docs.rs to so that it references the latest version ([#11](https://github.com/elastio/bon/pull/11)) ## [1.0.1](https://github.com/elastio/bon/compare/v1.0.0...v1.0.1) - 2024-07-29 diff --git a/website/docs/guide/into-conversions.md b/website/docs/guide/into-conversions.md index 683bfbfa..28810fe1 100644 --- a/website/docs/guide/into-conversions.md +++ b/website/docs/guide/into-conversions.md @@ -198,7 +198,11 @@ The following list describes the types that don't qualify for an automatic `Into ## Override the default behavior -Suppose automatic `Into` conversion qualification rules don't satisfy your use case. For example, you want the setter method to accept an `Into<(u32, u32)>` then you can use an explicit `#[builder(into)]` to override the default behavior. See [this attribute's docs](../reference/builder#into) for details. +Suppose automatic `Into` conversion qualification rules don't satisfy your use case. For example, you want the setter method to accept an `Into<(u32, u32)>` then you can use an explicit `#[builder(into)]` to override the default behavior. + +Use `#[builder(into = false)]` if you want to disable the automatic into conversion. + +See [this attribute's docs](../reference/builder#into) for details.