Skip to content
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
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();
}
12 changes: 12 additions & 0 deletions website/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@ export default defineConfig({
text: "start_fn",
link: "/reference/builder/top-level/start-fn",
},
{
text: "state_mod",
link: "/reference/builder/top-level/state-mod",
}
],
},
{
Expand All @@ -240,6 +244,10 @@ export default defineConfig({
text: "name",
link: "/reference/builder/member/name",
},
{
text: "overwritable",
link: "/reference/builder/member/overwritable",
},
{
text: "skip",
link: "/reference/builder/member/skip",
Expand All @@ -248,6 +256,10 @@ export default defineConfig({
text: "start_fn",
link: "/reference/builder/member/start-fn",
},
{
text: "transparent",
link: "/reference/builder/member/transparent",
},
],
},
],
Expand Down
46 changes: 35 additions & 11 deletions website/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,55 @@ All the breaking changes are very unlikely to actually break your code that was

### Changed

- Reject unnecessary empty attributes e.g. `#[builder()]` or `#[builder]` with no parameters on a member ([#145](https://github.com/elastio/bon/pull/145))
- Reject non-empty `#[bon(...)]` attribute. This attribute will accept some parameters in future releases ([#145](https://github.com/elastio/bon/pull/145))
- Reject square brackets and curly braces delimiters for `builder_type`, `finish_fn`, `start_fn` and `on` attributes syntax. Only parentheses are accepted e.g. `#[builder(finish_fn(...))]` or `#[builder(on(...))]`. This no longer works: `#[builder(finish_fn[...])]` or `#[builder(on{...})]` ([#145](https://github.com/elastio/bon/pull/145))
- `#[builder(derive(Clone, Debug))]` now generates impl blocks that follow the behavior of standard `Clone` and `Debug` derives in that it conservatively adds `Clone/Debug` trait bounds for all the generic types declared on the original item (struct or function). See the *Added* section for details on the way to override these bounds with `#[builder(derive(Clone/Debug(bounds(...))))]`.

- ⚠️ **Breaking.** Reject unnecessary empty attributes e.g. `#[builder()]` or `#[builder]` with no parameters on a member ([#145](https://github.com/elastio/bon/pull/145))

- ⚠️ **Breaking.** Reject square brackets and curly braces delimiters for `builder_type`, `finish_fn`, `start_fn` and `on` attributes syntax. Only parentheses are accepted e.g. `#[builder(finish_fn(...))]` or `#[builder(on(...))]`. This no longer works: `#[builder(finish_fn[...])]` or `#[builder(on{...})]` ([#145](https://github.com/elastio/bon/pull/145))

- ⚠️ **Breaking.** Reject non-consecutive `on(...)` clauses. For example, the following now generates a compile error: `#[builder(on(String, into), finish_fn = build, on(Vec<_>, into))]`, because there is a `finish_fn = ...` between `on(...)` clauses.

- ⚠️ **Breaking.** `#[builder(derive(Clone, Debug))]` now generates impl blocks that follow the behavior of standard `Clone` and `Debug` derives in that it conservatively adds `Clone/Debug` trait bounds for all the generic types declared on the original item (struct or function). Previously no additional bounds were required on `Clone` and `Debug` impls. See the *Added* section for details on the way to override these bounds with `#[builder(derive(Clone/Debug(bounds(...))))]`.

- ⚠️ **Breaking.** The name of the builder struct generated for methods named `builder` changed from `TBuilderBuilder` to just `TBuilder` making methods name `builder` work the same as methods named `new`.

### Removed
- Removed support for `#[bon::builder]` proc-macro attribute on top of a `struct`. Use `#[derive(bon::Builder)]` for that instead. This syntax has been deprecated since `2.1` and it is now removed as part of a major version cleanup ([#145](https://github.com/elastio/bon/pull/145))

- ⚠️ **Breaking.** Remove support for `#[bon::builder]` proc-macro attribute on top of a `struct`. Use `#[derive(bon::Builder)]` for that instead. This syntax has been deprecated since `2.1` and it is now removed as part of a major version cleanup ([#145](https://github.com/elastio/bon/pull/145))

### Added

- Add `#[builder(builder_type(vis = "..."))]` that allows overriding the visibility of the builder struct ([#145](https://github.com/elastio/bon/pull/145))
- Add `#[builder(finish_fn(vis = "..."))]` that allows overriding the visibility of the finishing function ([#145](https://github.com/elastio/bon/pull/145))
- Add `#[builder(with = closure)]` syntax to customize setters with a custom closure. If the closure returns a `Result<_, E>` the setters become fallible ([#145](https://github.com/elastio/bon/pull/145))
- Add `#[builder(with = Some)]`, `#[builder(with = FromIterator::from_iter)]`, `#[builder(with = <_>::from_iter)]` support for two well-known functions that will probably be used frequently ([#157](https://github.com/elastio/bon/pull/157))

- Add `#[builder(builder_type(vis = "...", docs { ... }))]` that allows overriding the visibility and docs of the builder struct ([#145](https://github.com/elastio/bon/pull/145))

- Add `#[builder(finish_fn(vis = "...", docs { ... } ))]` that allows overriding the visibility and docs of the finishing function ([#145](https://github.com/elastio/bon/pull/145))

- Add `#[builder(start_fn(docs { ... }))]` that allows overriding the docs of the starting function ([#145](https://github.com/elastio/bon/pull/145))

- Add `#[builder(with = closure)]` syntax to customize setters with a closure. If the closure returns a `Result<_, E>` the setters become fallible ([#145](https://github.com/elastio/bon/pull/145))

- Add `#[builder(with = Some)]`, `#[builder(with = FromIterator::from_iter)]`, `#[builder(with = <_>::from_iter)]` syntax support for two well-known functions that will probably be used frequently ([#157](https://github.com/elastio/bon/pull/157))

- Add `#[builder(transparent)]` for `Option` fields to opt out from their special handling which makes `bon` treat them as regular required fields. It's also available at the top-level via `#[builder(on(_, transparent))]` ([#145](https://github.com/elastio/bon/pull/145), [#155](https://github.com/elastio/bon/pull/155))

- Add `#[builder(state_mod)]` to configure the builder's type state API module name, visibility and docs ([#145](https://github.com/elastio/bon/pull/145))
- Add `#[builder(overwritable)]` and `#[builder(on(..., overwritable)]` to make it possible to call setters multiple times for the same member ([#145](https://github.com/elastio/bon/pull/145))

- 🔬 **Experimental.** Add `#[builder(overwritable)]` and `#[builder(on(..., overwritable)]` to make it possible to call setters multiple times for the same member. This attribute is available under the cargo feature `"experimental-overwritable"` (stabilization is tracked in [#149](https://github.com/elastio/bon/issues/149), let us know if you need this attribute!). ([#145](https://github.com/elastio/bon/pull/145))

- Add `#[builder(setters)]` to fine-tune the setters names, visibility and docs ([#145](https://github.com/elastio/bon/pull/145))

- Add `#[builder(derive(Clone/Debug(bounds(...))]` to allow overriding trait bounds on the `Clone/Debug` impl block of the builder ([#145](https://github.com/elastio/bon/pull/145))

- Improve rustdoc output ([#145](https://github.com/elastio/bon/pull/145))

- Add info that the member is required or optional.

- For members with defaults values show the default value in the docs.

- For optional members provide a link to a companion setter. The docs for `{member}(T)` setter mention the `maybe_{member}(Option<T>)` setter and vice versa.

- Remove `__` prefixes for generic types and lifetimes from internal symbols. Instead, the prefixes added only if the macro detects a name collision.
- Add inheritance of `#[allow()]` and `#[expect()]` lint attributes to all generated items. This is useful to suppress any lints coming from the generated code. Although, lints coming from the generated code are generally considered defects in `bon` and should be reported via a Github issue but this provides an easy temporary workaround the problem ([#145](https://github.com/elastio/bon/pull/145))

- Add inheritance of `#[allow()]` and `#[expect()]` lint attributes to all generated items. This is useful to suppress any lints coming from the generated code. Although, lints coming from the generated code are generally considered defects in `bon` and should be reported via a Github issue but this provides an easy temporary workaround for the problem ([#145](https://github.com/elastio/bon/pull/145))

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion website/reference/builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Click on the name of the attribute to view detailed docs.

| Attribute | Short description
| -- | -- |
| [`builder_type`](./builder/top-level/builder-type) | Overrides the name, visibility and docs of the generated builder
| [`builder_type`](./builder/top-level/builder-type) | Overrides the name, visibility and docs of the builder struct
| [`derive`](./builder/top-level/derive) | Generates additional derives on the builder struct itself
| [`finish_fn`](./builder/top-level/finish-fn) | Overrides the name, visibility and docs of the finishing function
| [`on`](./builder/top-level/on) | Applies the given builder attributes to all members matching a type pattern
Expand Down
3 changes: 3 additions & 0 deletions website/reference/builder/member/overwritable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 🔬 `overwritable`

This is an **experimental** attribute available under the cargo feature `"experimental-overwritable"`.
3 changes: 3 additions & 0 deletions website/reference/builder/member/transparent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `transparent`

TODO: add docs
58 changes: 46 additions & 12 deletions website/reference/builder/top-level/builder-type.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,55 @@
---
outline: deep
---

# `builder_type`

**Applies to:** <Badge text="structs"/> <Badge text="free functions"/> <Badge text="associated methods"/>

Overrides the name of the generated builder struct.
Overrides the name, visibility and docs of the builder struct.

**Short syntax** configures just the *name*.

```attr
#[builder(builder_type = CustomName)]
```

**Long syntax** provides more flexibility. All keys are optional.

```attr
#[builder(
builder_type(
name = CustomName,
vis = "pub(crate)",
doc {
/// Custom docs
}
)
)]
```

The default naming pattern is the following:
## `name`

| Underlying item | Naming pattern |
| -------------------------- | --------------------------------------------- |
| Struct | `{StructName}Builder` |
| `StructName::new()` method | `{StructName}Builder` |
| Free function | `{PascalCaseFunctionName}Builder` |
| Associated method | `{SelfTypeName}{PascalCaseMethodName}Builder` |
The default name for the builder struct is chosen according to the following rules:

The attribute expects the desired builder type identifier as its input.
| Syntax | Default
| -----------------------------------|------------------------
| `struct T` | `{T}Builder`
| Associated `fn` `T::new/builder()` | `{T}Builder`
| Associated `fn` `T::fn_name()` | `{T}{FnName}Builder`
| Free `fn` `fn_name()` | `{FnName}Builder`

### `vis`
## `vis`

**Example:**
The visibility must be enclosed with quotes. Use `""` or [`"pub(self)"`](https://doc.rust-lang.org/reference/visibility-and-privacy.html#pubin-path-pubcrate-pubsuper-and-pubself) for private visibility.

The default visibility is the same as the visibility of the underlying `struct` or `fn`.

## `doc`

Simple documentation is generated by default. The syntax of this attribute expects a block with doc comments.

## Examples

::: code-group

Expand Down Expand Up @@ -58,7 +90,9 @@ let builder: MyBuilder = Brush::builder();

:::

You'll usually want to override the builder type name when you already have such a name in scope. For example, if you have a struct and a function with the same name annotated with `#[builder]`:
---

You'll usually want to override the builder type name when you already have such a name in scope. For example, if you have a struct and a function with the same name both annotated with `#[derive(Builder)]` and `#[builder]`:

::: code-group

Expand Down
63 changes: 60 additions & 3 deletions website/reference/builder/top-level/derive.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

**Applies to:** <Badge text="structs"/> <Badge text="free functions"/> <Badge text="associated methods"/>

*⚠️ Do not confuse this with `derive(bon::Builder)`⚠️*

Generates additional derives on the builder struct itself. The syntax is similar to the regular `#[derive(...)]` attribute, but it must be wrapped in `#[builder(derive(...))]`. Expects one or more of the supported derives separated by commas.
*⚠️ Do not confuse this with `#[derive(bon::Builder)]`⚠️*

Generates additional derives on the builder struct itself. The syntax is similar to the regular `#[derive(...)]` attribute, but it must be wrapped in `#[builder(derive(...))]`. Expects one or more of the supported derives separated by a comma.

The following derives are supported: `Clone`, `Debug`.

Expand Down Expand Up @@ -120,6 +119,64 @@ assert_eq!(

:::

## Generic types handling

If the underlying `struct` or `fn` contains generic type parameters, then the generated impl block will include a `where` bound requiring the respective trait (`Clone` or `Debug`) to be implemented by all of them. This follows the behavior of the [standard `derive` macros](https://doc.rust-lang.org/std/clone/trait.Clone.html#derivable).

This works fine in most of the cases, but sometimes the generated bounds may be overly restrictive. To fix that, you can manually specify the bounds using the syntax `#[builder(derive(Trait(bounds(...))))]`, where `...` is a comma-separated list of `where` bounds.

See the example of this problem, and how it can be fixed (click on the tab `Fixed` in the code snippet):

::: code-group

```rust [Overly restrictive]
use bon::Builder;
use std::rc::Rc;

#[derive(Builder)]
#[builder(derive(Clone))]
struct Example<T, U> {
x: Rc<T>,
y: U,
}

struct NonCloneable;

let builder = Example::<_, ()>::builder().x(Rc::new(NonCloneable));

// `Rc` can be cloned even if `T` is not `Clone`, but this code // [!code error]
// doesn't compile, because the `Clone` impl for `ExampleBuilder` // [!code error]
// conservatively requires `T: Clone` // [!code error]
builder.clone(); // [!code error]
```

```rust [Fixed]
use bon::Builder;
use std::rc::Rc;

#[derive(Builder)]
// Only a bound `U: Clone` is needed in this case // [!code highlight]
#[builder(derive(Clone(bounds(U: Clone))))] // [!code highlight]
struct Example<T, U> {
x: Rc<T>,
y: U,
}

struct NonCloneable;

let builder = Example::<_, ()>::builder().x(Rc::new(NonCloneable));

// Now this works, because there is no bound `T: Clone` // [!code highlight]
builder.clone();
```
:::


::: tip
If you'd like to know why this attribute is this dumb and doesn't just add a `where Rc<T>: Clone` bound instead, then check this article about the ["Implied bounds and perfect derive"](https://smallcultfollowing.com/babysteps/blog/2022/04/12/implied-bounds-and-perfect-derive/) by Niko Matsakis 📖.

:::

## Compile errors

_Requires_ that all members of the builder including the receiver (if this is a builder for an associated method) implement the target trait. For example, this doesn't compile because not all members implement `Clone`:
Expand Down
Loading
Loading