Measured, 2026-08-25 (HEAD, self-hosted fixedpoint)
program plainobj;
{$mode delphi}
type
TO2 = object
X: Integer;
function Test(a: Integer): Integer;
end;
function TO2.Test(a: Integer): Integer;
begin Result := a + X; end;
var t: TO2;
begin t.X := 1; writeln(t.Test(42)); end.
pascal26: Expected: begin, but got: X (Kind: 1, Line: 5)
fpc -Mdelphi -O1 compiles and runs it (prints 43).
The comment in compiler/pasparser_decl.inc (the object arm of the builtin
type-name chain) already states the position explicitly:
NOT legacy Object Pascal's value-
object(record-with-methods); that syntax was never supported here.
So this is a known absence, not a bug — filed as a feature so the size of what it blocks is on the board.
What it blocks
tools/run_pascal_conformance.sh --only 'tgeneric*' (fpc-testsuite), after the
nested-type-in-record fix landed, still fails these five entirely on
object:
| test | shape |
|---|---|
tgeneric62.pp |
nested object inside a generic class |
tgeneric65.pp |
generic record with a nested object |
tgeneric66.pp |
generic TTest<T> = object with a nested record |
tgeneric67.pp |
generic TTest<T> = object with a nested class |
tgeneric68.pp |
generic TTest<T> = object with a nested object |
tobject*.pp in the same suite is 6 skipped / 4 auto-gated — none of it runs.
Shape of the work
Most of the machinery exists. A value object is, in pxx's model, a record:
ParseRecordFields already parses methods, class methods, const sections,
visibility sections, class operator signatures, and (as of this ticket's
sibling) nested type sections. The distances from a record are:
- the keyword — the type-declaration parser needs an
objectarm that routes to the record path, kept apart from theobjectreference type in ParseTypeKind (which is atyPointerovertyClass). The two meanings are distinguished by POSITION, not by lookahead:= objectin a type declaration opens a body;: objecton a var/field/param names the rooted reference. Anything else is guesswork and will get one of them wrong. - single inheritance —
TChild = object(TParent). Records do not inherit andUClsParentis already there for classes, so this is the one genuinely new piece for the record layout path. constructor/destructoron a value object, andnew(p, Init)/dispose(p, Done)— the two-argument forms. Worth deciding whether to support at all (see below) rather than assuming.- virtual methods on a value object — a VMT pointer field, only present if
the object declares one. This is where the real cost is, and it is what
tobject*.ppmostly tests.
Escalation (Track U)
Rungs 1+2 are cheap and unlock the five generics tests plus ordinary
record-with-inheritance code. Rungs 3+4 are a different size, and object is
deprecated in FPC's own documentation. Recommend: implement 1+2, refuse 3+4
with a clear diagnostic ("a value object cannot be virtual / cannot have a
constructor — use a class or an advanced record") rather than accepting them and
being silently wrong, which is the failure mode
devdocs/dev/root-cause-over-microfix.md is about. If that split is wrong, it
is a Track U call; file decide-how-much-of-legacy-object-we-implement.
RANKED DOWN 2026-08-25 — this is option B of a decision that chose option A
[[decide-old-style-object-types]] was answered the same day this ticket was
filed: we do not implement object types now. This ticket is not rejected —
it is the correct shape of the work if the answer flips — but it is ranked to
15 so it is not dispatched as ordinary queue work.
The decision's basis, in one line: frontend-compat-philosophy.md says "a
corpus is a measuring instrument, not a dependency" and "do not justify core
work with a corpus". This ticket's own motivation is "five fpc-testsuite
generics tests fail on this alone" — conformance tests, which is exactly the
population the rule names. Measured 2026-08-25: no = object declaration exists
anywhere in lib/, compiler/ or examples/, and no real-world target
(self-host, the FPC RTL subset, Synapse, fgl, zlib, sqlite, QuickJS) needs it.
The owner's standing framing is a pragmatic compiler, not a conformance
trophy.
The revisit trigger, and it is narrow: actual source someone wants to build. Not another failing conformance test. When that arrives, this ticket carries the work — in full (option B, a value type with a VMT), never the non-virtual subset, which the decision keeps refused as "the bad middle".
One finding here that IS worth acting on independently
This ticket records something the decision ticket did not know: object is
already claimed by an unrelated meaning in ParseTypeKind (a rooted object
reference, feature-object-reference-type). That is one keyword with two
meanings in one parser, which root-cause-over-microfix.md would call a
mechanism count of two for one token — worth a note wherever that feature is
documented, independently of whether value-objects are ever built.