Skip to content

Commit 3396b5e

Browse files
[codex] BridgeJS: support associated-value enums in import and async paths (#764)
* BridgeJS: support associated-value enums in import and async paths * Format Swift sources * Match Swift 6.1 formatter output
1 parent 6f4009c commit 3396b5e

21 files changed

Lines changed: 2196 additions & 52 deletions

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -936,12 +936,7 @@ extension BridgeType {
936936
let wasmType = rawType.wasmCoreType ?? .i32
937937
return LoweringParameterInfo(loweredParameters: [("value", wasmType)])
938938
case .associatedValueEnum:
939-
switch context {
940-
case .importTS:
941-
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
942-
case .exportSwift:
943-
return LoweringParameterInfo(loweredParameters: [("caseId", .i32)])
944-
}
939+
return LoweringParameterInfo(loweredParameters: [("caseId", .i32)])
945940
case .swiftStruct:
946941
switch context {
947942
case .importTS:
@@ -1011,12 +1006,7 @@ extension BridgeType {
10111006
let wasmType = rawType.wasmCoreType ?? .i32
10121007
return LiftingReturnInfo(valueToLift: wasmType)
10131008
case .associatedValueEnum:
1014-
switch context {
1015-
case .importTS:
1016-
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
1017-
case .exportSwift:
1018-
return LiftingReturnInfo(valueToLift: .i32)
1019-
}
1009+
return LiftingReturnInfo(valueToLift: .i32)
10201010
case .swiftStruct:
10211011
switch context {
10221012
case .importTS:

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,27 +1422,19 @@ struct IntrinsicJSFragment: Sendable {
14221422
return try .optionalLiftParameter(wrappedType: wrappedType, kind: kind, context: context)
14231423
case .rawValueEnum(_, .string): return .stringLiftParameter
14241424
case .associatedValueEnum(let fullName):
1425-
switch context {
1426-
case .importTS:
1427-
throw BridgeJSLinkError(
1428-
message:
1429-
"Associated value enums are not supported to be passed as parameters to imported JS functions: \(fullName)"
1430-
)
1431-
case .exportSwift:
1432-
let base = fullName.components(separatedBy: ".").last ?? fullName
1433-
return IntrinsicJSFragment(
1434-
parameters: ["caseId"],
1435-
printCode: { arguments, context in
1436-
let (scope, printer) = (context.scope, context.printer)
1437-
let caseId = arguments[0]
1438-
let resultVar = scope.variable("enumValue")
1439-
printer.write(
1440-
"const \(resultVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(caseId));"
1441-
)
1442-
return [resultVar]
1443-
}
1444-
)
1445-
}
1425+
let base = fullName.components(separatedBy: ".").last ?? fullName
1426+
return IntrinsicJSFragment(
1427+
parameters: ["caseId"],
1428+
printCode: { arguments, context in
1429+
let (scope, printer) = (context.scope, context.printer)
1430+
let caseId = arguments[0]
1431+
let resultVar = scope.variable("enumValue")
1432+
printer.write(
1433+
"const \(resultVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(caseId));"
1434+
)
1435+
return [resultVar]
1436+
}
1437+
)
14461438
case .swiftStruct(let fullName):
14471439
switch context {
14481440
case .importTS:
@@ -1502,15 +1494,7 @@ struct IntrinsicJSFragment: Sendable {
15021494
return try .optionalLowerReturn(wrappedType: wrappedType, kind: kind)
15031495
case .rawValueEnum(_, .string): return .stringLowerReturn
15041496
case .associatedValueEnum(let fullName):
1505-
switch context {
1506-
case .importTS:
1507-
throw BridgeJSLinkError(
1508-
message:
1509-
"Associated value enums are not supported to be returned from imported JS functions: \(fullName)"
1510-
)
1511-
case .exportSwift:
1512-
return associatedValueLowerReturn(fullName: fullName)
1513-
}
1497+
return associatedValueLowerReturn(fullName: fullName)
15141498
case .swiftStruct(let fullName):
15151499
switch context {
15161500
case .importTS:

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,10 +1611,10 @@ extension BridgeType {
16111611
/// Whether a value of this type can be passed to a generated `Promise_resolve_<mangled>`
16121612
/// settlement helper, i.e. lowered through the imported-parameter ABI. Every `async`
16131613
/// exported return settles through `_bjs_makePromise`; the few types that cannot be lowered
1614-
/// (associated-value enums, protocols, namespace enums, and their compositions) are diagnosed.
1614+
/// (protocols, namespace enums, and their compositions) are diagnosed.
16151615
public var isAsyncResolvable: Bool {
16161616
switch self {
1617-
case .associatedValueEnum, .swiftProtocol, .namespaceEnum:
1617+
case .swiftProtocol, .namespaceEnum:
16181618
return false
16191619
case .nullable(let wrapped, _):
16201620
return wrapped.isAsyncResolvable

Plugins/BridgeJS/Tests/BridgeJSToolTests/DiagnosticsTests.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -309,15 +309,14 @@ import Testing
309309

310310
@Test
311311
func asyncReturnOfUnsupportedTypeIsDiagnosed() throws {
312-
// An associated-value enum can be neither lowered through the imported-parameter ABI
313-
// nor settled via `_bjs_makePromise`, so an async return of one must be diagnosed.
312+
// Protocol existentials still can't be lowered through the imported-parameter ABI, so
313+
// an async return of one must still be diagnosed.
314314
let source = """
315-
@JS enum Payload {
316-
case text(String)
317-
case number(Int)
315+
@JS protocol PayloadDelegate {
316+
func notify()
318317
}
319-
@JS func loadPayload() async -> Payload {
320-
.number(1)
318+
@JS func loadPayload() async -> PayloadDelegate {
319+
fatalError()
321320
}
322321
"""
323322
let swiftAPI = SwiftToSkeleton(
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@JS enum AsyncPayloadResult {
2+
case success(String)
3+
case failure(Int)
4+
case idle
5+
}
6+
7+
@JS func asyncRoundTripAssociatedValueEnum(_ value: AsyncPayloadResult) async -> AsyncPayloadResult {
8+
return value
9+
}
10+
11+
@JS func asyncRoundTripOptionalAssociatedValueEnum(_ value: AsyncPayloadResult?) async -> AsyncPayloadResult? {
12+
return value
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@JS enum PayloadSignal {
2+
case start(String)
3+
case stop(Int)
4+
case idle
5+
}
6+
7+
// Associated-value enums bridge as their `Int32` case ID plus stack payload in imported
8+
// function parameters and return values.
9+
@JSClass struct PayloadSignalControls {
10+
@JSFunction func send(_ signal: PayloadSignal) throws(JSException)
11+
@JSFunction func current() throws(JSException) -> PayloadSignal
12+
@JSFunction static func roundTrip(_ signal: PayloadSignal) throws(JSException) -> PayloadSignal
13+
@JSFunction func roundTripOptional(_ signal: PayloadSignal?) throws(JSException) -> PayloadSignal?
14+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
{
2+
"exported" : {
3+
"classes" : [
4+
5+
],
6+
"enums" : [
7+
{
8+
"cases" : [
9+
{
10+
"associatedValues" : [
11+
{
12+
"type" : {
13+
"string" : {
14+
15+
}
16+
}
17+
}
18+
],
19+
"name" : "success"
20+
},
21+
{
22+
"associatedValues" : [
23+
{
24+
"type" : {
25+
"integer" : {
26+
"_0" : {
27+
"isSigned" : true,
28+
"width" : "word"
29+
}
30+
}
31+
}
32+
}
33+
],
34+
"name" : "failure"
35+
},
36+
{
37+
"associatedValues" : [
38+
39+
],
40+
"name" : "idle"
41+
}
42+
],
43+
"emitStyle" : "const",
44+
"name" : "AsyncPayloadResult",
45+
"staticMethods" : [
46+
47+
],
48+
"staticProperties" : [
49+
50+
],
51+
"swiftCallName" : "AsyncPayloadResult",
52+
"tsFullPath" : "AsyncPayloadResult"
53+
}
54+
],
55+
"exposeToGlobal" : false,
56+
"functions" : [
57+
{
58+
"abiName" : "bjs_asyncRoundTripAssociatedValueEnum",
59+
"effects" : {
60+
"isAsync" : true,
61+
"isStatic" : false,
62+
"isThrows" : false
63+
},
64+
"name" : "asyncRoundTripAssociatedValueEnum",
65+
"parameters" : [
66+
{
67+
"label" : "_",
68+
"name" : "value",
69+
"type" : {
70+
"associatedValueEnum" : {
71+
"_0" : "AsyncPayloadResult"
72+
}
73+
}
74+
}
75+
],
76+
"returnType" : {
77+
"associatedValueEnum" : {
78+
"_0" : "AsyncPayloadResult"
79+
}
80+
}
81+
},
82+
{
83+
"abiName" : "bjs_asyncRoundTripOptionalAssociatedValueEnum",
84+
"effects" : {
85+
"isAsync" : true,
86+
"isStatic" : false,
87+
"isThrows" : false
88+
},
89+
"name" : "asyncRoundTripOptionalAssociatedValueEnum",
90+
"parameters" : [
91+
{
92+
"label" : "_",
93+
"name" : "value",
94+
"type" : {
95+
"nullable" : {
96+
"_0" : {
97+
"associatedValueEnum" : {
98+
"_0" : "AsyncPayloadResult"
99+
}
100+
},
101+
"_1" : "null"
102+
}
103+
}
104+
}
105+
],
106+
"returnType" : {
107+
"nullable" : {
108+
"_0" : {
109+
"associatedValueEnum" : {
110+
"_0" : "AsyncPayloadResult"
111+
}
112+
},
113+
"_1" : "null"
114+
}
115+
}
116+
}
117+
],
118+
"protocols" : [
119+
120+
],
121+
"structs" : [
122+
123+
]
124+
},
125+
"moduleName" : "TestModule",
126+
"usedExternalModules" : [
127+
128+
]
129+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
extension AsyncPayloadResult: _BridgedSwiftAssociatedValueEnum {
2+
@_spi(BridgeJS) @_transparent public static func bridgeJSStackPopPayload(_ caseId: Int32) -> AsyncPayloadResult {
3+
switch caseId {
4+
case 0:
5+
return .success(String.bridgeJSStackPop())
6+
case 1:
7+
return .failure(Int.bridgeJSStackPop())
8+
case 2:
9+
return .idle
10+
default:
11+
fatalError("Unknown AsyncPayloadResult case ID: \(caseId)")
12+
}
13+
}
14+
15+
@_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPushPayload() -> Int32 {
16+
switch self {
17+
case .success(let param0):
18+
param0.bridgeJSStackPush()
19+
return Int32(0)
20+
case .failure(let param0):
21+
param0.bridgeJSStackPush()
22+
return Int32(1)
23+
case .idle:
24+
return Int32(2)
25+
}
26+
}
27+
}
28+
29+
@_expose(wasm, "bjs_asyncRoundTripAssociatedValueEnum")
30+
@_cdecl("bjs_asyncRoundTripAssociatedValueEnum")
31+
public func _bjs_asyncRoundTripAssociatedValueEnum(_ value: Int32) -> Int32 {
32+
#if arch(wasm32)
33+
let _tmp_value = AsyncPayloadResult.bridgeJSLiftParameter(value)
34+
return _bjs_makePromise(resolve: Promise_resolve_18AsyncPayloadResultO, reject: Promise_reject) {
35+
return await asyncRoundTripAssociatedValueEnum(_: _tmp_value)
36+
}
37+
#else
38+
fatalError("Only available on WebAssembly")
39+
#endif
40+
}
41+
42+
@_expose(wasm, "bjs_asyncRoundTripOptionalAssociatedValueEnum")
43+
@_cdecl("bjs_asyncRoundTripOptionalAssociatedValueEnum")
44+
public func _bjs_asyncRoundTripOptionalAssociatedValueEnum(_ valueIsSome: Int32, _ valueCaseId: Int32) -> Int32 {
45+
#if arch(wasm32)
46+
let _tmp_value = Optional<AsyncPayloadResult>.bridgeJSLiftParameter(valueIsSome, valueCaseId)
47+
return _bjs_makePromise(resolve: Promise_resolve_Sq18AsyncPayloadResultO, reject: Promise_reject) {
48+
return await asyncRoundTripOptionalAssociatedValueEnum(_: _tmp_value)
49+
}
50+
#else
51+
fatalError("Only available on WebAssembly")
52+
#endif
53+
}
54+
55+
@JSFunction func Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException)
56+
57+
#if arch(wasm32)
58+
@_extern(wasm, module: "bjs", name: "promise_reject_TestModule")
59+
fileprivate func promise_reject_TestModule_extern(_ promise: Int32, _ valueKind: Int32, _ valuePayload1: Int32, _ valuePayload2: Float64) -> Void
60+
#else
61+
fileprivate func promise_reject_TestModule_extern(_ promise: Int32, _ valueKind: Int32, _ valuePayload1: Int32, _ valuePayload2: Float64) -> Void {
62+
fatalError("Only available on WebAssembly")
63+
}
64+
#endif
65+
@inline(never) fileprivate func promise_reject_TestModule(_ promise: Int32, _ valueKind: Int32, _ valuePayload1: Int32, _ valuePayload2: Float64) -> Void {
66+
return promise_reject_TestModule_extern(promise, valueKind, valuePayload1, valuePayload2)
67+
}
68+
69+
func _$Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) -> Void {
70+
let promiseValue = promise.bridgeJSLowerParameter()
71+
let (valueKind, valuePayload1, valuePayload2) = value.bridgeJSLowerParameter()
72+
promise_reject_TestModule(promiseValue, valueKind, valuePayload1, valuePayload2)
73+
if let error = _swift_js_take_exception() { throw error }
74+
}
75+
76+
@JSFunction func Promise_resolve_18AsyncPayloadResultO(_ promise: JSObject, _ value: AsyncPayloadResult) throws(JSException)
77+
78+
#if arch(wasm32)
79+
@_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_18AsyncPayloadResultO")
80+
fileprivate func promise_resolve_TestModule_18AsyncPayloadResultO_extern(_ promise: Int32, _ value: Int32) -> Void
81+
#else
82+
fileprivate func promise_resolve_TestModule_18AsyncPayloadResultO_extern(_ promise: Int32, _ value: Int32) -> Void {
83+
fatalError("Only available on WebAssembly")
84+
}
85+
#endif
86+
@inline(never) fileprivate func promise_resolve_TestModule_18AsyncPayloadResultO(_ promise: Int32, _ value: Int32) -> Void {
87+
return promise_resolve_TestModule_18AsyncPayloadResultO_extern(promise, value)
88+
}
89+
90+
func _$Promise_resolve_18AsyncPayloadResultO(_ promise: JSObject, _ value: AsyncPayloadResult) throws(JSException) -> Void {
91+
let promiseValue = promise.bridgeJSLowerParameter()
92+
let valueCaseId = value.bridgeJSLowerParameter()
93+
promise_resolve_TestModule_18AsyncPayloadResultO(promiseValue, valueCaseId)
94+
if let error = _swift_js_take_exception() { throw error }
95+
}
96+
97+
@JSFunction func Promise_resolve_Sq18AsyncPayloadResultO(_ promise: JSObject, _ value: Optional<AsyncPayloadResult>) throws(JSException)
98+
99+
#if arch(wasm32)
100+
@_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_Sq18AsyncPayloadResultO")
101+
fileprivate func promise_resolve_TestModule_Sq18AsyncPayloadResultO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueCaseId: Int32) -> Void
102+
#else
103+
fileprivate func promise_resolve_TestModule_Sq18AsyncPayloadResultO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueCaseId: Int32) -> Void {
104+
fatalError("Only available on WebAssembly")
105+
}
106+
#endif
107+
@inline(never) fileprivate func promise_resolve_TestModule_Sq18AsyncPayloadResultO(_ promise: Int32, _ valueIsSome: Int32, _ valueCaseId: Int32) -> Void {
108+
return promise_resolve_TestModule_Sq18AsyncPayloadResultO_extern(promise, valueIsSome, valueCaseId)
109+
}
110+
111+
func _$Promise_resolve_Sq18AsyncPayloadResultO(_ promise: JSObject, _ value: Optional<AsyncPayloadResult>) throws(JSException) -> Void {
112+
let promiseValue = promise.bridgeJSLowerParameter()
113+
let (valueIsSome, valueCaseId) = value.bridgeJSLowerParameter()
114+
promise_resolve_TestModule_Sq18AsyncPayloadResultO(promiseValue, valueIsSome, valueCaseId)
115+
if let error = _swift_js_take_exception() { throw error }
116+
}

0 commit comments

Comments
 (0)