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.
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.chargedoes not thereby catchCardDeclinedor 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