From 48c84e3c6fbaaf8fd847079e3f2a95c42d12451b Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 20 Jun 2025 06:16:32 +0000 Subject: [PATCH] WIP --- Package.swift | 2 +- .../Sources/BridgeJSCore/ExportSwift.swift | 54 ++++- .../Sources/BridgeJSLink/BridgeJSLink.swift | 41 ++-- .../TS2Skeleton/JavaScript/src/index.d.ts | 5 + .../TS2Skeleton/JavaScript/src/processor.js | 35 +++- .../Tests/BridgeJSToolTests/Inputs/Async.d.ts | 7 + .../BridgeJSToolTests/Inputs/Async.swift | 19 ++ .../BridgeJSLinkTests/Async.Export.d.ts | 24 +++ .../BridgeJSLinkTests/Async.Export.js | 107 ++++++++++ .../BridgeJSLinkTests/Async.Import.d.ts | 24 +++ .../BridgeJSLinkTests/Async.Import.js | 93 +++++++++ .../__Snapshots__/ExportSwiftTests/Async.json | 167 ++++++++++++++++ .../ExportSwiftTests/Async.swift | 114 +++++++++++ .../__Snapshots__/ImportTSTests/Async.swift | 120 ++++++++++++ .../PackageToJS/Templates/instantiate.d.ts | 8 +- Plugins/PackageToJS/Templates/instantiate.js | 14 +- .../BasicObjects/JSPromise.swift | 31 +++ .../BridgeJSRuntimeTests/ExportAPITests.swift | 14 ++ .../Generated/BridgeJS.ExportSwift.swift | 108 ++++++++++ .../Generated/BridgeJS.ImportTS.swift | 16 ++ .../JavaScript/BridgeJS.ExportSwift.json | 184 ++++++++++++++++++ .../JavaScript/BridgeJS.ImportTS.json | 11 ++ Tests/BridgeJSRuntimeTests/bridge-js.d.ts | 2 + Tests/prelude.mjs | 125 +++++++----- 24 files changed, 1248 insertions(+), 77 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift diff --git a/Package.swift b/Package.swift index 435ae1a1..f9a31c91 100644 --- a/Package.swift +++ b/Package.swift @@ -156,7 +156,7 @@ let package = Package( ), .testTarget( name: "BridgeJSRuntimeTests", - dependencies: ["JavaScriptKit"], + dependencies: ["JavaScriptKit", "JavaScriptEventLoop"], exclude: [ "bridge-js.config.json", "bridge-js.d.ts", diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index fefcf40c..79999eb7 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -400,7 +400,9 @@ class ExportSwift { var callExpr: ExprSyntax = "\(raw: callee)(\(raw: abiParameterForwardings.map { $0.description }.joined(separator: ", ")))" if effects.isAsync { - callExpr = ExprSyntax(AwaitExprSyntax(awaitKeyword: .keyword(.await), expression: callExpr)) + callExpr = ExprSyntax( + AwaitExprSyntax(awaitKeyword: .keyword(.await).with(\.trailingTrivia, .space), expression: callExpr) + ) } if effects.isThrows { callExpr = ExprSyntax( @@ -410,6 +412,12 @@ class ExportSwift { ) ) } + + + if effects.isAsync, returnType != .void { + return CodeBlockItemSyntax(item: .init(StmtSyntax("return \(raw: callExpr).jsValue"))) + } + let retMutability = returnType == .string ? "var" : "let" if returnType == .void { return CodeBlockItemSyntax(item: .init(ExpressionStmtSyntax(expression: callExpr))) @@ -433,7 +441,40 @@ class ExportSwift { } func lowerReturnValue(returnType: BridgeType) { - abiReturnType = returnType.abiReturnType + if effects.isAsync { + // Async functions always return a Promise, which is a JSObject + _lowerReturnValue(returnType: .jsObject(nil)) + } else { + _lowerReturnValue(returnType: returnType) + } + } + + private func _lowerReturnValue(returnType: BridgeType) { + switch returnType { + case .void: + abiReturnType = nil + case .bool: + abiReturnType = .i32 + case .int: + abiReturnType = .i32 + case .float: + abiReturnType = .f32 + case .double: + abiReturnType = .f64 + case .string: + abiReturnType = nil + case .jsObject: + abiReturnType = .i32 + case .swiftHeapObject: + // UnsafeMutableRawPointer is returned as an i32 pointer + abiReturnType = .pointer + } + + if effects.isAsync { + // The return value of async function (T of `(...) async -> T`) is + // handled by the JSPromise.async, so we don't need to do anything here. + return + } switch returnType { case .void: break @@ -474,7 +515,14 @@ class ExportSwift { func render(abiName: String) -> DeclSyntax { let body: CodeBlockItemListSyntax - if effects.isThrows { + if effects.isAsync { + body = """ + let ret = JSPromise.async { + \(CodeBlockItemListSyntax(self.body)) + }.jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + """ + } else if effects.isThrows { body = """ do { \(CodeBlockItemListSyntax(self.body)) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 4d9ba596..ee6fff1f 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -126,10 +126,13 @@ struct BridgeJSLink { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); tmpRetString = textDecoder.decode(bytes); @@ -239,6 +242,14 @@ struct BridgeJSLink { } func call(abiName: String, returnType: BridgeType) -> String? { + if effects.isAsync { + return _call(abiName: abiName, returnType: .jsObject(nil)) + } else { + return _call(abiName: abiName, returnType: returnType) + } + } + + private func _call(abiName: String, returnType: BridgeType) -> String? { let call = "instance.exports.\(abiName)(\(parameterForwardings.joined(separator: ", ")))" var returnExpr: String? @@ -312,8 +323,14 @@ struct BridgeJSLink { } } - private func renderTSSignature(parameters: [Parameter], returnType: BridgeType) -> String { - return "(\(parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", "))): \(returnType.tsType)" + private func renderTSSignature(parameters: [Parameter], returnType: BridgeType, effects: Effects) -> String { + let returnTypeWithEffect: String + if effects.isAsync { + returnTypeWithEffect = "Promise<\(returnType.tsType)>" + } else { + returnTypeWithEffect = returnType.tsType + } + return "(\(parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", "))): \(returnTypeWithEffect)" } func renderExportedFunction(function: ExportedFunction) -> (js: [String], dts: [String]) { @@ -331,7 +348,7 @@ struct BridgeJSLink { ) var dtsLines: [String] = [] dtsLines.append( - "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" + "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" ) return (funcLines, dtsLines) @@ -362,7 +379,7 @@ struct BridgeJSLink { jsLines.append(contentsOf: funcLines.map { $0.indent(count: 4) }) dtsExportEntryLines.append( - "new\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));" + "new\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name), effects: constructor.effects));" .indent(count: 4) ) } @@ -384,7 +401,7 @@ struct BridgeJSLink { ).map { $0.indent(count: 4) } ) dtsTypeLines.append( - "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType));" + "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" .indent(count: 4) ) } @@ -446,7 +463,7 @@ struct BridgeJSLink { } func call(name: String, returnType: BridgeType) { - let call = "options.imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" + let call = "imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" if returnType == .void { bodyLines.append("\(call);") } else { @@ -455,7 +472,7 @@ struct BridgeJSLink { } func callConstructor(name: String) { - let call = "new options.imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" + let call = "new imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" bodyLines.append("let ret = \(call);") } @@ -533,9 +550,10 @@ struct BridgeJSLink { returnExpr: returnExpr, returnType: function.returnType ) + let effects = Effects(isAsync: false, isThrows: false) importObjectBuilder.appendDts( [ - "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" + "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));" ] ) importObjectBuilder.assignToImportObject(name: function.abiName(context: nil), function: funcLines) @@ -610,7 +628,8 @@ struct BridgeJSLink { importObjectBuilder.assignToImportObject(name: abiName, function: funcLines) importObjectBuilder.appendDts([ "\(type.name): {", - "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType));".indent(count: 4), + "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));" + .indent(count: 4), "}", ]) } diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts index e1daa4af..b53e2420 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts @@ -12,10 +12,15 @@ export type Parameter = { type: BridgeType; } +export type Effects = { + isAsync: boolean; +} + export type ImportFunctionSkeleton = { name: string; parameters: Parameter[]; returnType: BridgeType; + effects: Effects; documentation: string | undefined; } diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js index 0f97ea14..aeaf6a2d 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js @@ -162,6 +162,7 @@ export class TypeProcessor { parameters, returnType: bridgeReturnType, documentation, + effects: { isAsync: false }, }; } @@ -341,6 +342,10 @@ export class TypeProcessor { * @private */ visitType(type, node) { + // Treat A and A as the same type + if (isTypeReference(type)) { + type = type.target; + } const maybeProcessed = this.processedTypes.get(type); if (maybeProcessed) { return maybeProcessed; @@ -364,8 +369,13 @@ export class TypeProcessor { "object": { "jsObject": {} }, "symbol": { "jsObject": {} }, "never": { "void": {} }, + "Promise": { + "jsObject": { + "_0": "JSPromise" + } + }, }; - const typeString = this.checker.typeToString(type); + const typeString = type.getSymbol()?.name ?? this.checker.typeToString(type); if (typeMap[typeString]) { return typeMap[typeString]; } @@ -377,7 +387,7 @@ export class TypeProcessor { if (this.checker.isTypeAssignableTo(type, this.checker.getStringType())) { return { "string": {} }; } - if (type.getFlags() & ts.TypeFlags.TypeParameter) { + if (type.isTypeParameter()) { return { "jsObject": {} }; } @@ -412,3 +422,24 @@ export class TypeProcessor { return undefined; } } + +/** + * @param {ts.Type} type + * @returns {type is ts.ObjectType} + */ +function isObjectType(type) { + // @ts-ignore + return typeof type.objectFlags === "number"; +} + +/** + * + * @param {ts.Type} type + * @returns {type is ts.TypeReference} + */ +function isTypeReference(type) { + return ( + isObjectType(type) && + (type.objectFlags & ts.ObjectFlags.Reference) !== 0 + ); +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts new file mode 100644 index 00000000..cb0d0cef --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts @@ -0,0 +1,7 @@ +export function asyncReturnVoid(): Promise; +export function asyncRoundTripInt(v: number): Promise; +export function asyncRoundTripString(v: string): Promise; +export function asyncRoundTripBool(v: boolean): Promise; +export function asyncRoundTripFloat(v: number): Promise; +export function asyncRoundTripDouble(v: number): Promise; +export function asyncRoundTripJSObject(v: any): Promise; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift new file mode 100644 index 00000000..214331b3 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift @@ -0,0 +1,19 @@ +@JS func asyncReturnVoid() async {} +@JS func asyncRoundTripInt(_ v: Int) async -> Int { + return v +} +@JS func asyncRoundTripString(_ v: String) async -> String { + return v +} +@JS func asyncRoundTripBool(_ v: Bool) async -> Bool { + return v +} +@JS func asyncRoundTripFloat(_ v: Float) async -> Float { + return v +} +@JS func asyncRoundTripDouble(_ v: Double) async -> Double { + return v +} +@JS func asyncRoundTripJSObject(_ v: JSObject) async -> JSObject { + return v +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts new file mode 100644 index 00000000..aecab090 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts @@ -0,0 +1,24 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export type Exports = { + asyncReturnVoid(): Promise; + asyncRoundTripInt(v: number): Promise; + asyncRoundTripString(v: string): Promise; + asyncRoundTripBool(v: boolean): Promise; + asyncRoundTripFloat(v: number): Promise; + asyncRoundTripDouble(v: number): Promise; + asyncRoundTripJSObject(v: any): Promise; +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js new file mode 100644 index 00000000..bf14507a --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js @@ -0,0 +1,107 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** @param {WebAssembly.Imports} importObject */ + addImports: (importObject) => { + const bjs = {}; + importObject["bjs"] = bjs; + bjs["return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["make_jsstring"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + asyncReturnVoid: function bjs_asyncReturnVoid() { + const retId = instance.exports.bjs_asyncReturnVoid(); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripInt: function bjs_asyncRoundTripInt(v) { + const retId = instance.exports.bjs_asyncRoundTripInt(v); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripString: function bjs_asyncRoundTripString(v) { + const vBytes = textEncoder.encode(v); + const vId = swift.memory.retain(vBytes); + const retId = instance.exports.bjs_asyncRoundTripString(vId, vBytes.length); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + swift.memory.release(vId); + return ret; + }, + asyncRoundTripBool: function bjs_asyncRoundTripBool(v) { + const retId = instance.exports.bjs_asyncRoundTripBool(v); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripFloat: function bjs_asyncRoundTripFloat(v) { + const retId = instance.exports.bjs_asyncRoundTripFloat(v); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripDouble: function bjs_asyncRoundTripDouble(v) { + const retId = instance.exports.bjs_asyncRoundTripDouble(v); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripJSObject: function bjs_asyncRoundTripJSObject(v) { + const retId = instance.exports.bjs_asyncRoundTripJSObject(swift.memory.retain(v)); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts new file mode 100644 index 00000000..dea0bd18 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts @@ -0,0 +1,24 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export type Exports = { +} +export type Imports = { + asyncReturnVoid(): JSPromise; + asyncRoundTripInt(v: number): JSPromise; + asyncRoundTripString(v: string): JSPromise; + asyncRoundTripBool(v: boolean): JSPromise; + asyncRoundTripFloat(v: number): JSPromise; + asyncRoundTripDouble(v: number): JSPromise; + asyncRoundTripJSObject(v: any): JSPromise; +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js new file mode 100644 index 00000000..b901d8a4 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js @@ -0,0 +1,93 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** @param {WebAssembly.Imports} importObject */ + addImports: (importObject) => { + const bjs = {}; + importObject["bjs"] = bjs; + bjs["return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["make_jsstring"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + const TestModule = importObject["TestModule"] = {}; + TestModule["bjs_asyncReturnVoid"] = function bjs_asyncReturnVoid() { + let ret = options.imports.asyncReturnVoid(); + return swift.memory.retain(ret); + } + TestModule["bjs_asyncRoundTripInt"] = function bjs_asyncRoundTripInt(v) { + let ret = options.imports.asyncRoundTripInt(v); + return swift.memory.retain(ret); + } + TestModule["bjs_asyncRoundTripString"] = function bjs_asyncRoundTripString(v) { + const vObject = swift.memory.getObject(v); + swift.memory.release(v); + let ret = options.imports.asyncRoundTripString(vObject); + return swift.memory.retain(ret); + } + TestModule["bjs_asyncRoundTripBool"] = function bjs_asyncRoundTripBool(v) { + let ret = options.imports.asyncRoundTripBool(v); + return swift.memory.retain(ret); + } + TestModule["bjs_asyncRoundTripFloat"] = function bjs_asyncRoundTripFloat(v) { + let ret = options.imports.asyncRoundTripFloat(v); + return swift.memory.retain(ret); + } + TestModule["bjs_asyncRoundTripDouble"] = function bjs_asyncRoundTripDouble(v) { + let ret = options.imports.asyncRoundTripDouble(v); + return swift.memory.retain(ret); + } + TestModule["bjs_asyncRoundTripJSObject"] = function bjs_asyncRoundTripJSObject(v) { + let ret = options.imports.asyncRoundTripJSObject(swift.memory.getObject(v)); + return swift.memory.retain(ret); + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json new file mode 100644 index 00000000..97acb66b --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json @@ -0,0 +1,167 @@ +{ + "classes" : [ + + ], + "functions" : [ + { + "abiName" : "bjs_asyncReturnVoid", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncReturnVoid", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripInt", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripInt", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripString", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripString", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripBool", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripBool", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripFloat", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripFloat", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "float" : { + + } + } + } + ], + "returnType" : { + "float" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripDouble", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripDouble", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripJSObject", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripJSObject", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + + } + } + } + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift new file mode 100644 index 00000000..493d4530 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift @@ -0,0 +1,114 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "return_string") +private func _return_string(_ ptr: UnsafePointer?, _ len: Int32) +@_extern(wasm, module: "bjs", name: "init_memory") +private func _init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) + +@_extern(wasm, module: "bjs", name: "swift_js_retain") +private func _swift_js_retain(_ ptr: Int32) -> Int32 +@_extern(wasm, module: "bjs", name: "swift_js_throw") +private func _swift_js_throw(_ id: Int32) +#endif + +@_expose(wasm, "bjs_asyncReturnVoid") +@_cdecl("bjs_asyncReturnVoid") +public func _bjs_asyncReturnVoid() -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + await asyncReturnVoid() + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripInt") +@_cdecl("bjs_asyncRoundTripInt") +public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + let ret = await asyncRoundTripInt(_: Int(v)) + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripString") +@_cdecl("bjs_asyncRoundTripString") +public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + let v = String(unsafeUninitializedCapacity: Int(vLen)) { b in + _init_memory(vBytes, b.baseAddress.unsafelyUnwrapped) + return Int(vLen) + } + var ret = await asyncRoundTripString(_: v) + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripBool") +@_cdecl("bjs_asyncRoundTripBool") +public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + let ret = await asyncRoundTripBool(_: v == 1) + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripFloat") +@_cdecl("bjs_asyncRoundTripFloat") +public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + let ret = await asyncRoundTripFloat(_: v) + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripDouble") +@_cdecl("bjs_asyncRoundTripDouble") +public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + let ret = await asyncRoundTripDouble(_: v) + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripJSObject") +@_cdecl("bjs_asyncRoundTripJSObject") +public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + let ret = await asyncRoundTripJSObject(_: JSObject(id: UInt32(bitPattern: v))) + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift new file mode 100644 index 00000000..e11ebfa9 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift @@ -0,0 +1,120 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(JSObject_id) import JavaScriptKit + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "make_jsstring") +func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 +#else +func _make_jsstring(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "init_memory_with_result") +func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) +#else +func _init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) { + fatalError("Only available on WebAssembly") +} +#endif + +func asyncReturnVoid() -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncReturnVoid") + func bjs_asyncReturnVoid() -> Int32 + #else + func bjs_asyncReturnVoid() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncReturnVoid() + return JSPromise(takingThis: ret) +} + +func asyncRoundTripInt(_ v: Double) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripInt") + func bjs_asyncRoundTripInt(_ v: Float64) -> Int32 + #else + func bjs_asyncRoundTripInt(_ v: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripInt(v) + return JSPromise(takingThis: ret) +} + +func asyncRoundTripString(_ v: String) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripString") + func bjs_asyncRoundTripString(_ v: Int32) -> Int32 + #else + func bjs_asyncRoundTripString(_ v: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + var v = v + let vId = v.withUTF8 { b in + _make_jsstring(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_asyncRoundTripString(vId) + return JSPromise(takingThis: ret) +} + +func asyncRoundTripBool(_ v: Bool) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripBool") + func bjs_asyncRoundTripBool(_ v: Int32) -> Int32 + #else + func bjs_asyncRoundTripBool(_ v: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripBool(Int32(v ? 1 : 0)) + return JSPromise(takingThis: ret) +} + +func asyncRoundTripFloat(_ v: Double) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripFloat") + func bjs_asyncRoundTripFloat(_ v: Float64) -> Int32 + #else + func bjs_asyncRoundTripFloat(_ v: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripFloat(v) + return JSPromise(takingThis: ret) +} + +func asyncRoundTripDouble(_ v: Double) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripDouble") + func bjs_asyncRoundTripDouble(_ v: Float64) -> Int32 + #else + func bjs_asyncRoundTripDouble(_ v: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripDouble(v) + return JSPromise(takingThis: ret) +} + +func asyncRoundTripJSObject(_ v: JSObject) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripJSObject") + func bjs_asyncRoundTripJSObject(_ v: Int32) -> Int32 + #else + func bjs_asyncRoundTripJSObject(_ v: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripJSObject(Int32(bitPattern: v.id)) + return JSPromise(takingThis: ret) +} \ No newline at end of file diff --git a/Plugins/PackageToJS/Templates/instantiate.d.ts b/Plugins/PackageToJS/Templates/instantiate.d.ts index 86ea6e56..9074d8d2 100644 --- a/Plugins/PackageToJS/Templates/instantiate.d.ts +++ b/Plugins/PackageToJS/Templates/instantiate.d.ts @@ -75,9 +75,13 @@ export type InstantiateOptions = { module: ModuleSource, /* #if HAS_IMPORTS */ /** - * The imports provided by the embedder + * The function to get the imports provided by the embedder */ - imports: Imports, + getImports: (importsContext: { + getInstance: () => WebAssembly.Instance | null, + getExports: () => Exports | null, + _swift: SwiftRuntime, + }) => Imports, /* #endif */ /* #if IS_WASI */ /** diff --git a/Plugins/PackageToJS/Templates/instantiate.js b/Plugins/PackageToJS/Templates/instantiate.js index 65996d86..5d6fe6b9 100644 --- a/Plugins/PackageToJS/Templates/instantiate.js +++ b/Plugins/PackageToJS/Templates/instantiate.js @@ -23,8 +23,11 @@ import { createInstantiator } from "./bridge-js.js" */ async function createInstantiator(options, swift) { return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => {}, + /** + * @param {WebAssembly.Imports} importObject + * @param {unknown} importsContext + */ + addImports: (importObject, importsContext) => {}, /** @param {WebAssembly.Instance} instance */ setInstance: (instance) => {}, /** @param {WebAssembly.Instance} instance */ @@ -93,12 +96,13 @@ async function _instantiate( /* #endif */ /* #endif */ }; - instantiator.addImports(importObject); - options.addToCoreImports?.(importObject, { + const importsContext = { getInstance: () => instance, getExports: () => exports, _swift: swift, - }); + }; + instantiator.addImports(importObject, importsContext); + options.addToCoreImports?.(importObject, importsContext); let module; let instance; diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index 24a9ae48..48800795 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -23,6 +23,10 @@ public final class JSPromise: JSBridgedClass { self.init(from: jsObject) } + @_spi(BridgeJS) public convenience init(takingThis: Int32) { + self.init(unsafelyWrapping: JSObject(id: UInt32(bitPattern: takingThis))) + } + /// Creates a new `JSPromise` instance from a given JavaScript `Promise` object. If `value` /// is not an object and is not an instance of JavaScript `Promise`, this function will /// return `nil`. @@ -66,6 +70,33 @@ public final class JSPromise: JSBridgedClass { self.init(unsafelyWrapping: Self.constructor!.new(closure)) } + public static func async(body: @escaping () async throws(JSException) -> Void) -> JSPromise { + self.async { () throws(JSException) -> JSValue in + try await body() + return .undefined + } + } + public static func async(body: @escaping () async throws(JSException) -> JSValue) -> JSPromise { + JSPromise { resolver in + // NOTE: The context is fully transferred to the unstructured task + // isolation but the compiler can't prove it yet, so we need to + // use `@unchecked Sendable` to make it compile with the Swift 6 mode. + struct Context: @unchecked Sendable { + let resolver: (JSPromise.Result) -> Void + let body: () async throws(JSException) -> JSValue + } + let context = Context(resolver: resolver, body: body) + Task { + do throws(JSException) { + let result = try await context.body() + context.resolver(.success(result)) + } catch { + context.resolver(.failure(error.thrownValue)) + } + } + } + } + #if !hasFeature(Embedded) public static func resolve(_ value: ConvertibleToJSValue) -> JSPromise { self.init(unsafelyWrapping: Self.constructor!.resolve!(value).object!) diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 2b78b96b..b6c359df 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -1,5 +1,6 @@ import XCTest import JavaScriptKit +import JavaScriptEventLoop @_extern(wasm, module: "BridgeJSRuntimeTests", name: "runJsWorks") @_extern(c) @@ -49,6 +50,15 @@ struct TestError: Error { @JS func throwsWithSwiftHeapObjectResult() throws(JSException) -> Greeter { return Greeter(name: "Test") } @JS func throwsWithJSObjectResult() throws(JSException) -> JSObject { return JSObject() } +@JS func asyncRoundTripVoid() async -> Void { return } +@JS func asyncRoundTripInt(v: Int) async -> Int { return v } +@JS func asyncRoundTripFloat(v: Float) async -> Float { return v } +@JS func asyncRoundTripDouble(v: Double) async -> Double { return v } +@JS func asyncRoundTripBool(v: Bool) async -> Bool { return v } +@JS func asyncRoundTripString(v: String) async -> String { return v } +@JS func asyncRoundTripSwiftHeapObject(v: Greeter) async -> Greeter { return v } +@JS func asyncRoundTripJSObject(v: JSObject) async -> JSObject { return v } + @JS class Greeter { var name: String @@ -83,4 +93,8 @@ class ExportAPITests: XCTestCase { runJsWorks() XCTAssertTrue(hasDeinitGreeter) } + + func testAllAsync() async throws { + _ = try await runAsyncWorks().value() + } } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 2a91da9f..5a2f3927 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -300,6 +300,114 @@ public func _bjs_throwsWithJSObjectResult() -> Int32 { #endif } +@_expose(wasm, "bjs_asyncRoundTripVoid") +@_cdecl("bjs_asyncRoundTripVoid") +public func _bjs_asyncRoundTripVoid() -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + await asyncRoundTripVoid() + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripInt") +@_cdecl("bjs_asyncRoundTripInt") +public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripInt(v: Int(v)).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripFloat") +@_cdecl("bjs_asyncRoundTripFloat") +public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripFloat(v: v).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripDouble") +@_cdecl("bjs_asyncRoundTripDouble") +public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripDouble(v: v).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripBool") +@_cdecl("bjs_asyncRoundTripBool") +public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripBool(v: v == 1).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripString") +@_cdecl("bjs_asyncRoundTripString") +public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + let v = String(unsafeUninitializedCapacity: Int(vLen)) { b in + _swift_js_init_memory(vBytes, b.baseAddress.unsafelyUnwrapped) + return Int(vLen) + } + return await asyncRoundTripString(v: v).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripSwiftHeapObject") +@_cdecl("bjs_asyncRoundTripSwiftHeapObject") +public func _bjs_asyncRoundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripSwiftHeapObject(v: Unmanaged.fromOpaque(v).takeUnretainedValue()).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripJSObject") +@_cdecl("bjs_asyncRoundTripJSObject") +public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripJSObject(v: JSObject(id: UInt32(bitPattern: v))).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_takeGreeter") @_cdecl("bjs_takeGreeter") public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift index fd558ab8..255853fa 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift @@ -142,6 +142,22 @@ func jsThrowOrString(_ shouldThrow: Bool) throws(JSException) -> String { } } +func runAsyncWorks() throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_runAsyncWorks") + func bjs_runAsyncWorks() -> Int32 + #else + func bjs_runAsyncWorks() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_runAsyncWorks() + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise(takingThis: ret) +} + struct JsGreeter { let this: JSObject diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 7a467cc3..9282ab57 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -385,6 +385,190 @@ } } }, + { + "abiName" : "bjs_asyncRoundTripVoid", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripVoid", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripInt", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripInt", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripFloat", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripFloat", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "float" : { + + } + } + } + ], + "returnType" : { + "float" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripDouble", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripDouble", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripBool", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripBool", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripString", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripString", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripSwiftHeapObject", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripSwiftHeapObject", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + }, + { + "abiName" : "bjs_asyncRoundTripJSObject", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripJSObject", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + + } + } + }, { "abiName" : "bjs_takeGreeter", "effects" : { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json index bf3190b8..82515fec 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json @@ -138,6 +138,17 @@ } } + }, + { + "name" : "runAsyncWorks", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "JSPromise" + } + } } ], "types" : [ diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts index b03ef570..0f0d0155 100644 --- a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts +++ b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts @@ -14,3 +14,5 @@ export class JsGreeter { greet(): string; changeName(name: string): void; } + +export function runAsyncWorks(): Promise; diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index c6ac428a..1e1538f6 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -6,59 +6,69 @@ export async function setupOptions(options, context) { setupTestGlobals(globalThis); return { ...options, - imports: { - "jsRoundTripVoid": () => { - return; - }, - "jsRoundTripNumber": (v) => { - return v; - }, - "jsRoundTripBool": (v) => { - return v; - }, - "jsRoundTripString": (v) => { - return v; - }, - "jsThrowOrVoid": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - }, - "jsThrowOrNumber": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - return 1; - }, - "jsThrowOrBool": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - return true; - }, - "jsThrowOrString": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - return "Hello, world!"; - }, - JsGreeter: class { - /** - * @param {string} name - * @param {string} prefix - */ - constructor(name, prefix) { - this.name = name; - this.prefix = prefix; - } - greet() { - return `${this.prefix}, ${this.name}!`; - } - /** @param {string} name */ - changeName(name) { - this.name = name; + getImports: (importsContext) => { + return { + "jsRoundTripVoid": () => { + return; + }, + "jsRoundTripNumber": (v) => { + return v; + }, + "jsRoundTripBool": (v) => { + return v; + }, + "jsRoundTripString": (v) => { + return v; + }, + "jsThrowOrVoid": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + }, + "jsThrowOrNumber": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return 1; + }, + "jsThrowOrBool": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return true; + }, + "jsThrowOrString": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return "Hello, world!"; + }, + JsGreeter: class { + /** + * @param {string} name + * @param {string} prefix + */ + constructor(name, prefix) { + this.name = name; + this.prefix = prefix; + } + greet() { + return `${this.prefix}, ${this.name}!`; + } + /** @param {string} name */ + changeName(name) { + this.name = name; + } + }, + runAsyncWorks: async () => { + const exports = importsContext.getExports(); + if (!exports) { + throw new Error("No exports!?"); + } + BridgeJSRuntimeTests_runAsyncWorks(exports); + return; } - } + }; }, addToCoreImports(importObject, importsContext) { const { getInstance, getExports } = importsContext; @@ -68,7 +78,11 @@ export async function setupOptions(options, context) { } const bridgeJSRuntimeTests = importObject["BridgeJSRuntimeTests"] || {}; bridgeJSRuntimeTests["runJsWorks"] = () => { - return BridgeJSRuntimeTests_runJsWorks(getInstance(), getExports()); + const exports = getExports(); + if (!exports) { + throw new Error("No exports!?"); + } + return BridgeJSRuntimeTests_runJsWorks(getInstance(), exports); } importObject["BridgeJSRuntimeTests"] = bridgeJSRuntimeTests; } @@ -143,6 +157,11 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { } } +/** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ +async function BridgeJSRuntimeTests_runAsyncWorks(exports) { + await exports.asyncRoundTripVoid(); +} + function setupTestGlobals(global) { global.globalObject1 = { prop_1: {