From a47770bd1b63a7497d785c5b32e841812de16700 Mon Sep 17 00:00:00 2001 From: carsonfarmer Date: Sat, 26 Sep 2026 15:51:03 -0700 Subject: [PATCH] fix: emit bindings for imported resources without methods Seed resource function groups from imported resource types so opaque handles receive the existing class and disposal bindings. Add an ownership, borrowing, and disposal regression. Co-authored-by: Codex --- .../spidermonkey-embedding-splicer/src/bindgen.rs | 4 +++- .../opaque-resource-import.js | 14 ++++++++++++++ test/cases/opaque-resource-import/source.js | 10 ++++++++++ test/cases/opaque-resource-import/test.js | 7 +++++++ test/cases/opaque-resource-import/world.wit | 12 ++++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 test/cases/opaque-resource-import/opaque-resource-import.js create mode 100644 test/cases/opaque-resource-import/source.js create mode 100644 test/cases/opaque-resource-import/test.js create mode 100644 test/cases/opaque-resource-import/world.wit diff --git a/crates/spidermonkey-embedding-splicer/src/bindgen.rs b/crates/spidermonkey-embedding-splicer/src/bindgen.rs index 36be7fdc..5137898e 100644 --- a/crates/spidermonkey-embedding-splicer/src/bindgen.rs +++ b/crates/spidermonkey-embedding-splicer/src/bindgen.rs @@ -587,15 +587,17 @@ impl JsBindgen<'_> { .. } => { let iface = &self.resolve.interfaces[*i]; + let mut resource_function_groups = BTreeMap::<_, Vec<_>>::new(); for id in iface.types.values() { if let TypeDefKind::Resource = &self.resolve.types[*id].kind { self.resource_directions .insert(*id, AbiVariant::GuestImport); + resource_function_groups.entry(Some(*id)).or_default(); } } let by_resource = iface.functions.iter().fold( - BTreeMap::<_, Vec<_>>::new(), + resource_function_groups, |mut map, (name, func)| { map.entry(match &func.kind { FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => { diff --git a/test/cases/opaque-resource-import/opaque-resource-import.js b/test/cases/opaque-resource-import/opaque-resource-import.js new file mode 100644 index 00000000..77d93d47 --- /dev/null +++ b/test/cases/opaque-resource-import/opaque-resource-import.js @@ -0,0 +1,14 @@ +export let drops = 0; + +export class Item { + constructor(value) { + this.value = value; + } + + [Symbol.dispose]() { + drops += 1; + } +} + +export const make = value => new Item(value); +export const value = item => item.value; diff --git a/test/cases/opaque-resource-import/source.js b/test/cases/opaque-resource-import/source.js new file mode 100644 index 00000000..a05d5413 --- /dev/null +++ b/test/cases/opaque-resource-import/source.js @@ -0,0 +1,10 @@ +import { make, value } from 'test:test/opaque-resource-import'; + +export function test() { + const item = make(42); + try { + return value(item); + } finally { + item[Symbol.dispose](); + } +} diff --git a/test/cases/opaque-resource-import/test.js b/test/cases/opaque-resource-import/test.js new file mode 100644 index 00000000..26495df1 --- /dev/null +++ b/test/cases/opaque-resource-import/test.js @@ -0,0 +1,7 @@ +import { strictEqual } from 'node:assert'; +import { drops } from './opaque-resource-import.js'; + +export function test(instance) { + strictEqual(instance.test(), 42); + strictEqual(drops, 1); +} diff --git a/test/cases/opaque-resource-import/world.wit b/test/cases/opaque-resource-import/world.wit new file mode 100644 index 00000000..c9b705fe --- /dev/null +++ b/test/cases/opaque-resource-import/world.wit @@ -0,0 +1,12 @@ +package test:test; + +interface opaque-resource-import { + resource item; + make: func(value: u32) -> item; + value: func(item: borrow) -> u32; +} + +world test { + import opaque-resource-import; + export test: func() -> u32; +}