for-in over a qualified set member source fails
- Type: bug (compiler / parser)
- Status: done
- Owner: Track A
- Opened: 2026-06-21
- Relation: next
examples/adventureblocker after the text-file surface is made visible; sibling tofeature-forin-member-access-sourceandfeature-for-in-iteration.
Symptom
After importing the PAL-backed textfile unit explicitly and compiling
adventure with the POSIX platform path, the demo gets past Assign and stops at
the save path:
for sp in Player.Spells do
WriteLn(f, 'spell=' + LowerStr(SpellName(sp)));
pascal26:567: error: for-in: variable is not a string or array ()
Other similar uses exist in the inventory UI:
for sp in g.Player.Spells do ...
Player.Spells is a qualified member-access expression whose type is
TSpellSet = set of TSpell.
Root Cause
Set iteration itself is implemented (feature-for-in-iteration), and qualified
member-access for-in sources are implemented for arrays/strings
(feature-forin-member-access-source). The node-based classifier used for
qualified sources still routes only string/array metadata; it does not recover
set element metadata for a set-valued field expression.
Direction
- Extend the node-based for-in source classifier to recognize set-valued
qualified lvalues (
obj.field,Self.field, nested fields). - Recover the element enum type/range for the field's set type, equivalent to
the existing symbol-based
SymSetEnumIdpath for plain set variables. - Reuse the existing set-membership scan lowering: iterate ordinals and execute
the body only when
ord in setExpr. - Keep array/string member-access for-in behavior unchanged.
Acceptance
- A minimal program with
for e in obj.SetField do ...overset of <enum>prints members in ordinal order. for e in Self.SetFieldand a nested member source also work, if practical.examples/adventuregets pastfor sp in Player.Spells.- Existing for-in tests stay green.
Log
- 2026-06-21 - Opened from the adventure compile path. The previous member-access for-in ticket was array/string focused; this covers set-valued qualified sources specifically.
- 2026-06-21 - DONE (Track A). The set membership-scan desugar was extracted from
ParseForInSetASTinto a reusableBuildForInSetLoop(varIdx, setEnumId, setElemTk, setOperand, bodyNode)that takes an abstract set value node.ParseForInNodeAST(the qualified member-access source path) now recovers the set element enum/range — for anAN_IDENTsource fromSymSetEnumId/ElemTk, for anAN_FIELDsource from two new parallel arraysUFldSetEnumId/ElemTk(captured inAddUFieldfromLastTypeSetEnumId/ElemTk, mirroring the symbol/property paths) — and routesset-valued sources toBuildForInSetLoopoverCloneAST(contNode)before the array/string classifier.for sp in g.Player.Spells(nested member access) andfor sp in Self.Fieldboth work. Regression testtest/test_forin_set_member.pas.make testgreen; self-host- threadsafe fixedpoint byte-identical.
Resolved-in: 085be11 (finalizing commit)