Commit Graph
4 Commits
Author SHA1 Message Date
Colby MchenryandClaude Opus 5.5 063708ba53 fix(pascal): Delphi chained calls and method pointers follow declared types (#3065)
Four Delphi bugs that PR 2550 by @OleksiyPenkov found, reimplemented on
main's Pascal scope code rather than as a second resolver:

- A call through a chain of members and methods recorded only the outer
  method's bare name, which name matching gave to any namesake: SynEdit's
  Canvas.Font.Assign(Font) went to TProposalColumn::Assign, and
  TLogger2.Create.Write('y') linked nothing, a bare Write being a System
  name. The extractor now writes such a call as the typed chain
  pascal-scope already resolves for paren-less names ({call} for a
  statement, a new {args} mode for a last link written with its
  arguments). Each link is typed by its declaration: field and property
  types, function results, a local's declared type, through pointer and
  class reference types (PVirtualNode = ^TVirtualNode, TSynLogClass =
  class of TSynLog) and explicit dereferences (a^.Copy). A constructor
  called on a class gives an instance of it, a unit name, dotted ones
  too, qualifies its declarations, and a chain called with arguments may
  end at data that holds a routine. There is no bare fallback: a chain
  through a type from outside the project links nothing, and in a with
  body only a chain from Self or a typed local is read.
- A method given to a target was a method pointer only when the target
  was named On.../FOn...: Callback := FProvider.Value with Callback: TGet
  recorded a call, and FOnGet := @FProvider.Value nothing. A value given
  to an assignment now carries its target, whose declared type decides
  (routine types written out, of object, reference to, project aliases,
  the RTL's routine type names; a class, a record or a System value type
  holds no routine); the name decides only where the declaration can't
  be read. A function that returns a routine type is called for it, and
  @Obj.Method is a reference anywhere.
- A nested routine's locals leaked into the routine around it: the name
  matcher's Pascal receiver scan read every declaration above the call.
  It now steps over routines nested elsewhere, reads the calling
  routine's own name lists (Doc, Doc2: TDocVariantData), which main typed
  only by luck through a nested routine's parameter, and follows pointer
  and class reference types. Where the tree has errors, the source-text
  scopes give the outer routine its own names back after a nested one.
- A nested routine declared forward is in scope from that declaration.

Validation:
- New __tests__/pascal-reference-boundaries.test.ts, 41 tests, 24 of
  them ported from PR 2550: 22 fail on main's sources, the other 19 pin
  what main already got right. pascal-chained-calls and pascal-for-in-var
  pinned the bare outer-method names and now expect the typed chains
  (4 tests, failing on main). Twenty mutations, one per rule, each fail
  a test.
- A/B against main 80f6b505, both arms built from their own sources and
  diffed by natural keys, edges removed/added: SynEdit 195/66, HeidiSQL
  789/320, mORMot2 2553/4164, DelphiMVCFramework 2394/1850, DWScript
  without its Test folder of scripts (519 files) 864/801. leveldb and
  SDWebImage byte-identical.
- Removed edges, hand-triaged: SynEdit all 195, 193 namesakes or parse
  artifacts and 2 right ones lost (Get(i).LoadFromStream in a with
  body); samples of HeidiSQL 20/20, DelphiMVCFramework 30/30, DWScript
  12/12 and mORMot2 23/25 namesakes (of the other two, one now links the
  unit's own CompareMem, and one right edge is lost, through a field
  declared after the first name of a name list, which the extractor
  gives no node).
- Added edges sampled right: typed chains 186 of 187 (the wrong one
  reaches the FMX variant of a conditional ancestor), receiver calls
  40/40, method pointers, @-references and kind changes 57/57.
- CPU, median of 3 interleaved: SynEdit +6.6%, HeidiSQL +0.6%, mORMot2
  -2.5%, DWScript +4.7%, while the identical controls moved 0.0% and
  -5.9%. DelphiMVCFramework, timed before the scan's memo was split per
  routine (which only made it cheaper): -6.6%.

Reimplements part of PR 2550 by @OleksiyPenkov.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:29:03 +00:00
Colby MchenryandClaude Opus 5.5 f3fdba2955 fix(pascal): calls inside nested Delphi routines are indexed, each under its routine (#2810)
A Pascal routine may declare routines of its own between its header and
its begin. The extractor walked only a routine's body block, so a nested
routine's code was never read: what it called was missing, and a call to
it from the routine that declares it went by name to any routine
elsewhere with that name (in mORMot2's tests, Test, Check and One calls
landed on other classes' methods).

A nested routine is now a function node qualified under the routine it
is nested in (TFoo::Bar::Inner), contained by it and walked with itself
as the caller. It sees the parameters and locals of the routines around
it that are declared above it. Its name means it in the routine that
declares it, in the routines nested there after it and in its own body,
ahead of the class and the unit. A call resolved to a nested routine
from anywhere else is what the call's own scope declares, or nothing. A
nested routine's bare calls reach the class of the method it is nested
in first. Where a routine's tree has errors and its parse swallowed the
unit's routines after it (column 0, or after the body), those are walked
as the unit's own; of two bodies under {$IFDEF} the first is walked, as
before.

Validation against origin/main 7b50ca83, nodes and edges removed/added:
SynEdit +239, -6/+1127; HeidiSQL +404, -178/+2352; mORMot2 +574,
-956/+9565; DelphiMVCFramework +134, -279/+1049; DWScript +434,
-346/+3351; leveldb and SDWebImage byte-identical. Sampled: calls to
nested routines 38/38 right, calls made in them 44/59 (ordinary name
matching's misses), removed edges 30/30 wrong links. Index time within
about 5%, in proportion to the code now walked.

Covers the nested-routine part of #2550.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:50:35 +00:00
Colby MchenryandClaude Opus 5.5 7b50ca8321 fix(pascal): Delphi calls to compiler intrinsics and plain data no longer link namesakes (#2803)
A Pascal call to a name the compiler or the System unit gives meaning
to (SizeOf, TypeInfo, High, Low, Length, Inc, Dec, Assigned, Ord, Exit,
Include, Move, Assert, typecasts like Pointer(P)) went to name
matching, which linked any project member of that name: mORMot's
TRttiProp.TypeInfo, a record's length or high field, a property named
Pointer. The built-in name check compared case, so length(S) and dec(N)
got through too. And a call that resolved to a field, property or
variable linked whatever data had the name.

Now a bare call to a System name links only to a declaration its scope
reaches: the class and its ancestors, the unit, the units it uses last
first, skipping a unit's stand-in declared under {$IFDEF} or {$IF}. A
call resolved to data stands only when the data's declared type is a
routine type (written out, a project alias to one, generic or on two
lines, or an RTL procedural type by name), retargeted to what the scope
declares. A name the calling routine declares itself links nothing; a
name the scope doesn't declare at all (a with object's member, a
declaration the parse misplaced) links to the one routine-holding field
or variable of that name. Built-in names compare without case; the RTL
namespaces keep their spelling, except system. Scope lookups now see a
unit whose unit header the parser lost, by its file name (its indented,
lost class members aren't taken for its routines), and an include file
shares its unit's scope and uses clauses. A with body's bare call to a
System name is not extracted, since it may be the with object's.

Validation, edges removed/added against origin/main: SynEdit -243/+50,
HeidiSQL -858/+296, mORMot2 -9039/+987, DelphiMVCFramework -2420/+242,
DWScript -705/+222; leveldb and SDWebImage byte-identical. Sampled
precision of calls to data, before and after: 20/50 and 50/50; of calls
to System names: 2/50 and 47/47. Sampled removed edges: 40/40 wrong.

Follow-up to #2514.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:20:06 +00:00
Colby MchenryandClaude Opus 5.5 407efb1c65 fix(pascal): Delphi calls written without parentheses link to the routine (#2797)
Delphi calls a routine that takes no arguments without parentheses
(`Before;`, `X := FProvider.Value + 1`, `if Ready then`), and only a
statement-level `Obj.Method;` was recorded, so most call sites were
missing from callers, callees and impact. Bare names in arguments and
assignments were captured as function values and matched by name alone,
which linked locals and parameters to other classes' methods.

The extractor now records each name written without parentheses with
how it is used (a statement, a value, an event's method, a receiver),
the routine's class, and the declared type of a receiver the routine
declares. It leaves out what the routine declares, data the class and
the unit declare, names the class's code assigns or passes to Inc/Dec,
names Delphi's conventions give to data, and anything in a `with` body
but an event's method. A new resolver links such a name only to a method
or function Delphi's scope rules reach: the class and its ancestors, the
unit, then the units it uses, last first, or a method of a receiver's
declared type. Data of that name links nothing. A function runs (calls);
a procedure read as a value, an event's method, and a routine that needs
arguments written without them are method pointers (references). Where
the tree has errors, the class and declarations come from the source
text, and a routine's own header is no call. `class sealed (TBase)` and
`class abstract (TBase)` heads now name their ancestors. Pascal function
values capture only `@Handler`.

A/B against main 8073637d (SynEdit, HeidiSQL, mORMot2, DWScript,
DelphiMVCFramework): added edges 3011/4626/6501/5092/6776; main's
name-matched function values now linked by scope 2387/2727/948/726/712,
114/281/554/342/586 of them as calls; removed 85/139/802/333/707,
triaged as main's false positives (locals and parameters linked by
name, other classes' methods, routine headers misparsed in broken
trees) and own-class retargets, less deliberate losses (reads in `with`
bodies, lowercase or type-like names read as values). Sampled precision
of added edges: 196/196. leveldb and SDWebImage byte-identical. CPU,
median of 3 interleaved: +15.2%, +16.2%, +8.7%, +12.9%, and +0.8% for
DelphiMVCFramework (one run each).

Fixes #2514

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:10:10 +00:00