An array of const integer arm picks the Int64 overload
- Type: bug (Pascal frontend —
array of const/ overload resolution) - Track: P
- Found: 2026-08-25, by [[bug-a-the-fpjson-suite-overflows-the-fixed-4096-entry-data-ptr-fixup-table]] — the fpjson suite could not build at all until that landed, so these two failures had been invisible, not introduced.
Measured — differential, against the FPC oracle, on fcl-json's own sources
Same fpjson.pp, compiled both ways (pxx with --mimic-fpc):
pxx fpc
CreateJSON(Longint) -> TJSONIntegerNumber TJSONIntegerNumber agree
CreateJSON(1) -> TJSONIntegerNumber TJSONIntegerNumber agree
CreateJSONArray([1])[0] -> TJSONInt64Number TJSONIntegerNumber DIVERGE
The direct overload resolution is right. Only the value routed through
array of const comes out as Int64.
Where
fpjson.pp, VariantToJSON:
With Element do
case VType of
vtInteger : Result := CreateJSON(VInteger); { -> TJSONIntegerNumber }
...
vtInt64 : Result := CreateJSON(vInt64^); { -> TJSONInt64Number }
fpjson declares CreateJSON(Data: Integer), CreateJSON(Data: Int64),
CreateJSON(Data: QWord) and CreateJSON(Data: NativeInt) as four separate
overloads, and picks the result class from which one binds. So the question is
either
- the
TVarRecwe build for a plain integer literal is taggedvtInt64rather thanvtInteger(wrong arm taken), or - the arm is right and
CreateJSON(VInteger)— aLongintfield read throughwith— binds theInt64(orNativeInt) overload.
Measure which before theorising; they are one case apart and lead to
completely different fixes. A standalone Show([1]) printing VType gives 0
(vtInteger), which points at (2), but that probe did not go through fpjson's
overload set.
Note CreateJSON(Data: NativeInt) is a live confounder on 64-bit: it is a
third candidate that is layout-identical to Int64.
Repro
tools/install_lib_candidates.sh fcl-json
# stage the suite flat (see the test-fpjson recipe in the Makefile), then:
# probe.pp: uses fpjson; writeln(CreateJSONArray([1]).Items[0].ClassName)
# pxx --mimic-fpc -> TJSONInt64Number
# fpc -> TJSONIntegerNumber
Or just run the suite: TTestFactory.ArrayCreateInteger and
TTestFactory.ObjectCreateInteger fail with
"Correct class" expected: <TMyInteger> but was: <TMyInt64>.
Why prio 55
It is the entire remaining gap between the fpjson rung and its recorded 203/203 — the suite is at 201/203 — and it is a silent wrong type in a real library's public factory, not a diagnostic nicety. Below the segfault tier, above ordinary parity work.
Gate
make compiler/pascal26 + the fpjson suite reaching
run: 203 failures: 0 errors: 0 + tools/gate.sh quick.
Links
[[feature-pascal-corpus-fpjson]] · [[bug-a-the-fpjson-suite-overflows-the-fixed-4096-entry-data-ptr-fixup-table]]
Measured (frank1, 2026-08-26) — it was (2), then one layer further down
The ticket's own two candidates, resolved by probe against fpjson's real
overload set rather than a standalone Show([1]):
Mimic([1]):
VType = 0 <- the TAG IS RIGHT, so not (1)
arm taken = vtInteger
CreateJSON -> TJSONInt64Number
direct CreateJSON(Longint var) -> TJSONIntegerNumber <- binding is right
CreateJSON(E2.VInteger) -> TJSONInt64Number <- the field is not
So (2), and the with is innocent — a plain E.VInteger field read fails the
same way. The cause is one line of our own RTL: builtinheap.TVarRec declared
VInteger: NativeInt where FPC declares Longint. FixupTVarRecLayout
right-sizes the record regardless, so the width bought no storage; all it did
was pick a different overload at every reader. The ticket's third confounder
(CreateJSON(NativeInt)) is exactly the one it handed the argument to.
Two errors were cancelling, which is why neither showed alone. AN_VARREC_ARRAY
tagged a pointer-wide native int vtInteger and then stored eight bytes under a
tag that promises four — harmless only while VInteger was NativeInt and read
all eight back. Narrowing the field alone would have truncated
Format('%d', [NativeInt]). FPC has no such pair: on a 64-bit target NativeInt
IS Int64 and the element goes in as vtInt64, so both halves move together.
Suite: run: 203 failures: 0 errors: 0 — the recorded 203/203, reached.
Log
- 2026-08-26 — resolved, commit 4cd1a0c22.