← board

Generic class METHOD bodies break in a program (work in a unit)

Summary

Generic classes ARE supported — but only when the template + its method bodies live in a unit (interface/implementation), as lib/rtl/collections.pas (generic TList<T>) does; test/test_collections.pas specializes and uses it.

In a program, a generic template whose methods have implementations fails:

program g;
type generic TBox<T> = class
  Value: T;
  procedure SetIt(v: T);
end;
procedure TBox.SetIt(v: T); begin Self.Value := v; end;   { method impl }
type TIntBox = specialize TBox<Integer>;
var b: TIntBox;
begin b := TIntBox.Create; b.SetIt(42); writeln(b.Value); end.

error: expected name (reported at the template's first field line).

Narrowing

Suspected cause

ParseProgram runs a 2-pass declaration prescan; a generic class method impl (procedure TBox.SetIt) goes through ParseSubroutineBufferGenericMethod in BOTH passes (generic FUNCTIONS clear PreScanPass around themselves — parser.inc ~13844 — but generic class-method impls do not). Double-buffering / double-streaming corrupts the specialization token stream. ParseUnit already guards this (see the "clear PreScanPass around generic/operator + in ParseUnit" landmine), which is why the unit form works.

Fix direction

Guard BufferGenericMethod (and the specialize streaming it triggers) so a generic class method impl is buffered/streamed exactly once across the program's two prescan passes — mirror the generic-function PreScanPass handling.

Done when

Resolution (2026-06-25, Track A)

Fixed. A generic class-method impl (procedure TBox.Meth where TBox is a template) is consumed by ParseSubroutineBufferGenericMethod + Exit. The program/unit 2-pass declaration prescan recorded it as a replayable DeclItem, so pass 2 re-entered ParseSubroutine and buffered the SAME method a second time — duplicate TemplateTokens + GenericMethods entries corrupted the specialization stream ("expected name").

New flag GenericMethodBuffered (defs.inc): ParseSubroutine sets it when it buffers a generic method and Exits; the four pass-1 drivers (ParseProgram + ParseUnit, procedure/function and constructor/destructor) then skip recording the DeclItem, so the method is buffered once and streamed by specialize. Generic functions already avoided this by clearing PreScanPass around themselves; this is the class-method analogue.

Front-end only — self-host byte-identical. Verified program generics (single + multiple methods, function-result T, Integer + string specializations) and the unit form (test_collections) unchanged. Test: test/test_generic_class_in_program.pas (7 / hi) in make test; cross-checked i386/aarch64/arm32.