Skip to content

Commit b3a226f

Browse files
hyperpolymathclaude
andcommitted
feat(thenable): wasm-path thenable-resolution primitives (Closes #205)
The wasm/Node backend cannot await a host Thenable, so a languageClientSendRequest result was unconsumable by a wasm guest (blocking the #103 rsr-certifier pilot data path). The #199 function-value ABI now makes the host→guest re-entry expressible, so the minimal #103 design sketch is finally buildable. - stdlib/Vscode.affine: `thenableThen(t, on_settle: fn(Unit) -> Int) -> Disposable / Async` and `thenableResultJson(t) -> String`. - packages/affine-vscode/mod.js: thenableThen wraps the guest closure via the PR-5c closure-pointer marshalling (wrapHandler) and stores the settled value keyed by the Thenable handle; thenableResultJson returns it JSON-encoded via the established reg(string) convention used by every other `-> String` extern. Per-process __thenableResults map. Rejection path stored as { __error }. Verified: Vscode.affine typechecks; dune test --force 253/253, zero regression; mod.js node --check clean; a consumer using both primitives typechecks and lowers (`thenableThen(t, ((u) => 0))`). Unblocks the rsr-certifier pilot (PR-5d-B): sendRequest rsr/getCompliance → thenableThen/thenableResultJson → status-bar/diagnostics/webview. Closes #205 Refs #103, #199 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9f8a6ed commit b3a226f

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

packages/affine-vscode/mod.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) {
3333
const reg = (obj) => hostShim._registerHandle(obj);
3434
const get = (h) => hostShim._getHandle(h);
3535
const getInstance = () => hostShim._instance;
36+
// Settled host-Thenable values, keyed by Thenable handle (issue #205).
37+
const __thenableResults = new Map();
3638

3739
// ── String marshalling ─────────────────────────────────────────────
3840
// AffineScript's WASM 1.0 codegen stores string literals at the offset
@@ -333,6 +335,34 @@ module.exports = function makeVscodeBindings(vscode, lcModule, hostShim) {
333335
const ctx = get(ctxHandle);
334336
return reg(ctx ? ctx.asAbsolutePath(readString(relPtr)) : "");
335337
},
338+
339+
// ── Thenable resolution (issue #205) ───────────────────────────
340+
// The wasm guest cannot await; these let it observe a settled host
341+
// Thenable. thenableThen registers the guest closure (reusing the
342+
// #199 closure-pointer marshalling via wrapHandler) and stores the
343+
// settled value keyed by the Thenable handle; thenableResultJson
344+
// returns it JSON-encoded (same reg(string) return convention as
345+
// every other `-> String` extern).
346+
thenableThen: (tHandle, onSettlePtr) => {
347+
const thenable = get(tHandle);
348+
const cb = wrapHandler(onSettlePtr);
349+
if (!thenable || typeof thenable.then !== "function") {
350+
return reg({ dispose() {} });
351+
}
352+
Promise.resolve(thenable).then(
353+
(val) => { __thenableResults.set(tHandle, val); try { cb(); } catch (_e) {} },
354+
(err) => {
355+
__thenableResults.set(tHandle, { __error: String(err) });
356+
try { cb(); } catch (_e) {}
357+
}
358+
);
359+
return reg({ dispose() {} });
360+
},
361+
thenableResultJson: (tHandle) => {
362+
if (!__thenableResults.has(tHandle)) return reg("");
363+
try { return reg(JSON.stringify(__thenableResults.get(tHandle))); }
364+
catch (_e) { return reg(""); }
365+
},
336366
};
337367

338368
const VscodeLanguageClient = {

stdlib/Vscode.affine

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,3 +305,20 @@ pub extern fn extensionAbsolutePath(ctx: ExtensionContext, rel_path: String) ->
305305
/// explicit `Unit` param avoids the zero-param-fn return-type collapse).
306306
/// Returns the progress Thenable.
307307
pub extern fn withProgressNotification(title: String, work: fn(Unit) -> Thenable) -> Thenable / Async;
308+
309+
// ── Thenable resolution (issue #205) ─────────────────────────────────
310+
//
311+
// The source-to-source backend can `await` a Thenable directly; the
312+
// wasm/Node backend cannot, so these primitives let a wasm guest
313+
// observe a settled host Thenable. `thenableThen` registers a guest
314+
// callback (the #199 function-value ABI — the host re-enters it when
315+
// the Thenable settles); `thenableResultJson` reads the settled payload
316+
// JSON-encoded once the callback has fired.
317+
318+
/// Run `on_settle` when `t` resolves. `on_settle` is a function value
319+
/// (#199); the host invokes it after settlement. Returns a Disposable.
320+
pub extern fn thenableThen(t: Thenable, on_settle: fn(Unit) -> Int) -> Disposable / Async;
321+
322+
/// The resolved value of `t`, JSON-encoded. Empty string until `t` has
323+
/// settled (i.e. until the `thenableThen` callback has fired).
324+
pub extern fn thenableResultJson(t: Thenable) -> String;

0 commit comments

Comments
 (0)