export interface LowerJsClosureBodyToPythonOptions { lowerExpression(expr: string): string; lowerCondition?(expr: string): string; /** The closure's own parameter names. A bare-identifier write to a param (or * to a block-LOCAL declared inside the closure) is a def-local plain * assignment — it must NOT be reported as a written FREE name (no * `nonlocal`). Omitted = the closure has no params. */ paramNames?: string[]; /** Resolve a bare-identifier WRITE TARGET to its emitted Python name. The * class path passes `resolveLocalRename` so a write to a shadow-renamed * capture targets the SAME renamed binding its reads resolve to (without * this, `x = x + 10` against a shadowed capture wrote the OUTER binding * while reads hit the renamed inner one — silent wrong values both ways, * probe-verified). The route path has no renames — omitted = identity. * `writtenFreeNames` still reports SOURCE names; the consumer resolves * again when building its `nonlocal` line. */ lowerAssignTarget?(name: string): string; /** BLOCK-SCOPE hooks (Slice 2 review fix, round 3). The lowerer FLATTENS * nested blocks into one Python suite, so the consumer's per-expression * guards (e.g. the host-`RegExp` value screen) lose the lexical block scope * unless told the boundaries. `enterBlockScope` is called with a block's * TOP-LEVEL let/const/function/class names just BEFORE its statements lower * (JS hoists them for the whole block, so a reference anywhere inside — * even lexically before the declarator — sees the block-local); the matching * `exitBlockScope` is called after, with the SAME names. The consumer uses * them to push/pop block-local shadows so a `RegExp` reference fails-close * ONLY when no in-scope block-local/param shadows it — byte-aligned with the * TS-AST closure walk. * * REQUIRED (round-7 — was silently `?`-optional). An optional hook let a * consumer omit the wire and the lowerer silently no-op the block-scope * tracking → the Python leg would FAIL-OPEN on a destructured / nested-block * `RegExp` shadow while the TS leg stayed closed, a one-target divergence the * type system could not catch. Making both REQUIRED turns a missing wire into * a COMPILE ERROR. A consumer that genuinely wants NO block-scope tracking * (the route path, which screens host names through a different rewriter and * has no per-block shadow stack) passes EXPLICIT no-op (identity) functions — * an intentional opt-out that is visible at the call site, not an accident. */ enterBlockScope(names: string[]): void; exitBlockScope(names: string[]): void; } export interface LowerJsClosureBodyToPythonResult { ok: boolean; lines: string[]; reason?: string; /** The set of FREE identifier names this closure WRITES (bare-identifier * assignments / `++` / `--` to a name that is neither a closure param nor a * block-local declared inside the body). The CONSUMER decides what to do * with them: the Python class/native emitter (`emitBlockClosurePy`) throws * `closure-pinned-write` for any that are per-iteration loop captures and * prepends a `nonlocal` line for the rest; the route hoist wrapper * (`lowerArrowBlockClosure`) prepends `nonlocal` for ALL of them (route * hoisted defs nest in the handler function, with no pinning concept). * Member/index writes (`acc.x = …`) mutate a captured object by reference * and never appear here — Python needs no `nonlocal` for them. Empty when * `ok` is false. */ writtenFreeNames: Set; } export declare function lowerJsClosureBodyToPython(body: string, opts: LowerJsClosureBodyToPythonOptions): LowerJsClosureBodyToPythonResult;