diff --git a/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift b/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift index 53f06222..871938cf 100644 --- a/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift +++ b/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func benchmarkHelperNoop() -> Void { +func benchmarkHelperNoop() throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkHelperNoop") func bjs_benchmarkHelperNoop() -> Void @@ -16,9 +16,12 @@ func benchmarkHelperNoop() -> Void { } #endif bjs_benchmarkHelperNoop() + if let error = _swift_js_take_exception() { + throw error + } } -func benchmarkHelperNoopWithNumber(_ n: Double) -> Void { +func benchmarkHelperNoopWithNumber(_ n: Double) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkHelperNoopWithNumber") func bjs_benchmarkHelperNoopWithNumber(_ n: Float64) -> Void @@ -28,9 +31,12 @@ func benchmarkHelperNoopWithNumber(_ n: Double) -> Void { } #endif bjs_benchmarkHelperNoopWithNumber(n) + if let error = _swift_js_take_exception() { + throw error + } } -func benchmarkRunner(_ name: String, _ body: JSObject) -> Void { +func benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkRunner") func bjs_benchmarkRunner(_ name: Int32, _ body: Int32) -> Void @@ -44,4 +50,7 @@ func benchmarkRunner(_ name: String, _ body: JSObject) -> Void { _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } bjs_benchmarkRunner(nameId, Int32(bitPattern: body.id)) + if let error = _swift_js_take_exception() { + throw error + } } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Package.swift b/Examples/PlayBridgeJS/Package.swift index 76e95d1a..258518b4 100644 --- a/Examples/PlayBridgeJS/Package.swift +++ b/Examples/PlayBridgeJS/Package.swift @@ -22,6 +22,11 @@ let package = Package( .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ], + exclude: [ + "bridge-js.d.ts", + "bridge-js.config.json", + "Generated/JavaScript", + ], swiftSettings: [ .enableExperimentalFeature("Extern") ], diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift index 283a3fbb..b0656df9 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift @@ -26,12 +26,12 @@ public func _bjs_PlayBridgeJS_update(_self: UnsafeMutableRawPointer, swiftSource _swift_js_init_memory(swiftSourceBytes, b.baseAddress.unsafelyUnwrapped) return Int(swiftSourceLen) } - let dtsSource = String(unsafeUninitializedCapacity: Int(dtsSourceLen)) { b in + let dtsSource = String(unsafeUninitializedCapacity: Int(dtsSourceLen)) { b in _swift_js_init_memory(dtsSourceBytes, b.baseAddress.unsafelyUnwrapped) return Int(dtsSourceLen) } - let ret = try Unmanaged.fromOpaque(_self).takeUnretainedValue().update(swiftSource: swiftSource, dtsSource: dtsSource) - return Unmanaged.passRetained(ret).toOpaque() + let ret = try Unmanaged.fromOpaque(_self).takeUnretainedValue().update(swiftSource: swiftSource, dtsSource: dtsSource) + return Unmanaged.passRetained(ret).toOpaque() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift index 6698cab5..5d82db08 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func createTS2Skeleton() -> TS2Skeleton { +func createTS2Skeleton() throws(JSException) -> TS2Skeleton { #if arch(wasm32) @_extern(wasm, module: "PlayBridgeJS", name: "bjs_createTS2Skeleton") func bjs_createTS2Skeleton() -> Int32 @@ -16,6 +16,9 @@ func createTS2Skeleton() -> TS2Skeleton { } #endif let ret = bjs_createTS2Skeleton() + if let error = _swift_js_take_exception() { + throw error + } return TS2Skeleton(takingThis: ret) } @@ -30,7 +33,7 @@ struct TS2Skeleton { self.this = JSObject(id: UInt32(bitPattern: this)) } - func convert(_ ts: String) -> String { + func convert(_ ts: String) throws(JSException) -> String { #if arch(wasm32) @_extern(wasm, module: "PlayBridgeJS", name: "bjs_TS2Skeleton_convert") func bjs_TS2Skeleton_convert(_ self: Int32, _ ts: Int32) -> Int32 @@ -44,6 +47,9 @@ struct TS2Skeleton { _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } let ret = bjs_TS2Skeleton_convert(Int32(bitPattern: self.this.id), tsId) + if let error = _swift_js_take_exception() { + throw error + } return String(unsafeUninitializedCapacity: Int(ret)) { b in _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) return Int(ret) diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift index 7dc5a57f..8f224994 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift @@ -9,6 +9,8 @@ import class Foundation.JSONDecoder @JS func update(swiftSource: String, dtsSource: String) throws(JSException) -> PlayBridgeJSOutput { do { return try _update(swiftSource: swiftSource, dtsSource: dtsSource) + } catch let error as JSException { + throw error } catch { throw JSException(message: String(describing: error)) } @@ -20,8 +22,8 @@ import class Foundation.JSONDecoder try exportSwift.addSourceFile(sourceFile, "Playground.swift") let exportResult = try exportSwift.finalize() var importTS = ImportTS(progress: .silent, moduleName: "Playground") - let ts2skeleton = createTS2Skeleton() - let skeletonJSONString = ts2skeleton.convert(dtsSource) + let ts2skeleton = try createTS2Skeleton() + let skeletonJSONString = try ts2skeleton.convert(dtsSource) let decoder = JSONDecoder() let importSkeleton = try decoder.decode( ImportedFileSkeleton.self, diff --git a/Package.swift b/Package.swift index f8b3b6e9..435ae1a1 100644 --- a/Package.swift +++ b/Package.swift @@ -151,12 +151,17 @@ let package = Package( .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ], - path: "Plugins/BridgeJS/Sources/BridgeJSTool" + path: "Plugins/BridgeJS/Sources/BridgeJSTool", + exclude: ["TS2Skeleton/JavaScript"] ), .testTarget( name: "BridgeJSRuntimeTests", dependencies: ["JavaScriptKit"], - exclude: ["Generated/JavaScript"], + exclude: [ + "bridge-js.config.json", + "bridge-js.d.ts", + "Generated/JavaScript", + ], swiftSettings: [ .enableExperimentalFeature("Extern") ] diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 12960500..fefcf40c 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -56,7 +56,9 @@ class ExportSwift { fileprivate final class APICollector: SyntaxAnyVisitor { var exportedFunctions: [ExportedFunction] = [] - var exportedClasses: [String: ExportedClass] = [:] + /// The names of the exported classes, in the order they were written in the source file + var exportedClassNames: [String] = [] + var exportedClassByName: [String: ExportedClass] = [:] var errors: [DiagnosticError] = [] enum State { @@ -114,7 +116,7 @@ class ExportSwift { return .skipChildren case .classBody(let name): if let exportedFunction = visitFunction(node: node) { - exportedClasses[name]?.methods.append(exportedFunction) + exportedClassByName[name]?.methods.append(exportedFunction) } return .skipChildren } @@ -217,7 +219,7 @@ class ExportSwift { parameters: parameters, effects: effects ) - exportedClasses[name]?.constructor = constructor + exportedClassByName[name]?.constructor = constructor return .skipChildren } @@ -226,11 +228,12 @@ class ExportSwift { stateStack.push(state: .classBody(name: name)) guard node.attributes.hasJSAttribute() else { return .skipChildren } - exportedClasses[name] = ExportedClass( + exportedClassByName[name] = ExportedClass( name: name, constructor: nil, methods: [] ) + exportedClassNames.append(name) return .visitChildren } override func visitPost(_ node: ClassDeclSyntax) { @@ -242,7 +245,11 @@ class ExportSwift { let collector = APICollector(parent: self) collector.walk(sourceFile) exportedFunctions.append(contentsOf: collector.exportedFunctions) - exportedClasses.append(contentsOf: collector.exportedClasses.values) + exportedClasses.append( + contentsOf: collector.exportedClassNames.map { + collector.exportedClassByName[$0]! + } + ) return collector.errors } @@ -426,25 +433,7 @@ class ExportSwift { } 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 - } + abiReturnType = returnType.abiReturnType switch returnType { case .void: break diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 4aef01fd..37181114 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -151,6 +151,7 @@ struct ImportTS { } else { body.append("let ret = \(raw: call)") } + body.append("if let error = _swift_js_take_exception() { throw error }") } func liftReturnValue(returnType: BridgeType) throws { @@ -253,6 +254,7 @@ struct ImportTS { ) } }), + effectSpecifiers: ImportTS.buildFunctionEffect(throws: true, async: false), returnClause: ReturnClauseSyntax( arrow: .arrowToken(), type: IdentifierTypeSyntax(name: .identifier(returnType.swiftType)) @@ -280,7 +282,8 @@ struct ImportTS { ) } } - ) + ), + effectSpecifiers: ImportTS.buildFunctionEffect(throws: true, async: false) ), bodyBuilder: { self.renderImportDecl() @@ -364,6 +367,7 @@ struct ImportTS { try builder.liftReturnValue(returnType: property.type) return AccessorDeclSyntax( accessorSpecifier: .keyword(.get), + effectSpecifiers: Self.buildAccessorEffect(throws: true, async: false), body: CodeBlockSyntax { builder.renderImportDecl() builder.body @@ -371,31 +375,25 @@ struct ImportTS { ) } - func renderSetterDecl(property: ImportedPropertySkeleton) throws -> AccessorDeclSyntax { + func renderSetterDecl(property: ImportedPropertySkeleton) throws -> DeclSyntax { let builder = ImportedThunkBuilder( moduleName: moduleName, abiName: property.setterAbiName(context: type) ) + let newValue = Parameter(label: nil, name: "newValue", type: property.type) try builder.lowerParameter(param: Parameter(label: nil, name: "self", type: .jsObject(name))) - try builder.lowerParameter(param: Parameter(label: nil, name: "newValue", type: property.type)) + try builder.lowerParameter(param: newValue) builder.call(returnType: .void) - return AccessorDeclSyntax( - modifier: DeclModifierSyntax(name: .keyword(.nonmutating)), - accessorSpecifier: .keyword(.set), - body: CodeBlockSyntax { - builder.renderImportDecl() - builder.body - } + return builder.renderThunkDecl( + name: "set\(property.name.capitalizedFirstLetter())", + parameters: [newValue], + returnType: .void ) } func renderPropertyDecl(property: ImportedPropertySkeleton) throws -> [DeclSyntax] { - var accessorDecls: [AccessorDeclSyntax] = [] - accessorDecls.append(try renderGetterDecl(property: property)) - if !property.isReadonly { - accessorDecls.append(try renderSetterDecl(property: property)) - } - return [ + let accessorDecls: [AccessorDeclSyntax] = [try renderGetterDecl(property: property)] + var decls: [DeclSyntax] = [ DeclSyntax( VariableDeclSyntax( leadingTrivia: Self.renderDocumentation(documentation: property.documentation), @@ -418,6 +416,10 @@ struct ImportTS { ) ) ] + if !property.isReadonly { + decls.append(try renderSetterDecl(property: property)) + } + return decls } let classDecl = try StructDeclSyntax( leadingTrivia: Self.renderDocumentation(documentation: type.documentation), @@ -469,4 +471,36 @@ struct ImportTS { let lines = documentation.split { $0.isNewline } return Trivia(pieces: lines.flatMap { [TriviaPiece.docLineComment("/// \($0)"), .newlines(1)] }) } + + static func buildFunctionEffect(throws: Bool, async: Bool) -> FunctionEffectSpecifiersSyntax { + return FunctionEffectSpecifiersSyntax( + asyncSpecifier: `async` ? .keyword(.async) : nil, + throwsClause: `throws` + ? ThrowsClauseSyntax( + throwsSpecifier: .keyword(.throws), + leftParen: .leftParenToken(), + type: IdentifierTypeSyntax(name: .identifier("JSException")), + rightParen: .rightParenToken() + ) : nil + ) + } + static func buildAccessorEffect(throws: Bool, async: Bool) -> AccessorEffectSpecifiersSyntax { + return AccessorEffectSpecifiersSyntax( + asyncSpecifier: `async` ? .keyword(.async) : nil, + throwsClause: `throws` + ? ThrowsClauseSyntax( + throwsSpecifier: .keyword(.throws), + leftParen: .leftParenToken(), + type: IdentifierTypeSyntax(name: .identifier("JSException")), + rightParen: .rightParenToken() + ) : nil + ) + } +} + +extension String { + func capitalizedFirstLetter() -> String { + guard !isEmpty else { return self } + return prefix(1).uppercased() + dropFirst() + } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 05a29aab..4d9ba596 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -118,6 +118,7 @@ struct BridgeJSLink { export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -161,6 +162,9 @@ struct BridgeJSLink { setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { @@ -419,16 +423,24 @@ struct BridgeJSLink { func renderFunction( name: String, - returnExpr: String? + returnExpr: String?, + returnType: BridgeType ) -> [String] { var funcLines: [String] = [] funcLines.append( "function \(name)(\(parameterNames.joined(separator: ", "))) {" ) - funcLines.append(contentsOf: bodyLines.map { $0.indent(count: 4) }) + funcLines.append("try {".indent(count: 4)) + funcLines.append(contentsOf: bodyLines.map { $0.indent(count: 8) }) if let returnExpr = returnExpr { - funcLines.append("return \(returnExpr);".indent(count: 4)) + funcLines.append("return \(returnExpr);".indent(count: 8)) } + funcLines.append("} catch (error) {".indent(count: 4)) + funcLines.append("setException(error);".indent(count: 8)) + if let abiReturnType = returnType.abiReturnType { + funcLines.append("return \(abiReturnType.placeholderValue)".indent(count: 8)) + } + funcLines.append("}".indent(count: 4)) funcLines.append("}") return funcLines } @@ -518,7 +530,8 @@ struct BridgeJSLink { let returnExpr = try thunkBuilder.lowerReturnValue(returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( name: function.abiName(context: nil), - returnExpr: returnExpr + returnExpr: returnExpr, + returnType: function.returnType ) importObjectBuilder.appendDts( [ @@ -591,7 +604,8 @@ struct BridgeJSLink { let abiName = constructor.abiName(context: type) let funcLines = thunkBuilder.renderFunction( name: abiName, - returnExpr: returnExpr + returnExpr: returnExpr, + returnType: returnType ) importObjectBuilder.assignToImportObject(name: abiName, function: funcLines) importObjectBuilder.appendDts([ @@ -611,7 +625,8 @@ struct BridgeJSLink { let returnExpr = try emitCall(thunkBuilder) let funcLines = thunkBuilder.renderFunction( name: abiName, - returnExpr: returnExpr + returnExpr: returnExpr, + returnType: property.type ) return (funcLines, []) } @@ -629,7 +644,8 @@ struct BridgeJSLink { let returnExpr = try thunkBuilder.lowerReturnValue(returnType: method.returnType) let funcLines = thunkBuilder.renderFunction( name: method.abiName(context: context), - returnExpr: returnExpr + returnExpr: returnExpr, + returnType: method.returnType ) return (funcLines, []) } @@ -667,3 +683,11 @@ extension BridgeType { } } } + +extension WasmCoreType { + fileprivate var placeholderValue: String { + switch self { + case .i32, .i64, .f32, .f64, .pointer: return "0" + } + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 873849f9..5bfcc414 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -101,3 +101,20 @@ struct ImportedModuleSkeleton: Codable { let moduleName: String var children: [ImportedFileSkeleton] } + +extension BridgeType { + var abiReturnType: WasmCoreType? { + switch self { + case .void: return nil + case .bool: return .i32 + case .int: return .i32 + case .float: return .f32 + case .double: return .f64 + case .string: return nil + case .jsObject: return .i32 + case .swiftHeapObject: + // UnsafeMutableRawPointer is returned as an i32 pointer + return .pointer + } + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift index 044bff0d..6096e2b3 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift @@ -159,7 +159,7 @@ import SwiftParser ) let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true") let exporter = ExportSwift(progress: progress) - for inputFile in positionalArguments { + for inputFile in positionalArguments.sorted() { let sourceURL = URL(fileURLWithPath: inputFile) guard sourceURL.pathExtension == "swift" else { continue } let sourceContent = try String(contentsOf: sourceURL, encoding: .utf8) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/SnapshotTesting.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/SnapshotTesting.swift index 28b34bf6..ce7066a6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/SnapshotTesting.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/SnapshotTesting.swift @@ -24,14 +24,15 @@ func assertSnapshot( let existingSnapshot = try String(contentsOf: snapshotPath, encoding: .utf8) let ok = existingSnapshot == String(data: input, encoding: .utf8)! let actualFilePath = snapshotPath.path + ".actual" + let updateSnapshots = ProcessInfo.processInfo.environment["UPDATE_SNAPSHOTS"] != nil func buildComment() -> Comment { "Snapshot mismatch: \(actualFilePath) \(snapshotPath.path)" } - if !ok { - try input.write(to: URL(fileURLWithPath: actualFilePath)) - } - if ProcessInfo.processInfo.environment["UPDATE_SNAPSHOTS"] == nil { + if !updateSnapshots { #expect(ok, buildComment(), sourceLocation: sourceLocation) + if !ok { + try input.write(to: URL(fileURLWithPath: actualFilePath)) + } } else { try input.write(to: snapshotPath) } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js index f619fcf2..c90b3919 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -47,18 +48,33 @@ export async function createInstantiator(options, swift) { } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkArray"] = function bjs_checkArray(a) { - options.imports.checkArray(swift.memory.getObject(a)); + try { + options.imports.checkArray(swift.memory.getObject(a)); + } catch (error) { + setException(error); + } } TestModule["bjs_checkArrayWithLength"] = function bjs_checkArrayWithLength(a, b) { - options.imports.checkArrayWithLength(swift.memory.getObject(a), b); + try { + options.imports.checkArrayWithLength(swift.memory.getObject(a), b); + } catch (error) { + setException(error); + } } TestModule["bjs_checkArray"] = function bjs_checkArray(a) { - options.imports.checkArray(swift.memory.getObject(a)); + try { + options.imports.checkArray(swift.memory.getObject(a)); + } catch (error) { + setException(error); + } } }, setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js index fc6c3fcc..4d88bcdb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -47,21 +48,39 @@ export async function createInstantiator(options, swift) { } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_returnAnimatable"] = function bjs_returnAnimatable() { - let ret = options.imports.returnAnimatable(); - return swift.memory.retain(ret); + try { + let ret = options.imports.returnAnimatable(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } } TestModule["bjs_Animatable_animate"] = function bjs_Animatable_animate(self, keyframes, options) { - let ret = swift.memory.getObject(self).animate(swift.memory.getObject(keyframes), swift.memory.getObject(options)); - return swift.memory.retain(ret); + try { + let ret = swift.memory.getObject(self).animate(swift.memory.getObject(keyframes), swift.memory.getObject(options)); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } } TestModule["bjs_Animatable_getAnimations"] = function bjs_Animatable_getAnimations(self, options) { - let ret = swift.memory.getObject(self).getAnimations(swift.memory.getObject(options)); - return swift.memory.retain(ret); + try { + let ret = swift.memory.getObject(self).getAnimations(swift.memory.getObject(options)); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } } }, setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js index 0d31689c..1f4d6cbc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -50,6 +51,9 @@ export async function createInstantiator(options, swift) { setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index c8a58a3d..c6413b6b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -47,12 +48,19 @@ export async function createInstantiator(options, swift) { } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_check"] = function bjs_check(a, b) { - options.imports.check(a, b); + try { + options.imports.check(a, b); + } catch (error) { + setException(error); + } } }, setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index b2a16684..01dbcb74 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -50,6 +51,9 @@ export async function createInstantiator(options, swift) { setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index 690514e3..2a3292bc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -47,17 +48,30 @@ export async function createInstantiator(options, swift) { } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkNumber"] = function bjs_checkNumber() { - let ret = options.imports.checkNumber(); - return ret; + try { + let ret = options.imports.checkNumber(); + return ret; + } catch (error) { + setException(error); + return 0 + } } TestModule["bjs_checkBoolean"] = function bjs_checkBoolean() { - let ret = options.imports.checkBoolean(); - return ret !== 0; + try { + let ret = options.imports.checkBoolean(); + return ret !== 0; + } catch (error) { + setException(error); + return 0 + } } }, setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js index ca37f7f5..7c2e883d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -50,6 +51,9 @@ export async function createInstantiator(options, swift) { setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js index 466b7786..c12e6c1e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -47,19 +48,30 @@ export async function createInstantiator(options, swift) { } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkString"] = function bjs_checkString(a) { - const aObject = swift.memory.getObject(a); - swift.memory.release(a); - options.imports.checkString(aObject); + try { + const aObject = swift.memory.getObject(a); + swift.memory.release(a); + options.imports.checkString(aObject); + } catch (error) { + setException(error); + } } TestModule["bjs_checkStringWithLength"] = function bjs_checkStringWithLength(a, b) { - const aObject = swift.memory.getObject(a); - swift.memory.release(a); - options.imports.checkStringWithLength(aObject, b); + try { + const aObject = swift.memory.getObject(a); + swift.memory.release(a); + options.imports.checkStringWithLength(aObject, b); + } catch (error) { + setException(error); + } } }, setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js index d752e9e7..0362f3c4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -50,6 +51,9 @@ export async function createInstantiator(options, swift) { setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js index fb2ee18e..bb78c255 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -47,14 +48,21 @@ export async function createInstantiator(options, swift) { } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkString"] = function bjs_checkString() { - let ret = options.imports.checkString(); - tmpRetBytes = textEncoder.encode(ret); - return tmpRetBytes.length; + try { + let ret = options.imports.checkString(); + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } } }, setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index 58df1f25..7a5938a1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -50,6 +51,9 @@ export async function createInstantiator(options, swift) { setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js index 6ebd05df..d0f9b623 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -50,6 +51,9 @@ export async function createInstantiator(options, swift) { setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js index 41af7fa1..30639b4a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -47,12 +48,19 @@ export async function createInstantiator(options, swift) { } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_checkSimple"] = function bjs_checkSimple(a) { - options.imports.checkSimple(a); + try { + options.imports.checkSimple(a); + } catch (error) { + setException(error); + } } }, setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index 52d36b77..f5cdc9ef 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -47,39 +48,68 @@ export async function createInstantiator(options, swift) { } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_Greeter_init"] = function bjs_Greeter_init(name) { - const nameObject = swift.memory.getObject(name); - swift.memory.release(name); - let ret = new options.imports.Greeter(nameObject); - return swift.memory.retain(ret); + try { + const nameObject = swift.memory.getObject(name); + swift.memory.release(name); + let ret = new options.imports.Greeter(nameObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } } TestModule["bjs_Greeter_name_get"] = function bjs_Greeter_name_get(self) { - let ret = swift.memory.getObject(self).name; - tmpRetBytes = textEncoder.encode(ret); - return tmpRetBytes.length; + try { + let ret = swift.memory.getObject(self).name; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } } TestModule["bjs_Greeter_name_set"] = function bjs_Greeter_name_set(self, newValue) { - const newValueObject = swift.memory.getObject(newValue); - swift.memory.release(newValue); - swift.memory.getObject(self).name = newValueObject; + try { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self).name = newValueObject; + } catch (error) { + setException(error); + } } TestModule["bjs_Greeter_age_get"] = function bjs_Greeter_age_get(self) { - let ret = swift.memory.getObject(self).age; - return ret; + try { + let ret = swift.memory.getObject(self).age; + return ret; + } catch (error) { + setException(error); + return 0 + } } TestModule["bjs_Greeter_greet"] = function bjs_Greeter_greet(self) { - let ret = swift.memory.getObject(self).greet(); - tmpRetBytes = textEncoder.encode(ret); - return tmpRetBytes.length; + try { + let ret = swift.memory.getObject(self).greet(); + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } } TestModule["bjs_Greeter_changeName"] = function bjs_Greeter_changeName(self, name) { - const nameObject = swift.memory.getObject(name); - swift.memory.release(name); - swift.memory.getObject(self).changeName(nameObject); + try { + const nameObject = swift.memory.getObject(name); + swift.memory.release(name); + swift.memory.getObject(self).changeName(nameObject); + } catch (error) { + setException(error); + } } }, setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js index 29019e90..c7086eda 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -50,6 +51,9 @@ export async function createInstantiator(options, swift) { setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js index f57197b0..2482082c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js @@ -7,6 +7,7 @@ export async function createInstantiator(options, swift) { let instance; let memory; + let setException; const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); @@ -47,12 +48,19 @@ export async function createInstantiator(options, swift) { } const TestModule = importObject["TestModule"] = {}; TestModule["bjs_check"] = function bjs_check() { - options.imports.check(); + try { + options.imports.check(); + } catch (error) { + setException(error); + } } }, setInstance: (i) => { instance = i; memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift index 0a85c88f..34841ae8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func checkArray(_ a: JSObject) -> Void { +func checkArray(_ a: JSObject) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_checkArray") func bjs_checkArray(_ a: Int32) -> Void @@ -16,9 +16,12 @@ func checkArray(_ a: JSObject) -> Void { } #endif bjs_checkArray(Int32(bitPattern: a.id)) + if let error = _swift_js_take_exception() { + throw error + } } -func checkArrayWithLength(_ a: JSObject, _ b: Double) -> Void { +func checkArrayWithLength(_ a: JSObject, _ b: Double) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_checkArrayWithLength") func bjs_checkArrayWithLength(_ a: Int32, _ b: Float64) -> Void @@ -28,9 +31,12 @@ func checkArrayWithLength(_ a: JSObject, _ b: Double) -> Void { } #endif bjs_checkArrayWithLength(Int32(bitPattern: a.id), b) + if let error = _swift_js_take_exception() { + throw error + } } -func checkArray(_ a: JSObject) -> Void { +func checkArray(_ a: JSObject) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_checkArray") func bjs_checkArray(_ a: Int32) -> Void @@ -40,4 +46,7 @@ func checkArray(_ a: JSObject) -> Void { } #endif bjs_checkArray(Int32(bitPattern: a.id)) + if let error = _swift_js_take_exception() { + throw error + } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift index 44fbb6ba..be9f524e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func returnAnimatable() -> Animatable { +func returnAnimatable() throws(JSException) -> Animatable { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_returnAnimatable") func bjs_returnAnimatable() -> Int32 @@ -16,6 +16,9 @@ func returnAnimatable() -> Animatable { } #endif let ret = bjs_returnAnimatable() + if let error = _swift_js_take_exception() { + throw error + } return Animatable(takingThis: ret) } @@ -30,7 +33,7 @@ struct Animatable { self.this = JSObject(id: UInt32(bitPattern: this)) } - func animate(_ keyframes: JSObject, _ options: JSObject) -> JSObject { + func animate(_ keyframes: JSObject, _ options: JSObject) throws(JSException) -> JSObject { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_Animatable_animate") func bjs_Animatable_animate(_ self: Int32, _ keyframes: Int32, _ options: Int32) -> Int32 @@ -40,10 +43,13 @@ struct Animatable { } #endif let ret = bjs_Animatable_animate(Int32(bitPattern: self.this.id), Int32(bitPattern: keyframes.id), Int32(bitPattern: options.id)) + if let error = _swift_js_take_exception() { + throw error + } return JSObject(id: UInt32(bitPattern: ret)) } - func getAnimations(_ options: JSObject) -> JSObject { + func getAnimations(_ options: JSObject) throws(JSException) -> JSObject { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_Animatable_getAnimations") func bjs_Animatable_getAnimations(_ self: Int32, _ options: Int32) -> Int32 @@ -53,6 +59,9 @@ struct Animatable { } #endif let ret = bjs_Animatable_getAnimations(Int32(bitPattern: self.this.id), Int32(bitPattern: options.id)) + if let error = _swift_js_take_exception() { + throw error + } return JSObject(id: UInt32(bitPattern: ret)) } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift index c5d23884..c47f5f40 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func check(_ a: Double, _ b: Bool) -> Void { +func check(_ a: Double, _ b: Bool) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_check") func bjs_check(_ a: Float64, _ b: Int32) -> Void @@ -16,4 +16,7 @@ func check(_ a: Double, _ b: Bool) -> Void { } #endif bjs_check(a, Int32(b ? 1 : 0)) + if let error = _swift_js_take_exception() { + throw error + } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift index df1a469a..cf26a52f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func checkNumber() -> Double { +func checkNumber() throws(JSException) -> Double { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_checkNumber") func bjs_checkNumber() -> Float64 @@ -16,10 +16,13 @@ func checkNumber() -> Double { } #endif let ret = bjs_checkNumber() + if let error = _swift_js_take_exception() { + throw error + } return Double(ret) } -func checkBoolean() -> Bool { +func checkBoolean() throws(JSException) -> Bool { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_checkBoolean") func bjs_checkBoolean() -> Int32 @@ -29,5 +32,8 @@ func checkBoolean() -> Bool { } #endif let ret = bjs_checkBoolean() + if let error = _swift_js_take_exception() { + throw error + } return ret == 1 } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift index 8cac3ce6..aabffacc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func checkString(_ a: String) -> Void { +func checkString(_ a: String) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_checkString") func bjs_checkString(_ a: Int32) -> Void @@ -20,9 +20,12 @@ func checkString(_ a: String) -> Void { _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } bjs_checkString(aId) + if let error = _swift_js_take_exception() { + throw error + } } -func checkStringWithLength(_ a: String, _ b: Double) -> Void { +func checkStringWithLength(_ a: String, _ b: Double) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_checkStringWithLength") func bjs_checkStringWithLength(_ a: Int32, _ b: Float64) -> Void @@ -36,4 +39,7 @@ func checkStringWithLength(_ a: String, _ b: Double) -> Void { _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } bjs_checkStringWithLength(aId, b) + if let error = _swift_js_take_exception() { + throw error + } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift index 282476b0..af7b5162 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func checkString() -> String { +func checkString() throws(JSException) -> String { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_checkString") func bjs_checkString() -> Int32 @@ -16,6 +16,9 @@ func checkString() -> String { } #endif let ret = bjs_checkString() + if let error = _swift_js_take_exception() { + throw error + } return String(unsafeUninitializedCapacity: Int(ret)) { b in _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) return Int(ret) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift index 7eeefcc7..7523101f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func checkSimple(_ a: Double) -> Void { +func checkSimple(_ a: Double) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_checkSimple") func bjs_checkSimple(_ a: Float64) -> Void @@ -16,4 +16,7 @@ func checkSimple(_ a: Double) -> Void { } #endif bjs_checkSimple(a) + if let error = _swift_js_take_exception() { + throw error + } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift index 39b6a85c..7a1f2a2c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift @@ -17,7 +17,7 @@ struct Greeter { self.this = JSObject(id: UInt32(bitPattern: this)) } - init(_ name: String) { + init(_ name: String) throws(JSException) { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_Greeter_init") func bjs_Greeter_init(_ name: Int32) -> Int32 @@ -31,11 +31,14 @@ struct Greeter { _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } let ret = bjs_Greeter_init(nameId) + if let error = _swift_js_take_exception() { + throw error + } self.this = JSObject(id: UInt32(bitPattern: ret)) } var name: String { - get { + get throws(JSException) { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_Greeter_name_get") func bjs_Greeter_name_get(_ self: Int32) -> Int32 @@ -45,30 +48,37 @@ struct Greeter { } #endif let ret = bjs_Greeter_name_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } return String(unsafeUninitializedCapacity: Int(ret)) { b in _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) return Int(ret) } } - nonmutating set { - #if arch(wasm32) - @_extern(wasm, module: "Check", name: "bjs_Greeter_name_set") - func bjs_Greeter_name_set(_ self: Int32, _ newValue: Int32) -> Void - #else - func bjs_Greeter_name_set(_ self: Int32, _ newValue: Int32) -> Void { - fatalError("Only available on WebAssembly") - } - #endif - var newValue = newValue - let newValueId = newValue.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_Greeter_name_set(Int32(bitPattern: self.this.id), newValueId) + } + + func setName(_ newValue: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_Greeter_name_set") + func bjs_Greeter_name_set(_ self: Int32, _ newValue: Int32) -> Void + #else + func bjs_Greeter_name_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + var newValue = newValue + let newValueId = newValue.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + bjs_Greeter_name_set(Int32(bitPattern: self.this.id), newValueId) + if let error = _swift_js_take_exception() { + throw error } } var age: Double { - get { + get throws(JSException) { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_Greeter_age_get") func bjs_Greeter_age_get(_ self: Int32) -> Float64 @@ -78,11 +88,14 @@ struct Greeter { } #endif let ret = bjs_Greeter_age_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } return Double(ret) } } - func greet() -> String { + func greet() throws(JSException) -> String { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_Greeter_greet") func bjs_Greeter_greet(_ self: Int32) -> Int32 @@ -92,13 +105,16 @@ struct Greeter { } #endif let ret = bjs_Greeter_greet(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } return String(unsafeUninitializedCapacity: Int(ret)) { b in _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) return Int(ret) } } - func changeName(_ name: String) -> Void { + func changeName(_ name: String) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_Greeter_changeName") func bjs_Greeter_changeName(_ self: Int32, _ name: Int32) -> Void @@ -112,6 +128,9 @@ struct Greeter { _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } bjs_Greeter_changeName(Int32(bitPattern: self.this.id), nameId) + if let error = _swift_js_take_exception() { + throw error + } } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/VoidParameterVoidReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/VoidParameterVoidReturn.swift index eccd1728..ae7ae0e8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/VoidParameterVoidReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/VoidParameterVoidReturn.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func check() -> Void { +func check() throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Check", name: "bjs_check") func bjs_check() -> Void @@ -16,4 +16,7 @@ func check() -> Void { } #endif bjs_check() + if let error = _swift_js_take_exception() { + throw error + } } \ No newline at end of file diff --git a/Plugins/PackageToJS/Templates/runtime.mjs b/Plugins/PackageToJS/Templates/runtime.mjs index 66a2e0ad..447f2090 100644 --- a/Plugins/PackageToJS/Templates/runtime.mjs +++ b/Plugins/PackageToJS/Templates/runtime.mjs @@ -242,11 +242,11 @@ else if (typeof self !== "undefined") { class JSObjectSpace { constructor() { this._heapValueById = new Map(); - this._heapValueById.set(0, globalVariable); + this._heapValueById.set(1, globalVariable); this._heapEntryByValue = new Map(); - this._heapEntryByValue.set(globalVariable, { id: 0, rc: 1 }); - // Note: 0 is preserved for global - this._heapNextKey = 1; + this._heapEntryByValue.set(globalVariable, { id: 1, rc: 1 }); + // Note: 0 is preserved for invalid references, 1 is preserved for globalThis + this._heapNextKey = 2; } retain(value) { const entry = this._heapEntryByValue.get(value); diff --git a/Runtime/src/object-heap.ts b/Runtime/src/object-heap.ts index ecc2d218..b90fc4ef 100644 --- a/Runtime/src/object-heap.ts +++ b/Runtime/src/object-heap.ts @@ -12,13 +12,13 @@ export class JSObjectSpace { constructor() { this._heapValueById = new Map(); - this._heapValueById.set(0, globalVariable); + this._heapValueById.set(1, globalVariable); this._heapEntryByValue = new Map(); - this._heapEntryByValue.set(globalVariable, { id: 0, rc: 1 }); + this._heapEntryByValue.set(globalVariable, { id: 1, rc: 1 }); - // Note: 0 is preserved for global - this._heapNextKey = 1; + // Note: 0 is preserved for invalid references, 1 is preserved for globalThis + this._heapNextKey = 2; } retain(value: any) { diff --git a/Sources/JavaScriptKit/BridgeJSInstrincics.swift b/Sources/JavaScriptKit/BridgeJSInstrincics.swift index e2c54026..1a8bad7f 100644 --- a/Sources/JavaScriptKit/BridgeJSInstrincics.swift +++ b/Sources/JavaScriptKit/BridgeJSInstrincics.swift @@ -1,5 +1,7 @@ +import _CJavaScriptKit + #if !arch(wasm32) -private func _onlyAvailableOnWasm() -> Never { +@usableFromInline func _onlyAvailableOnWasm() -> Never { fatalError("Only available on WebAssembly") } #endif @@ -57,3 +59,16 @@ private func _onlyAvailableOnWasm() -> Never { _onlyAvailableOnWasm() } #endif + +@_spi(BridgeJS) @_transparent public func _swift_js_take_exception() -> JSException? { + #if arch(wasm32) + let value = _swift_js_exception_get() + if _fastPath(value == 0) { + return nil + } + _swift_js_exception_set(0) + return JSException(JSObject(id: UInt32(bitPattern: value)).jsValue) + #else + _onlyAvailableOnWasm() + #endif +} diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 8892e987..b9330ae8 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -218,7 +218,7 @@ public class JSObject: Equatable, ExpressibleByDictionaryLiteral { return swjs_instanceof(id, constructor.id) } - static let _JS_Predef_Value_Global: JavaScriptObjectRef = 0 + static let _JS_Predef_Value_Global: JavaScriptObjectRef = 1 /// A `JSObject` of the global scope object. /// This allows access to the global properties and global names by accessing the `JSObject` returned. diff --git a/Sources/JavaScriptKit/JSException.swift b/Sources/JavaScriptKit/JSException.swift index 714b83aa..4d95e207 100644 --- a/Sources/JavaScriptKit/JSException.swift +++ b/Sources/JavaScriptKit/JSException.swift @@ -34,6 +34,7 @@ public struct JSException: Error, Equatable, CustomStringConvertible { /// Initializes a new JSException instance with a value thrown from JavaScript. /// /// Only available within the package. This must be called on the thread where the exception object created. + @usableFromInline package init(_ thrownValue: JSValue) { self._thrownValue = thrownValue // Capture the stringified representation on the object owner thread diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index a3288180..9df18b88 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -1,4 +1,6 @@ +#include #include "_CJavaScriptKit.h" +#include "WasmGlobalMacros.h" #if __wasm32__ # ifndef __wasi__ # if __has_include("malloc.h") @@ -60,6 +62,9 @@ int swjs_library_features(void) { return _library_features(); } # endif + +WASM_GLOBAL_DEFINE_STORAGE(_swift_js_exception, i32) +WASM_EXPORT_NAME(_swift_js_exception, _swift_js_exception) #endif int swjs_get_worker_thread_id_cached(void) { diff --git a/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h b/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h new file mode 100644 index 00000000..4f5ba93c --- /dev/null +++ b/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h @@ -0,0 +1,11 @@ +#ifndef _CJavaScriptKit_BridgeJSInstrincics_h +#define _CJavaScriptKit_BridgeJSInstrincics_h + +#include +#include "WasmGlobalMacros.h" + +#if __wasm__ +WASM_GLOBAL_DEFINE_INLINE_ACCESSORS(_swift_js_exception, i32, int32_t) +#endif + +#endif diff --git a/Sources/_CJavaScriptKit/include/WasmGlobalMacros.h b/Sources/_CJavaScriptKit/include/WasmGlobalMacros.h new file mode 100644 index 00000000..5e7aeb97 --- /dev/null +++ b/Sources/_CJavaScriptKit/include/WasmGlobalMacros.h @@ -0,0 +1,45 @@ +#ifndef _CJavaScriptKit_WasmGlobalMacros_h +#define _CJavaScriptKit_WasmGlobalMacros_h + +// Define an inline getter for a Wasm global +#define WASM_GLOBAL_DEFINE_INLINE_GETTER(name, core_type, c_type) \ + static inline c_type name##_get(void) { \ + c_type val; \ + __asm__( \ + ".globaltype " #name ", " #core_type "\n" \ + "global.get " #name "\n" \ + "local.set %0\n" \ + : "=r"(val)); \ + return val; \ + } + +// Define an inline setter for a Wasm global +#define WASM_GLOBAL_DEFINE_INLINE_SETTER(name, core_type, c_type) \ + static inline void name##_set(c_type val) { \ + __asm__( \ + ".globaltype " #name ", " #core_type "\n" \ + "local.get %0\n" \ + "global.set " #name "\n" \ + : : "r"(val)); \ + } + +// Define an inline getter and setter for a Wasm global +#define WASM_GLOBAL_DEFINE_INLINE_ACCESSORS(name, core_type, c_type) \ + WASM_GLOBAL_DEFINE_INLINE_GETTER(name, core_type, c_type) \ + WASM_GLOBAL_DEFINE_INLINE_SETTER(name, core_type, c_type) + +// Define a Wasm global storage +#define WASM_GLOBAL_DEFINE_STORAGE(name, core_type) \ + __asm__( \ + ".globaltype " #name ", " #core_type "\n" \ + ".global " #name "\n" \ + #name ":\n" \ + ); + +// Define an export name for a Wasm value +#define WASM_EXPORT_NAME(name, export_name) \ + __asm__( \ + ".export_name " #name ", " #export_name "\n" \ + ); + +#endif diff --git a/Sources/_CJavaScriptKit/include/module.modulemap b/Sources/_CJavaScriptKit/include/module.modulemap index 8f4b6926..3afe1283 100644 --- a/Sources/_CJavaScriptKit/include/module.modulemap +++ b/Sources/_CJavaScriptKit/include/module.modulemap @@ -1,4 +1,5 @@ module _CJavaScriptKit { header "_CJavaScriptKit.h" + header "BridgeJSInstrincics.h" export * } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 6c11869f..2a91da9f 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -129,7 +129,7 @@ public func _bjs_throwsWithIntResult() -> Int32 { #if arch(wasm32) do { let ret = try throwsWithIntResult() - return Int32(ret) + return Int32(ret) } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -154,7 +154,7 @@ public func _bjs_throwsWithStringResult() -> Void { #if arch(wasm32) do { var ret = try throwsWithStringResult() - return ret.withUTF8 { ptr in + return ret.withUTF8 { ptr in _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) } } catch let error { @@ -181,7 +181,7 @@ public func _bjs_throwsWithBoolResult() -> Int32 { #if arch(wasm32) do { let ret = try throwsWithBoolResult() - return Int32(ret ? 1 : 0) + return Int32(ret ? 1 : 0) } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -206,7 +206,7 @@ public func _bjs_throwsWithFloatResult() -> Float32 { #if arch(wasm32) do { let ret = try throwsWithFloatResult() - return Float32(ret) + return Float32(ret) } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -231,7 +231,7 @@ public func _bjs_throwsWithDoubleResult() -> Float64 { #if arch(wasm32) do { let ret = try throwsWithDoubleResult() - return Float64(ret) + return Float64(ret) } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -256,7 +256,7 @@ public func _bjs_throwsWithSwiftHeapObjectResult() -> UnsafeMutableRawPointer { #if arch(wasm32) do { let ret = try throwsWithSwiftHeapObjectResult() - return Unmanaged.passRetained(ret).toOpaque() + return Unmanaged.passRetained(ret).toOpaque() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -281,7 +281,7 @@ public func _bjs_throwsWithJSObjectResult() -> Int32 { #if arch(wasm32) do { let ret = try throwsWithJSObjectResult() - return _swift_js_retain(Int32(bitPattern: ret.id)) + return _swift_js_retain(Int32(bitPattern: ret.id)) } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift index 95150e0e..fd558ab8 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -func jsRoundTripVoid() -> Void { +func jsRoundTripVoid() throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripVoid") func bjs_jsRoundTripVoid() -> Void @@ -16,9 +16,12 @@ func jsRoundTripVoid() -> Void { } #endif bjs_jsRoundTripVoid() + if let error = _swift_js_take_exception() { + throw error + } } -func jsRoundTripNumber(_ v: Double) -> Double { +func jsRoundTripNumber(_ v: Double) throws(JSException) -> Double { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripNumber") func bjs_jsRoundTripNumber(_ v: Float64) -> Float64 @@ -28,10 +31,13 @@ func jsRoundTripNumber(_ v: Double) -> Double { } #endif let ret = bjs_jsRoundTripNumber(v) + if let error = _swift_js_take_exception() { + throw error + } return Double(ret) } -func jsRoundTripBool(_ v: Bool) -> Bool { +func jsRoundTripBool(_ v: Bool) throws(JSException) -> Bool { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripBool") func bjs_jsRoundTripBool(_ v: Int32) -> Int32 @@ -41,10 +47,13 @@ func jsRoundTripBool(_ v: Bool) -> Bool { } #endif let ret = bjs_jsRoundTripBool(Int32(v ? 1 : 0)) + if let error = _swift_js_take_exception() { + throw error + } return ret == 1 } -func jsRoundTripString(_ v: String) -> String { +func jsRoundTripString(_ v: String) throws(JSException) -> String { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripString") func bjs_jsRoundTripString(_ v: Int32) -> Int32 @@ -58,6 +67,75 @@ func jsRoundTripString(_ v: String) -> String { _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } let ret = bjs_jsRoundTripString(vId) + if let error = _swift_js_take_exception() { + throw error + } + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } +} + +func jsThrowOrVoid(_ shouldThrow: Bool) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsThrowOrVoid") + func bjs_jsThrowOrVoid(_ shouldThrow: Int32) -> Void + #else + func bjs_jsThrowOrVoid(_ shouldThrow: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_jsThrowOrVoid(Int32(shouldThrow ? 1 : 0)) + if let error = _swift_js_take_exception() { + throw error + } +} + +func jsThrowOrNumber(_ shouldThrow: Bool) throws(JSException) -> Double { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsThrowOrNumber") + func bjs_jsThrowOrNumber(_ shouldThrow: Int32) -> Float64 + #else + func bjs_jsThrowOrNumber(_ shouldThrow: Int32) -> Float64 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_jsThrowOrNumber(Int32(shouldThrow ? 1 : 0)) + if let error = _swift_js_take_exception() { + throw error + } + return Double(ret) +} + +func jsThrowOrBool(_ shouldThrow: Bool) throws(JSException) -> Bool { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsThrowOrBool") + func bjs_jsThrowOrBool(_ shouldThrow: Int32) -> Int32 + #else + func bjs_jsThrowOrBool(_ shouldThrow: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_jsThrowOrBool(Int32(shouldThrow ? 1 : 0)) + if let error = _swift_js_take_exception() { + throw error + } + return ret == 1 +} + +func jsThrowOrString(_ shouldThrow: Bool) throws(JSException) -> String { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsThrowOrString") + func bjs_jsThrowOrString(_ shouldThrow: Int32) -> Int32 + #else + func bjs_jsThrowOrString(_ shouldThrow: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_jsThrowOrString(Int32(shouldThrow ? 1 : 0)) + if let error = _swift_js_take_exception() { + throw error + } return String(unsafeUninitializedCapacity: Int(ret)) { b in _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) return Int(ret) @@ -75,7 +153,7 @@ struct JsGreeter { self.this = JSObject(id: UInt32(bitPattern: this)) } - init(_ name: String, _ prefix: String) { + init(_ name: String, _ prefix: String) throws(JSException) { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_init") func bjs_JsGreeter_init(_ name: Int32, _ prefix: Int32) -> Int32 @@ -93,11 +171,14 @@ struct JsGreeter { _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } let ret = bjs_JsGreeter_init(nameId, prefixId) + if let error = _swift_js_take_exception() { + throw error + } self.this = JSObject(id: UInt32(bitPattern: ret)) } var name: String { - get { + get throws(JSException) { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_name_get") func bjs_JsGreeter_name_get(_ self: Int32) -> Int32 @@ -107,30 +188,37 @@ struct JsGreeter { } #endif let ret = bjs_JsGreeter_name_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } return String(unsafeUninitializedCapacity: Int(ret)) { b in _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) return Int(ret) } } - nonmutating set { - #if arch(wasm32) - @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_name_set") - func bjs_JsGreeter_name_set(_ self: Int32, _ newValue: Int32) -> Void - #else - func bjs_JsGreeter_name_set(_ self: Int32, _ newValue: Int32) -> Void { - fatalError("Only available on WebAssembly") - } - #endif - var newValue = newValue - let newValueId = newValue.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_JsGreeter_name_set(Int32(bitPattern: self.this.id), newValueId) + } + + func setName(_ newValue: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_name_set") + func bjs_JsGreeter_name_set(_ self: Int32, _ newValue: Int32) -> Void + #else + func bjs_JsGreeter_name_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + var newValue = newValue + let newValueId = newValue.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + bjs_JsGreeter_name_set(Int32(bitPattern: self.this.id), newValueId) + if let error = _swift_js_take_exception() { + throw error } } var prefix: String { - get { + get throws(JSException) { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_prefix_get") func bjs_JsGreeter_prefix_get(_ self: Int32) -> Int32 @@ -140,6 +228,9 @@ struct JsGreeter { } #endif let ret = bjs_JsGreeter_prefix_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } return String(unsafeUninitializedCapacity: Int(ret)) { b in _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) return Int(ret) @@ -147,7 +238,7 @@ struct JsGreeter { } } - func greet() -> String { + func greet() throws(JSException) -> String { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_greet") func bjs_JsGreeter_greet(_ self: Int32) -> Int32 @@ -157,13 +248,16 @@ struct JsGreeter { } #endif let ret = bjs_JsGreeter_greet(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } return String(unsafeUninitializedCapacity: Int(ret)) { b in _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) return Int(ret) } } - func changeName(_ name: String) -> Void { + func changeName(_ name: String) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_changeName") func bjs_JsGreeter_changeName(_ self: Int32, _ name: Int32) -> Void @@ -177,6 +271,9 @@ struct JsGreeter { _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) } bjs_JsGreeter_changeName(Int32(bitPattern: self.this.id), nameId) + if let error = _swift_js_take_exception() { + throw error + } } } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json index ad8fcd87..bf3190b8 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json @@ -64,6 +64,78 @@ "returnType" : { "string" : { + } + } + }, + { + "name" : "jsThrowOrVoid", + "parameters" : [ + { + "name" : "shouldThrow", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "name" : "jsThrowOrNumber", + "parameters" : [ + { + "name" : "shouldThrow", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "name" : "jsThrowOrBool", + "parameters" : [ + { + "name" : "shouldThrow", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "name" : "jsThrowOrString", + "parameters" : [ + { + "name" : "shouldThrow", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "string" : { + } } } diff --git a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift index a8d586bf..f0112ed1 100644 --- a/Tests/BridgeJSRuntimeTests/ImportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ImportAPITests.swift @@ -2,11 +2,11 @@ import XCTest import JavaScriptKit class ImportAPITests: XCTestCase { - func testRoundTripVoid() { - jsRoundTripVoid() + func testRoundTripVoid() throws { + try jsRoundTripVoid() } - func testRoundTripNumber() { + func testRoundTripNumber() throws { for v in [ 0, 1, -1, Double(Int32.max), Double(Int32.min), @@ -17,34 +17,71 @@ class ImportAPITests: XCTestCase { Double.infinity, Double.pi, ] { - XCTAssertEqual(jsRoundTripNumber(v), v) + try XCTAssertEqual(jsRoundTripNumber(v), v) } - XCTAssert(jsRoundTripNumber(Double.nan).isNaN) + try XCTAssert(jsRoundTripNumber(Double.nan).isNaN) } - func testRoundTripBool() { + func testRoundTripBool() throws { for v in [true, false] { - XCTAssertEqual(jsRoundTripBool(v), v) + try XCTAssertEqual(jsRoundTripBool(v), v) } } - func testRoundTripString() { + func testRoundTripString() throws { for v in ["", "Hello, world!", "🧑‍🧑‍🧒"] { - XCTAssertEqual(jsRoundTripString(v), v) + try XCTAssertEqual(jsRoundTripString(v), v) } } - func testClass() { - let greeter = JsGreeter("Alice", "Hello") - XCTAssertEqual(greeter.greet(), "Hello, Alice!") - greeter.changeName("Bob") - XCTAssertEqual(greeter.greet(), "Hello, Bob!") + func ensureThrows(_ f: (Bool) throws(JSException) -> T) throws { + do { + _ = try f(true) + XCTFail("Expected exception") + } catch { + XCTAssertTrue(error.description.contains("TestError")) + } + } + func ensureDoesNotThrow(_ f: (Bool) throws(JSException) -> T, _ assertValue: (T) -> Void) throws { + do { + let result = try f(false) + assertValue(result) + } catch { + XCTFail("Expected no exception") + } + } + + func testThrowOrVoid() throws { + try ensureThrows(jsThrowOrVoid) + try ensureDoesNotThrow(jsThrowOrVoid, { _ in }) + } + + func testThrowOrNumber() throws { + try ensureThrows(jsThrowOrNumber) + try ensureDoesNotThrow(jsThrowOrNumber, { XCTAssertEqual($0, 1) }) + } + + func testThrowOrBool() throws { + try ensureThrows(jsThrowOrBool) + try ensureDoesNotThrow(jsThrowOrBool, { XCTAssertEqual($0, true) }) + } + + func testThrowOrString() throws { + try ensureThrows(jsThrowOrString) + try ensureDoesNotThrow(jsThrowOrString, { XCTAssertEqual($0, "Hello, world!") }) + } + + func testClass() throws { + let greeter = try JsGreeter("Alice", "Hello") + XCTAssertEqual(try greeter.greet(), "Hello, Alice!") + try greeter.changeName("Bob") + XCTAssertEqual(try greeter.greet(), "Hello, Bob!") - greeter.name = "Charlie" - XCTAssertEqual(greeter.greet(), "Hello, Charlie!") - XCTAssertEqual(greeter.name, "Charlie") + try greeter.setName("Charlie") + XCTAssertEqual(try greeter.greet(), "Hello, Charlie!") + XCTAssertEqual(try greeter.name, "Charlie") - XCTAssertEqual(greeter.prefix, "Hello") + XCTAssertEqual(try greeter.prefix, "Hello") } } diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts index 664dd447..b03ef570 100644 --- a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts +++ b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts @@ -2,6 +2,10 @@ export function jsRoundTripVoid(): void export function jsRoundTripNumber(v: number): number export function jsRoundTripBool(v: boolean): boolean export function jsRoundTripString(v: string): string +export function jsThrowOrVoid(shouldThrow: boolean): void +export function jsThrowOrNumber(shouldThrow: boolean): number +export function jsThrowOrBool(shouldThrow: boolean): boolean +export function jsThrowOrString(shouldThrow: boolean): string export class JsGreeter { name: string; @@ -9,4 +13,4 @@ export class JsGreeter { constructor(name: string, prefix: string); greet(): string; changeName(name: string): void; -} \ No newline at end of file +} diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 0d49045b..c6ac428a 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -19,6 +19,29 @@ export async function setupOptions(options, context) { "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