import type { TsNode } from "../../ir/tsIR.js"; import type { AgencyNode } from "../../types.js"; import type { CompilationUnit } from "../../compilationUnit.js"; import type { NameClassifier } from "./nameClassifier.js"; /** * Emits the destructive-execution tracking codegen: the per-function * `__destructiveRan` init, the function-exit boundary stamp, the region-entry * flip (`markDestructiveRan`, emitted by a `markDestructiveRan` node from an * inlined `destructive { }` region), and the Rule-2 caller-side flips. Extracted * from TypeScriptBuilder so the tracking rules live in one declarative place. * * Stateless by design: every method is a pure function of its arguments (the * `inDestructiveFunction` flag is passed in, not held), so it can be * unit-tested directly — feed it a statement, assert the emitted nodes. * * How the flag works: every function activation carries a boolean * `__self.__destructiveRan`. It starts false and is only ever flipped to true * (sticky). It is set at ENTRY for a `destructive def` (init(true)), or at * region entry for a `destructive { }` region. The function exit folds it into * any departing failure via `stampFailureBoundary`, so a failure reports whether * execution entered a destructive region. NOTE: because a `destructive { }` * region is inlined into the function body before codegen, its flip lands on the * function's `__self` — never a block frame — which is what keeps a failure * escaping the function from carrying a false (fail-open) flag. */ export declare class DestructiveTracking { private readonly names; private readonly compilationUnit; constructor(names: NameClassifier, compilationUnit: Pick); /** `__self.__destructiveRan = __self.__destructiveRan ?? false` — emitted * UNCONDITIONALLY in every function. Decision 8 sets this at runtime (a * destructive tool inside an llm() call), which the builder cannot see * statically; the unconditional boolean init keeps the exit stamp from * ever computing `x || undefined`. */ init(inDestructiveFunction: boolean): TsNode; /** `stampFailureBoundary(runner.haltResult, __self.__destructiveRan)` — the * expression the function-exit halt check embeds to fold this activation's * flag into a departing failure. */ exitStamp(): TsNode; /** Post-statement destructive flip for one statement, for a function that is * NOT itself `destructive`. A `destructive def` commits at entry (see * `init`), so it needs no per-statement flips. * * Rule 2 (any function calling a destructive fn): when the call's result * binds to a simple local (`const r = burn()` / `const r = try burn()`), an * outcome-dependent POST flip trusts the result's own destructiveRan (a * swallowed destructive failure still marks us; a clean refusal does not). * Otherwise a conservative PRE flip fires whenever the statement textually * contains a destructive call. */ statementFlips(stmt: AgencyNode, inDestructiveFunction: boolean): { pre?: TsNode; post?: TsNode; }; /** `__self.__destructiveRan = true` — sets the activation's destructive-ran * flag. Emitted at a `destructive { }` region entry (via the * `markDestructiveRan` node) and as the Rule-2 conservative pre-flip. */ markDestructiveRan(): TsNode; /** `__self.__destructiveRan = __self.__destructiveRan || (isFailure(__self.v) * ? __self.v.destructiveRan : true)`. The destructive callee's result is * bound at `__self.`; OR its outcome into the activation. The ternary * printer parenthesizes itself, which is exactly what precedence needs here * (`||` binds tighter than `?:`), so no explicit paren option is required. */ private outcomeFlip; /** If `stmt` is a simple assignment whose value is a direct call to a * destructive function (optionally `try`-wrapped), return the local it * binds to. Null for bare calls, nested-in-expression calls, patterns, * access chains, or non-innermost block targets — those get the * conservative pre-flip instead. */ private destructiveOutcomeVar; }