From b052e7a7c984a0dbeaf99c7c579e27e9ded58b14 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 18 May 2026 08:00:01 +0100 Subject: [PATCH] =?UTF-8?q?feat(stdlib):=20async-extern=20binding=20surfac?= =?UTF-8?q?e=20=E2=80=94=20Thenable=20+=20withProgress/sendRequest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #103 slice 1 (Promise-returning extern, per owner decision). The AffineScript extern-call shape is synchronous, so Thenable-returning vscode APIs could not be bound at all; the rsr-certifier vscode port fell back to shelling out to the CLI. Adds the binding surface only (smallest root-cause slice): - stdlib/Vscode.affine: `extern type Thenable;` + `withProgressNotification(title, work_thunk) -> Thenable / Async`. - stdlib/VscodeLanguageClient.affine: `languageClientSendRequest(c, method, params_json) -> Thenable / Async`. No backend change needed: the Deno/Node source-to-source backend already lowers an unlisted extern to a plain host call, so a Thenable-returning extern emits a real JS Promise and a consumer's `await` is valid source JS. The `Async` effect (v1 registry, PR-1 #196) tracks it in signatures. Verified: both files typecheck standalone; dune test 253/253 (incl. the AOT Vscode smoke); a consumer compiled with --deno-esm emits `return languageClientSendRequest("rsr/getCompliance", "{}")` — a plain awaitable host call. Deferred to follow-up slices (per scope): vscode-host JS impl in packages/affine-vscode/mod.js, and the rsr-certifier pilot restore. Refs #103 Co-Authored-By: Claude Opus 4.7 (1M context) --- stdlib/Vscode.affine | 19 +++++++++++++++++++ stdlib/VscodeLanguageClient.affine | 13 +++++++++++++ 2 files changed, 32 insertions(+) diff --git a/stdlib/Vscode.affine b/stdlib/Vscode.affine index 1a0d7932..e9b420a2 100644 --- a/stdlib/Vscode.affine +++ b/stdlib/Vscode.affine @@ -283,3 +283,22 @@ pub extern fn processPlatform() -> String; /// `context.asAbsolutePath(rel_path)` — resolves a path relative to the /// extension's install location. pub extern fn extensionAbsolutePath(ctx: ExtensionContext, rel_path: String) -> String; + +// ── Async-extern ABI (issue #103) ──────────────────────────────────── +// +// AffineScript's extern-call shape is synchronous, but most of vscode's +// interactive surface returns a JS Thenable. `Thenable` is an opaque +// host handle; the Deno/Node source-to-source backend lowers a +// Thenable-returning extern to a plain host call, so the emitted JS is a +// real Promise and a consumer's `await` is valid source JS. The `Async` +// effect (v1 registry, #59) tracks this in signatures. The vscode-host +// JS implementation lives in packages/affine-vscode/mod.js (follow-up +// slice); these declarations are the binding surface + effect tracking. + +pub extern type Thenable; + +/// `vscode.window.withProgress({ location: Notification, title }, task)`. +/// `work_thunk` is a wasm/host table index of the `() -> Thenable` task +/// (same thunk-handle convention as `onDidSaveTextDocument`). Returns the +/// progress Thenable. +pub extern fn withProgressNotification(title: String, work_thunk: Int) -> Thenable / Async; diff --git a/stdlib/VscodeLanguageClient.affine b/stdlib/VscodeLanguageClient.affine index 955be65a..a0cdac1d 100644 --- a/stdlib/VscodeLanguageClient.affine +++ b/stdlib/VscodeLanguageClient.affine @@ -31,3 +31,16 @@ pub extern fn newLanguageClient(id: String, pub extern fn languageClientStart(c: LanguageClient) -> Int; pub extern fn languageClientStop(c: LanguageClient) -> Int; + +// ── Async-extern ABI (issue #103) ──────────────────────────────────── +// Opaque host handle, declared per-module (these binding modules are +// self-contained; the host erases the type). See stdlib/Vscode.affine. + +pub extern type Thenable; + +/// `LanguageClient.sendRequest(method, params)` — returns a Thenable of +/// the JSON-encoded response. `params_json` is the request params encoded +/// as a JSON string; the host shim parses it before the LSP call. +pub extern fn languageClientSendRequest(c: LanguageClient, + method: String, + params_json: String) -> Thenable / Async;