/** * hoistCalls — rewrite every statement so helper calls become their own * `const __hoist_N = ...` statements. * * Why: resume replay re-executes the statement that was in progress at a * pause, including helper calls that already completed. Those helpers * re-claim frames from the positional restore queue and desync it (the * still-live owner gets a blank frame and re-issues work). A hoisted * helper is its own runner step: completed steps are skipped on resume * and their value is read back from `__stack.locals`, so the helper * never re-runs. Spec: * docs/superpowers/specs/2026-07-22-hoist-calls-resume-safety-design.md * * The invariant this pass establishes: after preprocessing, a statement * re-executes no completed frame-pushing call on resume — the only call * remaining in unconditionally-evaluated position is the statement's own * tail. Tails stay deliberately: hoisting them uniformly would need a * graph-node-call exclusion (node calls are control flow and THROW in * value position — typescriptBuilder's processNodeCall), while at tail * position they need no rule at all. * * Structure comes from `expressionSlots` (lib/utils/expressionSlots.ts) * — positions and eval modes are data there, completeness-checked * against EXPRESSION_NODE_TYPES; an unregistered kind THROWS here * rather than falling into a silent generic walk. That guarantee is * type-level for EXPRESSION kinds only. Statement kinds have no type * mirror, so the statement dispatch is corpus-checked instead: every * statement kind observed across stdlib and the generator fixtures * must appear in one of the lists below (extracted, own-case, * deliberately-not-extracted, or skipped), and the statement-position * test in expressionSlots.test.ts fails by name on one that does not. * Statement-body recursion is driven by `bodySlots` (the single source * of truth for which fields hold statements); only the expression * interior uses a generic child walk. Rulings are data, not control * flow. Everything copies; * parsed AST is never mutated (in-place mutation burned this repo via * the parse cache clone-on-read fix). * * Temp naming: `__hoist_N`, ONE counter per frame-owning scope * (function, node, lifted block, fork branch), shared by every nested * statement list inside it. Frame locals are flat, so per-list * numbering would let a loop-body temp clobber the temp a loop * iterable re-reads on resume. Blocks own frames (blockSetup.mustache * pushes one), so block bodies restart at 0. Finalize bodies run on * the CONTAINER's frame (bodySlots documents this) and therefore share * the container's counter. Seeding scans the scope for existing * `__hoist_N` names and starts above the max — this seeding is the * collision protection; there is deliberately no lint rule (no other * compiler-reserved prefix has one). * * Known residuals (tripwire territory, not silent corruption): calls * nested inside opaque positions (short-circuit right sides, catch * expressions, try operands, `with`/`static` wrappers, mid-chain method * calls inside a hoisted chain), and block bodies nested inside opaque * expressions. */ import type { AgencyNode, AgencyProgram } from "../types.js"; type Counter = { n: number; }; export declare const SKIP_TYPES: string[]; export declare function hoistCallsInProgram(program: AgencyProgram): AgencyProgram; /** Rewrite one frame-owning scope's statements. Pass `counter` when * recursing into a nested statement list of the SAME frame (loop and * if bodies, finalize bodies, thread blocks); omit it at a frame * boundary (function, node, lifted block, fork branch) so numbering * restarts against that frame's own flat locals. */ export declare function hoistCallsInScope(body: AgencyNode[], counter?: Counter): AgencyNode[]; /** The statement kinds extraction applies to — the pre-slots dispatch * set, kept exactly for behavior identity. */ export declare const EXTRACTED_STATEMENT_KINDS: string[]; /** Statement kinds with a dedicated case in rewriteStatement. Kept in * sync with the switch by hand; the statement-position corpus test * (expressionSlots.test.ts) is what makes a kind missing from every * list here fail by name. */ export declare const STATEMENT_CASE_KINDS: string[]; /** Statement kinds the pass deliberately does NOT extract from. Each * entry is a recorded ruling, not an oversight — a new statement kind * is absent from every list and fails the corpus test by name, which * is the statement-side half of the anti-drift guarantee (the * expression side is type-checked against EXPRESSION_NODE_TYPES). */ export declare const NON_EXTRACTED_STATEMENT_KINDS: string[]; export {};