← board

An integer literal is not typed by its VALUE for overload resolution

What FPC does

An integer constant takes the smallest type that holds it, and overload resolution then ranks candidates against that type:

literal FPC's type picks, given p(Byte)/p(Word)/p(LongInt)/p(Int64)
5 ShortInt longint (no ShortInt candidate; widening is cheapest)
200 Byte byte
40000 Word word
100000 LongInt longint
5000000000 Int64 int64

pxx types every literal that fits in 32 bits as LongInt (wider ones as Int64), so it answers longint for the middle three.

Why it matters — the visible symptom

Hi/Lo/Swap dispatch on the argument's type, so a literal argument gets 32-bit halves where FPC gives 16-bit ones:

writeln(hi($1234), '|', lo($1234));   { FPC 18|52    pxx 0|4660 }
writeln(swap($1234));                 { FPC 13330    pxx 305397760 }

A cast works around it (hi(Word($1234)) = 18|52 on both), and every typed argument now agrees with FPC — this is literals only.

What is already fixed (do not re-file it)

The dangerous half of this landed 2026-08-02: resolution used to take the first COMPATIBLE candidate in chain order, so p(40000) with a p(Byte) overload declared first printed 64 — the argument was silently truncated. Phase 1d in MatchProcCall now prefers a candidate that does not narrow an integer argument (ArgNarrowsInt), so the value always survives; only the choice among lossless candidates still differs from FPC. Regression test: test/test_overload_no_narrowing.pas.

Approach

MatchProcCall receives argTypes: array of TTypeKind — kinds only, no constant values — so the fix is not local to it. Two options:

  1. Type the literal at the front end (FPC's actual model): give an integer constant the smallest fitting kind at the point it becomes an argument, and let the existing exact-match phase do the rest. Closest to FPC, and fixes every overload set at once — but it changes the type of every integer literal everywhere, so arithmetic promotion has to stay unaffected (b + 0 must still be LongInt, as it is in FPC).
  2. Pass the constant value into resolution alongside the kind, and let Phase 1d prefer the narrowest parameter that can hold it. Contained, but adds a parallel array through the resolver's callers.

Option 1 is the honest one; option 2 is what to do if 1 turns out to disturb promotion. Either way the ranking rule to match is "smallest fitting type, then cheapest widening".

Acceptance

Resolution 2026-08-03 (claude-A@opus5)

Neither option as written — a third one, which is option 1's ranking model applied only where ranking happens, so it carries none of option 1's risk to arithmetic promotion and none of option 2's parallel array through callers.

Exact-only is what makes the 5 row come out right without a scoring pass: 5 is ShortInt, ShortInt does not widen into Byte, and FPC ranks it onto LongInt for exactly that reason — which is what the fallback already does. The rows that need the fix are the ones where the literal's own type is a candidate (200/Byte, 40000/Word, $1234/SmallInt).

The literal's expression type is untouched, so promotion is unaffected (b + 0 stays LongInt).

Verified against FPC, not against expectation

Both regression tests now produce output identical to FPC's, diffed against a real fpc build of the same source:

p(5) p(200) p(40000) p(100000) p(5000000000)
  ->  longint / byte / word / longint / int64          (was longint x4 + int64)
hi($1234)|lo($1234) -> 18|52                           (was 0|4660)
swap($1234)         -> 13330                           (was 305397760)

test/test_hilo_swap.pas gains six literal rows (5, 200, $1234, 40000, $12345678, $1122334455667788) covering every width; all 20 rows match FPC. test/test_overload_no_narrowing.pas keeps its shape, with the middle two rows now byte/word; its comment no longer describes the gap as open. Makefile expectations updated for both.

Blast radius is small by construction: the probe only wins on an EXACT hit for the literal's smallest type, i.e. only where someone declared an overload taking precisely Byte/ShortInt/SmallInt/Word/Cardinal. A non-overloaded Foo(b: Byte) called Foo(200) resolves to the same routine, just one phase earlier.

tools/gate.sh quick GREEN (self-host fixedpoint byte-identical, testmgr quick, FPC seed canary). No compiler/builtin/** change, so no repin needed.

Log