← board

The for-in exception-runtime trigger is the whole token shape

Filed by frankb-78 immediately after the fix it describes, so the cost is on the board rather than implied by a commit message.

What it costs, measured

test/test_forin_native.pas — only native for-in (array, string, enum, set), no try, nothing that can raise:

target pin v403 HEAD
x86-64 114456 B 118552 B
xtensa 294764 B 298860 B
riscv32 364396 B 368492 B

+4096 bytes on every target, and it is the same 4096 because it is the same stub set. On a small ESP program that is the difference that matters most.

Why it is the whole shape

Two for .. in forms synthesise a try/finally: the class-enumerator form wraps its enumerator's Free, and the generator form wraps SlFree / CoFree. The native forms (array, string, set, enum range) wrap nothing.

The stubs are code, and code emitted after the body has started lands inside it, so the decision must be made before parsing — in ParseProgram's token pre-scan. At that point for x in c and for c in s are the same tokens; which one wraps depends on what c IS, which needs the symbol table. So the scan triggers on the shape and over-approximates. See bug-a-a-generator-instance-is-not-freed-when-an-exception-escapes-the-for-in for how the under-approximation failed (call 0, a build error on every target).

The shape of a fix

Not a better pre-scan — the information is not there. What is needed is an emission point for the exception stubs that is reachable AFTER parsing, so EnableExceptionRuntime can be called from the desugar that actually needs it. The stubs are reached only by call, so a region reserved before the body and filled at the end would do it; the same move would retire the class operator Finalize token trigger, which exists for exactly this reason and has the same over-approximation (any declaration of that operator, used or not).

Three synthesised-try sites now depend on a token guess. That is the argument for the refactor, not the 4 KB.