fix(java): emit Record interface heritage

Synthesize inheritance references for Java record implements clauses so scope resolution emits canonical heritage and interface-dispatch edges.
This commit is contained in:
azizur100389 2026-08-09 19:35:09 +01:00
parent 49c5b7d81f
commit 9615f098c8
3 changed files with 63 additions and 4 deletions

View file

@ -638,13 +638,17 @@ function findEnclosingTypeDeclaration(node: SyntaxNode): SyntaxNode | null {
* is dropped for registry-primary languages in the worker pipeline (issue #1951).
*
* Scope covers `class_declaration` (`superclass` extends + `interfaces`
* implements clauses) AND `interface_declaration` (`extends_interfaces` →
* interface-to-interface EXTENDS), matching the legacy Java heritage query
* 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. Enum/record heritage stays unemitted (no legacy arm). Generic
* 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<T>`, `implements IFoo<T>`) 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
@ -671,7 +675,7 @@ function synthesizeJavaInheritanceReferences(root: SyntaxNode): CaptureMatch[] {
const stack: SyntaxNode[] = [root];
while (stack.length > 0) {
const node = stack.pop()!;
if (node.type === 'class_declaration') {
if (node.type === 'class_declaration' || node.type === 'record_declaration') {
const superclass = node.childForFieldName('superclass');
if (superclass !== null) {
for (const base of superclass.namedChildren) emitJavaInheritanceBase(out, base);

View file

@ -1273,6 +1273,40 @@ describe('Java record method resolution (#2564)', () => {
fs.rmSync(root, { recursive: true, force: true });
}
}, 60000);
it('links a Record implementor to its canonical Interface node (#2900)', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-java-record-heritage-'));
try {
writeFixtureRepo(root, {
'RecordHeritage.java': `interface Named { String name(); }
record User(String value) implements Named {
public String name() { return value; }
}
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');
expect(fanout.map((edge) => `${edge.targetLabel}:${edge.targetFilePath}`)).toEqual([
'Method:RecordHeritage.java',
]);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
}, 60000);
});
// ---------------------------------------------------------------------------

View file

@ -29,6 +29,13 @@ function ctorRefs(src: string) {
}));
}
/** All synthesized inheritance references in `src`, reduced to lookup names. */
function inheritanceRefs(src: string): string[] {
return emitJavaScopeCaptures(src, 'C.java')
.filter((m) => m['@reference.inherits'] !== undefined)
.flatMap((m) => m['@reference.name']?.text ?? []);
}
describe('emitJavaScopeCaptures — constructor reference names (F35 #1928)', () => {
it('binds the simple name for an unqualified `new User()`', () => {
const refs = ctorRefs(wrapExpr('new User()'));
@ -82,6 +89,20 @@ describe('emitJavaScopeCaptures — constructor reference names (F35 #1928)', ()
});
});
describe('emitJavaScopeCaptures — record interface heritage (#2900)', () => {
it('captures simple, generic, and qualified record interfaces by lookup name', () => {
const refs = inheritanceRefs(
'record User(int id) implements Named, Comparable<User>, audit.Auditable {}',
);
expect(refs).toEqual(['Named', 'Comparable', 'Auditable']);
});
it('does not widen enum heritage while adding record support', () => {
expect(inheritanceRefs('enum Status implements Named { ACTIVE }')).toEqual([]);
});
});
describe('emitJavaScopeCaptures — explicit constructor invocations (F38 #1928)', () => {
it('captures `super(...)` as a constructor ref to the superclass simple name', () => {
const src = 'class C extends pkg.Base { C() { super(1, 2); } }';