8000 feat(backends): consume checked IR and enforce executable Python/TypeScript semantic conformance · Issue #202 · Mnehmos/mnehmos.nls.lang · GitHub
[go: up one dir, main page]

Skip to content

feat(backends): consume checked IR and enforce executable Python/TypeScript semantic conformance #202

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

Python and TypeScript implement semantics separately. TypeScript even derives its types through Python type strings (nlsc/emitter_typescript.py:16–44). The shared semantic suite currently has only a PythonRunner in tests/semantic/runners.py and tests/semantic/conftest.py; CLI output validation only invokes Python's compiler in nlsc/cli.py:224–232.

Reproduced against current emitters:

Input/operation Python TypeScript under Node
Return "True and False" original literal "true && false"
Distinct lists [1] and [1], compare a == b true false
1 if items else 0, with items=[] 0 1
1 / 0 ZeroDivisionError non-finite number rather than a typed failure
Branch-local result used after IF path-dependent unbound error out-of-scope reference even on true path

The equality, truthiness, and division behavior contradict the existing core document. Separately, nlsc/emitter_typescript.py:173–191 emits invariant descriptions as comments, while Python generates constructor checks. Python @literal implementations also need explicit target support; silently ignoring them is not semantic equivalence.

Implementation hints

  • Add a TypeScriptRunner using strict compilation plus Node execution and parameterize the existing semantic tests over both runners. Start this harness immediately; phase in checked-IR adapters as the dependency items land.
  • Both backends consume the same checked operations/TypeRefs and stop parsing LOGIC descriptions or converting Python type strings to infer meaning.
  • Implement small specified runtime helpers for structural equality, NLS truthiness, numeric/error behavior, construction constraints, and normalized error payloads where native target behavior differs.
  • Define construction/import boundaries for structural records so constraints/invariants are actually checked. Document the limits of construction-time checks when mutable aliases exist.
  • Maintain a target capability matrix. Unsupported IR nodes, foreign/literal implementations, contracts, or control policies must produce diagnostics, not silently incomplete output.
  • Validate generated TypeScript with tsc --strict in the required CI job. Runtime tests are essential: the corrupted string fixture already passes strict compilation.
  • Compare values, normalized failures, and ordered effect traces. Normalize only permitted representation differences.

Acceptance criteria

  • All table fixtures match the documented semantic contract on both targets.
  • Guard error identities/codes and field constraints/invariants are executable on both supported targets.
  • Core semantic tests run against both backends in CI, with missing required toolchains treated as a gate failure.
  • Invalid source is rejected by shared semantic validation before target-specific output.
  • Unsupported target capabilities fail explicitly; Python-only literal blocks cannot silently disappear.
  • The supported emitter paths consume checked IR and have no independent prose interpretation.
  • Deterministic output/lock identity includes the backend semantics/capability version.

This is a scoped execution and migration plan under #147 and the already-open #20; Rust stays tracked in #24 rather than being required for the first conformance milestone.

Implementation ordering

Integration depends on #194, #195, #196, #197, #198. Add the behavior/diagnostic fixtures before implementation. The TypeScript runner and current regression cases can begin immediately; migration to fully checked IR follows the listed passes.


Osprey-informed implementation refinements

Updated foundation

The initial IR is now implemented by #194 / #203. #191 / #204, #190 / #205, and #193 / #206 are also closed with merged PRs. The original table above records the earlier audit snapshot; it should remain a regression inventory, not be presented as a re-test of current master. Extend the existing IR, lowering, and shared pipeline.

