different-ai/openwork · error · InterpreterRuntimeError
for...of supports one declared binding.
Error message
for...of supports one declared binding.
What it means
The for...of head may declare exactly one binding per iteration element. If the left side is a VariableDeclaration with more than one declarator (e.g. `for (const a = 1, b = 2 of ...)`), the interpreter throws at the declaration node. Real JS cannot express multiple declarators in a for-of head either; this enforces that and the interpreter's simpler binding model.
Source
Thrown at packages/codemode/src/interpreter/runtime.ts:1098
return Effect.gen(function* () {
const left = getNode(node, "left")
const right = yield* self.evaluateExpression(getNode(node, "right"))
const body = getNode(node, "body")
// Arrays iterate in place; strings iterate code points; Maps iterate [key, value]
// pairs and Sets iterate values over a snapshot (mutation during iteration is safe).
const iterable = Array.isArray(right) ? right : spreadItems(right)
if (iterable === undefined) {
throw new InterpreterRuntimeError("for...of requires an array, string, Map, or Set value in CodeMode.", node)
}
let declaration: { readonly pattern: AstNode; readonly mutable: boolean } | undefined
let assignmentName: string | undefined
if (left.type === "VariableDeclaration") {
const declarations = getArray(left, "declarations")
if (declarations.length !== 1) {
throw new InterpreterRuntimeError("for...of supports one declared binding.", left)
}
const declarator = asNode(declarations[0], "declarations[0]")
declaration = { pattern: getNode(declarator, "id"), mutable: getString(left, "kind") !== "const" }
} else if (left.type === "Identifier") {
assignmentName = getString(left, "name")
} else {
throw new InterpreterRuntimeError("Unsupported for...of binding.", left)
}
for (const value of iterable) {
if (declaration) {
self.pushScope()
yield* self.declarePattern(declaration.pattern, value, declaration.mutable, left)
} else if (assignmentName) {
self.setIdentifierValue(assignmentName, value, left)
}
View on GitHub (pinned to 2b7df46e8a)
Solutions
- Use exactly one declarator: `for (const x of iterable)`.
- Destructure in the single binding if you need multiple values: `for (const [a, b] of pairs)`.
- Move extra declarations outside the loop.
Example fix
// before (AST with two declarators) for (const a, b of items) ... // after for (const [a, b] of items) ...
Defensive patterns
Strategy: validation
Validate before calling
// AST check before execution
if (forOfNode.left.type === "VariableDeclaration" && forOfNode.left.declarations.length !== 1) {
throw new Error("for...of head must declare exactly one binding");
} Try / catch
try {
interpret(src);
} catch (e) {
if (e instanceof InterpreterRuntimeError && e.message.includes("one declared binding")) {
// fix the AST/code to a single declarator or destructuring
}
throw e;
} Prevention
- Emit exactly one declarator in for-of heads when generating ASTs.
- Use destructuring ([a, b]) to bind multiple values from one element.
- Add AST validation in your code generator for loop heads.
When it happens
Trigger: A for...of whose left side is a VariableDeclaration whose `declarations` array length !== 1, typically from malformed or hand-written AST-like code.
Common situations: Programmatically generated CodeMode ASTs (e.g. by an LLM emitting raw nodes) that accidentally include two declarators; minified/transformed code that merged declarations.
Related errors
- Unsupported for...of binding.
- for...of requires an array, string, Map, or Set value in Cod
- for...in supports one declared binding.
- Unsupported for...in binding.
- Expected '${key}' to be a boolean.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/1c64631e7e6d56e6.
Report an issue: GitHub.