Repository navigation
Seed referenced builders via field functions when no copy path exists (#310) - #321
Merged
AndreasIgel merged 14 commits intoOct 3, 2026
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
AndreasIgel
reviewed
Oct 3, 2026
AndreasIgel
reviewed
Oct 3, 2026
Builders lacking a seeded path (ctor(T), static T-factory, toBuilder) are now tried against field-function seeding: when every readable property of the referenced type has a matching name(v)/setName(v) function on the builder, the generated builder emits a private seed<Builder>(T) method that obtains an empty builder and calls one field function per property. Builders whose properties are only partially covered still resolve but are marked degraded, so consumer helpers that would silently drop state are skipped with a debug note.
- propertyNameOf only strips get/is before an uppercase letter, so accessors like island()/getting() keep their own name. - Resolver tests now exercise record components, direct fields, and isX() boolean accessors on top of the getter-based shapes.
…/setName The resolver now collects the builder's single-parameter methods and derives candidate naming conventions: '' (fluent name(v)) and 'set' (JavaBeans) first, then any other camel-case prefix shared by at least two methods (e.g. withName(v) as builder frameworks emit). The first prefix covering every readable property wins and is stored on FieldSeedingCall inside ResolvedBuilder; SeededField reduces to (property, accessor) and the emitted call is derived via builderMethodFor().
Candidate prefixes are now sorted by how many readable properties each actually covers instead of a fixed '' -> set -> detected order; '' and 'set' remain always-tried candidates and only win on ties. The matching logic is shared by coveredCount and matchByPrefix via findFieldFunction.
A detected prefix now needs at least min(2, properties) occurrences, so a single-property type resolving against one withX-style method is accepted instead of rejected; for several properties full coverage already implies the count.
… off) The seeding analysis (property enumeration + builder method scan + prefix ranking) is per-type compile cost, so it is opt-in: a new OptionState on @SimpleBuilder.Options / BuilderConfiguration / -Asimplebuilder. usingFieldFunctionSeeding, only consulted when usingExistingBuilders is enabled. Disabled, builders without a seeded path degrade as before. Resolver and processing tests enable the flag; a probe asserts the disabled gate.
- Emit 'prefill<Builder>' (private static, ordered last) instead of 'seed<Builder>' for consistency with funcForPrefilledBuilder. - Trust a field-function convention once it covers the majority of the readable properties; uncovered properties stay unset. - Reorder resolveByBuilderContract so the empty-path gate precedes the prefilled checks; factor anchored static/instance factory lookups into named methods instead of an Optional.or chain; collectSeededFields returns a list reused by coveredCount; Optional.ofNullable in ResolvedBuilder; StringUtils.substring for prefix extraction; single-colon filter predicates.
Matches the emitted prefill<Builder> naming. The option is now ENABLED in BuilderConfiguration.DEFAULT for @SimpleBuilder and explicitly DISABLED in @SimpleMinimalBuilder, keeping its feature set minimal.
FieldSeedingCall -> PrefillCall, SeededField -> PrefilledField, FieldSeedingEnhancer -> FieldPrefillEnhancer, resolveFieldSeeding -> resolveFieldFunctionPrefill, plus prose: seeded path -> prefilled path, seeding -> prefilling. Matches usingFieldFunctionPrefill and the emitted prefill<Builder> methods.
Review fixes: replace the hand-rolled firstUpperCaseIndex loop with StringUtils.indexOfAny, and the negated lambda with Predicate.not(prefixes::contains).
devin-ai-integration
Bot
force-pushed
the
devin/1791048085-field-function-seeding
branch
from
October 3, 2026 20:36
23a2543 to
d5b005d
Compare
…Fields, guard record components - S3776: extract unwrapContainerInstances from findAnnotationInstances (cognitive complexity 19 -> under limit) - S9391: stream pipeline in collectPrefilledFields - S2259: Optional.ofNullable around getRecordComponents()
The flagged deref was the receiver, not the record-components list: sibling finders already guard a null type the same way.
…nvention - 'consumer helpers are skipped' debug only when usingFieldFunctionPrefill is enabled - fieldFunctions collection becomes JavaLangAnalyser.findFieldFunctions - all candidate conventions are scored and the best-covered one wins; Optional.empty only when the best misses the majority - empty/set keep tie priority through the stable sort
AndreasIgel
reviewed
Oct 3, 2026
- rename PrefilledField to Field: PrefillCall.Field carries the context - static prefilledField factory makes the coverage map a :: reference - test fixture builders now retain their values so build() produces a populated instance - the mock builders act as real builders
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Implements #310 (last sub-task of #301).
Summary
Builders that satisfy the empty-instantiation +
build()contract but offer no prefilled path (noctor(T), no staticT-factory, notoBuilder()) previously resolved but skipped every consumer helper — the field-silent-loss issue #310 describes. This adds a fourth prefilled-path tier: field-function prefilling, controlled by a newusingFieldFunctionPrefilloption (default ENABLED for@SimpleBuilder, disabled by@SimpleMinimalBuilder; only consulted whenusingExistingBuildersis enabled, since the per-type analysis adds compile cost).JavaLangAnalyser.findReadablePropertiesenumerates a type's readable properties in declaration order (record components →x(),getX()/isX()methods →x(), fields →x; first source wins).name(v)(fluent) andsetName(v)(JavaBeans) plus any other camel-case prefix found on the builder's single-parameter methods, ranked by how many properties each actually covers. The detected prefix is stored onPrefillCallinsideResolvedBuilder.funcForPrefilledBuilder=Optional.empty()), so helpers that would silently drop state stay skipped and a debug note is logged.FieldPrefillEnhancer(priority 15, dedup viadistinct()) emits oneprivate static prefill<Builder>(T value)method per referenced builder type, ordered last in the generated class (belowtoString).setName-style), AutoValue-fluent, andwithX-style builder shapes now resolve where they previously fell back.Design notes
builder(T)→toBuilder()→ field functions (last, since it produces per-property calls). Anchored static/instance lookups are separate named functions, noOptional.orchains.Verification
set/detected-prefix shapes, single-property prefix acceptance, majority-vs-uncovered coverage, record/field/isXproperties, and the disabled gate — plus generated-source assertions on the emittedprefill<Builder>method.