← board

Typed metaclasses: class of TFoo, so a TClass variable can call the class's methods

What works today

Self in a class method is the metaclass and carries the RUNTIME class, and every route to a class method passes it (named class, through an instance, bare sibling call). Class-reference OPERATIONS work on any class reference:

var cr: TClass;
cr := TDerived;
writeln(cr.ClassName);              { works }
writeln(cr.InheritsFrom(TBase));    { works }

What does not

Calling a class METHOD through a class-reference variable:

var cr: TClass;
begin
  cr := TDerived;
  cr.W;      { error: member access on a bare object reference }

The value is right (it IS the blob pointer, and the call would pass it as Self unchanged) — what is missing is knowing WHICH class's methods are in scope. TClass is untyped: it is class of TObject, so only TObject's class methods should resolve through it. FPC's answer is the TYPED metaclass:

type
  TTestCaseClass = class of TTestCase;   { fpcunit declares exactly this }
var
  tc: TTestCaseClass;
begin
  tc := TMyTest;
  tc.Suite;        { resolves against TTestCase's class methods }

Shape of the work

Note the dispatch is already dynamic: because Self is a real argument, a class method called through a metaclass variable runs with whatever class the variable holds. So this ticket is about the FRONTEND's name resolution, not about the runtime.

Gate

make test + self-host byte-identical + cross.

RESOLVED 2026-07-14 — already landed (stale ticket)

Everything in "shape of the work" shipped during the fpcunit/fpjson arcs: typed metaclass member resolution + virtual class methods (b278), any constructor through the metaclass (BuildMetaclassNew), descendant checking on assignment (CheckMetaclassAssign), metaclass ARRAY ELEMENTS as receivers (b328). Verified tonight: class method via tc: TBaseClass dispatches on the RUNTIME class, virtual ctor builds the derived instance, ClassName answers. Pinned by test_typed_metaclass_b278.pas + test_metaclass_array_element_b328.pas.

Log