Skip to content

Commit 40e944c

Browse files
Restored null-vs-undefined handling for optional heap returns by switching back to a strict pointer === null check in Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift, keeping undefined mapping separate from null.
Aligned `JSUndefinedOr` intrinsics with what `Optional` actually supports by dropping unavailable lower/lift helpers for raw-value enums with `Bool` raw values and bridged structs, eliminating single-element tuple labels and missing method calls in `Sources/JavaScriptKit/JSUndefinedOr.swift`. Tests run: - `swift test --package-path ./Plugins/BridgeJS` - `SWIFT_SDK_ID=DEVELOPMENT-SNAPSHOT-2025-11-03-a-wasm32-unknown-wasip1 make unittest`
1 parent 8ceb703 commit 40e944c

File tree

4 files changed

+79
-29
lines changed

4 files changed

+79
-29
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,8 @@ struct StackCodegen {
818818
return "\(raw: type.swiftType).bridgeJSLiftParameter(_swift_js_pop_i32())"
819819
case .optional(let wrappedType):
820820
return liftOptionalExpression(wrappedType: wrappedType)
821+
case .undefinedOr(let wrappedType):
822+
return liftUndefinedExpression(wrappedType: wrappedType)
821823
case .array(let elementType):
822824
return liftArrayExpression(elementType: elementType)
823825
case .closure:
@@ -835,7 +837,7 @@ struct StackCodegen {
835837
return "[\(raw: elementType.swiftType)].bridgeJSLiftParameter()"
836838
case .swiftProtocol(let protocolName):
837839
return "[Any\(raw: protocolName)].bridgeJSLiftParameter()"
838-
case .optional, .array, .closure:
840+
case .optional, .undefinedOr, .array, .closure:
839841
return liftArrayExpressionInline(elementType: elementType)
840842
case .void, .namespaceEnum:
841843
fatalError("Invalid array element type: \(elementType)")
@@ -877,11 +879,34 @@ struct StackCodegen {
877879
}
878880
}()
879881
"""
880-
case .void, .namespaceEnum, .closure, .optional, .unsafePointer, .swiftProtocol:
882+
case .undefinedOr, .void, .namespaceEnum, .closure, .optional, .unsafePointer, .swiftProtocol:
881883
fatalError("Invalid optional wrapped type: \(wrappedType)")
882884
}
883885
}
884886

887+
private func liftUndefinedExpression(wrappedType: BridgeType) -> ExprSyntax {
888+
switch wrappedType {
889+
case .string, .int, .uint, .bool, .float, .double, .jsObject,
890+
.swiftStruct, .swiftHeapObject, .caseEnum, .associatedValueEnum, .rawValueEnum:
891+
return "JSUndefinedOr<\(raw: wrappedType.swiftType)>.bridgeJSLiftParameter()"
892+
case .array(let elementType):
893+
let arrayLift = liftArrayExpression(elementType: elementType)
894+
let swiftTypeName = elementType.swiftType
895+
return """
896+
{
897+
let __isDefined = _swift_js_pop_i32()
898+
if __isDefined == 0 {
899+
return JSUndefinedOr<\(raw: swiftTypeName)>.undefined
900+
} else {
901+
return JSUndefinedOr<\(raw: swiftTypeName)>(optional: \(arrayLift))
902+
}
903+
}()
904+
"""
905+
case .void, .namespaceEnum, .closure, .optional, .undefinedOr, .unsafePointer, .swiftProtocol:
906+
fatalError("Invalid undefinedOr wrapped type: \(wrappedType)")
907+
}
908+
}
909+
885910
/// Generates statements to lower/push a value onto the stack.
886911
/// - Parameters:
887912
/// - type: The BridgeType to lower
@@ -909,6 +934,8 @@ struct StackCodegen {
909934
return ["\(raw: accessor).bridgeJSLowerReturn()"]
910935
case .optional(let wrappedType):
911936
return lowerOptionalStatements(wrappedType: wrappedType, accessor: accessor, varPrefix: varPrefix)
937+
case .undefinedOr(let wrappedType):
938+
return lowerOptionalStatements(wrappedType: wrappedType, accessor: accessor, varPrefix: varPrefix)
912939
case .void, .namespaceEnum:
913940
return []
914941
case .array(let elementType):
@@ -928,7 +955,7 @@ struct StackCodegen {
928955
return ["\(raw: accessor).bridgeJSLowerReturn()"]
929956
case .swiftProtocol(let protocolName):
930957
return ["\(raw: accessor).map { $0 as! Any\(raw: protocolName) }.bridgeJSLowerReturn()"]
931-
case .optional, .array, .closure:
958+
case .optional, .undefinedOr, .array, .closure:
932959
return lowerArrayStatementsInline(
933960
elementType: elementType,
934961
accessor: accessor,
@@ -1585,6 +1612,7 @@ extension BridgeType {
15851612
case .swiftProtocol(let name): return "Any\(name)"
15861613
case .void: return "Void"
15871614
case .optional(let wrappedType): return "Optional<\(wrappedType.swiftType)>"
1615+
case .undefinedOr(let wrappedType): return "JSUndefinedOr<\(wrappedType.swiftType)>"
15881616
case .array(let elementType): return "[\(elementType.swiftType)]"
15891617
case .caseEnum(let name): return name
15901618
case .rawValueEnum(let name, _): return name
@@ -1632,6 +1660,10 @@ extension BridgeType {
16321660
var optionalParams: [(name: String, type: WasmCoreType)] = [("isSome", .i32)]
16331661
optionalParams.append(contentsOf: try wrappedType.liftParameterInfo().parameters)
16341662
return LiftingIntrinsicInfo(parameters: optionalParams)
1663+
case .undefinedOr(let wrappedType):
1664+
var params: [(name: String, type: WasmCoreType)] = [("isDefined", .i32)]
1665+
params.append(contentsOf: try wrappedType.liftParameterInfo().parameters)
1666+
return LiftingIntrinsicInfo(parameters: params)
16351667
case .caseEnum: return .caseEnum
16361668
case .rawValueEnum(_, let rawType):
16371669
return rawType.liftingIntrinsicInfo
@@ -1681,6 +1713,7 @@ extension BridgeType {
16811713
case .swiftProtocol: return .jsObject
16821714
case .void: return .void
16831715
case .optional: return .optional
1716+
case .undefinedOr: return .optional
16841717
case .caseEnum: return .caseEnum
16851718
case .rawValueEnum(_, let rawType):
16861719
return rawType.loweringIntrinsicInfo

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3405,6 +3405,8 @@ extension BridgeType {
34053405
return "number"
34063406
case .optional(let wrappedType):
34073407
return "\(wrappedType.tsType) | null"
3408+
case .undefinedOr(let wrappedType):
3409+
return "\(wrappedType.tsType) | undefined"
34083410
case .caseEnum(let name):
34093411
return "\(name)Tag"
34103412
case .rawValueEnum(let name, _):

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ struct IntrinsicJSFragment: Sendable {
554554
? "\(className).__construct(\(pointerVar))"
555555
: "_exports['\(className)'].__construct(\(pointerVar))"
556556
printer.write(
557-
"const \(resultVar) = \(pointerVar) == null ? \(absenceLiteral) : \(constructExpr);"
557+
"const \(resultVar) = \(pointerVar) === null ? \(absenceLiteral) : \(constructExpr);"
558558
)
559559
case .caseEnum:
560560
printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalInt);")
@@ -643,7 +643,7 @@ struct IntrinsicJSFragment: Sendable {
643643

644644
static func optionalLowerReturn(
645645
wrappedType: BridgeType,
646-
presenceCheck: ((String) -> String)? = nil
646+
presenceCheck: (@Sendable (String) -> String)? = nil
647647
) throws -> IntrinsicJSFragment {
648648
switch wrappedType {
649649
case .void, .optional, .namespaceEnum, .closure:
@@ -2436,6 +2436,8 @@ struct IntrinsicJSFragment: Sendable {
24362436
return try! arrayLift(elementType: innerElementType)
24372437
case .optional(let wrappedType):
24382438
return try optionalElementRaiseFragment(wrappedType: wrappedType)
2439+
case .undefinedOr(let wrappedType):
2440+
return try optionalElementRaiseFragment(wrappedType: wrappedType, absenceLiteral: "undefined")
24392441
case .unsafePointer:
24402442
return IntrinsicJSFragment(
24412443
parameters: [],
@@ -2563,6 +2565,14 @@ struct IntrinsicJSFragment: Sendable {
25632565
return []
25642566
}
25652567
)
2568+
case .undefinedOr:
2569+
return IntrinsicJSFragment(
2570+
parameters: ["value"],
2571+
printCode: { arguments, scope, printer, cleanup in
2572+
printer.write("throw new Error(\"Unsupported array element type for lowering: \(elementType)\");")
2573+
return []
2574+
}
2575+
)
25662576
case .swiftHeapObject:
25672577
return IntrinsicJSFragment(
25682578
parameters: ["value"],
@@ -2585,6 +2595,8 @@ struct IntrinsicJSFragment: Sendable {
25852595
)
25862596
case .array(let innerElementType):
25872597
return try! arrayLower(elementType: innerElementType)
2598+
case .undefinedOr:
2599+
throw BridgeJSLinkError(message: "Unsupported array element type: \(elementType)")
25882600
case .optional(let wrappedType):
25892601
return try optionalElementLowerFragment(wrappedType: wrappedType)
25902602
case .swiftProtocol:
@@ -2612,7 +2624,10 @@ struct IntrinsicJSFragment: Sendable {
26122624
}
26132625
}
26142626

2615-
private static func optionalElementRaiseFragment(wrappedType: BridgeType) throws -> IntrinsicJSFragment {
2627+
private static func optionalElementRaiseFragment(
2628+
wrappedType: BridgeType,
2629+
absenceLiteral: String = "null"
2630+
) throws -> IntrinsicJSFragment {
26162631
return IntrinsicJSFragment(
26172632
parameters: [],
26182633
printCode: { arguments, scope, printer, cleanup in
@@ -2623,7 +2638,7 @@ struct IntrinsicJSFragment: Sendable {
26232638
printer.write("let \(resultVar);")
26242639
printer.write("if (\(isSomeVar) === 0) {")
26252640
printer.indent {
2626-
printer.write("\(resultVar) = null;")
2641+
printer.write("\(resultVar) = \(absenceLiteral);")
26272642
}
26282643
printer.write("} else {")
26292644
printer.indent {
@@ -2642,14 +2657,18 @@ struct IntrinsicJSFragment: Sendable {
26422657
)
26432658
}
26442659

2645-
private static func optionalElementLowerFragment(wrappedType: BridgeType) throws -> IntrinsicJSFragment {
2660+
private static func optionalElementLowerFragment(
2661+
wrappedType: BridgeType,
2662+
presenceCheck: (@Sendable (String) -> String)? = nil
2663+
) throws -> IntrinsicJSFragment {
26462664
return IntrinsicJSFragment(
26472665
parameters: ["value"],
26482666
printCode: { arguments, scope, printer, cleanup in
26492667
let value = arguments[0]
26502668
let isSomeVar = scope.variable("isSome")
26512669

2652-
printer.write("const \(isSomeVar) = \(value) != null ? 1 : 0;")
2670+
let presenceExpr = presenceCheck?(value) ?? "\(value) != null"
2671+
printer.write("const \(isSomeVar) = \(presenceExpr) ? 1 : 0;")
26532672
// Cleanup is written inside the if block so retained id is in scope
26542673
let localCleanupWriter = CodeFragmentPrinter()
26552674
printer.write("if (\(isSomeVar)) {")
@@ -3201,6 +3220,14 @@ struct IntrinsicJSFragment: Sendable {
32013220
}
32023221
}
32033222
)
3223+
case .undefinedOr:
3224+
return IntrinsicJSFragment(
3225+
parameters: ["value"],
3226+
printCode: { arguments, scope, printer, cleanup in
3227+
printer.write("throw new Error(\"Unsupported struct field type for lowering: \(field.type)\");")
3228+
return []
3229+
}
3230+
)
32043231
case .swiftStruct(let nestedName):
32053232
return IntrinsicJSFragment(
32063233
parameters: ["value"],
@@ -3557,6 +3584,14 @@ struct IntrinsicJSFragment: Sendable {
35573584
return [varName]
35583585
}
35593586
)
3587+
case .undefinedOr:
3588+
return IntrinsicJSFragment(
3589+
parameters: [],
3590+
printCode: { arguments, scope, printer, cleanup in
3591+
printer.write("throw new Error(\"Unsupported struct field type: \(field.type)\");")
3592+
return []
3593+
}
3594+
)
35603595
case .void, .swiftProtocol, .namespaceEnum, .closure:
35613596
// These types should not appear as struct fields
35623597
return IntrinsicJSFragment(

Sources/JavaScriptKit/JSUndefinedOr.swift

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,6 @@ extension JSUndefinedOr where Wrapped: _BridgedSwiftEnumNoPayload, Wrapped: RawR
310310
}
311311

312312
extension JSUndefinedOr where Wrapped: _BridgedSwiftEnumNoPayload, Wrapped: RawRepresentable, Wrapped.RawValue == Bool {
313-
@_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> (isSome: Int32, value: Int32) {
314-
optionalRepresentation.bridgeJSLowerParameter()
315-
}
316-
317313
@_spi(BridgeJS) public static func bridgeJSLiftParameter(_ isSome: Int32, _ wrappedValue: Int32) -> Self {
318314
Self(optional: Optional<Wrapped>.bridgeJSLiftParameter(isSome, wrappedValue))
319315
}
@@ -322,10 +318,6 @@ extension JSUndefinedOr where Wrapped: _BridgedSwiftEnumNoPayload, Wrapped: RawR
322318
Self(optional: Optional<Wrapped>.bridgeJSLiftParameter())
323319
}
324320

325-
@_spi(BridgeJS) public static func bridgeJSLiftReturnFromSideChannel() -> Self {
326-
Self(optional: Optional<Wrapped>.bridgeJSLiftReturnFromSideChannel())
327-
}
328-
329321
@_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void {
330322
optionalRepresentation.bridgeJSLowerReturn()
331323
}
@@ -398,14 +390,6 @@ extension JSUndefinedOr where Wrapped: _BridgedSwiftAssociatedValueEnum {
398390
}
399391

400392
extension JSUndefinedOr where Wrapped: _BridgedSwiftStruct {
401-
@_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> (isSome: Int32) {
402-
optionalRepresentation.bridgeJSLowerParameter()
403-
}
404-
405-
@_spi(BridgeJS) public consuming func bridgeJSLowerParameterForClosure() -> (isSome: Int32) {
406-
optionalRepresentation.bridgeJSLowerParameterForClosure()
407-
}
408-
409393
@_spi(BridgeJS) public static func bridgeJSLiftParameter(_ isSome: Int32) -> Self {
410394
Self(optional: Optional<Wrapped>.bridgeJSLiftParameter(isSome))
411395
}
@@ -414,10 +398,6 @@ extension JSUndefinedOr where Wrapped: _BridgedSwiftStruct {
414398
Self(optional: Optional<Wrapped>.bridgeJSLiftParameter())
415399
}
416400

417-
@_spi(BridgeJS) public static func bridgeJSLiftReturnFromSideChannel() -> Self {
418-
Self(optional: Optional<Wrapped>.bridgeJSLiftReturnFromSideChannel())
419-
}
420-
421401
@_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void {
422402
optionalRepresentation.bridgeJSLowerReturn()
423403
}

0 commit comments

Comments
 (0)