Skip to content

Commit 38a937c

Browse files
authored
Merge pull request #522 from swiftwasm/bridgejs-imported-ts-type-export-interface
BridgeJS: allow exported APIs to use imported @jsclass types
2 parents 8565d3b + d9d6c56 commit 38a937c

File tree

10 files changed

+469
-1
lines changed

10 files changed

+469
-1
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,16 @@ public final class SwiftToSkeleton {
235235
return nil
236236
}
237237
let swiftCallName = SwiftToSkeleton.computeSwiftCallName(for: typeDecl, itemName: typeDecl.name.text)
238+
239+
// A type annotated with @JSClass is a JavaScript object wrapper (imported),
240+
// even if it is declared as a Swift class.
241+
if let classDecl = typeDecl.as(ClassDeclSyntax.self), classDecl.attributes.hasAttribute(name: "JSClass") {
242+
return .jsObject(swiftCallName)
243+
}
244+
if let actorDecl = typeDecl.as(ActorDeclSyntax.self), actorDecl.attributes.hasAttribute(name: "JSClass") {
245+
return .jsObject(swiftCallName)
246+
}
247+
238248
return .swiftHeapObject(swiftCallName)
239249
}
240250

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@JSClass class Foo {
2+
@JSFunction init() throws(JSException)
3+
}
4+
5+
@JS func makeFoo() throws(JSException) -> Foo {
6+
return try Foo()
7+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
export interface Foo {
8+
}
9+
export type Exports = {
10+
makeFoo(): Foo;
11+
}
12+
export type Imports = {
13+
Foo: {
14+
new(): Foo;
15+
}
16+
}
17+
export function createInstantiator(options: {
18+
imports: Imports;
19+
}, swift: any): Promise<{
20+
addImports: (importObject: WebAssembly.Imports) => void;
21+
setInstance: (instance: WebAssembly.Instance) => void;
22+
createExports: (instance: WebAssembly.Instance) => Exports;
23+
}>;
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
export async function createInstantiator(options, swift) {
8+
let instance;
9+
let memory;
10+
let setException;
11+
const textDecoder = new TextDecoder("utf-8");
12+
const textEncoder = new TextEncoder("utf-8");
13+
let tmpRetString;
14+
let tmpRetBytes;
15+
let tmpRetException;
16+
let tmpRetOptionalBool;
17+
let tmpRetOptionalInt;
18+
let tmpRetOptionalFloat;
19+
let tmpRetOptionalDouble;
20+
let tmpRetOptionalHeapObject;
21+
let tmpRetTag;
22+
let tmpRetStrings = [];
23+
let tmpRetInts = [];
24+
let tmpRetF32s = [];
25+
let tmpRetF64s = [];
26+
let tmpParamInts = [];
27+
let tmpParamF32s = [];
28+
let tmpParamF64s = [];
29+
let tmpRetPointers = [];
30+
let tmpParamPointers = [];
31+
const enumHelpers = {};
32+
const structHelpers = {};
33+
34+
let _exports = null;
35+
let bjs = null;
36+
37+
return {
38+
/**
39+
* @param {WebAssembly.Imports} importObject
40+
*/
41+
addImports: (importObject, importsContext) => {
42+
bjs = {};
43+
importObject["bjs"] = bjs;
44+
const imports = options.getImports(importsContext);
45+
bjs["swift_js_return_string"] = function(ptr, len) {
46+
const bytes = new Uint8Array(memory.buffer, ptr, len);
47+
tmpRetString = textDecoder.decode(bytes);
48+
}
49+
bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) {
50+
const source = swift.memory.getObject(sourceId);
51+
const bytes = new Uint8Array(memory.buffer, bytesPtr);
52+
bytes.set(source);
53+
}
54+
bjs["swift_js_make_js_string"] = function(ptr, len) {
55+
const bytes = new Uint8Array(memory.buffer, ptr, len);
56+
return swift.memory.retain(textDecoder.decode(bytes));
57+
}
58+
bjs["swift_js_init_memory_with_result"] = function(ptr, len) {
59+
const target = new Uint8Array(memory.buffer, ptr, len);
60+
target.set(tmpRetBytes);
61+
tmpRetBytes = undefined;
62+
}
63+
bjs["swift_js_throw"] = function(id) {
64+
tmpRetException = swift.memory.retainByRef(id);
65+
}
66+
bjs["swift_js_retain"] = function(id) {
67+
return swift.memory.retainByRef(id);
68+
}
69+
bjs["swift_js_release"] = function(id) {
70+
swift.memory.release(id);
71+
}
72+
bjs["swift_js_push_tag"] = function(tag) {
73+
tmpRetTag = tag;
74+
}
75+
bjs["swift_js_push_int"] = function(v) {
76+
tmpRetInts.push(v | 0);
77+
}
78+
bjs["swift_js_push_f32"] = function(v) {
79+
tmpRetF32s.push(Math.fround(v));
80+
}
81+
bjs["swift_js_push_f64"] = function(v) {
82+
tmpRetF64s.push(v);
83+
}
84+
bjs["swift_js_push_string"] = function(ptr, len) {
85+
const bytes = new Uint8Array(memory.buffer, ptr, len);
86+
const value = textDecoder.decode(bytes);
87+
tmpRetStrings.push(value);
88+
}
89+
bjs["swift_js_pop_param_int32"] = function() {
90+
return tmpParamInts.pop();
91+
}
92+
bjs["swift_js_pop_param_f32"] = function() {
93+
return tmpParamF32s.pop();
94+
}
95+
bjs["swift_js_pop_param_f64"] = function() {
96+
return tmpParamF64s.pop();
97+
}
98+
bjs["swift_js_push_pointer"] = function(pointer) {
99+
tmpRetPointers.push(pointer);
100+
}
101+
bjs["swift_js_pop_param_pointer"] = function() {
102+
return tmpParamPointers.pop();
103+
}
104+
bjs["swift_js_return_optional_bool"] = function(isSome, value) {
105+
if (isSome === 0) {
106+
tmpRetOptionalBool = null;
107+
} else {
108+
tmpRetOptionalBool = value !== 0;
109+
}
110+
}
111+
bjs["swift_js_return_optional_int"] = function(isSome, value) {
112+
if (isSome === 0) {
113+
tmpRetOptionalInt = null;
114+
} else {
115+
tmpRetOptionalInt = value | 0;
116+
}
117+
}
118+
bjs["swift_js_return_optional_float"] = function(isSome, value) {
119+
if (isSome === 0) {
120+
tmpRetOptionalFloat = null;
121+
} else {
122+
tmpRetOptionalFloat = Math.fround(value);
123+
}
124+
}
125+
bjs["swift_js_return_optional_double"] = function(isSome, value) {
126+
if (isSome === 0) {
127+
tmpRetOptionalDouble = null;
128+
} else {
129+
tmpRetOptionalDouble = value;
130+
}
131+
}
132+
bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) {
133+
if (isSome === 0) {
134+
tmpRetString = null;
135+
} else {
136+
const bytes = new Uint8Array(memory.buffer, ptr, len);
137+
tmpRetString = textDecoder.decode(bytes);
138+
}
139+
}
140+
bjs["swift_js_return_optional_object"] = function(isSome, objectId) {
141+
if (isSome === 0) {
142+
tmpRetString = null;
143+
} else {
144+
tmpRetString = swift.memory.getObject(objectId);
145+
}
146+
}
147+
bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) {
148+
if (isSome === 0) {
149+
tmpRetOptionalHeapObject = null;
150+
} else {
151+
tmpRetOptionalHeapObject = pointer;
152+
}
153+
}
154+
bjs["swift_js_get_optional_int_presence"] = function() {
155+
return tmpRetOptionalInt != null ? 1 : 0;
156+
}
157+
bjs["swift_js_get_optional_int_value"] = function() {
158+
const value = tmpRetOptionalInt;
159+
tmpRetOptionalInt = undefined;
160+
return value;
161+
}
162+
bjs["swift_js_get_optional_string"] = function() {
163+
const str = tmpRetString;
164+
tmpRetString = undefined;
165+
if (str == null) {
166+
return -1;
167+
} else {
168+
const bytes = textEncoder.encode(str);
169+
tmpRetBytes = bytes;
170+
return bytes.length;
171+
}
172+
}
173+
bjs["swift_js_get_optional_float_presence"] = function() {
174+
return tmpRetOptionalFloat != null ? 1 : 0;
175+
}
176+
bjs["swift_js_get_optional_float_value"] = function() {
177+
const value = tmpRetOptionalFloat;
178+
tmpRetOptionalFloat = undefined;
179+
return value;
180+
}
181+
bjs["swift_js_get_optional_double_presence"] = function() {
182+
return tmpRetOptionalDouble != null ? 1 : 0;
183+
}
184+
bjs["swift_js_get_optional_double_value"] = function() {
185+
const value = tmpRetOptionalDouble;
186+
tmpRetOptionalDouble = undefined;
187+
return value;
188+
}
189+
bjs["swift_js_get_optional_heap_object_pointer"] = function() {
190+
const pointer = tmpRetOptionalHeapObject;
191+
tmpRetOptionalHeapObject = undefined;
192+
return pointer || 0;
193+
}
194+
const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
195+
TestModule["bjs_Foo_init"] = function bjs_Foo_init() {
196+
try {
197+
return swift.memory.retain(new imports.Foo());
198+
} catch (error) {
199+
setException(error);
200+
return 0
201+
}
202+
}
203+
},
204+
setInstance: (i) => {
205+
instance = i;
206+
memory = instance.exports.memory;
207+
208+
setException = (error) => {
209+
instance.exports._swift_js_exception.value = swift.memory.retain(error)
210+
}
211+
},
212+
/** @param {WebAssembly.Instance} instance */
213+
createExports: (instance) => {
214+
const js = swift.memory.heap;
215+
const exports = {
216+
makeFoo: function bjs_makeFoo() {
217+
const ret = instance.exports.bjs_makeFoo();
218+
const ret1 = swift.memory.getObject(ret);
219+
swift.memory.release(ret);
220+
if (tmpRetException) {
221+
const error = swift.memory.getObject(tmpRetException);
222+
swift.memory.release(tmpRetException);
223+
tmpRetException = undefined;
224+
throw error;
225+
}
226+
return ret1;
227+
},
228+
};
229+
_exports = exports;
230+
return exports;
231+
},
232+
}
233+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"classes" : [
3+
4+
],
5+
"enums" : [
6+
7+
],
8+
"exposeToGlobal" : false,
9+
"functions" : [
10+
{
11+
"abiName" : "bjs_makeFoo",
12+
"effects" : {
13+
"isAsync" : false,
14+
"isStatic" : false,
15+
"isThrows" : true
16+
},
17+
"name" : "makeFoo",
18+
"parameters" : [
19+
20+
],
21+
"returnType" : {
22+
"jsObject" : {
23+
"_0" : "Foo"
24+
}
25+
}
26+
}
27+
],
28+
"protocols" : [
29+
30+
],
31+
"structs" : [
32+
33+
]
34+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@_expose(wasm, "bjs_makeFoo")
2+
@_cdecl("bjs_makeFoo")
3+
public func _bjs_makeFoo() -> Int32 {
4+
#if arch(wasm32)
5+
do {
6+
let ret = try makeFoo()
7+
return ret.bridgeJSLowerReturn()
8+
} catch let error {
9+
if let error = error.thrownValue.object {
10+
withExtendedLifetime(error) {
11+
_swift_js_throw(Int32(bitPattern: $0.id))
12+
}
13+
} else {
14+
let jsError = JSError(message: String(describing: error))
15+
withExtendedLifetime(jsError.jsObject) {
16+
_swift_js_throw(Int32(bitPattern: $0.id))
17+
}
18+
}
19+
return 0
20+
}
21+
#else
22+
fatalError("Only available on WebAssembly")
23+
#endif
24+
}

Tests/BridgeJSRuntimeTests/ExportAPITests.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import XCTest
2-
import JavaScriptKit
2+
@_spi(Experimental) import JavaScriptKit
33
import JavaScriptEventLoop
44

55
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "runJsWorks")
@@ -33,6 +33,15 @@ func runJsWorks() -> Void
3333
return v
3434
}
3535

36+
@JSClass struct Foo {
37+
@JSGetter var value: String
38+
@JSFunction init(_ value: String) throws(JSException)
39+
}
40+
41+
@JS func makeImportedFoo(value: String) throws(JSException) -> Foo {
42+
return try Foo(value)
43+
}
44+
3645
struct TestError: Error {
3746
let message: String
3847
}

0 commit comments

Comments
 (0)