mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-31 06:52:49 +00:00
fix(java): resolve E.CONST.method() enum-constant receiver dispatch (#2561)
Calling a method on an enum-constant receiver (E.CONST.method()) emitted no CALLS edge. The receiver "E.CONST" is a two-segment compound receiver; resolveCompoundReceiverClass walks each dotted segment via the owning class scope's typeBindings map, but enum constants had no typeBinding, so the constant segment dead-ended and no target was ever resolved. #2555/#2558 gave bodied constants a first-class synthesized E$N class with an MRO that includes the host enum; this is the receiver-side follow-up. synthesizeJavaAnonymousClassDeclarations now emits a class-scope typeBinding for every enum constant's simple name -> its E$N class (bodied) or the host enum itself (body-less), reusing the exact mechanism a field declaration uses. The generic compound-receiver chain walk then resolves E.CONST.method() with no change to any shared scope-resolution code. Bodied dispatch (EnumConst.A.hook() -> EnumConst$1.hook#0) and body-less inherited dispatch (Plain.A.m() -> Plain.m#0) are covered by new tests in the existing java-enum-constant-body fixture; both were verified to fail against the pre-fix tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
73dbd9e640
commit
caf117cf98
4 changed files with 79 additions and 14 deletions
|
|
@ -372,23 +372,42 @@ function synthesizeJavaAnonymousClassDeclarations(rootNode: SyntaxNode): Capture
|
|||
// constant's class extends its HOST ENUM (javac semantics), so the
|
||||
// inherits reference names the enum — giving `mroFor(E$N) ∋ E` and
|
||||
// keeping bare calls from the body to the enum's own helpers alive
|
||||
// through the ownership gate's MRO arm. No receiver typeBinding piece:
|
||||
// constants are not variable initializers; `E.A.hook()` dispatch rides
|
||||
// the existing enum receiver machinery.
|
||||
// through the ownership gate's MRO arm.
|
||||
for (const constant of rootNode.descendantsOfType('enum_constant')) {
|
||||
const name = synthesizeJavaAnonymousClassName(constant);
|
||||
if (name === undefined) continue;
|
||||
const body = constant.childForFieldName?.('body');
|
||||
if (body === null || body === undefined || body.type !== 'class_body') continue;
|
||||
out.push({
|
||||
'@declaration.class': nodeToCapture('@declaration.class', body),
|
||||
'@declaration.name': syntheticCapture('@declaration.name', body, name),
|
||||
});
|
||||
const hostEnum = javaEnclosingEnumNameOf(constant);
|
||||
if (hostEnum !== undefined) {
|
||||
const bodiedName = synthesizeJavaAnonymousClassName(constant);
|
||||
if (bodiedName !== undefined) {
|
||||
const body = constant.childForFieldName?.('body');
|
||||
if (body !== null && body !== undefined && body.type === 'class_body') {
|
||||
out.push({
|
||||
'@declaration.class': nodeToCapture('@declaration.class', body),
|
||||
'@declaration.name': syntheticCapture('@declaration.name', body, bodiedName),
|
||||
});
|
||||
if (hostEnum !== undefined) {
|
||||
out.push({
|
||||
'@reference.inherits': nodeToCapture('@reference.inherits', body),
|
||||
'@reference.name': syntheticCapture('@reference.name', body, hostEnum),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Receiver dispatch (#2561): `E.CONST.method()` resolves through the
|
||||
// generic compound-receiver chain walk, which looks up each dotted
|
||||
// segment via the owning class scope's `typeBindings` map — the same
|
||||
// mechanism a field declaration uses (`private User user;` binds
|
||||
// `user` on the class scope). Binding the constant's own simple name
|
||||
// there — to its synthesized `E$N` class when bodied (MRO includes E,
|
||||
// so members inherited from the enum still resolve), or to the host
|
||||
// enum itself when body-less — makes `E.CONST.method()` resolve with
|
||||
// no changes to the shared receiver-binding machinery.
|
||||
const constantNameNode = constant.childForFieldName?.('name');
|
||||
const constantType = bodiedName ?? hostEnum;
|
||||
if (constantNameNode !== null && constantNameNode !== undefined && constantType !== undefined) {
|
||||
out.push({
|
||||
'@reference.inherits': nodeToCapture('@reference.inherits', body),
|
||||
'@reference.name': syntheticCapture('@reference.name', body, hostEnum),
|
||||
'@type-binding.annotation': nodeToCapture('@type-binding.annotation', constant),
|
||||
'@type-binding.name': nodeToCapture('@type-binding.name', constantNameNode),
|
||||
'@type-binding.type': syntheticCapture('@type-binding.type', constant, constantType),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,4 +21,8 @@ class Unrelated {
|
|||
public void caller() {
|
||||
hook();
|
||||
}
|
||||
|
||||
public void dispatchToConstant() {
|
||||
EnumConst.A.hook();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
gitnexus/test/fixtures/lang-resolution/java-enum-constant-body/src/Plain.java
vendored
Normal file
13
gitnexus/test/fixtures/lang-resolution/java-enum-constant-body/src/Plain.java
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
public enum Plain {
|
||||
A;
|
||||
|
||||
public void m() {
|
||||
System.out.println("plain m");
|
||||
}
|
||||
}
|
||||
|
||||
class PlainCaller {
|
||||
public void callPlain() {
|
||||
Plain.A.m();
|
||||
}
|
||||
}
|
||||
|
|
@ -3055,3 +3055,32 @@ describe('Java enum constant bodies (#2555)', () => {
|
|||
expect(misattributed).toBeUndefined();
|
||||
}, 60000);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #2561: E.CONST.method() emits no CALLS edge — the receiver-side follow-up
|
||||
// to #2555. A bodied constant's receiver must resolve to its synthesized
|
||||
// E$N class; a body-less constant's receiver must resolve to the host enum
|
||||
// itself.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Java enum-constant receiver dispatch (#2561)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'java-enum-constant-body'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('resolves EnumConst.A.hook() to the bodied constant override (EnumConst$1.hook#0)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const dispatch = calls.find((c) => c.source === 'dispatchToConstant' && c.target === 'hook');
|
||||
expect(dispatch).toBeDefined();
|
||||
expect(dispatch!.rel.targetId).toBe('Method:src/EnumConst.java:EnumConst$1.hook#0');
|
||||
});
|
||||
|
||||
it("resolves Plain.A.m() to the body-less constant's inherited enum method (Plain.m#0)", () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const dispatch = calls.find((c) => c.source === 'callPlain' && c.target === 'm');
|
||||
expect(dispatch).toBeDefined();
|
||||
expect(dispatch!.rel.targetId).toBe('Method:src/Plain.java:Plain.m#0');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue