diff --git a/gitnexus/bench/scope-capture/baselines.json b/gitnexus/bench/scope-capture/baselines.json index aee412fc0..c36db239f 100644 --- a/gitnexus/bench/scope-capture/baselines.json +++ b/gitnexus/bench/scope-capture/baselines.json @@ -132,8 +132,9 @@ "_rebaselined": "#1919 review CF3 fix: extended kotlin-local-property-owner (init/accessor destructuring) + new dart-accessor-owner fixture (getter/setter ownership). Fingerprint-only corpus drift; scaling ~1.0." }, "java": { - "fingerprint": "b29e263524f55151dcb7cfc4c929d3d1d7bb360355cee4e832158f927857f663", + "fingerprint": "36d689c58526c4482fbd701d1d9ca156623a3970734ead145717858712271ab5", "scaling_budget": 1.5, + "_rebaselined_2900_record_heritage": "#2900 review follow-up: the Java scale unit now includes a record implementing Marker, so the record-declaration @reference.inherits path is fingerprinted and exercised at scale. Prior b29e263524f55151dcb7cfc4c929d3d1d7bb360355cee4e832158f927857f663 -> 36d689c58526c4482fbd701d1d9ca156623a3970734ead145717858712271ab5; scaling 1.042 < 1.5.", "_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata; same-name lexical regions use an O(ancestor-depth) ID-set lookup. Prior d5c59d7dc9e206637515d5aea1163f7c1cdd76410c38c5fe6143d13d19677d6a -> 004a3592998dca1193bd1429a8284513725de7764f2a3eceedaaa984cfd763b4; scaling 0.992 < 1.5.", "_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: Java method-reference/SAM callable flow facts with invocation-result suppression. Prior 062d754764aaa8a6772fb90875c710502a63e3e7a300e633942381ed914faada -> d5c59d7dc9e206637515d5aea1163f7c1cdd76410c38c5fe6143d13d19677d6a; scaling 1.074 < 1.5.", "_rebaselined": "#2357 (supersedes #2353): + java-cast-receiver, java-this-field-chain, java-this-dispatch fixtures (cast-wrapped receivers, this.field chains incl. initializer contexts, bare-this dispatch pinning). Drift is purely fixture-additive: with the three new dirs parked, the fingerprint reproduces the prior baseline byte-identically — no emit/capture change. #1956 synth-widening: + java-iface-extends fixture; synthesizeJavaInheritanceReferences now ALSO walks interface_declaration extends_interfaces (interface IA extends IB, IC), matching the #1940 legacy leg. (Earlier U2+review: java-qualified-base fixture covers 2- AND 3-segment qualified bases guarding the legacy end-anchor; synth tail-resolves scoped bases.) Linear (~1.03). (Earliest: java added to bench, exposed+fixed the O(n^2) findNodeAtRange root-walk; 3.09 -> ~0.99.) | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged.", @@ -146,9 +147,9 @@ "_rebaselined_2562_local_classes": "#2562: Java block-local classes, enums, records, and interfaces use source-type-relative JLS 13.1 Host$NLocal identities with javac-compatible per-(host, simple-name) numbering; anonymous numbering remains separate. Lexical aliases begin at each declaration and end with its immediate block. Expanded java-local-class-naming fixtures cover declaration order, disjoint blocks, initializers, lambdas, local type kinds, and recursive local/member/anonymous host chains. Prior d04298a91beec76d0fa7099b3d71265723be60c1df688969aa954f135dd49686 -> 6dd5913a58400a191ff54abf9b852b03d5add657d16c11e60a7c4608ba186197; scaling 1.204 < 1.5.", "_rebaselined_receiver_chain_2747": "#2747 receiver-chain rollout: call matches whose receiver is itself an expression now carry `@reference.receiver-chain`, a compact encoding of the receiver's structure, so resolution types it by folding instead of re-parsing receiver source text. Capture GROUP counts are unchanged — the tag is added to existing call matches, never a new match — so this is digest drift only. Prior 6dd5913a58400a191ff54abf9b852b03d5add657d16c11e60a7c4608ba186197 -> 310adbc2e0827b5ac749acaa981cd12d256fc5b7cbc5592c5bee219e92abf9ee.", "_rebaselined_2766_receiver_chain_wire_v2": "#2766: receiver-chain wire format v1 -> v2 (name-free `await` / `index` step kinds). The VERSION prefix is part of every emitted `@reference.receiver-chain` capture, so every chain-minting language's capture text changed. WIRE-FORMAT CHANGE, NOT A CAPTURE-SET CHANGE: the same chains are minted for the same sites, spelled `2|…` instead of `1|…`. Exactly the 12 chain-minting languages drifted; c, cobol and dart did not, which is the check that this is the prefix and not a capture regression. Accompanied by SCHEMA_BUMP 34 -> 37 and INCREMENTAL_SCHEMA_VERSION 28 -> 31 so a stale index is rejected rather than replaying chains a v2 decoder refuses. Prior 310adbc2e0827b5ac749acaa981cd12d256fc5b7cbc5592c5bee219e92abf9ee -> a9943355e945e03ddb87c800f4cc1f62b3d04feefb3ec64c258d8e0bb3b3fcd9.", - "capture_groups_small": 5005, - "capture_groups_large": 16005, - "capture_groups_fp": 3452, + "capture_groups_small": 5755, + "capture_groups_large": 18405, + "capture_groups_fp": 3512, "fixture_count": 206 }, "java-local-types": { diff --git a/gitnexus/bench/scope-capture/measure.mjs b/gitnexus/bench/scope-capture/measure.mjs index 56887e467..7a4e4057b 100644 --- a/gitnexus/bench/scope-capture/measure.mjs +++ b/gitnexus/bench/scope-capture/measure.mjs @@ -267,14 +267,15 @@ const LANGS = [ fixturePrefix: 'java', exts: ['.java'], file: 'bench.java', - // Java was previously unbenched. Heritage-bearing: extends Base + implements - // Marker (both forms) so the @reference.inherits synth (#1951) is driven at scale. + // Java was previously unbenched. Class and record heritage both implement + // Marker so the @reference.inherits synth (#1951, #2900) is driven at scale. header: 'package generated;\n\nclass Base {}\n\ninterface Marker {}\n\n', unit: (n) => `class Entity${n} extends Base implements Marker {\n` + ` long id = 0L;\n String name = "";\n` + ` public long getId() { return this.id; }\n` + - ` public void setName(String v) { this.name = v; }\n}\n\n`, + ` public void setName(String v) { this.name = v; }\n}\n\n` + + `record RecordEntity${n}(long id) implements Marker {}\n\n`, }, { name: 'java-local-types', diff --git a/gitnexus/src/core/ingestion/languages/java/captures.ts b/gitnexus/src/core/ingestion/languages/java/captures.ts index a683d0152..b71775534 100644 --- a/gitnexus/src/core/ingestion/languages/java/captures.ts +++ b/gitnexus/src/core/ingestion/languages/java/captures.ts @@ -631,55 +631,42 @@ function findEnclosingTypeDeclaration(node: SyntaxNode): SyntaxNode | null { } /** - * Synthesize `@reference.inherits` captures from Java class heritage so the - * registry-primary scope-resolution path emits EXTENDS / IMPLEMENTS edges - * (mirrors C++ `emitCppInheritanceCaptures`). Without this, Java inheritance - * edges came only from the legacy heritage-capture leg (removed in #942), which - * is dropped for registry-primary languages in the worker pipeline (issue #1951). + * Synthesize `@reference.inherits` captures from Java type heritage for the + * authoritative registry-primary EXTENDS / IMPLEMENTS pre-pass (mirrors C++ + * `emitCppInheritanceCaptures`). * * Scope covers `class_declaration` (`superclass` extends + `interfaces` * implements clauses), `record_declaration` (`interfaces` implements clauses), - * AND `interface_declaration` (`extends_interfaces` → interface-to-interface - * EXTENDS), matching the legacy Java heritage query - * (tree-sitter-queries.ts), which has a dedicated `interface_declaration - * (extends_interfaces (type_list …))` arm. Without the interface arm the - * registry-primary synth silently dropped every `interface IA extends IB` - * edge while the legacy leg emitted it — the exact =0/=N parity break #1951 - * targets. Record heritage is emitted even though the legacy query had no - * matching arm: Record graph nodes are canonical link targets since #2801, so - * dropping their `implements` clause prevents interface dispatch (#2900). Enum - * heritage remains unemitted. Generic - * bases (`extends Box`, `implements IFoo`) ARE emitted here: the legacy - * heritage query was widened to capture the inner `type_identifier` of a - * `generic_type` (tree-sitter-queries.ts), so both paths now agree on SIMPLE - * (unqualified) generic bases — the more-correct behavior, consistent with - * C#/Rust (#1951). Qualified bases (`a.b.Base`, `a.b.Box`, `a.b.IFoo`) are - * ALSO now at parity (#1956 tri-review U2): the synth resolves them by their - * `scoped_type_identifier` tail, and the legacy heritage query was widened - * with matching `scoped_type_identifier` arms (plain + generic-wrapped). The + * and `interface_declaration` (`extends_interfaces` clauses). Interface + * inheritance was restored for registry-primary resolution in #1951. Record + * graph nodes became canonical link targets in #2801 / PR #2871, so their + * `implements` clauses must participate for interface dispatch (#2900). Java + * enum interface heritage remains a separately tracked gap (#2918). + * + * Generic bases (`extends Box`, `implements IFoo`) and qualified bases + * (`a.b.Base`, `a.b.Box`, `a.b.IFoo`) are normalized to their simple + * lookup-name tails, consistent with C#/Rust and the V1 binding contract. The * EXTENDS-vs-IMPLEMENTS split is decided downstream from the resolved target's * symbol kind (`preEmitInheritanceEdges`): a superclass resolves to a class * (EXTENDS), an implemented interface resolves to an interface (IMPLEMENTS). * An `interface IA extends IB` base resolves to an Interface too, so it is - * emitted as IMPLEMENTS — matching the legacy `interface_declaration` arm, - * which tagged the bases as implements (`kind: 'implements'`) and likewise - * resolves them as interfaces. The synth therefore does not need to know the - * declaration's own kind; it only emits inherits sites and lets the resolved - * target decide the edge type. - * Base names are normalized to their bare simple identifier (`Box` → `Box`, - * `java.io.Serializable` → `Serializable`) to match the V1 simple-name - * `findClassBindingInScope` contract. + * emitted as IMPLEMENTS. The synth only emits inheritance sites and lets the + * resolved target decide the edge type. */ function synthesizeJavaInheritanceReferences(root: SyntaxNode): CaptureMatch[] { const out: CaptureMatch[] = []; const stack: SyntaxNode[] = [root]; while (stack.length > 0) { const node = stack.pop()!; - if (node.type === 'class_declaration' || node.type === 'record_declaration') { + if (node.type === 'class_declaration') { const superclass = node.childForFieldName('superclass'); if (superclass !== null) { for (const base of superclass.namedChildren) emitJavaInheritanceBase(out, base); } + } + if (node.type === 'class_declaration' || node.type === 'record_declaration') { + // Records cannot declare a superclass; they share only the class + // `interfaces` arm. const interfaces = node.childForFieldName('interfaces'); if (interfaces !== null) { for (const typeList of interfaces.namedChildren) { diff --git a/gitnexus/test/integration/resolvers/java.test.ts b/gitnexus/test/integration/resolvers/java.test.ts index e80ce8ed2..44bfbc46f 100644 --- a/gitnexus/test/integration/resolvers/java.test.ts +++ b/gitnexus/test/integration/resolvers/java.test.ts @@ -1274,7 +1274,7 @@ describe('Java record method resolution (#2564)', () => { } }, 60000); - it('links a Record implementor to its canonical Interface node (#2900)', async () => { + it('links and dispatches an explicit Record interface method (#2900)', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-java-record-heritage-')); try { writeFixtureRepo(root, { @@ -1300,13 +1300,45 @@ describe('Java record method resolution (#2564)', () => { expect(implementsEdge?.sourceLabel).toBe('Record'); expect(implementsEdge?.targetLabel).toBe('Interface'); - expect(fanout.map((edge) => `${edge.targetLabel}:${edge.targetFilePath}`)).toEqual([ + expect(fanout.map((edge) => `${edge.targetLabel}:${edge.targetFilePath}`).sort()).toEqual([ 'Method:RecordHeritage.java', ]); } finally { fs.rmSync(root, { recursive: true, force: true }); } }, 60000); + + it('documents missing dispatch to an implicit Record component accessor (#2917)', async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-java-record-accessor-')); + try { + writeFixtureRepo(root, { + 'RecordAccessor.java': `interface Named { String name(); } + record User(String name) implements Named {} + class Reader { + String read(Named value) { return value.name(); } + }`, + }); + + const linked = await runPipelineFromRepo(root, () => {}); + const implementsEdge = getRelationships(linked, 'IMPLEMENTS').find( + (edge) => edge.source === 'User' && edge.target === 'Named', + ); + const fanout = getRelationships(linked, 'CALLS').filter( + (edge) => + edge.source === 'read' && + edge.target === 'name' && + edge.rel.reason === 'interface-dispatch', + ); + + expect(implementsEdge?.sourceLabel).toBe('Record'); + expect(implementsEdge?.targetLabel).toBe('Interface'); + // TODO(#2917): implicit component accessors are not Method nodes yet. + // Replace this characterization with the expected User.name target. + expect(fanout).toEqual([]); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } + }, 60000); }); // --------------------------------------------------------------------------- diff --git a/gitnexus/test/unit/scope-resolution/java/java-captures.test.ts b/gitnexus/test/unit/scope-resolution/java/java-captures.test.ts index d0f6764f0..84880fb6f 100644 --- a/gitnexus/test/unit/scope-resolution/java/java-captures.test.ts +++ b/gitnexus/test/unit/scope-resolution/java/java-captures.test.ts @@ -33,7 +33,8 @@ function ctorRefs(src: string) { function inheritanceRefs(src: string): string[] { return emitJavaScopeCaptures(src, 'C.java') .filter((m) => m['@reference.inherits'] !== undefined) - .flatMap((m) => m['@reference.name']?.text ?? []); + .flatMap((m) => m['@reference.name']?.text ?? []) + .sort(); } describe('emitJavaScopeCaptures — constructor reference names (F35 #1928)', () => { @@ -95,10 +96,11 @@ describe('emitJavaScopeCaptures — record interface heritage (#2900)', () => { 'record User(int id) implements Named, Comparable, audit.Auditable {}', ); - expect(refs).toEqual(['Named', 'Comparable', 'Auditable']); + expect(refs).toEqual(['Auditable', 'Comparable', 'Named']); }); - it('does not widen enum heritage while adding record support', () => { + it('does not yet emit enum heritage (#2918)', () => { + // Delete this characterization when #2918 adds enum interface heritage. expect(inheritanceRefs('enum Status implements Named { ACTIVE }')).toEqual([]); }); });