Integer vs LongInt: the same type lost the exact overload match
- Type: bug (overload resolution,
compiler/symtab.incMatchProcCall). Track P / shared core (sole-A confirmed). - Status: done
- Found: 2026-08-16, Pascal oracle sweep vs
fpc -O- -Mobjfpc(sysutils topic) —IntToHex(-1, 8)came back sixteen digits wide.
Symptom
var i: integer; li: longint;
begin i := -1; li := -1;
writeln(IntToHex(i, 8)); { FPC FFFFFFFF pxx FFFFFFFFFFFFFFFF }
writeln(IntToHex(li, 8)); { FPC FFFFFFFF pxx FFFFFFFF }
sysutils declares IntToHex three times — Int64, LongInt, LongWord —
precisely so a 32-bit value renders eight digits and not sixteen, and the
LongInt body already masks correctly. So the library was right and the
binding was wrong: longint hit the exact-match phase, integer fell past it
into the compatible phases and bound Int64.
Root cause: integer types as tyInteger and longint as tyInt32 — the
same 4-byte signed type wearing two kinds (FPC declares one as the other's
alias) — and the exact phase compared kinds directly, so it discriminated on
the spelling. integer is the far more common spelling, which is what made
the wrong half the visible one.
Fix
MatchParamExact(paramTk, argTk) — the exact-match test used by Phase 1 and
Phase 1b — treats tyInteger and tyInt32 as one. A unit cannot legitimately
declare both overloads (FPC rejects the pair as duplicate), so unifying them
cannot make a previously-unambiguous call ambiguous. The ranking phases below
are untouched.
Residuals (both deliberate, both measured)
smallintstill bindsInt64where FPC bindsLongInt, because FPC's narrowest-that-fits rule lives behind--strict-overload-width: the widening is the dialect by decision (user, 2026-08-14, [[compat-pascal-strict-fpc-should-pick-the-narrowest-integer-overload]]).IntToHex(-2147483648, 8)printsFFFFFFFF80000000where FPC prints80000000: the literal at Integer's exact minimum types as Int64 here (2147483648exceeds maxint before the unary minus folds). A separate, narrower literal-typing question — not this ticket, and filed nowhere yet because it needs a decision on when the negation folds.
The default column of test_strict_overload_width moved — deliberately
Track T filed a NEW-RED on this commit within the hour
(test-core#src:test/test_strict_overload_width.pas@1), and it was a real
expectation change, not a flake. That test asserts BOTH columns from one
source; four rows of the default column moved (Integer, literal,
MyInt -> longint, and hex -> FFFFFFFF) and the --strict-overload-width
column did not move at all.
That is not the flag leaking into the default. Nothing is being ranked:
Integer and LongInt are one type, so the LongInt overload is an EXACT match.
What the user settled on 2026-08-14 was ranking between DIFFERENT widths, and
the default still declines to do it — SmallInt, Byte and Cardinal still
widen to Int64 in the unflagged column. The four rows that moved all moved
TOWARD FPC, which is the standing default (reference behaviour by default,
deviations behind --strict-*). Makefile expectation updated with that
reasoning inline.
Gate
make compiler/pascal26 fixedpoint; tools/gate.sh quick GREEN;
test/test_integer_longint_overload.pas (IntToHex across integer/longint/
cardinal/int64 plus a hand-written three-way overload set proving which one
each spelling binds) matches fpc -O- -Mobjfpc byte for byte. Six earlier
sweep probes (sysutils strings, strings/Copy, records, exceptions, OOP
dispatch) re-run unchanged.