8000 bug(control): check branch outputs, binding mutation, and loop-carried values before emission · Issue #196 · Mnehmos/mnehmos.nls.lang · GitHub
[go: up one dir, main page]

Skip to content
7FFF

bug(control): check branch outputs, binding mutation, and loop-carried values before emission #196

Closed

Description

@Mnehmos

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

  1. added
    bugSomething isn't working
    coreCore language semantics and design
    multi-targetCross-platform compilation
    on Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcoreCore language semantics and designmulti-targetCross-platform compilationpriority-highImportant, address soon

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0