Skip to content

Delphi extractor drops the receiver call of a chained call expression and everything inside it #2547

Closed

Description

@stalexteam

Summary

In a Delphi (Object Pascal) unit, a call whose receiver is itself a call - MD5FileSized(TMeta.ExtendedPath(APath)).IsSame(AKey) - contributes one edge: the outer method (IsSame). The receiver call (MD5FileSized) and the calls inside its arguments (TMeta.ExtendedPath) are missing from callees of the routine and from callers of each callee.
The same holds for the paren-less statement form MD5FileSized(…).Reset;. The tree is parsed correctly; the walk stops at the receiver.

Versions

  • codegraph --version: 1.6.2 (Windows x64 bundle).
  • Windows 10 Pro 10.0.19045.

Minimal reproduction

Chain.pas:

unit Chain;

interface

type
  TKey = record
    function IsSame(const AOther: TKey): Boolean;
    procedure Reset;
  end;

  TMeta = class
    class function ExtendedPath(const APath: string): string; static;
  end;

function MD5FileSized(const APath: string): TKey;
function TargetIsTheSame(const APath: string; const AKey: TKey): Boolean;
procedure Stmt(const APath: string);

implementation

function TKey.IsSame(const AOther: TKey): Boolean;
begin
  Result := True;
end;

procedure TKey.Reset;
begin
end;

class function TMeta.ExtendedPath(const APath: string): string;
begin
  Result := APath;
end;

function MD5FileSized(const APath: string): TKey;
begin
end;

function TargetIsTheSame(const APath: string; const AKey: TKey): Boolean;
begin
  Result := MD5FileSized(TMeta.ExtendedPath(APath)).IsSame(AKey);
end;

procedure Stmt(const APath: string);
begin
  MD5FileSized(TMeta.ExtendedPath(APath)).Reset;
end;

end.

Steps, in a directory holding only that file:

codegraph init -y .
codegraph callees TargetIsTheSame
codegraph callees Stmt
codegraph callers MD5FileSized
codegraph callers ExtendedPath

Observed (1.6.2):

●  10 nodes, 11 edges
Callees of "TargetIsTheSame" (1):
method      IsSame
Callees of "Stmt" (1):
method      Reset
Callers of "MD5FileSized" (0):
No callers found for "MD5FileSized"
Callers of "ExtendedPath" (0):
No callers found for "ExtendedPath"

Expected: TargetIsTheSame with the callees MD5FileSized, ExtendedPath, IsSame; Stmt with MD5FileSized, ExtendedPath, Reset; MD5FileSized and ExtendedPath each with two callers.

The grammar's tree is complete (tree-sitter-pascal.wasm shipped in 1.6.2):

assignment
  identifier "Result"
  exprCall
    exprDot
      exprCall
        identifier "MD5FileSized"
        exprArgs
          exprCall
            exprDot
              identifier "TMeta"
              identifier "ExtendedPath"
            exprArgs
              identifier "APath"
      identifier "IsSame"
    exprArgs
      identifier "AKey"

extractPascalCall reads the receiver exprCall only to build the TFoo.GetInstance().DoIt name for a T/I-prefixed factory (#645/#608); it records neither the receiver call nor the calls in its arguments, and only the outer exprArgs are visited. extractPascalParenlessCall records the receiver call only on the same T/I gate.

Where it was found

A product's Result := MD5FileSized(TFSMetadata.ExtendedPath(APath)).IsSame(AContentKey);: callers TFSMetadata.ExtendedPath --limit 500 named 12 of the 13 routines that call it, the routine holding that line being the one left out; its callees listed IsSame alone.

Activity

  1. stalexteam commented on Oct 13, 2026

    @stalexteam
    Author

    possible solution:

    --- lib/dist/extraction/tree-sitter.js
    +++ lib/dist/extraction/tree-sitter.js
    @@ -7317,6 +7317,8 @@
                     calleeName = innerCallee && /^[TI][A-Z]/.test(innerCallee)
                         ? `${innerCallee}().${method}`
                         : method;
    +                // The receiver call and its arguments are calls too.
    +                this.extractPascalCall(innerCall);
                 }
                 else {
                     // Qualified call: Obj.Method(...)
    @@ -7409,6 +7411,8 @@
                 }
                 else {
                     calleeName = method; // non-class receiver: a bare method ref (no field-access ref)
    +                if (receiver.type === 'exprCall')
    +                    this.extractPascalCall(receiver);
                 }
             }
             else {
    
  2. colbymchenry commented on Oct 22, 2026

    @colbymchenry
    Owner

    Thanks for the precise report and the tree dump!

    #2787 is merged to main. The receiver call of a chained call and the calls in its arguments are now indexed, so MD5FileSized and TMeta.ExtendedPath list both routines as callers, in both the expression and the statement form.

    Typecasts are handled too. A typecast receiver like TButton(Sender).Caption, or a value cast like F := TFoo(Obj), now counts as a use of the type rather than as creating one. The fix ships in the next release.

  3. added a commit that references this issue on Oct 22, 2026
    0953084
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions