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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ jobs:
cargo +${{ matrix.toolchain }} clippy --all-features --all-targets --locked \
-- \
--allow edition-2024-expr-fragment-specifier \
--allow if_let_rescope \
--allow impl-trait-overcaptures
cargo-miri:
Expand All @@ -149,7 +150,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: nightly-2024-10-14
toolchain: nightly-2024-10-30
components: miri

- run: |
Expand All @@ -159,6 +160,7 @@ jobs:
RUSTFLAGS: >-
--deny warnings
--allow edition-2024-expr-fragment-specifier
--allow if_let_rescope
--allow impl-trait-overcaptures
cargo-doc:
Expand Down
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();
}
2 changes: 2 additions & 0 deletions bon/tests/integration/builder/attr_default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ fn struct_alloc() {
);

#[bon]
#[allow(clippy::items_after_statements)]
impl Sut {
#[builder]
fn assoc(
Expand Down Expand Up @@ -174,6 +175,7 @@ fn struct_no_std() {
);

#[bon]
#[allow(clippy::items_after_statements)]
impl Sut {
#[builder]
fn assoc(self, #[builder(default)] arg1: u32, #[builder(default = 43)] arg2: u32) -> Self {
Expand Down
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))"],
);
}
}
1 change: 1 addition & 0 deletions bon/tests/integration/builder/init_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ fn struct_init_order() {
);

#[bon]
#[allow(clippy::items_after_statements)]
impl Sut {
#[builder]
fn sut(
Expand Down
20 changes: 12 additions & 8 deletions bon/tests/integration/builder/raw_idents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,19 @@ fn struct_case() {
#[test]
#[allow(non_camel_case_types)]
fn fn_case() {
#[builder]
fn r#type(r#type: u32, #[builder(name = r#while)] other: u32) {
let _ = (r#type, other);
}
{
#[builder]
fn r#type(r#type: u32, #[builder(name = r#while)] other: u32) {
let _ = (r#type, other);
}

r#type().r#type(42).r#while(100).call();
r#type().r#type(42).r#while(100).call();
}

#[builder(builder_type = r#type, state_mod = r#mod)]
fn sut() {}
{
#[builder(builder_type = r#type, state_mod = r#mod)]
fn sut() {}

let _: r#type = sut();
let _: r#type = sut();
}
}
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