Skip to content

Commit 128f696

Browse files
committed
BridgeJS: Protocol support for enum values (all types + optionals)
BridgeJS: Final refinings to enum support and revisiting tests
1 parent d59368a commit 128f696

File tree

15 files changed

+1830
-511
lines changed

15 files changed

+1830
-511
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 100 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1751,13 +1751,13 @@ public class ExportSwift {
17511751
return bridgeJSRawValue
17521752
}
17531753
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> \(raw: typeName) {
1754-
return \(raw: typeName)(bridgeJSRawValue: value)!
1754+
return bridgeJSLiftParameter(value)
17551755
}
17561756
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> \(raw: typeName) {
17571757
return \(raw: typeName)(bridgeJSRawValue: value)!
17581758
}
17591759
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 {
1760-
return bridgeJSRawValue
1760+
return bridgeJSLowerParameter()
17611761
}
17621762
17631763
private init?(bridgeJSRawValue: Int32) {
@@ -1774,13 +1774,32 @@ public class ExportSwift {
17741774
func renderAssociatedValueEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax {
17751775
let typeName = enumDef.swiftCallName
17761776
return """
1777-
extension \(raw: typeName): _BridgedSwiftAssociatedValueEnum {
1778-
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ caseId: Int32) -> \(raw: typeName) {
1777+
extension \(raw: typeName): _BridgedSwiftAssociatedValueEnum {
1778+
private static func _bridgeJSLiftFromCaseId(_ caseId: Int32) -> \(raw: typeName) {
17791779
switch caseId {
17801780
\(raw: generateStackLiftSwitchCases(enumDef: enumDef).joined(separator: "\n"))
17811781
default: fatalError("Unknown \(raw: typeName) case ID: \\(caseId)")
17821782
}
17831783
}
1784+
1785+
// MARK: Protocol Export
1786+
1787+
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 {
1788+
switch self {
1789+
\(raw: generateLowerParameterSwitchCases(enumDef: enumDef).joined(separator: "\n"))
1790+
}
1791+
}
1792+
1793+
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn() -> \(raw: typeName) {
1794+
let caseId = _swift_js_pop_param_int32()
1795+
return _bridgeJSLiftFromCaseId(caseId)
1796+
}
1797+
1798+
// MARK: ExportSwift
1799+
1800+
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ caseId: Int32) -> \(raw: typeName) {
1801+
return _bridgeJSLiftFromCaseId(caseId)
1802+
}
17841803
17851804
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() {
17861805
switch self {
@@ -1849,66 +1868,94 @@ public class ExportSwift {
18491868
return cases
18501869
}
18511870

1871+
/// Generates code to push associated value payloads to side channels
1872+
/// This helper is reused by both lowerParameter and lowerReturn to avoid duplication
1873+
private func generatePayloadPushingCode(
1874+
associatedValues: [AssociatedValue]
1875+
) -> [String] {
1876+
var bodyLines: [String] = []
1877+
for (index, associatedValue) in associatedValues.enumerated() {
1878+
let paramName = associatedValue.label ?? "param\(index)"
1879+
switch associatedValue.type {
1880+
case .string:
1881+
bodyLines.append("var __bjs_\(paramName) = \(paramName)")
1882+
bodyLines.append("__bjs_\(paramName).withUTF8 { ptr in")
1883+
bodyLines.append("_swift_js_push_string(ptr.baseAddress, Int32(ptr.count))")
1884+
bodyLines.append("}")
1885+
case .int:
1886+
bodyLines.append("_swift_js_push_int(Int32(\(paramName)))")
1887+
case .bool:
1888+
bodyLines.append("_swift_js_push_int(\(paramName) ? 1 : 0)")
1889+
case .float:
1890+
bodyLines.append("_swift_js_push_f32(\(paramName))")
1891+
case .double:
1892+
bodyLines.append("_swift_js_push_f64(\(paramName))")
1893+
case .optional(let wrappedType):
1894+
bodyLines.append("let __bjs_isSome_\(paramName) = \(paramName) != nil")
1895+
bodyLines.append("if let __bjs_unwrapped_\(paramName) = \(paramName) {")
1896+
switch wrappedType {
1897+
case .string:
1898+
bodyLines.append("var __bjs_str_\(paramName) = __bjs_unwrapped_\(paramName)")
1899+
bodyLines.append("__bjs_str_\(paramName).withUTF8 { ptr in")
1900+
bodyLines.append("_swift_js_push_string(ptr.baseAddress, Int32(ptr.count))")
1901+
bodyLines.append("}")
1902+
case .int:
1903+
bodyLines.append("_swift_js_push_int(Int32(__bjs_unwrapped_\(paramName)))")
1904+
case .bool:
1905+
bodyLines.append("_swift_js_push_int(__bjs_unwrapped_\(paramName) ? 1 : 0)")
1906+
case .float:
1907+
bodyLines.append("_swift_js_push_f32(__bjs_unwrapped_\(paramName))")
1908+
case .double:
1909+
bodyLines.append("_swift_js_push_f64(__bjs_unwrapped_\(paramName))")
1910+
default:
1911+
bodyLines.append(
1912+
"preconditionFailure(\"BridgeJS: unsupported optional wrapped type in generated code\")"
1913+
)
1914+
}
1915+
bodyLines.append("}")
1916+
bodyLines.append("_swift_js_push_int(__bjs_isSome_\(paramName) ? 1 : 0)")
1917+
default:
1918+
bodyLines.append(
1919+
"preconditionFailure(\"BridgeJS: unsupported associated value type in generated code\")"
1920+
)
1921+
}
1922+
}
1923+
return bodyLines
1924+
}
1925+
1926+
private func generateLowerParameterSwitchCases(enumDef: ExportedEnum) -> [String] {
1927+
var cases: [String] = []
1928+
for (caseIndex, enumCase) in enumDef.cases.enumerated() {
1929+
if enumCase.associatedValues.isEmpty {
1930+
cases.append("case .\(enumCase.name):")
1931+
cases.append("return Int32(\(caseIndex))")
1932+
} else {
1933+
let payloadCode = generatePayloadPushingCode(associatedValues: enumCase.associatedValues)
1934+
let pattern = enumCase.associatedValues.enumerated()
1935+
.map { index, associatedValue in "let \(associatedValue.label ?? "param\(index)")" }
1936+
.joined(separator: ", ")
1937+
cases.append("case .\(enumCase.name)(\(pattern)):")
1938+
cases.append(contentsOf: payloadCode)
1939+
cases.append("return Int32(\(caseIndex))")
1940+
}
1941+
}
1942+
return cases
1943+
}
1944+
18521945
private func generateReturnSwitchCases(enumDef: ExportedEnum) -> [String] {
18531946
var cases: [String] = []
18541947
for (caseIndex, enumCase) in enumDef.cases.enumerated() {
18551948
if enumCase.associatedValues.isEmpty {
18561949
cases.append("case .\(enumCase.name):")
18571950
cases.append("_swift_js_push_tag(Int32(\(caseIndex)))")
18581951
} else {
1859-
var bodyLines: [String] = []
1860-
bodyLines.append("_swift_js_push_tag(Int32(\(caseIndex)))")
1861-
for (index, associatedValue) in enumCase.associatedValues.enumerated() {
1862-
let paramName = associatedValue.label ?? "param\(index)"
1863-
switch associatedValue.type {
1864-
case .string:
1865-
bodyLines.append("var __bjs_\(paramName) = \(paramName)")
1866-
bodyLines.append("__bjs_\(paramName).withUTF8 { ptr in")
1867-
bodyLines.append("_swift_js_push_string(ptr.baseAddress, Int32(ptr.count))")
1868-
bodyLines.append("}")
1869-
case .int:
1870-
bodyLines.append("_swift_js_push_int(Int32(\(paramName)))")
1871-
case .bool:
1872-
bodyLines.append("_swift_js_push_int(\(paramName) ? 1 : 0)")
1873-
case .float:
1874-
bodyLines.append("_swift_js_push_f32(\(paramName))")
1875-
case .double:
1876-
bodyLines.append("_swift_js_push_f64(\(paramName))")
1877-
case .optional(let wrappedType):
1878-
bodyLines.append("let __bjs_isSome_\(paramName) = \(paramName) != nil")
1879-
bodyLines.append("if let __bjs_unwrapped_\(paramName) = \(paramName) {")
1880-
switch wrappedType {
1881-
case .string:
1882-
bodyLines.append("var __bjs_str_\(paramName) = __bjs_unwrapped_\(paramName)")
1883-
bodyLines.append("__bjs_str_\(paramName).withUTF8 { ptr in")
1884-
bodyLines.append("_swift_js_push_string(ptr.baseAddress, Int32(ptr.count))")
1885-
bodyLines.append("}")
1886-
case .int:
1887-
bodyLines.append("_swift_js_push_int(Int32(__bjs_unwrapped_\(paramName)))")
1888-
case .bool:
1889-
bodyLines.append("_swift_js_push_int(__bjs_unwrapped_\(paramName) ? 1 : 0)")
1890-
case .float:
1891-
bodyLines.append("_swift_js_push_f32(__bjs_unwrapped_\(paramName))")
1892-
case .double:
1893-
bodyLines.append("_swift_js_push_f64(__bjs_unwrapped_\(paramName))")
1894-
default:
1895-
bodyLines.append(
1896-
"preconditionFailure(\"BridgeJS: unsupported optional wrapped type in generated code\")"
1897-
)
1898-
}
1899-
bodyLines.append("}")
1900-
bodyLines.append("_swift_js_push_int(__bjs_isSome_\(paramName) ? 1 : 0)")
1901-
default:
1902-
bodyLines.append(
1903-
"preconditionFailure(\"BridgeJS: unsupported associated value type in generated code\")"
1904-
)
1905-
}
1906-
}
19071952
let pattern = enumCase.associatedValues.enumerated()
19081953
.map { index, associatedValue in "let \(associatedValue.label ?? "param\(index)")" }
19091954
.joined(separator: ", ")
19101955
cases.append("case .\(enumCase.name)(\(pattern)):")
1911-
cases.append(contentsOf: bodyLines)
1956+
cases.append("_swift_js_push_tag(Int32(\(caseIndex)))")
1957+
let payloadCode = generatePayloadPushingCode(associatedValues: enumCase.associatedValues)
1958+
cases.append(contentsOf: payloadCode)
19121959
}
19131960
}
19141961
return cases

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -447,14 +447,41 @@ extension BridgeType {
447447
}
448448
case .swiftProtocol:
449449
throw BridgeJSCoreError("swiftProtocol is not supported in imported signatures")
450-
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
451-
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
450+
case .caseEnum:
451+
switch context {
452+
case .importTS:
453+
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
454+
case .protocolExport:
455+
return LoweringParameterInfo(loweredParameters: [("value", .i32)])
456+
}
457+
case .rawValueEnum(_, let rawType):
458+
switch context {
459+
case .importTS:
460+
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
461+
case .protocolExport:
462+
let wasmType = rawType == .string ? WasmCoreType.i32 : (rawType.wasmCoreType ?? .i32)
463+
return LoweringParameterInfo(loweredParameters: [("value", wasmType)])
464+
}
465+
case .associatedValueEnum:
466+
switch context {
467+
case .importTS:
468+
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
469+
case .protocolExport:
470+
return LoweringParameterInfo(loweredParameters: [("caseId", .i32)])
471+
}
472+
case .namespaceEnum:
473+
throw BridgeJSCoreError("Namespace enums cannot be used as parameters")
452474
case .optional(let wrappedType):
453475
switch context {
454476
case .importTS:
455477
throw BridgeJSCoreError("Optional types are not yet supported in TypeScript imports")
456478
case .protocolExport:
457-
return try wrappedType.loweringParameterInfo(context: context)
479+
let wrappedInfo = try wrappedType.loweringParameterInfo(context: context)
480+
guard wrappedInfo.loweredParameters.count == 1 else {
481+
throw BridgeJSCoreError("Optional wrapped type must lower to single parameter")
482+
}
483+
let (_, wrappedWasmType) = wrappedInfo.loweredParameters[0]
484+
return LoweringParameterInfo(loweredParameters: [("value", wrappedWasmType)])
458485
}
459486
}
460487
}
@@ -494,22 +521,38 @@ extension BridgeType {
494521
}
495522
case .swiftProtocol:
496523
throw BridgeJSCoreError("swiftProtocol is not supported in imported signatures")
497-
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
498-
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
499-
case .optional:
524+
case .caseEnum:
500525
switch context {
501526
case .importTS:
502-
throw BridgeJSCoreError("Optional types are not yet supported in TypeScript imports")
527+
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
528+
case .protocolExport:
529+
return LiftingReturnInfo(valueToLift: .i32)
530+
}
531+
case .rawValueEnum(_, let rawType):
532+
switch context {
533+
case .importTS:
534+
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
535+
case .protocolExport:
536+
let wasmType = rawType == .string ? WasmCoreType.i32 : (rawType.wasmCoreType ?? .i32)
537+
return LiftingReturnInfo(valueToLift: wasmType)
538+
}
539+
case .associatedValueEnum:
540+
switch context {
541+
case .importTS:
542+
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
503543
case .protocolExport:
504-
// For Optional<String>, return the length (or -1 for null)
505-
if case .optional(.string) = self {
506-
return .string
507-
}
508-
if case .optional(.swiftHeapObject) = self {
509-
return LiftingReturnInfo(valueToLift: .pointer)
510-
}
511544
return LiftingReturnInfo(valueToLift: nil)
512545
}
546+
case .namespaceEnum:
547+
throw BridgeJSCoreError("Namespace enums cannot be used as return values")
548+
case .optional(let wrappedType):
549+
switch context {
550+
case .importTS:
551+
throw BridgeJSCoreError("Optional types are not yet supported in TypeScript imports")
552+
case .protocolExport:
553+
let wrappedInfo = try wrappedType.liftingReturnInfo(context: context)
554+
return wrappedInfo
555+
}
513556
}
514557
}
515558
}

0 commit comments

Comments
 (0)