GitNexus/gitnexus/bench/lib/identity-guard.mjs
ChunxueLi 4aa6bddd0a
feat(jvm): synthesize Lombok and Kotlin JVM accessor methods (#2885)
* feat(java): synthesize Lombok @Data/@Getter/@Setter accessor methods

* fix(lombok): resolve class identity by AST node id, not simple name

Root-cause fix for the bot review's name-ambiguity findings:

1. Cross-file collision: the owner map was rebuilt per file from
   result.symbols, which accumulates across the whole language group —
   a later Java file with the same simple class name resolved to the
   earlier file's class node. The map is now filled INSIDE the capture
   loop (per-file scope) and keyed by the class_declaration AST node
   id (SyntaxNode.id), which is unique by construction.

2. Same-tail nested classes (Outer.A vs Other.A): a name-keyed map
   overwrote one with the other; AST-node-id keys cannot collide.

3. Synthesized method ids now follow the SAME convention real nested
   member ids use (keyed by the class's own simple name, matching
   findEnclosingClassInfo().className), so call resolution can hit
   synthesized accessors exactly like hand-written ones.

4. Lombok semantics: setters are no longer generated for final fields
   (Lombok never emits those) and @Setter(AccessLevel.NONE) now
   suppresses setters, symmetric to the existing getter suppression.

Also tightens two vacuous test loops flagged by the bot (empty-array
for..of passed trivially): counts are asserted before property loops,
and a new regression test pins distinct owners for same-tailed nested
classes plus the real id convention for nested accessors.

* feat(java): synthesize Lombok accessors via provider hook and scope dual-path

Replace the worker language===Java branch with LanguageProvider.synthesizeStructureMembers,
align MethodRegistry ownership through scope captures, and bump parse-cache schema to 83
so warm caches cannot replay pre-synthesis worker output.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(java): cover Lombok synthesis semantics, cache replay, and CI bench

Add unit/integration matrices (including durable cold/warm/historical parse-cache),
a permanent no-Lombok vs Lombok-heavy harness with fingerprint budgets, and a CI
--check step. Document that Kotlin→Java member CALLS remains a pre-existing gap.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(lombok): drop dead state and redundant scans from accessor synthesis

Collapse Lombok import provenance into one compilation-unit scan with a cached
wildcard flag, remove unused planned-accessor fields and the duplicate @Data
enable flag, and plan scope captures without wrapping a fake Parser.Tree.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#2885)

Give each Lombok accessor a unique scope range so multi-declarator fields do not share @scope.function IDs, and type the owner map as ReadonlyMap to match the provider hook.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(bench): pin the real Lombok synthesis fingerprint (#2885)

The committed baseline held a fingerprint no revision of this branch ever
produced, so the CI guard failed on every push. Re-pin it to the value the
synthesizer deterministically emits and correct the method count the comment
claims (800 x 4 x 2 = 6400, not 12800).

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(kotlin): synthesize JVM accessors using shared beanspec helpers (#2885)

Kotlin val/var properties now emit the same JavaBeans get/set Methods as Lombok, via jvm/beanspec + jvm/synthetic-accessors. SCHEMA_BUMP 84 invalidates warm caches that would omit those callables.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(kotlin): match kotlinc JVM accessor ABI (#2885)

Emit custom getters, preserve is-prefix names, and convert synthetic
graph lines to 0-based so same-name accessors resolve to the owner.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#2885)

Restrict Lombok provenance to lombok/experimental FQNs and match Kotlin existing methods by exact JVM name.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(jvm): consolidate accessor synthesis (#2885)

Keep language-specific discovery in Java and Kotlin adapters while centralizing owner orchestration, collision policy, graph emission, and captures.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(jvm): align accessor synthesis with compiler ABI (#2885)

Match Lombok and kotlinc provenance, companion owners, and collision
arity so mixed-JVM CALLS bind to the Methods compilers actually emit.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#2885)

Mark Kotlin interface accessors abstract, pin the Lombok case-fold collision test, and document the non-lowercase is-prefix rule.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#2885)

Honor explicit Getter/Setter over @Data regardless of order, and let field @Accessors replace class-level fluent/chain.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): pin Kotlin scope-capture fingerprint after interface accessors (#2885)

Invalidate warm parse cache so interface property Methods are not replayed as concrete.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: ChunxueLi <mecoloud@users.noreply.gitee.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 17:55:52 +00:00

62 lines
1.8 KiB
JavaScript

/**
* Shared fingerprint + --check for JVM accessor synthesis benches.
*/
import fs from 'node:fs';
import crypto from 'node:crypto';
export function fingerprintIds(ids) {
return crypto
.createHash('sha256')
.update([...ids].sort().join('\n'))
.digest('hex');
}
export function minSample(run, warmup, reps) {
for (let w = 0; w < warmup; w++) run();
const samples = [];
let last;
for (let r = 0; r < reps; r++) {
const t0 = performance.now();
last = run();
samples.push(performance.now() - t0);
}
return { last, ms: Math.min(...samples) };
}
export function runMethodCountCheck(report, expectedCounts) {
const errors = [];
for (const [arm, expected] of Object.entries(expectedCounts)) {
const actual = report[arm]?.methods;
if (actual !== expected) {
errors.push(`${arm}.methods ${String(actual)} != ${expected}`);
}
}
if (errors.length) {
console.error(JSON.stringify({ report, errors }, null, 2));
process.exit(1);
}
}
export function runBaselineCheck(report, baselinePath) {
const baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
const errors = [];
if (report.fingerprint !== baseline.fingerprint) {
errors.push(`fingerprint drift: ${report.fingerprint} != ${baseline.fingerprint}`);
}
if (report.scaling_ratio > baseline.scaling_budget) {
errors.push(`scaling_ratio ${report.scaling_ratio} > ${baseline.scaling_budget}`);
}
if (
baseline.widening_overhead_budget !== undefined &&
report.widening_overhead > baseline.widening_overhead_budget
) {
errors.push(
`widening_overhead ${report.widening_overhead} > ${baseline.widening_overhead_budget}`,
);
}
if (errors.length) {
console.error(JSON.stringify({ report, errors }, null, 2));
process.exit(1);
}
console.log(JSON.stringify({ ok: true, report }, null, 2));
}