Skip to content

Commit 6ddd225

Browse files
committed
lombok: force field-access in @EqualsAndHashCode / @tostring
Set lombok.equalsAndHashCode.doNotUseGetters = true and lombok.toString.doNotUseGetters = true in lombok.config so Lombok- generated equals/hashCode/toString read fields directly instead of routing through `this.getX()`. Motivation: several value classes expose getters that wrap their `@Nullable T` field in Optional<T> (ChatRequest.getToolChoice, ChatMessage.getToolCallId) or wrap a list field in Collections.unmodifiableList + Optional (ChatMessage.getParts) for the public-API contract. Those wrappers are not the equality contract. The previous getter-routing behaviour caused two real defects: 1. fb-contrib OI_OPTIONAL_ISSUES_CHECKING_REFERENCE fired on every Lombok-generated `this$x == null` branch when the getter returned an Optional. Optional is the standard "never null" type, so the null arm was dead code — fb-contrib correctly flagged it, contradicting my prior characterisation as a "false positive". 2. ChatMessage.getParts() allocated a fresh Collections.unmodifiableList AND a fresh Optional on every equals or hashCode call. With field access these allocations disappear entirely. Switching is semantically identical: Optional.equals and Collections.unmodifiableList(x).equals(...) both delegate to value- based comparison of the underlying state. Verified by an audit covering every Lombok-annotated class: - Bucket 1 (verbatim getter): 11 classes — bit-identical output. - Bucket 2 (Optional/unmodifiable wrapper getter): ChatRequest, ChatMessage — both benefit from the switch. - Bucket 3 (getter does non-trivial work equality should see): 0. - Bucket 4 (@ToString-only identity classes): unaffected. All value classes are `final`, so subclass-override of a getter cannot change equality. callSuper=true chains (InferenceParameters/ModelParameters) are unaffected because the parent classes have no getter on their own included field. No test in the repository pins a Lombok-format `Class(field=value)` substring. SpotBugs Max+Low: 6 -> 2 findings (the 4 OI_OPTIONAL_ISSUES_CHECKING_REFERENCE entries on ChatRequest/ChatMessage equals + hashCode clear). The remaining 2 findings (DLS_DEAD_LOCAL_STORE on cancelHook, SPP_FIELD_COULD_BE_STATIC on LlamaModel.ctx) are pre-existing and unrelated. Full test suite: 921 tests pass (the 1 error is the environmental RerankingModelTest.setup UnsatisfiedLinkError — no native library on this sandbox — not a regression).
1 parent 4f1fbd7 commit 6ddd225

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

lombok.config

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,36 @@ lombok.addLombokGeneratedAnnotation = true
1818
lombok.equalsAndHashCode.callSuper = skip
1919
lombok.toString.callSuper = skip
2020

21+
# Force Lombok's @EqualsAndHashCode / @ToString to read FIELDS directly
22+
# instead of routing through `this.getX()` (the default). Rationale:
23+
#
24+
# Some classes expose value-add getters that wrap their @Nullable field
25+
# in an Optional (e.g. ChatRequest.getToolChoice / ChatMessage.getToolCallId)
26+
# or wrap a list field in Collections.unmodifiableList + Optional
27+
# (ChatMessage.getParts). Those wrappers are the public-API contract,
28+
# not equality contracts:
29+
#
30+
# 1. fb-contrib's OI_OPTIONAL_ISSUES_CHECKING_REFERENCE fires on every
31+
# Lombok-generated `this$x == null` branch when `x` is an Optional —
32+
# Optional is the standard "never null" type, so the null branch is
33+
# dead code.
34+
# 2. ChatMessage.getParts() allocates a fresh
35+
# Collections.unmodifiableList AND a fresh Optional on every equals
36+
# call. Field-access avoids both allocations per comparison.
37+
# 3. The two forms are semantically equivalent: Optional.equals and
38+
# Collections.unmodifiableList(x).equals(...) both delegate to
39+
# value-based comparison of the underlying state.
40+
#
41+
# All value classes in this repo are `final`, so subclass-override of a
42+
# getter cannot change equality (no subclasses exist). callSuper=true
43+
# chains are unaffected — `super.equals()` is still a method call, and
44+
# the parent class's own field handling is governed by the same setting.
45+
# Verified by the audit at commit time: zero Bucket-3 classes (where
46+
# getter form would be preferred), zero tests pinning Lombok-format
47+
# `Class(field=value)` substrings.
48+
lombok.equalsAndHashCode.doNotUseGetters = true
49+
lombok.toString.doNotUseGetters = true
50+
2151
# Do NOT generate Spring-style @ConstructorProperties; java.beans is not
2252
# needed by this codebase and pulls in the desktop module on some JDKs.
2353
lombok.anyConstructor.addConstructorProperties = false

0 commit comments

Comments
 (0)