← board

Bare function name in an expression: PXX calls it, FPC/ISO reads the result var

The divergence

Inside a function F's own body, a bare F used in an expression (RHS of an assignment, an argument, etc.) means:

These are opposite. PXX is the non-standard one.

NOT a mode mismatch (tested 2026-06-21). function F: Integer; begin if calls<3 then F := F else F := 42; end; prints F=0 calls=1 under both fpc (default) and fpc -Mobjfpc — i.e. F := F reads the result var, no recursion, in every FPC mode. So building the seed with -Mobjfpc does NOT align FPC with PXX; this is a genuine PXX semantic bug, not a build-flag fix. (Outside its own body, x := F does call F in all modes — bare-name-is-result- var is specific to reads inside the function's own scope.)

function ConstEval: Int64;
begin
  ...
  r := ConstEval;     { PXX: recursive call. FPC: read the result var. }
  r := ConstEval();   { both: recursive call }
end;

Why it bit (and why it is subtle)

The compiler's own ConstEval (parser.inc, the tkLParen (expr) branch) writes bare r := ConstEval and relies on PXX calling it. Consequences:

So the FPC seed is not a faithful oracle for this corner of PXX semantics. A change validated only on the FPC-built binary can look broken (or pass) and behave the opposite way on the real self-hosted compiler. The bootstrap still converges because compiler/compiler.pas's own const expressions do not hit the seed's broken path in a value-affecting way; cmp build == verify is between two PXX-built binaries, both using the PXX (call) semantics.

Repro

program r;
const X = (5);     { fails on an FPC-built pascal26, works on the self-hosted one }
begin writeln(X); end.

Build one pascal26 with FPC directly and one via make bootstrap; compile the above with each.

@ is the third meaning of a bare function name. Tested 2026-06-21:

So in our target (modern FPC, objfpc-ish), @ is required for a procedural pointer; only Delphi mode makes it optional. PXX already handles @proc ([[project_procedural_types_arc]]); audit that PXX does not silently accept a bare F where @F is meant. The clean three-way model PXX should adopt: F/F() = call (result var inside own body), @F = pointer, Result = result var.

Decision needed / fix direction

This is a genuine PXX bug (FPC is consistent across modes), so the fix is one-way:

-Mobjfpc on the seed is NOT a fix (see above). Eventually wanted for correctness + feature-mimic-fpc, but it is a careful, audited flip.

Interim rule (already in practice)

Sibling gotcha (split into its own ticket)

Self-host requires declaration-before-use; FPC resolves the whole unit (the LowerCase-used-before-its-definition trap). Principled fix tracked separately: [[feature-declaration-prescan]] (a header pre-scan, not lazy-linking). Interim: use an early-defined equivalent (CaseEqual in defs.inc) / order callees first.

Partial fix landed 2026-06-21 (Track A)

The parameterised half of the divergence is fixed: inside a function with ParamCount > 0, a bare own-name read in an expression (not followed by () now resolves to the result variable (parser.inc ParseFactor, new branch before the call paths, reading Procs[CurProc].RetSymIdx via ParseLValueAST). This is the unambiguous case — with parameters a bare name with no parens cannot be a call — so it needs no audit and matches FPC. Unblocked examples/chess MoveText (MoveText := MoveText + Promo[...]); chess advances past chess.pas:872. Test: test/test_func_name_result_read.pas; output-equal x86-64/i386/aarch64/ arm32 (QEMU), compiles esp riscv32+xtensa; default + --threadsafe self-host byte-identical.

Still open — the paramless flip (the risky half): a bare own-name in a PARAMLESS function is still a recursive call in PXX (the historical behaviour the self-hosted compiler relies on, e.g. r := ConstEval in ConstEval's (expr) branch). My branch deliberately excludes ParamCount = 0 to preserve that; function Counter: Integer; begin Counter := Counter + 10; end; still infinitely recurses (consistent on all targets). Completing the FPC match = flip the 0-param case too, which still requires the bare-name-recursion-site audit described above. So this ticket stays backlog for that remaining flip.

Log