8000 feat(errors): add checked failure sets and a portable typed exception contract · Issue #198 · Mnehmos/mnehmos.nls.lang · GitHub
[go: up one dir, main page]

Skip to content

feat(errors): add checked failure sets and a portable typed exception contract #198

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.

Gap and current evidence

The existing specification deliberately uses exceptions; a typed failure contract can preserve that decision. nlsc/schema.py:80–87 records guard error type/code/message, but ANLU call signatures do not express what can escape.

There is also an immediate backend mismatch: nlsc/emitter.py:304–334 raises the selected error with its optional code, while nlsc/emitter_typescript.py:228–237 discards the code and emits new ValueError(...) without defining that class. A reached False -> ValueError("called") guard produced ReferenceError: ValueError is not defined under Node during this audit.

Implementation hints

  • Add normalized error identities and payloads (type, optional code, message), plus a failure set on each operation. Surface spelling such as FAILS/RAISES should be specified/versioned rather than inferred from PURPOSE.
  • Infer escaping failures from guards, raise nodes, arithmetic/indexing primitives, callees, and explicit handlers; native/unknown operations get a conservative unknown-failure marker.
  • Check declared failure sets as upper bounds and validate handler coverage/subtraction. Preserve ordered guard evaluation and existing early-return/EDGE CASE ordering until a deliberate specification change says otherwise.
  • Supply Python/TypeScript adapters for the same error identity/payload. Decide mappings for builtin versus custom exceptions and reject unresolved error constructors.
  • Model handler/control behavior structurally instead of relying on the Python emitter's special RETURNS regex for expr, or fallback on Error.
  • Expose failure information to scheduling/retry analysis. Purity alone does not imply an operation cannot fail.
  • Keep the current exception model; introducing a user-facing Result type is outside this slice.

Acceptance criteria

  • A guard error preserves type, code, and message on both targets without undefined exception constructors.
  • Callee failures propagate into caller summaries; declared narrower sets are rejected.
  • Handled failures no longer escape, while handler/rethrow failures remain represented.
  • First failing guard and side-effect ordering are identical across targets.
  • Unknown external failures are not treated as an empty set.
  • Tests distinguish normal return, typed failure, and compiler/runtime infrastructure failure.

Related: #88's exception decision, #148's compiler diagnostic infrastructure, and #20's runtime behavior. Compiler diagnostics and program exceptions remain distinct contracts.

Implementation ordering

Integration depends on #194, #195. Add the behavior/diagnostic fixtures before implementation.


Osprey-informed implementation refinements

Retain NLS's chosen typed-exception behavior. Osprey is an implementation reference for typed operation contracts; adopting its Result conventions is outside this issue.

  • Distinguish exception handling from effect handling in the IR and checker. A handler supplying Payment.charge does not thereby catch CardDeclined or an exception raised by its gateway implementation.
  • Compute escaping failures through the selected handler implementation as well as the original body. Subtract a failure only where a structurally represented catch covers that error identity; add failures from the catch/handler body and rethrows.
  • In feat(effects): add typed lexical handlers and injectable operation implementations #207, check direct-return arms against the operation result and its declared failure bound. In feat(control): enforce affine single-shot handler resumption #208, a failed resuming arm terminates the region without supplying a success value.
  • Keep three identities/types separate: operation result, handled region answer, and error identity/payload. Do not encode infrastructure errors or continuation misuse as ordinary successful operation results.
  • Preserve cleanup and ordered events when a handler implementation raises. Unknown external failures remain explicit even when every operation has an installed handler.

Additional acceptance cases:

  • A fully supplied effect handler that raises a declared error still contributes that escaping failure to its caller.
  • A matching catch removes only the caught failure; failures from the catch body and rethrows remain visible.
  • An arm result mismatch and an undeclared handler failure produce distinct shared diagnostics.
  • Python and TypeScript preserve the same error type/code/message and perform no downstream success-only effects after the failure.

References: Osprey typed operations, handler results, and resumed-region answers, plus the retry/decline fixture. The NLS fixture should use NLS exceptions; copying Osprey's Result syntax or answer-replacement behavior is not necessary.

This remains a foundation for #207/#208; it does not require completing those runtime extensions first.

Activity

  1. added
    enhancementNew feature or request
    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

    coreCore language semantics and designenhancementNew 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