Skip to content

Build from - #391

Open
BernardIgiri wants to merge 12 commits into
elastio:masterfrom
BernardIgiri:build_from
Open

Build from#391
BernardIgiri wants to merge 12 commits into
elastio:masterfrom
BernardIgiri:build_from

Conversation

@BernardIgiri

Copy link
Copy Markdown

Add build_from and build_from_clone builder methods

Closes #310

This PR is a fresh continuation of the original #313 , which I accidentally closed when rebasing my master branch. I have since transplanted my work on here:

Original PR Overview

This PR adds support for two new optional top-level builder attributes:

  • #[builder(build_from)] – Adds a .build_from(T) / .call_from(T) method (takes T by value).
  • #[builder(build_from_clone)] – Adds a .build_from_clone(&T) / .call_from_clone(&T) method (takes &T and clones fields).

These methods allow partially configured builders to fill in missing fields from an existing instance of the target type before finalizing the build. This is useful when:

  1. You want to override only a few fields but reuse most of the existing data.
  2. You're working with types that aren't Default, making field reuse non-trivial.

Example

Rust

#[derive(Builder, Clone)]
#[builder(build_from, build_from_clone)]
struct User {
    name: String,
    age: u8,
}

let jon = User::builder().name("Jon".into()).age(25).build();
let alice = User::builder().name("Alice".into()).build_from_clone(&jon);

assert_eq!(alice.name, "Alice");
assert_eq!(alice.age, 25);

Implementation Notes

  • The code generation engine lives in builder_gen/build_from.rs and is conditionally compiled/emitted via the experimental-build-from feature flag.
  • Integrating ItemSigConfig directly into darling::FromMeta allows native attribute parsing without structural wrapper types.
  • Each field is handled according to its member kind:
    • Named fields: Fallback to the instance data if not explicitly set in the builder state.
    • FinishFn fields: Defer to the instance data.
    • Skip fields: Drop back to Default::default().

WIP Status & Next Steps

This PR is currently open as a Draft / WIP. Before marking it ready for full review, I am working on polishing a few key items:

  • Inherit Attributes: Ensure that custom visibility (vis) and documentation (doc) passed inside #[builder(build_from(...))] are properly inherited by the generated methods.
  • Function/Method Builders: Refactor code generation to correctly support bon when applied to functions or associated methods (ensuring we invoke the execution block rather than blindly emitting a structural literal path).
  • Expanded Integration Tests: Adding test coverage for complex namespaces, turbofish paths, and custom vis/doc combinations.

@RobertasJ

Copy link
Copy Markdown

just saw this pr, got a suggestions.

build_from_clone seems like a duplicate of build_from. if you have a builder where all the fields are clone, you can just implement Clone for the struct like its done in your example, then you can call .build_from(template.clone()).

Another thing is that you should probably mark this pr as a draft pr (makes it easy to quickly see why a pr isnt merged).

also isnt your pr description kind of weird. theres no need to inherit attributes or to write weird implementation details that don't make sense without looking at the code in the first place. you also mention .call_from when you never elaborate on what it is. please make it more sense xd

@arsenyinfo

Copy link
Copy Markdown

Function-builder build_from/call_from bypasses the wrapped function body

  • Lens: correctness, security, simplicity
  • Priority: P1
  • Location: bon-macros/src/builder/builder_gen/build_from.rs:73-84
  • Scenario: #[builder(build_from)] or #[builder(build_from_clone)] on a free function or associated method
  • Potential solution: Generate the method body by reusing self.finish_fn.body.generate(ctx) (which already binds member variables and invokes the renamed positional function / awaits async fn output) instead of emitting a raw #ctor_path { ... } struct literal of the return type.

build_from fails to compile for Option<T> named members

  • Lens: correctness
  • Priority: P1
  • Location: bon-macros/src/builder/builder_gen/build_from.rs:98-114
  • Scenario: A struct with a plain Option<T> field and #[builder(build_from)]
  • Potential solution: In field_vars_from_members, detect member.is_special_option_ty() and use the stored Option<T> directly with from.#ident as the fallback, mirroring finish_fn_member_expr and skipping the Some(value) unwrap.

#[builder(skip = expr)] value is ignored in build_from paths

  • Lens: correctness, security, simplicity
  • Priority: P2
  • Location: bon-macros/src/builder/builder_gen/build_from.rs:127-129
  • Scenario: A field annotated #[builder(skip = custom_expr)] is used with build_from or build_from_clone
  • Potential solution: Emit the member's configured value expression (via self.sanitize_expr) when present, falling back to Default::default() only when absent, matching the behavior of finish_fn_member_expr.

finish_fn members generate invalid field access on the target type

  • Lens: correctness, simplicity
  • Priority: P2
  • Location: bon-macros/src/builder/builder_gen/build_from.rs:116-125
  • Scenario: A builder with a #[builder(finish_fn)] member is used with build_from or build_from_clone
  • Potential solution: Reject build_from/build_from_clone at attribute-validation time when finish_fn members exist, or emit Default::default()/the member's default value for them instead of from.#ident.

User-configured vis/doc for build_from methods is parsed but ignored

  • Lens: correctness, security, simplicity
  • Priority: P2
  • Location: bon-macros/src/builder/builder_gen/build_from.rs:49-54, 75-78
  • Scenario: #[builder(build_from(name = "x", vis = "pub(crate)", doc = "..."))] or the same on build_from_clone
  • Potential solution: Emit #vis fn #name(...) and #(#docs)* from the parsed ItemSigConfig, defaulting visibility to the finish function's/builder's visibility, instead of hardcoding pub fn and fixed doc strings.

Per-member and on(...) build_from flags are parsed but never used

  • Lens: correctness, simplicity
  • Priority: P3
  • Location: bon-macros/src/builder/builder_gen/member/config/mod.rs:68-69, 241-247; bon-macros/src/builder/builder_gen/top_level_config/on.rs:13-14, 79-85, 121
  • Scenario: #[builder(build_from)] on an individual field or on(_, build_from)
  • Potential solution: Remove the per-member and on(...) build_from fields/params until per-member opt-in/out is implemented, or gate the instance fallback in field_vars_from_members on those flags.

Unused impl FromMeta for ItemSigConfig<N>

  • Lens: simplicity
  • Priority: P3
  • Location: bon-macros/src/parsing/item_sig.rs:100-104
  • Scenario: always
  • Potential solution: Delete the unused impl FromMeta; all current consumers parse via ItemSigConfigParsing::new(...).parse().

🔍 Reviewed by nitpicker

@RobertasJ

Copy link
Copy Markdown

I feel like im reading something written in riddles... Isnt ai supposed to be good at making people understand stuff? I feel like i am trying to read a raw slotmap and its entries instead of printing out a tree it represents, this response has the same exact vibe

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: method to fill the remaining values from an instance of existing built object

3 participants