Shared frontend and checked phases

  • Establish the same boundary for NLS's existing regex/Tree-sitter and localization paths that Osprey establishes for its two syntaxes: spelling/parsing differences end before shared semantic analysis. Checkers and emitters do not branch on which frontend produced a module.
  • Compare a defined semantic projection of equivalent IRs: exclude source positions and diagnostic-only raw spelling, normalize incidental IDs when necessary, and retain every type/control/effect/failure distinction. Do not confuse byte-identical serialized source metadata with semantic equivalence.
  • Preserve source spans for diagnostics while erasing spelling-only differences. Lowering must reject or explicitly mark constructs the canonical IR cannot represent.
  • Represent completed validation phases explicitly (for example a CheckedModule wrapper with versioned pass evidence). Parsed/lowered successfully, contains no Foreign nodes, type-checked, control-checked, and effect/failure-checked are different facts. Never let a legacy checked flag imply passes that have not run.
  • Route every executable-producing CLI path through the same required passes. Diagnostic/raw IR inspection can remain available, but must label incomplete validation and cannot advertise the module as executable checked IR.

Target capability gate and conformance

Additional acceptance cases:

  • Equivalent supported source through each available NLS frontend has the same semantic projection and shared diagnostics.
  • An incomplete/failed semantic phase cannot be bypassed by any executable-producing command; explicit scaffold/inspection modes remain visibly distinct.
  • An unsupported capability produces a source diagnostic with no partial executable output or toolchain launch.
  • Builtin aliasing/shadowing cannot bypass target validation.
  • Handler and resumption fixtures join the common value/failure/event-trace matrix as those features land.

Osprey references: canonical frontend law, target capability validator, capability regression tests, and multiplicity implementation. The pinned Osprey checker explicitly rejects unsupported many and declaration-level abort; NLS must likewise gate semantic support rather than infer it from accepted syntax.

Activity

  1. added 7 commits that reference this issue on Sep 12, 2026
  2. Mnehmos commented on Sep 12, 2026

    @Mnehmos
    OwnerAuthor

    All acceptance criteria are now implemented and merged. Mapping, with the evidence:

    Acceptance criterion Where it landed
    Table fixtures match the documented contract on both targets tests/semantic/test_cross_target_divergences.py (string literals, structural list equality, empty-list truthiness, typed division-by-zero, branch-local bindings) on the shared python/typescript runner fixture; TS runtime helpers (__nls_eq, __nls_truthy, __nls_div)
    Guard error identities/codes and constraints/invariants executable on both targets #240: coded guards raise with .code on both targets and ExecutionResult.exception_code carries it; constraints/invariants via Python __post_init__ and TS validating make_<Type> factories (tests/semantic/test_type_constraints.py)
    Core semantic tests run against both backends in CI; missing toolchain is a gate failure #239: single require_typescript_runner() rule (skip locally, fail under NLSC_REQUIRE_TS=1); typescript-conformance CI job runs tests/semantic with the gate active
    Invalid source is rejected by shared semantic validation before target-specific output Shared gate (evaluate_semantic_gate) runs before capability gate and emission in compile/run/ci/verify; #237 closed the remaining holes (silently truncated ANLU blocks now EPARSE001; missing RETURNS is EIR005 scaffold/fatal-strict)
    Unsupported target capabilities fail explicitly; Python-only literal blocks cannot silently disappear ETARGET002 capability matrix: @literal/@main (fatal), @property (strict-only); #238 added loop_steps (fatal on TypeScript, which previously emitted the step verbatim as invalid TS)
    The supported emitter paths consume checked IR and have no independent prose interpretation #241 (Python) + #242 (TypeScript): checked ANLU bodies render from IROperation statement/expression nodes — guards, bindings, branches, augmented assigns, result — with per-ANLU fallback to the legacy prose path only for explicitly foreign (scaffold) content
    Deterministic output/lock identity includes the backend semantics/capability version #236: semantics_version markers in .nl.lock targets (py-3, ts-2); --frozen-lockfile/ci refuse stale markers with ELOCK004

    Final state: 1017 tests green (semantic suite parameterized over both runners), tsc --strict conformance in CI, capability matrix and lock identity versioned. Remaining roadmap items live in their own issues: #197, #200, #201, #207, #208, #209, #147 (spec-against-IR validation and emitter plugin boundary), #24, #146, #90, #27.

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

    emitterCode generation targetsenhancementNew feature or requestmulti-targetCross-platform compilationpriority-highImportant, address soon

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0