Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions 34 Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -426,21 +426,45 @@ public class ExportSwift {
/// A throwing async body needs an explicit closure type, otherwise Swift infers
/// `throws(any Error)` instead of `throws(JSException)`.
/// See: https://github.com/swiftlang/swift/issues/76165
private func asyncThrowsClosureHead(returnSpelling: String?) -> String {
private func asyncThrowsClosureHead(returnSpelling: String?, forcesCapture: Bool) -> String {
guard effects.isThrows else { return "" }
let returns = returnSpelling.map { " -> \($0)" } ?? ""
return " () async throws(JSException)\(returns) in"
let capture = forcesCapture ? "[__bjs_capture] " : ""
return " \(capture)() async throws(JSException)\(returns) in"
}

/// A captureless throwing async body closure lowers via `thin_to_thick_function`,
/// which miscompiles typed-error calls on wasm32. Forcing a capture that the body
/// reads turns the closure into a partial apply with a context, avoiding the
/// broken convention. An unread capture list entry is dropped by capture analysis,
/// so the body must also read the captured value.
/// See: https://github.com/swiftlang/swift/issues/89320
private var asyncThrowsBodyForcesCapture: Bool {
effects.isThrows && abiParameterSignatures.isEmpty && asyncHoistedBindings.isEmpty
}

func render(abiName: String) -> DeclSyntax {
let body: CodeBlockItemListSyntax
if effects.isAsync, let resolveType = asyncResolveReturnType {
let resolveName = "Promise_resolve_\(resolveType.mangleTypeName)"
let closureHead = asyncThrowsClosureHead(returnSpelling: resolveType.swiftType)
let forcesCapture = asyncThrowsBodyForcesCapture
let closureHead = asyncThrowsClosureHead(
returnSpelling: resolveType.swiftType,
forcesCapture: forcesCapture
)
var hoistedBindings = asyncHoistedBindings
var bodyItems = self.body
if forcesCapture {
hoistedBindings.append("let __bjs_capture = 0")
if !bodyItems.isEmpty {
bodyItems[0] = bodyItems[0].with(\.leadingTrivia, .newline)
}
bodyItems.insert("_ = __bjs_capture", at: 0)
}
body = """
\(CodeBlockItemListSyntax(asyncHoistedBindings))
\(CodeBlockItemListSyntax(hoistedBindings))
return _bjs_makePromise(resolve: \(raw: resolveName), reject: Promise_reject) {\(raw: closureHead)
\(CodeBlockItemListSyntax(self.body))
\(CodeBlockItemListSyntax(bodyItems))
}
"""
} else if effects.isThrows {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
return v
}

@JS func asyncThrowsZeroArg() async throws(JSException) -> String {
return "ok"
}

@JS func asyncCombineStructs(_ a: AsyncPoint, _ b: AsyncPoint) async -> AsyncPoint {
return AsyncPoint(x: a.x + b.x, y: a.y + b.y)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,23 @@
}
}
},
{
"abiName" : "bjs_asyncThrowsZeroArg",
"effects" : {
"isAsync" : true,
"isStatic" : false,
"isThrows" : true
},
"name" : "asyncThrowsZeroArg",
"parameters" : [

],
"returnType" : {
"string" : {

}
}
},
{
"abiName" : "bjs_asyncCombineStructs",
"effects" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,20 @@ public func _bjs_asyncRoundTripStructThrows() -> Int32 {
#endif
}

@_expose(wasm, "bjs_asyncThrowsZeroArg")
@_cdecl("bjs_asyncThrowsZeroArg")
public func _bjs_asyncThrowsZeroArg() -> Int32 {
#if arch(wasm32)
let __bjs_capture = 0
return _bjs_makePromise(resolve: Promise_resolve_SS, reject: Promise_reject) { [__bjs_capture] () async throws(JSException) -> String in
_ = __bjs_capture
return try await asyncThrowsZeroArg()
}
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_asyncCombineStructs")
@_cdecl("bjs_asyncCombineStructs")
public func _bjs_asyncCombineStructs() -> Int32 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type Exports = {
asyncRoundTripJSObject(v: any): Promise<any>;
asyncRoundTripStruct(v: AsyncPoint): Promise<AsyncPoint>;
asyncRoundTripStructThrows(v: AsyncPoint): Promise<AsyncPoint>;
asyncThrowsZeroArg(): Promise<string>;
asyncCombineStructs(a: AsyncPoint, b: AsyncPoint): Promise<AsyncPoint>;
asyncRoundTripEnum(v: AsyncDirectionTag): Promise<AsyncDirectionTag>;
asyncRoundTripRawEnum(v: AsyncThemeTag): Promise<AsyncThemeTag>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,18 @@ export async function createInstantiator(options, swift) {
}
return ret1;
},
asyncThrowsZeroArg: function bjs_asyncThrowsZeroArg() {
const ret = instance.exports.bjs_asyncThrowsZeroArg();
const ret1 = swift.memory.getObject(ret);
swift.memory.release(ret);
if (tmpRetException) {
const error = swift.memory.getObject(tmpRetException);
swift.memory.release(tmpRetException);
tmpRetException = undefined;
throw error;
}
return ret1;
},
asyncCombineStructs: function bjs_asyncCombineStructs(a, b) {
structHelpers.AsyncPoint.lower(a);
structHelpers.AsyncPoint.lower(b);
Expand Down
4 changes: 4 additions & 0 deletions 4 Tests/BridgeJSRuntimeTests/ExportAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ struct TestError: Error {
@JS func throwsWithSwiftHeapObjectResult() throws(JSException) -> Greeter { return Greeter(name: "Test") }
@JS func throwsWithJSObjectResult() throws(JSException) -> JSObject { return JSObject() }

@JS func zeroArgAsyncThrows() async throws(JSException) -> String {
throw JSException(JSError(message: "ZeroArgAsyncThrowsError").jsValue)
}

@JS func asyncRoundTripVoid() async -> Void { return }
@JS func asyncRoundTripInt(v: Int) async -> Int { return v }
@JS func asyncRoundTripFloat(v: Float) async -> Float { return v }
Expand Down
58 changes: 36 additions & 22 deletions 58 Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7189,6 +7189,20 @@ public func _bjs_throwsWithJSObjectResult() -> Int32 {
#endif
}

@_expose(wasm, "bjs_zeroArgAsyncThrows")
@_cdecl("bjs_zeroArgAsyncThrows")
public func _bjs_zeroArgAsyncThrows() -> Int32 {
#if arch(wasm32)
let __bjs_capture = 0
return _bjs_makePromise(resolve: Promise_resolve_SS, reject: Promise_reject) { [__bjs_capture] () async throws(JSException) -> String in
_ = __bjs_capture
return try await zeroArgAsyncThrows()
}
#else
fatalError("Only available on WebAssembly")
#endif
}

@_expose(wasm, "bjs_asyncRoundTripVoid")
@_cdecl("bjs_asyncRoundTripVoid")
public func _bjs_asyncRoundTripVoid() -> Int32 {
Expand Down Expand Up @@ -11403,6 +11417,28 @@ func _$Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException)
if let error = _swift_js_take_exception() { throw error }
}

@JSFunction func Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException)

#if arch(wasm32)
@_extern(wasm, module: "bjs", name: "promise_resolve_BridgeJSRuntimeTests_SS")
fileprivate func promise_resolve_BridgeJSRuntimeTests_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void
#else
fileprivate func promise_resolve_BridgeJSRuntimeTests_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void {
fatalError("Only available on WebAssembly")
}
#endif
@inline(never) fileprivate func promise_resolve_BridgeJSRuntimeTests_SS(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void {
return promise_resolve_BridgeJSRuntimeTests_SS_extern(promise, valueBytes, valueLength)
}

func _$Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException) -> Void {
let promiseValue = promise.bridgeJSLowerParameter()
value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in
promise_resolve_BridgeJSRuntimeTests_SS(promiseValue, valueBytes, valueLength)
}
if let error = _swift_js_take_exception() { throw error }
}

@JSFunction func Promise_resolve_y(_ promise: JSObject) throws(JSException)

#if arch(wasm32)
Expand Down Expand Up @@ -11507,28 +11543,6 @@ func _$Promise_resolve_Sb(_ promise: JSObject, _ value: Bool) throws(JSException
if let error = _swift_js_take_exception() { throw error }
}

@JSFunction func Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException)

#if arch(wasm32)
@_extern(wasm, module: "bjs", name: "promise_resolve_BridgeJSRuntimeTests_SS")
fileprivate func promise_resolve_BridgeJSRuntimeTests_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void
#else
fileprivate func promise_resolve_BridgeJSRuntimeTests_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void {
fatalError("Only available on WebAssembly")
}
#endif
@inline(never) fileprivate func promise_resolve_BridgeJSRuntimeTests_SS(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void {
return promise_resolve_BridgeJSRuntimeTests_SS_extern(promise, valueBytes, valueLength)
}

func _$Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException) -> Void {
let promiseValue = promise.bridgeJSLowerParameter()
value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in
promise_resolve_BridgeJSRuntimeTests_SS(promiseValue, valueBytes, valueLength)
}
if let error = _swift_js_take_exception() { throw error }
}

@JSFunction func Promise_resolve_7GreeterC(_ promise: JSObject, _ value: Greeter) throws(JSException)

#if arch(wasm32)
Expand Down
17 changes: 17 additions & 0 deletions 17 Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json
Original file line number Diff line number Diff line change
Expand Up @@ -12171,6 +12171,23 @@
}
}
},
{
"abiName" : "bjs_zeroArgAsyncThrows",
"effects" : {
"isAsync" : true,
"isStatic" : false,
"isThrows" : true
},
"name" : "zeroArgAsyncThrows",
"parameters" : [

],
"returnType" : {
"string" : {

}
}
},
{
"abiName" : "bjs_asyncRoundTripVoid",
"effects" : {
Expand Down
5 changes: 5 additions & 0 deletions 5 Tests/BridgeJSRuntimeTests/JavaScript/AsyncImportTests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ export async function runAsyncWorksTests(exports) {
(error) => error instanceof Error && error.message === "async struct failure"
);

await assert.rejects(
() => exports.zeroArgAsyncThrows(),
(error) => error instanceof Error && error.message === "ZeroArgAsyncThrowsError"
);

const richContact = {
name: "Alice",
age: 30,
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.