Skip to content

Deno source-to-source host→guest callback ABI (blocks withProgress/registerCommand/onDidSave) #199

Closed

Description

@hyperpolymath

Problem

The Deno/Node source-to-source backend (lib/codegen_deno.ml) has no host→guest callback mechanism. It explicitly has "no handle table" (codegen_deno.ml:9) and ExprHandle lowering ignores handlers. The only working callback path in the estate is the wasm adapter packages/affine-vscode/mod.js, which resolves a handler Int via inst.exports.__indirect_function_table (wrapHandler).

Consequently, on the Deno source-to-source target, any extern that takes an AffineScript thunk the host must invoke is unimplementable:

These currently only function on the wasm backend.

Why separate from #103

#103 (async-extern ABI) split into:

The callback ABI is a distinct, larger, one-way-door design (it dictates how every host→guest callback is shaped on the source-to-source backend, not just async), so it is tracked here rather than expanding #103.

Design space (not yet decided)

  • Guest exports a thunk-dispatch entrypoint; codegen_deno emits a JS-side handle registry the adapter calls back into.
  • Alternatively: closures-as-values lowering so handlers are real JS functions passed directly (no Int handle indirection on the source-to-source path at all).

Acceptance

  • registerCommand, onDidSaveTextDocument, withProgress all functional on --deno-esm / --vscode-extension output.
  • rsr-certifier vscode pilot fully restored (the withProgress-wrapped checks).

Refs #103

Activity

  1. hyperpolymath commented on May 18, 2026

    @hyperpolymath
    OwnerAuthor

    Decisive finding — ABI shape resolved without a compiler change

    Investigation of the chosen function-value ABI: a function-typed extern param typechecks today — provided the handler takes an explicit argument:

    • extern fn onSave(handler: fn(Int) -> Int) + onSave(fn(x: Int) => x) → ✅ typechecks
    • extern fn onCmd(handler: fn(Unit) -> Int) + onCmd(fn(u: Unit) => 0) → ✅ typechecks
    • extern fn onCmd(handler: fn() -> Int) + onCmd(fn() => 0) → ❌ TypeMismatch((Unit -> Int), Int)

    Root cause: AffineScript has a deliberate convention (lib/typecheck.ml:752, mirrored in ExprLambda synth at ~742) that a zero-parameter function is typed as its bare return type, not Unit -> T. But the type syntax fn() -> T lowers to TArrow(Unit, _, T, _). So zero-arg lambda values and the fn() -> T type don't unify. Changing that convention is a language-wide one-way door (it governs every zero-arg call site) and is out of scope here.

    Resolution (no compiler change)

    Handler bindings use an explicit Unit param: handler: fn(Unit) -> R; consumers write fn(_u: Unit) => { ... }. Slightly more ceremony than fn(), but it is the honest, zero-risk realisation of the function-value ABI — the zero-arg-fn convention stays intact.

    Scope (consumers to migrate at root)

    Tracked as PR-5a (binding + example + editors extension, 253-gated), PR-5b Deno lowering, PR-5c wasm marshalling, PR-5d rsr-certifier pilot.

    Refs #199

  2. hyperpolymath commented on May 18, 2026

    @hyperpolymath
    OwnerAuthor

    Closing — host->guest callback ABI complete (STAGE-B).

    The function-value callback ABI is shipped on main:

    This unblocked withProgress / registerCommand / onDidSave. The rsr-certifier pilot that would have demonstrated it in a real consumer (was PR-5d-B) is decoupled to an independent standards-repo follow-up and no longer gates this issue.

    Closed on owner approval (joint close, 2026-05-18).

  3. added 14 commits that reference this issue on May 18, 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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions