Ports the #645/#608 chained-receiver mechanism to Pascal/Delphi — which I'd previously mis-scoped as blocked. The paren'd chained form extracts fine; it just hit the chained-call gap like the others (with a decoy, `TFoo.GetInstance().DoIt()` mis-resolved to a same-named method on an unrelated class). - pascal.ts: getReturnType reads the method's `typeref` (a `function GetInstance: TBar` returns TBar; an interface return `IFoo` is captured too). - tree-sitter.ts: extractPascalCall now re-encodes a chained call `TFoo.GetInstance().DoIt` (the exprDot's receiver is an exprCall) instead of collapsing it to bare `DoIt`. Gated on the Delphi type-naming convention (`TFoo`/`IFoo`) so a capitalized VARIABLE chain (Pascal capitalizes locals too — `Curve.X().Y()`, `Self.X().Y()`) stays bare and keeps its existing bare-name resolution. - name-matcher.ts: `pascal` joins the dotted-chain gate + CHAIN_LANGUAGES + CONSTRUCTS_VIA_BARE_CALL (a `TFoo(x)` typecast yields a TFoo). When the factory's return type wasn't captured (a `constructor Create` has no `: TBar` but returns its class), resolve the method on the factory class itself. resolveMethodOnType validates, so a wrong inference yields no edge. Validation: 4 synthetic tests (factory+decoy, constructor chain, typecast chain, absent-method safety). Real-repo A/B on PascalCoin (772 files): +19 / -18 — 15 of the -18 are correct class→interface retargets (`GetInstance(): IAsn1OctetString` resolves `.GetOctets` on the declared interface, not baseline's concrete-class guess); 3 are negligible drops (0.02%). EXTRACTION_VERSION 15->16. Full suite green. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a4d19a5ed8
commit
af56f3539d
@@ -3131,4 +3131,139 @@ void run() {
|
||||
expect(callerNamesOf('Decoy::clearAll')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pascal/Delphi chained static-factory call resolution (#645/#608 mechanism)', () => {
|
||||
function callerNamesOf(qualifiedName: string): string[] {
|
||||
const target = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName);
|
||||
if (!target) return [];
|
||||
const names = cg
|
||||
.getIncomingEdges(target.id)
|
||||
.filter((e) => e.kind === 'calls')
|
||||
.map((e) => cg.getNode(e.source)?.name)
|
||||
.filter((n): n is string => !!n);
|
||||
return [...new Set(names)].sort();
|
||||
}
|
||||
function isCalled(qn: string): boolean {
|
||||
const t = cg.getNodesByKind('method').find((n) => n.qualifiedName === qn);
|
||||
return !!t && cg.getIncomingEdges(t.id).some((e) => e.kind === 'calls');
|
||||
}
|
||||
|
||||
it('resolves a chained factory call TFoo.GetInstance().DoIt() via the return type, never a same-named decoy', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'main.pas'),
|
||||
`unit Main;
|
||||
interface
|
||||
type
|
||||
TBar = class
|
||||
procedure DoIt;
|
||||
end;
|
||||
TDecoy = class
|
||||
procedure DoIt;
|
||||
end;
|
||||
TFoo = class
|
||||
class function GetInstance: TBar;
|
||||
end;
|
||||
implementation
|
||||
procedure TBar.DoIt; begin end;
|
||||
procedure TDecoy.DoIt; begin end;
|
||||
class function TFoo.GetInstance: TBar; begin Result := nil; end;
|
||||
procedure Run;
|
||||
begin
|
||||
TFoo.GetInstance().DoIt();
|
||||
end;
|
||||
end.
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
expect(isCalled('TBar::DoIt')).toBe(true);
|
||||
expect(isCalled('TDecoy::DoIt')).toBe(false);
|
||||
});
|
||||
|
||||
it('resolves a constructor chain TFoo.Create().Configure() on the constructed class', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'main.pas'),
|
||||
`unit Main;
|
||||
interface
|
||||
type
|
||||
TFoo = class
|
||||
constructor Create;
|
||||
procedure Configure;
|
||||
end;
|
||||
TDecoy = class
|
||||
procedure Configure;
|
||||
end;
|
||||
implementation
|
||||
constructor TFoo.Create; begin end;
|
||||
procedure TFoo.Configure; begin end;
|
||||
procedure TDecoy.Configure; begin end;
|
||||
procedure Run;
|
||||
begin
|
||||
TFoo.Create().Configure();
|
||||
end;
|
||||
end.
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
// A constructor returns its own class (no `: TBar` annotation), so Configure
|
||||
// resolves on TFoo, not the same-named decoy.
|
||||
expect(isCalled('TFoo::Configure')).toBe(true);
|
||||
expect(isCalled('TDecoy::Configure')).toBe(false);
|
||||
});
|
||||
|
||||
it('resolves a typecast chain TFoo(x).DoIt() on the cast type', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'main.pas'),
|
||||
`unit Main;
|
||||
interface
|
||||
type
|
||||
TFoo = class
|
||||
procedure DoIt;
|
||||
end;
|
||||
TDecoy = class
|
||||
procedure DoIt;
|
||||
end;
|
||||
implementation
|
||||
procedure TFoo.DoIt; begin end;
|
||||
procedure TDecoy.DoIt; begin end;
|
||||
procedure Run(obj: TObject);
|
||||
begin
|
||||
TFoo(obj).DoIt();
|
||||
end;
|
||||
end.
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
expect(isCalled('TFoo::DoIt')).toBe(true);
|
||||
expect(isCalled('TDecoy::DoIt')).toBe(false);
|
||||
});
|
||||
|
||||
it('creates NO edge when the factory return type lacks the method (silent miss)', async () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'main.pas'),
|
||||
`unit Main;
|
||||
interface
|
||||
type
|
||||
TBar = class
|
||||
end;
|
||||
TOther = class
|
||||
procedure OnlyOther;
|
||||
end;
|
||||
TFoo = class
|
||||
class function GetInstance: TBar;
|
||||
end;
|
||||
implementation
|
||||
procedure TOther.OnlyOther; begin end;
|
||||
class function TFoo.GetInstance: TBar; begin Result := nil; end;
|
||||
procedure Run;
|
||||
begin
|
||||
TFoo.GetInstance().OnlyOther();
|
||||
end;
|
||||
end.
|
||||
`
|
||||
);
|
||||
cg = await CodeGraph.init(tempDir, { index: true });
|
||||
// TBar has no OnlyOther — must not mis-attach to the same-named TOther::OnlyOther.
|
||||
expect(isCalled('TOther::OnlyOther')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user