← board

{$mode delphi} support — incl. the @-optional proc-pointer disambiguation

Closing summary (2026-06-22)

DONE and FPC -Mdelphi oracle-matched: the two behavioural deltas (@-optional procedural value; bare own-name is never the result var) plus the @-relax at the assignment site (p := F), the call-argument site (g(F)), and the method pointer assignment (p := obj.M). All self-host byte-identical + cross-bootstrap green. Tests: test/test_mode_delphi*.pas in make test. The narrower leftover bind sites (proc-value comparison, proc-typed record/array fields, method ptr at call-arg, per-unit mode reset) are low priority and tracked in [[feature-mode-delphi-remaining]]; pull one in only when a real Synapse compile trips on it.

Why

A large amount of real Pascal library/source is written for {$mode delphi} (Synapse, many Delphi-portable units). PXX currently largely swallows the {$mode} directive (only PasObjFpcModeOption peeks for objfpc). To compile that source we need a real Delphi mode, and the headline behavioural delta is the @-optional procedural value — exactly the kind of mode-specific detail that silently mis-compiles otherwise.

The verified delta (the headline)

Assigning/passing a function as a procedural value (tested 2026-06-21):

So in Delphi mode a bare function name in a procedural-target context means "the pointer", not "call it".

NOT a rabbit hole — deterministic call-first precedence (FPC-verified 2026-06-21)

p := F is NOT ambiguous in FPC; it is resolved by a fixed precedence, not by guessing intention:

  1. Try the call. If F's result type is assignment-compatible with the target → call wins — in ALL modes, even when @F would also fit. Tested: function F: Pointer; var q: Pointer; q := F; calls F (no warning) in default, objfpc, and delphi, although @F is equally Pointer-compatible. Call has priority, period.
  2. @F is a pure Delphi-mode fallback, used only when the call result does NOT fit the target AND the target is procedural AND @F fits. Tested: function F: Pointer; var p: TFn; p := F; → default/objfpc ERROR (Pointer ≠ TFn); -Mdelphi compiles (takes @F). No warnings either way.

So the implementer does not detect intention and does not need expected- type propagation through ParseExpr. The rule is: at the few bind sites where the target type is known, the EXISTING type check already prefers the call; just add, for delphi mode only, a fallback when that type check fails on a procedural target.

Bind sites (where the target type is in hand): assignment RHS to a proc-typed lvalue (p := F), a call arg bound to a proc-typed parameter (g(F)), proc-value comparison (if p = F), proc-typed record/array fields. At each: if the call interpretation type-checks → call (all modes); else if delphi AND target is procedural AND the operand is a bare function-name (no required args, not already @'d) AND @F fits → take the address. Method pointers (@obj.M) carry Self — 2-word TMethod shape.

Other Delphi-mode deltas (scope, lower priority than @-relax)

Acceptance

Dependencies / ordering

Log