Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ assert_eq!(user.level, Some(24));
assert!(user.is_admin);
```

See [the guide](https://bon-rs.com/guide/overview) for the rest.
See [the guide](https://bon-rs.com/guide/overview) for more.

---

Expand Down
13 changes: 7 additions & 6 deletions bon-macros/src/builder/builder_gen/input_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,14 @@ impl<'a> FnInputCtx<'a> {
docs: finish_fn_docs,
} = self.config.finish_fn;

let is_special_builder_method = self.impl_ctx.is_some()
&& (self.fn_item.norm.sig.ident == "new" || self.fn_item.norm.sig.ident == "builder");

let finish_fn_ident = finish_fn_ident
.map(SpannedKey::into_value)
.unwrap_or_else(|| {
// For `builder` methods the `build` finisher is more conventional
if self.impl_ctx.is_some() && self.start_fn.ident == "builder" {
if is_special_builder_method {
format_ident!("build")
} else {
format_ident!("call")
Expand Down Expand Up @@ -381,16 +384,14 @@ impl<'a> FnInputCtx<'a> {

let ty_prefix = self.self_ty_prefix.unwrap_or_default();

// A special case for the starting function named `builder`.
// A special case for the `new` or `builder` method.
// We don't insert the `Builder` suffix in this case because
// this special case should be compatible with deriving
// a builder from a struct.
//
// We can arrive inside of this branch only if the function under
// the macro is called `new` or `builder` without `start_fn`
// name override, or if the `start_fn = builder/start_fn(name = builder)`
// is specified in the macro invocation explicitly.
if self.impl_ctx.is_some() && self.start_fn.ident == "builder" {
// the macro is called `new` or `builder`.
if is_special_builder_method {
return format_ident!("{ty_prefix}Builder");
}

Expand Down
12 changes: 7 additions & 5 deletions bon-macros/src/builder/item_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ pub(crate) fn generate(
} = ctx.into_builder_gen_ctx()?.output()?;

Ok(quote! {
#start_fn
#other_items

// Keep original function at the end. It seems like rust-analyzer
// Keep original function at the top. It seems like rust-analyzer
// does better job of highlighting syntax when it is here. Assuming
// this is because rust-analyzer prefers the last occurrence of the
// this is because rust-analyzer prefers the first occurrence of the
// span when highlighting.
//
// See this issue for details: https://github.com/rust-lang/rust-analyzer/issues/18438
#adapted_fn

#start_fn
#other_items
})
}
11 changes: 9 additions & 2 deletions bon-macros/src/builder/item_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ pub(crate) fn generate(

let new_impl_items = outputs.iter().flat_map(|(adapted_fn, output)| {
let start_fn = &output.start_fn;
[syn::parse_quote!(#start_fn), syn::parse_quote!(#adapted_fn)]
[syn::parse_quote!(#adapted_fn), syn::parse_quote!(#start_fn)]
});

norm_selfful_impl_block.items = other_items;
Expand All @@ -168,8 +168,15 @@ pub(crate) fn generate(
let other_items = outputs.iter().map(|(_, output)| &output.other_items);

Ok(quote! {
#(#other_items)*
// Keep the original impl block at the top. It seems like rust-analyzer
// does better job of highlighting syntax when it is here. Assuming
// this is because rust-analyzer prefers the first occurrence of the
// span when highlighting.
//
// See this issue for details: https://github.com/rust-lang/rust-analyzer/issues/18438
#norm_selfful_impl_block

#(#other_items)*
})
}

Expand Down
24 changes: 20 additions & 4 deletions bon/tests/integration/builder/attr_bon.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
use crate::prelude::*;

#[test]
fn new_method_special_case() {
struct Sut;

#[bon]
impl Sut {
#[builder]
fn new() {}
}

let _: SutBuilder = Sut::builder();
let builder: SutBuilder<sut_builder::Empty> = Sut::builder();

builder.build();
}

#[test]
fn builder_method_special_case() {
struct Sut;
Expand All @@ -17,7 +33,7 @@ fn builder_method_special_case() {
}

#[test]
fn builder_start_fn_special_case() {
fn builder_start_fn_is_not_special_case() {
struct Sut;

#[bon]
Expand All @@ -26,10 +42,10 @@ fn builder_start_fn_special_case() {
fn some_other_name() {}
}

let _: SutBuilder = Sut::builder();
let builder: SutBuilder<sut_builder::Empty> = Sut::builder();
let _: SutSomeOtherNameBuilder = Sut::builder();
let builder: SutSomeOtherNameBuilder<sut_some_other_name_builder::Empty> = Sut::builder();

builder.build();
builder.call();

Sut::some_other_name();
}
48 changes: 38 additions & 10 deletions bon/tests/integration/builder/attr_transparent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ mod attr_on {
#[builder(on(_, transparent))]
#[allow(dead_code)]
struct Sut<T> {
#[builder(start_fn)]
start_fn: u32,

regular: Option<u32>,
generic: Option<T>,

Expand All @@ -152,14 +155,15 @@ mod attr_on {
}

assert_debug_eq(
Sut::builder()
Sut::builder(11)
.regular(Some(1))
.generic(Some(false))
.with_into(2)
.maybe_with_default_2(Some(Some(3)))
.build(),
expect![[r#"
Sut {
start_fn: 11,
regular: Some(
1,
),
Expand All @@ -183,23 +187,31 @@ mod attr_on {
fn test_free_fn() {
#[builder(on(_, transparent))]
fn sut<T: fmt::Debug>(
#[builder(start_fn)] start_fn: u32,
regular: Option<u32>,
generic: Option<T>,
#[builder(into)] with_into: Option<u32>,
#[builder(default = Some(99))] with_default: Option<u32>,
#[builder(default = Some(10))] with_default_2: Option<u32>,
) -> impl fmt::Debug {
(regular, generic, with_into, with_default, with_default_2)
(
start_fn,
regular,
generic,
with_into,
with_default,
with_default_2,
)
}

assert_debug_eq(
sut()
sut(11)
.regular(Some(1))
.generic(Some(false))
.with_into(2)
.maybe_with_default_2(Some(Some(3)))
.call(),
expect!["(Some(1), Some(false), Some(2), Some(99), Some(3))"],
expect!["(11, Some(1), Some(false), Some(2), Some(99), Some(3))"],
);
}

Expand All @@ -211,47 +223,63 @@ mod attr_on {
impl Sut {
#[builder(on(_, transparent))]
fn sut<T: fmt::Debug>(
#[builder(start_fn)] start_fn: u32,
regular: Option<u32>,
generic: Option<T>,
#[builder(into)] with_into: Option<u32>,
#[builder(default = Some(99))] with_default: Option<u32>,
#[builder(default = Some(10))] with_default_2: Option<u32>,
) -> impl fmt::Debug {
(regular, generic, with_into, with_default, with_default_2)
(
start_fn,
regular,
generic,
with_into,
with_default,
with_default_2,
)
}

#[builder(on(_, transparent))]
fn with_self<T: fmt::Debug>(
&self,
#[builder(start_fn)] start_fn: u32,
regular: Option<u32>,
generic: Option<T>,
#[builder(into)] with_into: Option<u32>,
#[builder(default = Some(99))] with_default: Option<u32>,
#[builder(default = Some(10))] with_default_2: Option<u32>,
) -> impl fmt::Debug {
let _ = self;
(regular, generic, with_into, with_default, with_default_2)
(
start_fn,
regular,
generic,
with_into,
with_default,
with_default_2,
)
}
}

assert_debug_eq(
Sut::sut()
Sut::sut(11)
.regular(Some(1))
.generic(Some(false))
.with_into(2)
.maybe_with_default_2(Some(Some(3)))
.call(),
expect!["(Some(1), Some(false), Some(2), Some(99), Some(3))"],
expect!["(11, Some(1), Some(false), Some(2), Some(99), Some(3))"],
);

assert_debug_eq(
Sut.with_self()
Sut.with_self(11)
.regular(Some(1))
.generic(Some(false))
.with_into(2)
.maybe_with_default_2(Some(Some(3)))
.call(),
expect!["(Some(1), Some(false), Some(2), Some(99), Some(3))"],
expect!["(11, Some(1), Some(false), Some(2), Some(99), Some(3))"],
);
}
}
18 changes: 18 additions & 0 deletions bon/tests/integration/ui/compile_fail/attr_with.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,22 @@ struct NonCollectionWithFromIter2 {
value: Option<u32>,
}

#[derive(Builder)]
struct IncompatibleWithStartFn {
#[builder(with = |x: u32| x + 1, start_fn)]
value: u32,
}

#[derive(Builder)]
struct IncompatibleWithFinishFn {
#[builder(with = |x: u32| x + 1, finish_fn)]
value: u32,
}

#[derive(Builder)]
struct IncompatibleWithInto {
#[builder(with = |x: u32| x + 1, into)]
value: u32,
}

fn main() {}
18 changes: 18 additions & 0 deletions bon/tests/integration/ui/compile_fail/attr_with.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,24 @@ error: the underlying type of this member is not a known collection type; only a
104 | value: Option<u32>,
| ^^^

error: `start_fn` attribute can't be specified together with `with`
--> tests/integration/ui/compile_fail/attr_with.rs:109:38
|
109 | #[builder(with = |x: u32| x + 1, start_fn)]
| ^^^^^^^^

error: `finish_fn` attribute can't be specified together with `with`
--> tests/integration/ui/compile_fail/attr_with.rs:115:38
|
115 | #[builder(with = |x: u32| x + 1, finish_fn)]
| ^^^^^^^^^

error: `with` attribute can't be specified together with `into`
--> tests/integration/ui/compile_fail/attr_with.rs:121:15
|
121 | #[builder(with = |x: u32| x + 1, into)]
| ^^^^

error[E0308]: mismatched types
--> tests/integration/ui/compile_fail/attr_with.rs:54:12
|
Expand Down
Loading
Loading