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
@@ -21,4 +21,4 @@
|
||||
* turns the re-index hint into noise — keep it honest (see CLAUDE.md, "Honesty
|
||||
* in the product is load-bearing").
|
||||
*/
|
||||
export const EXTRACTION_VERSION = 15;
|
||||
export const EXTRACTION_VERSION = 16;
|
||||
|
||||
@@ -17,6 +17,16 @@ export const pascalExtractor: LanguageExtractor = {
|
||||
bodyField: 'body',
|
||||
paramsField: 'args',
|
||||
returnField: 'type',
|
||||
// Pascal/Delphi `function GetInstance: TBar` — the return type is a `typeref`
|
||||
// child. Capture its bare class name for the chained static-factory call
|
||||
// mechanism (#750). A procedure (no return) has no typeref → undefined.
|
||||
getReturnType: (node, source) => {
|
||||
const typeref = node.namedChildren.find((c: SyntaxNode) => c.type === 'typeref');
|
||||
if (!typeref) return undefined;
|
||||
const id = typeref.namedChildren.find((c: SyntaxNode) => c.type === 'identifier') ?? typeref;
|
||||
const name = getNodeText(id, source).trim();
|
||||
return /^[A-Za-z_]\w*$/.test(name) ? name : undefined;
|
||||
},
|
||||
getSignature: (node, source) => {
|
||||
const args = getChildByField(node, 'args');
|
||||
const returnType = node.namedChildren.find(
|
||||
|
||||
@@ -4312,12 +4312,41 @@ export class TreeSitterExtractor {
|
||||
|
||||
let calleeName = '';
|
||||
if (firstChild.type === 'exprDot') {
|
||||
// Qualified call: Obj.Method(...)
|
||||
const identifiers = firstChild.namedChildren.filter(
|
||||
(c: SyntaxNode) => c.type === 'identifier'
|
||||
);
|
||||
if (identifiers.length > 0) {
|
||||
calleeName = identifiers.map((id: SyntaxNode) => getNodeText(id, this.source)).join('.');
|
||||
// Chained static-factory call: `TFoo.GetInstance().DoIt()` — the exprDot's
|
||||
// receiver is itself an `exprCall`, so the bare identifier list would
|
||||
// collapse to just `DoIt` and mis-resolve to a same-named method on an
|
||||
// unrelated class. Encode `TFoo.GetInstance().DoIt` so resolution infers
|
||||
// DoIt's class from what `TFoo.GetInstance` RETURNS (#645/#608). Only a
|
||||
// capitalized class-factory chain; a unary outer method.
|
||||
const innerCall = firstChild.namedChildren.find((c: SyntaxNode) => c.type === 'exprCall');
|
||||
const outerId = firstChild.namedChildren.filter((c: SyntaxNode) => c.type === 'identifier').pop();
|
||||
const method = outerId ? getNodeText(outerId, this.source) : '';
|
||||
if (innerCall && method && /^\w+$/.test(method)) {
|
||||
const innerFirst = innerCall.namedChild(0);
|
||||
let innerCallee = '';
|
||||
if (innerFirst?.type === 'exprDot') {
|
||||
innerCallee = innerFirst.namedChildren
|
||||
.filter((c: SyntaxNode) => c.type === 'identifier')
|
||||
.map((id: SyntaxNode) => getNodeText(id, this.source))
|
||||
.join('.');
|
||||
} else if (innerFirst?.type === 'identifier') {
|
||||
innerCallee = getNodeText(innerFirst, this.source);
|
||||
}
|
||||
// Gate on the Delphi type-naming convention — `TFoo` classes / `IFoo`
|
||||
// interfaces — so a class-factory chain re-encodes but a capitalized
|
||||
// VARIABLE/parameter chain (Pascal capitalizes locals too: `Curve.X().Y()`,
|
||||
// `Self.X().Y()`) stays bare and keeps its existing bare-name resolution.
|
||||
calleeName = innerCallee && /^[TI][A-Z]/.test(innerCallee)
|
||||
? `${innerCallee}().${method}`
|
||||
: method;
|
||||
} else {
|
||||
// Qualified call: Obj.Method(...)
|
||||
const identifiers = firstChild.namedChildren.filter(
|
||||
(c: SyntaxNode) => c.type === 'identifier'
|
||||
);
|
||||
if (identifiers.length > 0) {
|
||||
calleeName = identifiers.map((id: SyntaxNode) => getNodeText(id, this.source)).join('.');
|
||||
}
|
||||
}
|
||||
} else if (firstChild.type === 'identifier') {
|
||||
calleeName = getNodeText(firstChild, this.source);
|
||||
|
||||
Reference in New Issue
Block a user