Repository navigation
Description
Audited on master at 5630d74ec9cd912d4e2f325349f1943cbc15bcc6. This is a scoped implementation item under #147, based on the typed control/dataflow/effect audit. Proposed syntax below is illustrative unless explicitly marked as a current reproducer.
Problem and current evidence
nlsc/parser.py:294–382 treats conditional assignments as prior producers without checking paths. nlsc/emitter.py:391–439 emits statement-style IFs; nlsc/emitter_typescript.py:261–299 binds branch outputs with block-scoped const and later reads them outside that block.
Current reproducer:
@module branch_probe
[probe]
PURPOSE: Conditional output
INPUTS:
- flag: boolean
LOGIC:
1. IF flag THEN 1 -> x
RETURNS: x
CLI verify reports success. Python raises UnboundLocalError for false. TypeScript raises ReferenceError for both boolean values, and tsc --strict reports TS2304. A second fixture, x = 1; x = 2; RETURNS: x, returns 2 in Python but emits duplicate const x declarations (TS2451).
Implementation hints
- Implement explicit sequence/branch/loop regions and lexical scopes in the IR. Derive value availability from definitions that dominate each use, not merely a previous textual assignment.
- A value-producing branch needs compatible values on all reachable paths, or an explicitly optional/result-valued contract. Never silently initialize missing results to None/undefined.
- Lower branch joins via block parameters or equivalent phi-like values. Backend-specific scoping must preserve the same result.
- Enforce immutable binding semantics in the checked language. Model supported mutation explicitly; represent loop-carried variables as loop parameters/results rather than untracked rebinding.
- Account for zero-iteration loops, early returns, guard failures, and nested branches.
- Do not infer arbitrary complementary conditions or exhaustiveness from English. Provide explicit ELSE/merge syntax and a migration for existing separate-IF patterns.
- Preserve existing atomized mutable Python through an explicit legacy/foreign path or a documented lowering; do not silently break the completed roundtrip work in bug(roundtrip): loop-carried mutation is not preserved by atomizer #157/bug(roundtrip): augmented assignments outside basic arithmetic lose operator semantics #160.
Acceptance criteria
- The missing-branch-output fixture is rejected consistently, before either emitter runs.
- An explicit total branch returns the same value for both paths under Python and TypeScript and passes strict target compilation.
- Illegal rebinding is rejected; legal explicit mutation/loop-carried state has consistent behavior and dependencies.
- Tests cover zero iterations, nested joins, early return, conditional use-before-definition, and field access on a branch-only value.
- Source spans identify the invalid use and relevant definition/branch.
- Graph/control analysis and backend lowering consume the same regions.
This implements computational control semantics; named FSM annotations alone are not sufficient.
Implementation ordering
Integration depends on #194, #195. Add the regression fixtures before implementation. Concrete bug fixes can land incrementally while those foundations are developed.
Osprey-informed implementation refinements
#194 / #203 has supplied the initial IR. Use its existing binding, branch, and loop nodes as the substrate for this checker; the original evidence above remains an audit-snapshot record.
- Give structured regions explicit entry/exit and lexical-scope information. Compute reachability, definitions available on all paths, and join types once for every backend.
- Typed failure edges do not produce successful branch outputs. A value assigned only before an escaping failure cannot serve as a normal-path definition.
- Make the region analysis reusable by handlers feat(effects): add typed lexical handlers and injectable operation implementations #207 and resumption feat(control): enforce affine single-shot handler resumption #208. A handler operation's result type and the answer type of the enclosing region are distinct; later resumption checking must query these structures rather than count words in LOGIC descriptions.
- Keep direct-return handler arms and explicitly resuming arms as separate IR modes when that extension arrives. A sibling containing a resume must not change another arm's control semantics.
- Do not require handler support to complete today's missing-output, rebinding, and loop-carried-value fixes.
Additional acceptance cases:
- A total branch with one returning path and one declared failing path has correctly computed reachable outputs.
- Nested joins and zero-iteration loops yield target-independent availability and type diagnostics.
- The region representation exposes mutually exclusive paths and exits for later token-consumption analysis without reparsing source prose.
Osprey references: canonical shared control representation, per-arm handler/resume semantics, and path-sensitive multiplicity checks. These inform the extension boundary; NLS retains its own mutation and exception contract.
Activity