Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions bon-macros/src/builder/builder_gen/input_func.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -89,17 +90,23 @@ impl FuncInputCtx {
Some(prefix)
}

fn receiver_ctx(&self) -> Option<ReceiverCtx> {
let receiver = self.norm_func.sig.receiver()?;
let mut without_self_ty = receiver.ty.clone();
fn assoc_method_ctx(&self) -> Option<AssocMethodCtx> {
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 {
Expand Down Expand Up @@ -231,7 +238,7 @@ impl FuncInputCtx {
}

pub(crate) fn into_builder_gen_ctx(self) -> Result<BuilderGenCtx> {
let receiver = self.receiver_ctx();
let receiver = self.assoc_method_ctx();

if self.impl_ctx.is_none() {
let explanation = "\
Expand Down Expand Up @@ -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,

Expand Down
2 changes: 1 addition & 1 deletion bon-macros/src/builder/builder_gen/input_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
65 changes: 50 additions & 15 deletions bon-macros/src/builder/builder_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,43 @@ 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<syn::Type>,
pub(crate) struct AssocMethodReceiverCtx {
pub(crate) with_self_keyword: syn::Receiver,
pub(crate) without_self_keyword: Box<syn::Type>,
}
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<syn::Type>,
}

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 {
pub(crate) members: Vec<Member>,

pub(crate) generics: Generics,
pub(crate) vis: syn::Visibility,
pub(crate) receiver: Option<ReceiverCtx>,
pub(crate) assoc_method_ctx: Option<AssocMethodCtx>,

pub(crate) start_func: StartFunc,
pub(crate) finish_func: FinishFunc,
Expand Down Expand Up @@ -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),*>(
Expand All @@ -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<(
Expand All @@ -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<U>
Expand All @@ -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.
Expand Down Expand Up @@ -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| {
Expand Down
5 changes: 4 additions & 1 deletion bon-macros/src/builder/builder_gen/setter_methods.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,));

Expand Down
2 changes: 1 addition & 1 deletion bon-macros/src/builder/item_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result<TokenStream
let mut norm_selfful_impl_block = norm_impl_block.clone();

crate::normalization::NormalizeSelfTy {
self_ty: &orig_impl_block.self_ty,
self_ty: &norm_impl_block.self_ty.clone(),
}
.visit_item_impl_mut(&mut norm_impl_block);

Expand Down
33 changes: 33 additions & 0 deletions bon/tests/integration/builder_on_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,36 @@ fn raw_identifiers() {

let _: r#type = sut();
}

// This is based on the issue https://github.com/elastio/bon/issues/16
#[test]
fn self_only_generic_param() {
struct Sut<'a, 'b: 'a, T, U> {
buf: Vec<T>,
bar: Option<U>,
str: &'a str,
other_ref: &'b (),
}

#[bon::bon]
impl<T, U> Sut<'_, '_, T, U> {
#[builder]
fn new() -> Self {
Self {
buf: vec![],
bar: None,
str: "littlepip",
other_ref: &(),
}
}
}

let actual = Sut::<u32, std::convert::Infallible>::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;
}
2 changes: 1 addition & 1 deletion website/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion website/docs/guide/into-conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.



Expand Down