/** * expressionSlots — the single source of truth for "which positions of a * node hold expressions, and when those positions execute". The * expression twin of `bodySlots` (bodySlots.ts), which owns the same * question for statement bodies. * * Why it exists: hand-written expression-position lists drift. The * hoisting pass (lib/preprocessors/hoistCalls.ts) shipped with its own * list and three holes were found in one week — thread blocks in value * position, goto arguments, indexed-assignment targets — each a place * where the resume desync the pass exists to fix silently survived. * `bodySlots`' header records the identical history for statement * bodies; this file is the same cure one level over. * * Consumers: * - `expressionChildren` (node.ts) — the derived read view * - `hoistCalls` (preprocessors) — rewrites via each slot's `write` * * NOT yet on this table (each threads ancestor/scope state a plain slot * list does not model; migrating them is a recorded follow-up, not an * oversight): the `walkNodes` generator's own expression descent and * `getAllVariablesInBody`, both in node.ts. Until then, a new node kind * registers expression positions HERE and, if it needs symbol-table or * variable-collection coverage, in those two as well. * * The expression/statement seam: some expressions also own statement * bodies (a thread block in value position; a call with a block * argument). The rule, mirrored in bodySlots.ts: when a consumer walks a * slot's expression and that node has `bodySlots`, its statement bodies * are reached through `bodySlots`, never through expression slots. An * `ifElse` therefore contributes only its condition here — its branches * are statement bodies even in the (parse-time-lowered) value form. * * Slots are returned in EVALUATION order and never overlap (each targets * a distinct field or index) — that is what makes folding `write` calls * safe; the corpus round-trip test enforces both properties. * * Completeness is enforced, not assumed: every member of * `EXPRESSION_NODE_TYPES` must appear either in the switch (tracked in * `HANDLED_KINDS`) or in `NO_EXPRESSION_SLOTS`; the unit test fails by * name on an unregistered kind, and `hoistCalls` throws at compile time * if one ever reaches it. */ import type { AgencyNode } from "../types.js"; /** When a slot's expression executes, relative to its owner running. * Rewriters key safety decisions on this; read-only consumers ignore * it. NOTE: no consumer today branches on conditional-vs-opaque — both * mean "skip" to rewriters and "include" to readers. The distinction * is kept as documentation of WHY a position is untouchable; do not * build logic on it without adding tests that distinguish them. * - "once": exactly once when the owner executes. Hoisting a call out * to a prior statement preserves semantics. * - "perIteration": re-evaluated every loop pass (a while condition). * A rewriter cannot hoist it before the owner; it must restructure * or leave it. The mode is the prohibition; the restructure strategy * belongs to the rewriter and is keyed on the owner's kind. * - "conditional": may never execute (short-circuit right sides, * catch fallbacks). Hoisting would run code the program skipped. * - "opaque": executes inside a runtime boundary that must not move * (try operands, the whole catch expression, with/static wrapped * statements, pipe stages). */ export type EvalMode = "once" | "perIteration" | "conditional" | "opaque"; export type ExpressionSlot = { /** The expression at this position. A read view — never mutate. */ expr: AgencyNode; mode: EvalMode; /** Fresh copy of `owner` with this slot's expression replaced. Takes * the CURRENT owner (not the node expressionSlots was called on) so * a fold over several slots composes. */ write: (owner: AgencyNode, expr: AgencyNode) => AgencyNode; }; /** Expression node kinds that genuinely carry no expression children. * The completeness test requires every EXPRESSION_NODE_TYPES member to * appear either here or in HANDLED_KINDS — an unlisted kind is a test * failure, not a silent []. */ export declare const NO_EXPRESSION_SLOTS: Record; /** Every kind the switch below enumerates — expression kinds AND the * statement kinds that carry expression positions. Exported for the * completeness test and for isRegisteredExpressionKind. */ export declare const HANDLED_KINDS: readonly string[]; /** True when `type` is known to this enumeration — either it has slots * or it is declared expression-free. hoistCalls throws on anything * else: an unregistered kind must fail by name, not fall into a silent * generic walk (that walk is how the drift holes stayed hidden). */ export declare function isRegisteredExpressionKind(type: string): boolean; /** Immediate expression positions of `node`, in evaluation order, each * with an immutable writer. Returns `[]` for kinds in * NO_EXPRESSION_SLOTS and for statement kinds that carry no * expressions. Shallow by design: consumers drive their own * recursion. */ export declare function expressionSlots(node: AgencyNode): ExpressionSlot[];