Repository navigation
ReferenceError when resource has no functions #221
Description
Activity
Running into the same issue with
wasi:graphics-context/graphics-context#abstract-buffer.It either gives a similar error to the one posted by the OP or sometimes even just hangs indefinitely without any errors.
Adding a simple
foo: func();to the resource gets it to work correctly.Confirming this bug in
0.19.4-rc.1with a different WASI interface:wasi:sockets/network(wasi:sockets@0.2.3).wasi:sockets/networkdefines a singlenetworkresource with no methods. It is referenced bywasi:sockets/instance-network(instanceNetwork()returns anetwork) and bywasi:sockets/tcp(socket methods takeborrow<network>). Neither of those interfaces defines the class — they just use it — so the class is never emitted and any guest code that callsinstanceNetwork()or binds/connects a TCP socket throws at runtime:ReferenceError: import_network_0_2_3$Network is not definedMinimal reproducer
WIT world:
package example:sockets-demo@0.1.0; world sockets-demo { import wasi:sockets/instance-network@0.2.3; import wasi:sockets/tcp@0.2.3; import wasi:sockets/tcp-create-socket@0.2.3; import wasi:io/poll@0.2.0; export wasi:cli/run@0.2.0; }
TypeScript guest:
import { instanceNetwork } from 'wasi:sockets/instance-network@0.2.3'; import { createTcpSocket } from 'wasi:sockets/tcp-create-socket@0.2.3'; export const run = { run(): void { const network = instanceNetwork(); // throws: import_network_0_2_3$Network is not defined const socket = createTcpSocket('ipv4'); // ... }, };
The generated bindings emit
finalizationRegistry_import$network_0_2_3$network = new FinalizationRegistry(...)but neverclass import_network_0_2_3$Network { ... }. The class is only generated when the owning interface (wasi:sockets/network) has at least one callable function — since it has none, the class is skipped entirely.Root cause
The issue is in
spidermonkey-embedding-splicer. The class-generation code path is gated on the interface having callable functions; resource types in function-less interfaces fall through without a class definition. The class should be emitted for every imported resource, regardless of whether the owning interface has any callable functions.JS-side workaround patch
While the right fix is in the splicer, here is a patch applied to
componentize.jsthat works around the issue. It scans the generated bindings for finalization registries with no matching class and injects minimal class definitions aftersymbolDisposeis declared:// After spliceBindings() — inject missing resource classes { const registryRe = /finalizationRegistry_import\$([^$]+)\$([^\s=]+) = new FinalizationRegistry/g; let m; const missingClasses = []; while ((m = registryRe.exec(jsBindings)) !== null) { const iface = m[1]; const rsc = m[2]; const className = `import_${iface}$${rsc.charAt(0).toUpperCase()}${rsc.slice(1)}`; if (!jsBindings.includes(`class ${className}`)) { missingClasses.push({ iface, rsc, className }); } } if (missingClasses.length > 0) { const injected = missingClasses.map(({ iface, rsc, className }) => `class ${className} {\n` + ` [symbolDispose]() {\n` + ` finalizationRegistry_import$${iface}$${rsc}.unregister(this);\n` + ` $resource_import$${iface}$drop$${rsc}(this[symbolRscHandle]);\n` + ` this[symbolRscHandle] = undefined;\n` + ` }\n` + `}\n` ).join(''); jsBindings = jsBindings.replace( '\nconst symbolDispose = Symbol.dispose || Symbol.for(\'dispose\');', '\nconst symbolDispose = Symbol.dispose || Symbol.for(\'dispose\');\n' + injected, ); } }
This was developed while building a TypeScript wasmCloud service-and-component template that uses
wasi:socketsfor intra-workload TCP communication: wasmCloud/typescript#583I reproduced this on 0.23.0 and
4d812f5. #357 fixes the missing class generation and adds a regression for returning, borrowing and disposing of a resource with no methods.
Having an empty resource:
Calling
new-join-setfrom a JavaScript guest fails with an error:ReferenceError: import_execution_1_0_0$JoinSetId is not defined.This is because the generated
initializer.jsdoes not containclass import_execution_1_0_0$JoinSetId {...}, but tries to use it in the generatednew-join-setfunction:var rsc2 = new.target === import_execution_1_0_0$JoinSetId ? this : Object.create(import_execution_1_0_0$JoinSetId.prototype);The workaround is to add a function, which triggers the
classgeneration.Tested with componentize-js version 0.18.2 .