/** v1 closure gate — fail-closed eligibility predicate AND emission * precondition for block-bodied arrow functions in native KERN bodies. * * Architecture (council + tribunal decided, Option B′ "TS-AST-grounded raw * body"): the lambda IR carries the raw block text (`bodyBlock.raw`) for * verbatim TS re-emit ONLY. Every analyzer/consumer reads the TS AST obtained * through `parseClosureBlockAst` here — never scans the string. The IR does * NOT store the `ts.Block` (serialization safety); this helper recomputes it * (cheap; memoized via a module-level `Map`). * * The gate is the SINGLE owner of "is this block body supported" — used by: * - the parser (parse-time validation, fail-closed) * - the migrator eligibility classifier (`native-eligibility-ast.ts`) * - the Python lowerer precondition (`codegen-body-python.ts`) * so nothing eligible can fail to lower. * * ── Mutation v1 (the closure-mutation slice) ─────────────────────────────── * The gate now ACCEPTS in statement position: * (a) assignments to bare identifiers — block-LOCAL or FREE alike. The gate * is a SHAPE classifier: it sees only the block (params live on the arrow * and are stripped before the block is parsed), so it CANNOT tell a free * capture from a closure param. Both are accepted here; the Python * EMITTER (`emitBlockClosurePy`) decides pinned-vs-`nonlocal` using the * enclosing loop context it alone can see, and the LOWERER excludes * params from the written-free set (so a param write stays a plain local * assignment, never `nonlocal`). The compound forms `+=,-=,*=,/=,%=` are * accepted; statement-position `++`/`--` lower to `+= 1`/`-= 1`. * (b) member/index writes on any non-`this` base (`acc.total = 1`, * `acc[i] = v`, compound forms) — by-reference parity, no `nonlocal`. * It KEEPS REJECTING, with precise reasons: * - `this`-rooted targets → `closure-this` (unchanged). * - destructuring / parenthesized targets → `closure-unsupported-assign-target`. * - assignment operators outside {=,+=,-=,*=,/=,%=} (e.g. `&=`, `|=`, `<<=`) * → `closure-unsupported-operator`. * - value-position `++`/`--` (operand of a larger expression, e.g. * `arr.push(x++)`) → `closure-incdec-value-position`. * The eligibility≢lowerability gap for PINNED captures (a free write to a * per-iteration loop binding) is intentional and surfaces as a LOUD compile * error at emission (`closure-pinned-write`), not here: the single-statement * gate cannot see the enclosing loop header. * * v1 is deliberately NARROWER than the lowering machinery * (`lowerJsClosureBodyToPython` supports try/for-of; the gate rejects them). * Widening the gate is a future slice. */ import ts from 'typescript'; /** Parse a raw closure block (`{ ... }`, braces included) into a `ts.Block`, * or `null` if it does not parse cleanly as a single function body block. * * Generalized from the former `parseClosureBlock` in * closure-python-lowering.ts (which now imports this). Route behavior is * unchanged: the lowerer still validates the same way. */ export declare function parseClosureBlockAst(raw: string): ts.Block | null; export interface LegacyParamSignature { /** Simple identifier binding name, or `null` for destructuring / non-identifier * patterns (which contribute no single legacy-param name — structured bindings * flow through the `param` child path instead). */ name: string | null; /** Default-value expression text (`param.initializer`), or `null` when absent. */ default: string | null; } /** BLOCKER 2 + IMPORTANT 3 — parse a legacy `params="..."` string with the REAL * TypeScript parser and return one entry per parameter. Owns the * `ts.createSourceFile` call (this module already statically imports * `typescript`) so `host-namespace-ir.ts` need not — that keeps the core * barrel's `typescript`-importer pin at 5 (browser-spine-import-graph.test.ts). * * Wrapping the raw list in `function _(){}` and reading each * `ParameterDeclaration` auto-handles every case a hand-rolled char-scanner * mis-split: `==`/`===`/`<=`/`>=` inside a default, regex literals with commas, * nested generics, template literals. * * Fails CLOSED on malformed input: if `source.parseDiagnostics` is non-empty * (e.g. `params="process = ("`), returns `null` instead of producing phantom * recovery-AST bindings — so a caller never treats a host root (`process`) as * shadowed by a binding the user never actually wrote. A successful-but-empty * parse (no params) returns `[]`. */ export declare function parseLegacyParamSignature(raw: string): LegacyParamSignature[] | null; export interface ClosureBlockMemberAccess { root: string; member: string; locallyShadowed: boolean; } export declare function collectClosureBlockLocalBindingNames(raw: string): Set; export declare function collectClosureBlockMemberAccesses(raw: string): ClosureBlockMemberAccess[]; /** Slice 2 review fix (round 3) — a regex-host violation found INSIDE a * block-bodied arrow. Carries the EXACT fail-close `message` the EXPRESSION-level * path would emit, so the TS-verbatim block leg agrees with that path (and with * the Python leg, which lowers the body through the IR) BY CONSTRUCTION rather * than re-deriving the truth table: * - `bareRegexp` — a bare VALUE reference to `RegExp` (`return RegExp`, * `const R = RegExp`, `{ x: RegExp }`, `({ RegExp })`, `[RegExp]`, a ternary * branch, an argument). The generic member scan never sees these (no * identifier-rooted `Root.member`), so they slip the verbatim TS leg. The * MEMBER-OBJECT position (`RegExp.prototype`, `RegExp[$1]`) is deliberately * EXCLUDED — the generic scan owns it and emits the GENERIC host-namespace * message there (matching the expression-level member-receiver screen). * `message` is always `REGEX_HOST_REGEXP_FAILCLOSE`. `locallyShadowed` * reports a block-scope re-declaration so the caller can also honor an outer * user binding. * - `regexLiteralAccess` — a property/element access on a regex LITERAL * (`/x/.source`, `/x/["flags"]`, `/x/.test(s)`, `/x/.exec(s)`, * `/x/.compile(y)`). The receiver is a literal (never user-bindable), so it * never honors a binding (`locallyShadowed` is always false). `message` is * the shared classifier's verdict: `null` when the access is PORTABLE * (`/x/.test(s)` — NO violation pushed) or the exact fail-close message * otherwise (regex-host for reads/non-portable methods, `REGEX_EXEC_FAILCLOSE` * for `.exec`, `REGEX_TEST_G_FAILCLOSE` for `/g`-literal `.test`). */ export interface ClosureBlockRegexHostViolation { kind: 'bareRegexp' | 'regexLiteralAccess'; root: string; locallyShadowed: boolean; /** The exact fail-close message to throw (mirrors the expression-level path). */ message: string; } /** Walk a closure block's TS AST and collect the regex-host violations the * generic member-access scan cannot see (bare `RegExp` VALUE references and * regex-LITERAL property/element accesses). Tracks REAL JS block scope: each * block predeclares its top-level let/const/function/class names BEFORE visiting * its refs, so a name shadows only within its own block + nested blocks. A * parse failure yields an empty list (the gate already rejected such bodies). */ export declare function collectClosureBlockRegexHostViolations(raw: string): ClosureBlockRegexHostViolation[]; /** A bare-identifier operand of a `typeof` expression found inside a closure * block (`typeof Date`, `typeof process`). The host-root decision is made by the * CONSUMER (`typescript-closure-classifier.ts`), so this collector stays free of * host-namespace coupling — it only reports the operand `name` and whether a * block-scope local shadows it. RegExp is NOT special-cased here: a bare `RegExp` * in `typeof` position is already a value reference caught by * `collectClosureBlockRegexHostViolations` (round-6 removed the `typeof` exemption * in `isRegExpNonValuePosition`), so it fails-close there with the regex message. * This collector covers the OTHER reserved host roots with the generic message. */ export interface ClosureBlockTypeofOperand { name: string; locallyShadowed: boolean; } /** Walk a closure block and collect every `typeof ` operand, * tracking REAL JS block scope identically to * {@link collectClosureBlockRegexHostViolations}: each block predeclares its * top-level let/const/function/class names (incl. destructuring) BEFORE visiting * its refs, so a block-local shadow is honored for the whole block. Only the * bare-identifier operand of a `typeof` is reported, AFTER recursively peeling * the transparent TS-AST wrappers via {@link unwrapRegexReceiverTS} — so a * WRAPPED operand (`typeof (Date as any)`, `typeof (Date!)`, parenthesized * `typeof (Date)`, nested `typeof (Date as any as unknown)`) records the * underlying `Date` name, identically to the ValueIR legs that peel * `typeAssert`/`nonNull` via `unwrapTransparentReceiverIR` (round-7 closes the * wrapped-operand bypass on this leg too). `typeof Date.now` (a member operand) * is owned by the generic member-access scan, and any operand that does NOT * unwrap to a bare identifier records nothing. A parse failure yields an empty * list (the gate already rejected such bodies). */ export declare function collectClosureBlockTypeofOperands(raw: string): ClosureBlockTypeofOperand[]; /** Flatten a binding NAME (plain identifier OR an object/array destructuring * pattern) to the identifier names it binds. `const { RegExp } = x` → * `['RegExp']`, `const [a, , b] = arr` → `['a','b']`. Exported so the Python * closure lowerer's block-scope tracker (`blockTopLevelDeclaredNames` in * closure-python-lowering.ts) extracts shadow names the SAME way as the TS-AST * closure walk here — a destructured `RegExp` shadow is then honored * symmetrically on both legs (no fail-open on one target). */ export declare function bindingPatternIdentifierNames(name: ts.BindingName): string[]; /** Collect the raw source text of every CALL expression nested anywhere in a * closure block, via the shared TS AST (`parseClosureBlockAst`) — never a * string scan. Returned to consumers that re-parse each call into their own * IR (the TS body emitter's bound-regex-method fail-close), so those callers * need no static `typescript` import of their own. Keeping the `ts` AST walk * quarantined in this Node-only module is what keeps `body-ts.js` OFF the * browser-spine TS-importer pin (`browser-spine-import-graph.test.ts`). A * parse failure yields an empty list (the gate already rejected such bodies, * so this is defensive). */ export declare function collectClosureBlockCallTexts(raw: string): string[]; /** Collect the set of free identifier NAMES referenced in a closure block — * identifiers used in the block that are NOT declared inside the block and * NOT in `paramNames` (the closure's own parameters). These are exactly the * names the closure CAPTURES from its enclosing scope. * * Slice-2 loop-variable pinning consumes this: a captured name whose binding * resolves at-or-inside the enclosing loop body must be pinned via a Python * default arg, so each closure sees its own iteration's value (JS per-iteration * capture) instead of late-binding to the last value. * * Uses the TS AST (via `parseClosureBlockAst`) — never string scanning. * Excludes, per the spec: * - the `.name` side of a member access (`a.b` references only `a`), * - object-literal property keys (`{ a: 1 }` — `a` is a key, not a ref), * - declaration names themselves (`const x = …` — `x` is the bound name), * - shorthand-property assignment names are NOT excluded: `{ a }` reads `a`, * so the shorthand identifier IS a real reference and stays in the set. * * A name both declared-inside and referenced (a block-local, or a shadowing * re-declaration) is NOT free — `collectLocalDeclaredNames` removes it. The * block is parsed once (memoized); a parse failure yields an empty set (the * gate already rejected such bodies, so this is defensive). */ export declare function collectFreeIdentifierNames(raw: string, paramNames: string[]): Set; /** The assignment operators the mutation-v1 gate accepts (mirrored by the * Python lowerer, which emits the same compound operator directly). Anything * else (`&=`, `|=`, `^=`, `<<=`, `>>=`, `>>>=`, `**=`, `&&=`, `||=`, `??=`) * rejects with `closure-unsupported-operator`. The lowerer SHARES this set so * the gate and the emitter never drift. */ export declare const CLOSURE_ASSIGN_OPERATORS: ReadonlySet; /** Classify a closure block body. Returns `null` if the body is supported by * the v1 gate, or a distinct reject-reason string otherwise. * * ACCEPT set: `let`/`const` (identifier names + initializers, no * destructuring), `return` (with or without expression), expression * statements, `if`/`else` (block or single-statement branches, nesting fine). * Statement-position MUTATIONS are now accepted: assignments to a bare * identifier (local OR free) and to a non-`this` member/index target, with the * operators {=,+=,-=,*=,/=,%=}, plus statement-position `++`/`--`. * * REJECT (whole-block walk): `this` (incl. a `this`-rooted assign target → * `closure-this`), nested arrow/function/class, `yield`, `await`, any loop, * `throw`, `try`, `switch`, `break`/`continue`, `var`, parameter default * values, spread, labeled statements, `with`; a destructuring/parenthesized * assign target (`closure-unsupported-assign-target`); an assignment operator * outside the accepted set (`closure-unsupported-operator`); and a * value-position `++`/`--` (`closure-incdec-value-position`). Member/index * mutation and method calls on a captured object (`acc.push(x)`) are allowed. * Any statement outside the accept set rejects. NOTE: a free write to a * PINNED per-iteration loop capture passes the gate but is rejected LOUDLY at * Python emission (`closure-pinned-write`) — the single-statement gate cannot * see the enclosing loop header (eligibility≢lowerability, by design). */ export declare function classifyClosureBlock(raw: string): null | string;