Skip to content

Commit 1f40fc6

Browse files
hyperpolymathclaude
andcommitted
feat(vscode): function-value callback ABI — registerCommand/onDidSave/withProgress (Refs #199)
#199 PR-5a (owner chose the function-value ABI). Replaces the brittle wasm-table-index `handler: Int` convention with real function values. ABI shape: `handler: fn(Unit) -> R` (not `fn() -> R`). AffineScript types a zero-param function as its bare return type (typecheck.ml:752), so `fn() -> Int` collapses to `Int`; the explicit `Unit` param is the honest callback shape and needs zero compiler change (verified). - stdlib/Vscode.affine: registerCommand / onDidSaveTextDocument / withProgressNotification handler params Int -> fn(Unit) -> R. Also removes the duplicate `extern type Thenable;` introduced in #198 (line 43 already declares it) — resolved at source. - examples/vscode_extension_minimal.affine + editors/vscode/src/ extension.affine: call sites migrated from numbered indices to `fn(u: Unit) => handler_x()` lambdas (behaviour-preserving; named handlers unchanged). Verified: stdlib/example/editors all typecheck; dune test 253/253 (the prior Examples-parse regression was my own `_u` typo — leading- underscore idents parse-error in params, a separate minor lexer observation; used `u`). Deno-ESM emits `registerCommand("…", ((u) => handler_0()))` — a real JS arrow passed directly, so the Deno fn-value extern path (PR-5b) needs no codegen change. Wasm/mod.js marshalling (PR-5c) and the rsr-certifier pilot (PR-5d) follow. Refs #199 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 42e6e2b commit 1f40fc6

3 files changed

Lines changed: 22 additions & 19 deletions

File tree

editors/vscode/src/extension.affine

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,15 @@ fn start_lsp() -> Int {
142142
pub fn activate(ctx: ExtensionContext) -> Int {
143143
let _ = consoleLog("AffineScript extension activated");
144144

145-
// Register commands. The integer second argument to registerCommand is
146-
// the wasm-table index of the handler — populated by the JS-side adapter
147-
// from the `__indirect_function_table` export. The order here must
148-
// match the order handlers appear in the file.
149-
let _ = pushSubscription(ctx, registerCommand("affinescript.check", 0));
150-
let _ = pushSubscription(ctx, registerCommand("affinescript.eval", 1));
151-
let _ = pushSubscription(ctx, registerCommand("affinescript.compile", 2));
152-
let _ = pushSubscription(ctx, registerCommand("affinescript.format", 3));
153-
let _ = pushSubscription(ctx, registerCommand("affinescript.restartLsp", 4));
145+
// Register commands. The handler is now a function value (#199
146+
// function-value callback ABI) — no more brittle wasm-table indices.
147+
// Each lambda takes the explicit `Unit` param (zero-param-fn return-
148+
// type collapse) and delegates to the named handler.
149+
let _ = pushSubscription(ctx, registerCommand("affinescript.check", fn(u: Unit) => handler_check()));
150+
let _ = pushSubscription(ctx, registerCommand("affinescript.eval", fn(u: Unit) => handler_eval()));
151+
let _ = pushSubscription(ctx, registerCommand("affinescript.compile", fn(u: Unit) => handler_compile()));
152+
let _ = pushSubscription(ctx, registerCommand("affinescript.format", fn(u: Unit) => handler_format()));
153+
let _ = pushSubscription(ctx, registerCommand("affinescript.restartLsp", fn(u: Unit) => handler_restart_lsp()));
154154

155155
let cfg = getConfiguration("affinescript");
156156
if workspaceConfigGetBool(cfg, "lsp.enabled", 1) != 0 {

examples/vscode_extension_minimal.affine

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub fn handler_0() -> Int {
2323
}
2424

2525
pub fn activate(ctx: ExtensionContext) -> Int {
26-
let d = registerCommand("affinescript.helloAffine", 0); // 0 = handler table index
26+
let d = registerCommand("affinescript.helloAffine", fn(u: Unit) => handler_0());
2727
let _ = pushSubscription(ctx, d);
2828
return 0;
2929
}

stdlib/Vscode.affine

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ pub extern type TextDocument;
5151
// ── vscode.commands ───────────────────────────────────────────────────
5252

5353
/// `vscode.commands.registerCommand(name, handler)`.
54-
/// `handler` is a function-pointer index into the Wasm table; the JS shim
55-
/// wraps it as a JS callback that re-enters Wasm when invoked.
56-
pub extern fn registerCommand(name: String, handler: Int) -> Disposable;
54+
/// `handler` is a function value (#199 function-value callback ABI). The
55+
/// `Unit` parameter is explicit because AffineScript types a zero-param
56+
/// function as its bare return type — `fn() -> Int` would collapse to
57+
/// `Int`; `fn(Unit) -> Int` is the honest callback shape.
58+
pub extern fn registerCommand(name: String, handler: fn(Unit) -> Int) -> Disposable;
5759

5860
// ── vscode.workspace ──────────────────────────────────────────────────
5961

@@ -263,7 +265,7 @@ pub extern fn clipboardWriteText(text: String) -> Int;
263265
/// `editorActiveFilePath()` from inside the thunk. That covers the
264266
/// 95% case where the saved doc is the active editor; the remaining
265267
/// cases (background-saved docs) degrade to "no action".
266-
pub extern fn onDidSaveTextDocument(handler: Int) -> Disposable;
268+
pub extern fn onDidSaveTextDocument(handler: fn(Unit) -> Int) -> Disposable;
267269

268270
// ── Path helpers ─────────────────────────────────────────────────────
269271

@@ -295,10 +297,11 @@ pub extern fn extensionAbsolutePath(ctx: ExtensionContext, rel_path: String) ->
295297
// JS implementation lives in packages/affine-vscode/mod.js (follow-up
296298
// slice); these declarations are the binding surface + effect tracking.
297299

298-
pub extern type Thenable;
300+
// `Thenable` is already declared in the opaque-type block above; no
301+
// re-declaration here (the duplicate from #198 is removed at source).
299302

300303
/// `vscode.window.withProgress({ location: Notification, title }, task)`.
301-
/// `work_thunk` is a wasm/host table index of the `() -> Thenable` task
302-
/// (same thunk-handle convention as `onDidSaveTextDocument`). Returns the
303-
/// progress Thenable.
304-
pub extern fn withProgressNotification(title: String, work_thunk: Int) -> Thenable / Async;
304+
/// `work` is the task function (#199 function-value callback ABI; the
305+
/// explicit `Unit` param avoids the zero-param-fn return-type collapse).
306+
/// Returns the progress Thenable.
307+
pub extern fn withProgressNotification(title: String, work: fn(Unit) -> Thenable) -> Thenable / Async;

0 commit comments

Comments
 (0)