8000 feat(effects): add typed lexical handlers and injectable operation implementations · Issue #207 · Mnehmos/mnehmos.nls.lang · GitHub
[go: up one dir, main page]

Skip to content

feat(effects): add typed lexical handlers and injectable operation implementations #207

Open

Description

@Mnehmos

Motivation and scope

NLS needs a deterministic way for a typed operation to request storage, transport, or another service while allowing an enclosing program or test to supply the implementation. Osprey provides a concrete reference for typed operations, operation-specific lexical handlers, and test injection.

Build on the IR already delivered by #194 / #203 and the semantic passes in #195–#198. This is a new language feature; the illustrative node names below are a proposed design, not existing .nl syntax.

Minimum deliverable

Add typed operation declarations and direct-return lexical handlers. An operation request receives its declared result from the innermost matching arm, and execution continues normally. Implement this synchronous slice before adding resumable continuations.

  • Model stable effect/operation identities, parameter and result TypeRefs, failure bounds, and resource contracts. Add Perform and Handle regions to the shared IR; do not infer a request from narrative English.
  • Resolve each arm to an operation symbol and check argument arity/types, arm result type, and duplicate/unknown arms. Begin with monomorphic operations; generic effect instances require distinct resolved identities if introduced.
  • A partial handler discharges only its supplied operations. Inner arms override matching operations; missing operations can fall through to an enclosing matching handler.
  • Propagate required operations through actual calls. For supported callbacks, invocation contributes latent requirements; merely constructing a closure does not execute them. Reject unsupported dynamic provenance conservatively.
  • An executable entry must discharge every requirement. A library may expose a checked requirement contract; a host adapter must validate its installation before execution. Writing an effect annotation neither installs a handler nor grants authority.
  • A closure returned from a handled region does not retain an implicit permission to perform there after the region ends. Check requirements at later invocation. Make the behavior of performs inside handler arms explicit: initially reject recursive invocation of the active operation; allow different operations to resolve outward under documented rules.
  • Preserve both the remaining operation requirements and the selected implementation's resource footprint and failures. A disk-backed Storage handler still writes files after Storage requirements are discharged. An in-memory handler has a different footprint; an unknown handler implementation is not pure.

Implementation hints

Extend nlsc/ir.py, nlsc/lowering.py, and the shared pipeline, reusing the symbol/type tables from #195 and summaries from #197/#198. Avoid a second prose parser inside either emitter.

An explicit scoped handler environment threaded through generated calls is a reasonable first lowering. It must restore the outer environment on normal return and typed failure, avoid global cross-request state, and reject unsupported escape/concurrency cases. This issue does not require native stack capture, first-class handler values, fibers, or an asynchronous runtime.

Specify the .nl spelling and migration separately from the IR contract. Keep PURPOSE/descriptions narrative and resolve every executable operation mechanically.

Acceptance criteria

  • The same storage-using ANLU runs through a temporary-directory adapter and an in-memory adapter without changing its business logic; results and ordered operation traces match the declared contract.
  • A refusing adapter produces a declared typed failure for a write it cannot perform; it cannot report successful persistence by silently returning a placeholder.
  • Unknown operations, wrong argument/arm types, duplicate arms, missing handlers at entry, and an unhandled operation in a partial handler are rejected with source spans.
  • Nested partial handlers select the innermost matching arm and retain the appropriate outer arms.
  • A supported callback carries its latent requirements through wrappers; an escaping callable cannot inherit discharged requirements from its creation site.
  • Handler failures propagate; handler installation is restored after failure; independent invocations cannot share handler state accidentally.
  • Disk and memory implementations retain distinct effect summaries, and empty unhandled requirements never independently certify parallel safety.
  • Python and TypeScript have matching direct-handler behavior, or the shared capability check explicitly rejects the feature for an unsupported target before emission.

Dependencies and boundaries

Depends on #195, #196, #197, and #198; #194 is already complete. Integrate with #202's backend harness. Explicit resumption and its consumption rules are tracked in #208; they do not block this direct-return slice. Existing typestate, retry, and scheduling work remains separately scoped.

Osprey implementation references

Inspected at 8792de9f6d159922141f9d191e4bc5d256fbb971; these are design/code references, not a claim that its runtime tests were executed in this audit.

Activity

  1. added
    enhancementNew feature or request
    coreCore language semantics and design
    specificationLanguage specification documents
    on Sep 10, 2026
  2. Mnehmos commented on Sep 12, 2026

    @Mnehmos
    OwnerAuthor

    Staging note (planning pass, 2026-09-13)

    Audited against current master. This remains the largest language feature on the roadmap and is not blocked on missing infrastructure so much as on sequencing: the checked-IR pipeline (lowering → typecheck/controlflow/failures/effects) and both IR-consuming emitters have landed, but every #207 acceptance criterion is behavioral (same ANLU across disk/memory adapters, ordered effect traces, refusing-adapter typed failures), so a checker-only slice would deliver syntax/IR freezing at best — not the issue's deliverable.

    Slice 1 design, recorded so the work can start without re-deriving it:

    • Surface (regex-parser canonical, like @test/@literal): a HANDLE: ANLU section with ON <operation> -> <expr> arms, attachable to any ANLU; PERFORM <op>(...) -> name LOGIC steps. _should_use_regex_canonical_parse must gain HANDLE:/PERFORM alternatives, and _sanitize_source_for_treesitter should blank the new sections — the tree-sitter grammar cannot be rebuilt in this environment.
    • Schema/IR: HandleArm(op_path, expr, span) in the schema; IRPerform(op, args, result_name) statement plus IROperation.handlers: tuple[IRHandleArm, ...] following the existing optional-contract-slot pattern (effects/failures/typestate).
    • Diagnostics: next free codes are ESEM014+ (ESEM013 is the current maximum): unknown operation, arity/type mismatch, duplicate arm, arm result-type mismatch, unhandled required operation at an executable entry point.
    • Emitters in slice 1: not emitted; both targets refuse via a fatal ETARGET002 capability entry, so nothing is silently dropped while the runtime is being designed.
    • Runtime slice 2: direct-return handler environments threaded through calls (Python first, following the loop-step precedent), then the adapter fixtures the acceptance criteria require.

    Rationale for keeping this open rather than closing: the issue's own refinements section already fixes the boundary (synchronous direct-return lexical handlers first; #208 owns resumption), so the design is recorded but the deliverable is not delivered. #208 depends on this landing first; it should not start earlier.

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 requestpriority-mediumShould be addressedspecificationLanguage specification documents

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      2BB2
      0