← board

for x in ... iteration — FPC-exact (arrays, sets, strings, enums, enumerators)

Why separate

The generator for-in is one fixed shape: for x in Gen(args) desugars to CoAlloc / while CoNext do x := CoCurrent / CoFree. General iteration is a much broader surface with many container shapes and edge cases, none of which generators need. Keep it out of the concurrency arc.

Decision (2026-06-17) — clone FPC's for-in surface exactly

Design locked after review: implement exactly what FPC/Delphi do, no invented protocol, no new grammar. FPC's for-in compiles unmodified → the Lazarus-compat line ([[project_gtk_gui_arc]]) holds. Dialect extensions (two-var for k, v in, reverse, index-pair) are explicitly deferred — add later only when needed, never as v1 divergence.

The protocol is FPC's structural enumerator — NOT a custom contract

for X in C do resolves in this fixed order:

  1. Native — C is array / set / string / enum-type → compiler emits the loop directly from known layout. No method lookup.
  2. Structural enumerator — C has a method GetEnumerator returning E, where E has function MoveNext: Boolean and a readable Current member. Pure duck-typing on those three names — no base class, no TCollection, no interface required. (My earlier TCollection mention was misleading; FPC does NOT require any hierarchy.)

Hardcoding the three identifier names (GetEnumerator / MoveNext / Current) in the compiler is accepted as fair — it is the FPC contract verbatim.

Exact desugar (matches FPC manual)

for X in C do Body;

__e := C.GetEnumerator;
try
  while __e.MoveNext do begin
    X := __e.Current;     { Current may be property (getter) or field }
    Body;
  end;
finally
  __e.Free;               { ONLY when E descends TObject; record E = no free }
end;

Generator = one enumerator implementer

The shipped stackless generator ([[project_next_arc_concurrency]]) folds in: GetEnumerator may return a generator; MoveNext = resume, Current = last yield. The existing generator for-in special-case becomes one path under this umbrella — same win generators already proved.

Sources to cover — exactly FPC's set (each a side-case study)

Native tier (compiler knows layout, no GetEnumerator):

Structural tier (GetEnumerator):

Deferred (NOT v1 — dialect extension later):

Hard questions / side-cases to resolve in design

Approach — sliced

Library-first: array/string/set iteration is compiler-native (Slice A); collection enumerators live in lib/rtl. for-in lowering in the compiler stays minimal and shared (pre-codegen) so all targets get it free.

Acceptance

for x in <static-array|dynarray|open-array|string|set|enum-type> yields the right value sequence with correct element typing (Slice A). for x in <object with GetEnumerator> desugars correctly incl. enumerator lifetime (Slice B). Generator for-in routes through the same path (Slice C). A representative FPC for-in program compiles unmodified. Self-host fixedpoint + cross-bootstrap byte-identical (run make cross-bootstrap — the generator work proved x86-64 fixedpoint alone misses cross regressions).

Log