diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ClosureCodegen.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ClosureCodegen.swift index cbec4ba31..01dd419e7 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ClosureCodegen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ClosureCodegen.swift @@ -123,23 +123,23 @@ public struct ClosureCodegen { for (index, paramType) in signature.parameters.enumerated() { let paramName = "param\(index)" - let liftInfo = try paramType.liftParameterInfo() + let liftParams = try paramType.liftParameterInfo() - for (argName, wasmType) in liftInfo.parameters { + for (argName, wasmType) in liftParams { let fullName = - liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName + liftParams.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName abiParams.append((fullName, wasmType)) } - let argNames = liftInfo.parameters.map { (argName, _) in - liftInfo.parameters.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName + let argNames = liftParams.map { (argName, _) in + liftParams.count > 1 ? "\(paramName)\(argName.capitalizedFirstLetter)" : paramName } liftedParams.append("\(paramType.swiftType).bridgeJSLiftParameter(\(argNames.joined(separator: ", ")))") } let closureCallExpr = ExprSyntax("closure(\(raw: liftedParams.joined(separator: ", ")))") - let abiReturnWasmType = try signature.returnType.loweringReturnInfo().returnType + let abiReturnWasmType = try signature.returnType.loweringReturnInfo() // Build signature using SwiftSignatureBuilder let funcSignature = SwiftSignatureBuilder.buildABIFunctionSignature( diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index bb42e9d1e..70d9c3d63 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -20,23 +20,17 @@ import BridgeJSUtilities public class ExportSwift { let progress: ProgressReporting let moduleName: String - private let exposeToGlobal: Bool private var skeleton: ExportedSkeleton - private var sourceFiles: [(sourceFile: SourceFileSyntax, inputFilePath: String)] = [] public init(progress: ProgressReporting, moduleName: String, skeleton: ExportedSkeleton) { self.progress = progress self.moduleName = moduleName - self.exposeToGlobal = skeleton.exposeToGlobal self.skeleton = skeleton } /// Finalizes the export process and generates the bridge code /// - /// - Parameters: - /// - exposeToGlobal: Whether to expose exported APIs to the global namespace (default: false) - /// - Returns: A tuple containing the generated Swift code and a skeleton - /// describing the exported APIs + /// - Returns: The generated Swift glue code, or nil if skeleton is empty public func finalize() throws -> String? { guard let outputSwift = try renderSwiftGlue() else { return nil @@ -101,7 +95,6 @@ public class ExportSwift { var parameters: [Parameter] = [] var abiParameterSignatures: [(name: String, type: WasmCoreType)] = [] var abiReturnType: WasmCoreType? - var externDecls: [DeclSyntax] = [] let effects: Effects init(effects: Effects) { @@ -119,12 +112,12 @@ public class ExportSwift { func liftParameter(param: Parameter) throws { parameters.append(param) - let liftingInfo = try param.type.liftParameterInfo() + let liftingParams = try param.type.liftParameterInfo() let argumentsToLift: [String] - if liftingInfo.parameters.count == 1 { + if liftingParams.count == 1 { argumentsToLift = [param.name] } else { - argumentsToLift = liftingInfo.parameters.map { (name, _) in param.name + name.capitalizedFirstLetter } + argumentsToLift = liftingParams.map { (name, _) in param.name + name.capitalizedFirstLetter } } let typeNameForIntrinsic: String @@ -134,50 +127,37 @@ public class ExportSwift { case .closure(let signature, _): typeNameForIntrinsic = param.type.swiftType liftingExpr = ExprSyntax("_BJS_Closure_\(raw: signature.mangleName).bridgeJSLift(\(raw: param.name))") - case .swiftStruct(let structName): - typeNameForIntrinsic = structName - liftingExpr = ExprSyntax("\(raw: structName).bridgeJSLiftParameter()") case .array: typeNameForIntrinsic = param.type.swiftType liftingExpr = StackCodegen().liftExpression(for: param.type) case .nullable(let wrappedType, let kind): - let optionalSwiftType: String - if case .null = kind { - optionalSwiftType = "Optional" - } else { - optionalSwiftType = "JSUndefinedOr" - } - typeNameForIntrinsic = "\(optionalSwiftType)<\(wrappedType.swiftType)>" + let optionalSwiftType: String = + if case .null = kind { "Optional" } else { "JSUndefinedOr" } + let wrappedName: String = + if case .cast(let name) = wrappedType.descriptor.accessorTransform { name } else { + wrappedType.swiftType + } + typeNameForIntrinsic = "\(optionalSwiftType)<\(wrappedName)>" liftingExpr = ExprSyntax( "\(raw: typeNameForIntrinsic).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))" ) default: - typeNameForIntrinsic = param.type.swiftType + if case .cast(let name) = param.type.descriptor.accessorTransform { + typeNameForIntrinsic = name + } else { + typeNameForIntrinsic = param.type.swiftType + } liftingExpr = ExprSyntax( "\(raw: typeNameForIntrinsic).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))" ) } liftedParameterExprs.append(liftingExpr) - for (name, type) in zip(argumentsToLift, liftingInfo.parameters.map { $0.type }) { + for (name, type) in zip(argumentsToLift, liftingParams.map { $0.type }) { abiParameterSignatures.append((name, type)) } } - private func protocolCastSuffix(for returnType: BridgeType) -> (prefix: String, suffix: String) { - switch returnType { - case .swiftProtocol: - return ("", " as! \(returnType.swiftType)") - case .nullable(let wrappedType, _): - if case .swiftProtocol = wrappedType { - return ("(", ").flatMap { $0 as? \(wrappedType.swiftType) }") - } - return ("", "") - default: - return ("", "") - } - } - private func removeFirstLiftedParameter() -> (parameter: Parameter, expr: ExprSyntax) { let parameter = parameters.removeFirst() let expr = liftedParameterExprs.removeFirst() @@ -211,10 +191,9 @@ public class ExportSwift { if returnType == .void { return CodeBlockItemSyntax(item: .init(ExpressionStmtSyntax(expression: callExpr))) } else { - let (prefix, suffix) = protocolCastSuffix(for: returnType) - return CodeBlockItemSyntax( - item: .init(DeclSyntax("let ret = \(raw: prefix)\(raw: callExpr)\(raw: suffix)")) - ) + let binding = returnType.descriptor.accessorTransform + .applyToReturnBinding("\(callExpr)", isOptional: returnType.isOptional) + return CodeBlockItemSyntax(item: .init(DeclSyntax("\(raw: binding)"))) } } @@ -228,8 +207,9 @@ public class ExportSwift { if returnType == .void { append("\(raw: name)") } else { - let (prefix, suffix) = protocolCastSuffix(for: returnType) - append("let ret = \(raw: prefix)\(raw: name)\(raw: suffix)") + let binding = returnType.descriptor.accessorTransform + .applyToReturnBinding(name, isOptional: returnType.isOptional) + append("\(raw: binding)") } } @@ -246,14 +226,7 @@ public class ExportSwift { /// Generates intermediate variables for stack-using parameters if needed for LIFO compatibility private func generateParameterLifting() { let stackParamIndices = parameters.enumerated().compactMap { index, param -> Int? in - switch param.type { - case .swiftStruct, .nullable(.swiftStruct, _), - .associatedValueEnum, .nullable(.associatedValueEnum, _), - .array: - return index - default: - return nil - } + param.type.descriptor.usesStackLifting ? index : nil } guard stackParamIndices.count > 1 else { return } @@ -270,11 +243,13 @@ public class ExportSwift { func callPropertyGetter(propertyName: String, returnType: BridgeType) { let (_, selfExpr) = removeFirstLiftedParameter() + let expr = "\(selfExpr).\(propertyName)" if returnType == .void { - append("\(raw: selfExpr).\(raw: propertyName)") + append("\(raw: expr)") } else { - let (prefix, suffix) = protocolCastSuffix(for: returnType) - append("let ret = \(raw: prefix)\(raw: selfExpr).\(raw: propertyName)\(raw: suffix)") + let binding = returnType.descriptor.accessorTransform + .applyToReturnBinding(expr, isOptional: returnType.isOptional) + append("\(raw: binding)") } } @@ -299,8 +274,7 @@ public class ExportSwift { } private func _lowerReturnValue(returnType: BridgeType) throws { - let loweringInfo = try returnType.loweringReturnInfo() - abiReturnType = loweringInfo.returnType + abiReturnType = try returnType.loweringReturnInfo() if returnType == .void { return } @@ -736,10 +710,6 @@ struct StackCodegen { /// - Returns: An ExprSyntax representing the lift expression func liftExpression(for type: BridgeType) -> ExprSyntax { switch type { - case .string, .int, .uint, .bool, .float, .double, - .jsObject(nil), .jsValue, .swiftStruct, .swiftHeapObject, .unsafePointer, - .swiftProtocol, .caseEnum, .associatedValueEnum, .rawValueEnum: - return "\(raw: type.swiftType).bridgeJSLiftParameter()" case .jsObject(let className?): return "\(raw: className)(unsafelyWrapping: JSObject.bridgeJSLiftParameter())" case .nullable(let wrappedType, let kind): @@ -752,20 +722,36 @@ struct StackCodegen { return "JSObject.bridgeJSLiftParameter()" case .void, .namespaceEnum: return "()" + default: + return "\(raw: type.swiftType).bridgeJSLiftParameter()" } } func liftArrayExpression(elementType: BridgeType) -> ExprSyntax { - switch elementType { - case .jsObject(let className?) where className != "JSObject": + if case .jsObject(let className?) = elementType, className != "JSObject" { return "[JSObject].bridgeJSLiftParameter().map { \(raw: className)(unsafelyWrapping: $0) }" - case .nullable, .closure: + } + if needsInlineLowering(elementType) { return liftArrayExpressionInline(elementType: elementType) - case .void, .namespaceEnum: - fatalError("Invalid array element type: \(elementType)") - default: - return "[\(raw: elementType.swiftType)].bridgeJSLiftParameter()" } + return "[\(raw: elementType.swiftType)].bridgeJSLiftParameter()" + } + + func liftDictionaryExpression(valueType: BridgeType) -> ExprSyntax { + if case .jsObject(let className?) = valueType, className != "JSObject" { + return """ + { + let __dict = [String: JSObject].bridgeJSLiftParameter() + return __dict.mapValues { \(raw: className)(unsafelyWrapping: $0) } + }() + """ + } + if needsInlineLowering(valueType) { + return liftDictionaryExpressionInline(valueType: valueType) + } + if case .void = valueType { fatalError("Invalid dictionary value type: \(valueType)") } + if case .namespaceEnum = valueType { fatalError("Invalid dictionary value type: \(valueType)") } + return "[String: \(raw: valueType.swiftType)].bridgeJSLiftParameter()" } private func liftArrayExpressionInline(elementType: BridgeType) -> ExprSyntax { @@ -785,24 +771,6 @@ struct StackCodegen { """ } - func liftDictionaryExpression(valueType: BridgeType) -> ExprSyntax { - switch valueType { - case .jsObject(let className?) where className != "JSObject": - return """ - { - let __dict = [String: JSObject].bridgeJSLiftParameter() - return __dict.mapValues { \(raw: className)(unsafelyWrapping: $0) } - }() - """ - case .nullable, .closure: - return liftDictionaryExpressionInline(valueType: valueType) - case .void, .namespaceEnum: - fatalError("Invalid dictionary value type: \(valueType)") - default: - return "[String: \(raw: valueType.swiftType)].bridgeJSLiftParameter()" - } - } - private func liftDictionaryExpressionInline(valueType: BridgeType) -> ExprSyntax { let valueLift = liftExpression(for: valueType) let swiftTypeName = valueType.swiftType @@ -824,14 +792,10 @@ struct StackCodegen { private func liftNullableExpression(wrappedType: BridgeType, kind: JSOptionalKind) -> ExprSyntax { let typeName = kind == .null ? "Optional" : "JSUndefinedOr" switch wrappedType { - case .string, .int, .uint, .bool, .float, .double, .jsObject(nil), .jsValue, - .swiftStruct, .swiftHeapObject, .caseEnum, .associatedValueEnum, .rawValueEnum, - .array, .dictionary: - return "\(raw: typeName)<\(raw: wrappedType.swiftType)>.bridgeJSLiftParameter()" - case .jsObject(let className?): + case .jsObject(let className?) where className != "JSObject": return "\(raw: typeName).bridgeJSLiftParameter().map { \(raw: className)(unsafelyWrapping: $0) }" - case .nullable, .void, .namespaceEnum, .closure, .unsafePointer, .swiftProtocol: - fatalError("Invalid nullable wrapped type: \(wrappedType)") + default: + return "\(raw: typeName)<\(raw: wrappedType.swiftType)>.bridgeJSLiftParameter()" } } @@ -847,25 +811,33 @@ struct StackCodegen { varPrefix: String ) -> [CodeBlockItemSyntax] { switch type { - case .string, .int, .uint, .bool, .float, .double, .jsValue, - .jsObject(nil), .swiftHeapObject, .unsafePointer, .closure, - .caseEnum, .rawValueEnum: - return ["\(raw: accessor).bridgeJSLowerStackReturn()"] - case .jsObject(_?): - return ["\(raw: accessor).jsObject.bridgeJSLowerStackReturn()"] - case .swiftProtocol: - return ["(\(raw: accessor) as! \(raw: type.swiftType)).bridgeJSLowerStackReturn()"] - case .associatedValueEnum, .swiftStruct: - return ["\(raw: accessor).bridgeJSLowerReturn()"] + case .nullable(let wrappedType, _): return lowerOptionalStatements(wrappedType: wrappedType, accessor: accessor, varPrefix: varPrefix) - case .void, .namespaceEnum: - return [] case .array(let elementType): return lowerArrayStatements(elementType: elementType, accessor: accessor, varPrefix: varPrefix) case .dictionary(let valueType): return lowerDictionaryStatements(valueType: valueType, accessor: accessor, varPrefix: varPrefix) + default: + break } + + let desc = type.descriptor + let transformed = desc.accessorTransform.apply(accessor) + switch desc.lowerMethod { + case .stackReturn: + return ["\(raw: transformed).bridgeJSLowerStackReturn()"] + case .fullReturn, .pushParameter: + return ["\(raw: transformed).bridgeJSLowerReturn()"] + case .none: + return [] + } + } + + private func needsInlineLowering(_ type: BridgeType) -> Bool { + if case .nullable = type { return true } + if case .closure = type { return true } + return false } private func lowerArrayStatements( @@ -873,22 +845,32 @@ struct StackCodegen { accessor: String, varPrefix: String ) -> [CodeBlockItemSyntax] { - switch elementType { - case .jsObject(let className?) where className != "JSObject": - return ["\(raw: accessor).map { $0.jsObject }.bridgeJSLowerReturn()"] - case .swiftProtocol: - return ["\(raw: accessor).map { $0 as! \(raw: elementType.swiftType) }.bridgeJSLowerReturn()"] - case .nullable, .closure: - return lowerArrayStatementsInline( - elementType: elementType, - accessor: accessor, - varPrefix: varPrefix - ) - case .void, .namespaceEnum: - fatalError("Invalid array element type: \(elementType)") - default: - return ["\(raw: accessor).bridgeJSLowerReturn()"] + if needsInlineLowering(elementType) { + return lowerArrayStatementsInline(elementType: elementType, accessor: accessor, varPrefix: varPrefix) + } + return lowerCollectionSimple(elementType: elementType, accessor: accessor, mapMethod: "map") + } + + private func lowerDictionaryStatements( + valueType: BridgeType, + accessor: String, + varPrefix: String + ) -> [CodeBlockItemSyntax] { + if needsInlineLowering(valueType) { + return lowerDictionaryStatementsInline(valueType: valueType, accessor: accessor, varPrefix: varPrefix) + } + return lowerCollectionSimple(elementType: valueType, accessor: accessor, mapMethod: "mapValues") + } + + private func lowerCollectionSimple( + elementType: BridgeType, + accessor: String, + mapMethod: String + ) -> [CodeBlockItemSyntax] { + if let mapClosure = elementType.descriptor.accessorTransform.mapClosure { + return ["\(raw: accessor).\(raw: mapMethod) { \(raw: mapClosure) }.bridgeJSLowerReturn()"] } + return ["\(raw: accessor).bridgeJSLowerReturn()"] } private func lowerArrayStatementsInline( @@ -915,29 +897,6 @@ struct StackCodegen { return Array(parsed) } - private func lowerDictionaryStatements( - valueType: BridgeType, - accessor: String, - varPrefix: String - ) -> [CodeBlockItemSyntax] { - switch valueType { - case .jsObject(let className?) where className != "JSObject": - return ["\(raw: accessor).mapValues { $0.jsObject }.bridgeJSLowerReturn()"] - case .swiftProtocol: - return ["\(raw: accessor).mapValues { $0 as! \(raw: valueType.swiftType) }.bridgeJSLowerReturn()"] - case .nullable, .closure: - return lowerDictionaryStatementsInline( - valueType: valueType, - accessor: accessor, - varPrefix: varPrefix - ) - case .void, .namespaceEnum: - fatalError("Invalid dictionary value type: \(valueType)") - default: - return ["\(raw: accessor).bridgeJSLowerReturn()"] - } - } - private func lowerDictionaryStatementsInline( valueType: BridgeType, accessor: String, @@ -977,11 +936,8 @@ struct StackCodegen { accessor: String, varPrefix: String ) -> [CodeBlockItemSyntax] { - switch wrappedType { - case .array, .dictionary, .swiftStruct: + if wrappedType.descriptor.optionalConvention == .stackABI { return ["\(raw: accessor).bridgeJSLowerReturn()"] - default: - break } var statements: [String] = [] @@ -1006,16 +962,14 @@ struct StackCodegen { wrappedType: BridgeType, unwrappedVar: String ) -> [CodeBlockItemSyntax] { - switch wrappedType { - case .jsObject(_?): - return ["\(raw: unwrappedVar).jsObject.bridgeJSLowerStackReturn()"] - case .swiftProtocol: - return ["(\(raw: unwrappedVar) as! \(raw: wrappedType.swiftType)).bridgeJSLowerStackReturn()"] - case .string, .int, .uint, .bool, .float, .double, .jsValue, - .jsObject(nil), .swiftHeapObject, .unsafePointer, .closure, - .caseEnum, .rawValueEnum, .associatedValueEnum: - return ["\(raw: unwrappedVar).bridgeJSLowerStackReturn()"] - default: + let desc = wrappedType.descriptor + let transformed = desc.accessorTransform.apply(unwrappedVar) + switch desc.lowerMethod { + case .stackReturn, .pushParameter: + return ["\(raw: transformed).bridgeJSLowerStackReturn()"] + case .fullReturn: + return ["\(raw: transformed).bridgeJSLowerReturn()"] + case .none: return ["preconditionFailure(\"BridgeJS: unsupported optional wrapped type\")"] } } @@ -1603,137 +1557,31 @@ extension BridgeType { } } - var isClosureType: Bool { - if case .closure = self { return true } - return false - } - - struct LiftingIntrinsicInfo: Sendable { - let parameters: [(name: String, type: WasmCoreType)] - - static let bool = LiftingIntrinsicInfo(parameters: [("value", .i32)]) - static let int = LiftingIntrinsicInfo(parameters: [("value", .i32)]) - static let float = LiftingIntrinsicInfo(parameters: [("value", .f32)]) - static let double = LiftingIntrinsicInfo(parameters: [("value", .f64)]) - static let string = LiftingIntrinsicInfo(parameters: [("bytes", .i32), ("length", .i32)]) - static let jsObject = LiftingIntrinsicInfo(parameters: [("value", .i32)]) - static let jsValue = LiftingIntrinsicInfo(parameters: [("kind", .i32), ("payload1", .i32), ("payload2", .f64)]) - static let swiftHeapObject = LiftingIntrinsicInfo(parameters: [("value", .pointer)]) - static let unsafePointer = LiftingIntrinsicInfo(parameters: [("pointer", .pointer)]) - static let void = LiftingIntrinsicInfo(parameters: []) - static let caseEnum = LiftingIntrinsicInfo(parameters: [("value", .i32)]) - static let associatedValueEnum = LiftingIntrinsicInfo(parameters: [ - ("caseId", .i32) - ]) - } - - func liftParameterInfo() throws -> LiftingIntrinsicInfo { + func liftParameterInfo() throws -> [(name: String, type: WasmCoreType)] { switch self { - case .bool: return .bool - case .int, .uint: return .int - case .float: return .float - case .double: return .double - case .string: return .string - case .jsObject: return .jsObject - case .jsValue: return .jsValue - case .swiftHeapObject: return .swiftHeapObject - case .unsafePointer: return .unsafePointer - case .swiftProtocol: return .jsObject - case .void: return .void case .nullable(let wrappedType, _): - let wrappedInfo = try wrappedType.liftParameterInfo() - if wrappedInfo.parameters.isEmpty { - return LiftingIntrinsicInfo(parameters: []) + let wrappedParams = try wrappedType.liftParameterInfo() + if wrappedParams.isEmpty { + return [] } var optionalParams: [(name: String, type: WasmCoreType)] = [("isSome", .i32)] - optionalParams.append(contentsOf: wrappedInfo.parameters) - return LiftingIntrinsicInfo(parameters: optionalParams) - case .caseEnum: return .caseEnum - case .rawValueEnum(_, let rawType): - return rawType.liftingIntrinsicInfo - case .associatedValueEnum: - return .associatedValueEnum - case .swiftStruct: - return LiftingIntrinsicInfo(parameters: []) + optionalParams.append(contentsOf: wrappedParams) + return optionalParams case .namespaceEnum: throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") - case .closure: - return LiftingIntrinsicInfo(parameters: [("callbackId", .i32)]) - case .array, .dictionary: - return LiftingIntrinsicInfo(parameters: []) + default: + return descriptor.wasmParams } } - struct LoweringIntrinsicInfo: Sendable { - let returnType: WasmCoreType? - - static let bool = LoweringIntrinsicInfo(returnType: .i32) - static let int = LoweringIntrinsicInfo(returnType: .i32) - static let float = LoweringIntrinsicInfo(returnType: .f32) - static let double = LoweringIntrinsicInfo(returnType: .f64) - static let string = LoweringIntrinsicInfo(returnType: nil) - static let jsObject = LoweringIntrinsicInfo(returnType: .i32) - static let jsValue = LoweringIntrinsicInfo(returnType: nil) - static let swiftHeapObject = LoweringIntrinsicInfo(returnType: .pointer) - static let unsafePointer = LoweringIntrinsicInfo(returnType: .pointer) - static let void = LoweringIntrinsicInfo(returnType: nil) - static let caseEnum = LoweringIntrinsicInfo(returnType: .i32) - static let rawValueEnum = LoweringIntrinsicInfo(returnType: .i32) - static let associatedValueEnum = LoweringIntrinsicInfo(returnType: nil) - static let swiftStruct = LoweringIntrinsicInfo(returnType: nil) - static let optional = LoweringIntrinsicInfo(returnType: nil) - static let array = LoweringIntrinsicInfo(returnType: nil) - } - - func loweringReturnInfo() throws -> LoweringIntrinsicInfo { + func loweringReturnInfo() throws -> WasmCoreType? { switch self { - case .bool: return .bool - case .int, .uint: return .int - case .float: return .float - case .double: return .double - case .string: return .string - case .jsObject: return .jsObject - case .jsValue: return .jsValue - case .swiftHeapObject: return .swiftHeapObject - case .unsafePointer: return .unsafePointer - case .swiftProtocol: return .jsObject - case .void: return .void - case .nullable: return .optional - case .caseEnum: return .caseEnum - case .rawValueEnum(_, let rawType): - return rawType.loweringIntrinsicInfo - case .associatedValueEnum: - return .associatedValueEnum - case .swiftStruct: - return .swiftStruct + case .nullable: + return nil case .namespaceEnum: throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") - case .closure: - return .jsObject - case .array, .dictionary: - return .array - } - } -} - -extension SwiftEnumRawType { - var liftingIntrinsicInfo: BridgeType.LiftingIntrinsicInfo { - switch self { - case .bool: return .bool - case .int, .int32, .int64, .uint, .uint32, .uint64: return .int - case .float: return .float - case .double: return .double - case .string: return .string - } - } - - var loweringIntrinsicInfo: BridgeType.LoweringIntrinsicInfo { - switch self { - case .bool: return .bool - case .int, .int32, .int64, .uint, .uint32, .uint64: return .int - case .float: return .float - case .double: return .double - case .string: return .string + default: + return descriptor.wasmReturnType } } } @@ -1765,17 +1613,6 @@ extension DeclModifierSyntax { } } - var isAtLeastPackage: Bool { - switch self.name.tokenKind { - case .keyword(.private): false - case .keyword(.fileprivate): false - case .keyword(.internal): true - case .keyword(.package): true - case .keyword(.public): true - case .keyword(.open): true - default: false - } - } } extension WithModifiersSyntax { diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 8eead20b7..9416d3457 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -695,143 +695,51 @@ enum SwiftCodePattern { extension BridgeType { struct LoweringParameterInfo { let loweredParameters: [(name: String, type: WasmCoreType)] - - static let bool = LoweringParameterInfo(loweredParameters: [("value", .i32)]) - static let int = LoweringParameterInfo(loweredParameters: [("value", .i32)]) - static let float = LoweringParameterInfo(loweredParameters: [("value", .f32)]) - static let double = LoweringParameterInfo(loweredParameters: [("value", .f64)]) - static let string = LoweringParameterInfo(loweredParameters: [("value", .i32)]) - static let jsObject = LoweringParameterInfo(loweredParameters: [("value", .i32)]) - static let jsValue = LoweringParameterInfo(loweredParameters: [ - ("kind", .i32), - ("payload1", .i32), - ("payload2", .f64), - ]) - static let void = LoweringParameterInfo(loweredParameters: []) } func loweringParameterInfo(context: BridgeContext = .importTS) throws -> LoweringParameterInfo { switch self { - case .bool: return .bool - case .int, .uint: return .int - case .float: return .float - case .double: return .double - case .string: return .string - case .jsObject: return .jsObject - case .jsValue: return .jsValue - case .void: return .void - case .closure: - // Swift closure is passed to JS as a JS function reference. - return LoweringParameterInfo(loweredParameters: [("funcRef", .i32)]) - case .unsafePointer: - return LoweringParameterInfo(loweredParameters: [("pointer", .pointer)]) - case .swiftHeapObject: - return LoweringParameterInfo(loweredParameters: [("pointer", .pointer)]) - case .swiftProtocol: - throw BridgeJSCoreError("swiftProtocol is not supported in imported signatures") - case .caseEnum: - switch context { - case .importTS: - throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") - case .exportSwift: - return LoweringParameterInfo(loweredParameters: [("value", .i32)]) - } - case .rawValueEnum(_, let rawType): - let wasmType = rawType.wasmCoreType ?? .i32 - return LoweringParameterInfo(loweredParameters: [("value", wasmType)]) - case .associatedValueEnum: - switch context { - case .importTS: - throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") - case .exportSwift: - return LoweringParameterInfo(loweredParameters: [("caseId", .i32)]) - } - case .swiftStruct: - switch context { - case .importTS: - // Swift structs are bridged as JS objects (object IDs) in imported signatures. - return LoweringParameterInfo(loweredParameters: [("objectId", .i32)]) - case .exportSwift: - return LoweringParameterInfo(loweredParameters: []) - } - case .namespaceEnum: - throw BridgeJSCoreError("Namespace enums cannot be used as parameters") case .nullable(let wrappedType, _): let wrappedInfo = try wrappedType.loweringParameterInfo(context: context) var params = [("isSome", WasmCoreType.i32)] params.append(contentsOf: wrappedInfo.loweredParameters) return LoweringParameterInfo(loweredParameters: params) - case .array, .dictionary: - return LoweringParameterInfo(loweredParameters: []) + case .namespaceEnum: + throw BridgeJSCoreError("Namespace enums cannot be used as parameters") + case .swiftProtocol: + throw BridgeJSCoreError("swiftProtocol is not supported in imported signatures") + case .caseEnum, .associatedValueEnum: + if context == .importTS { + throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") + } + fallthrough + default: + return LoweringParameterInfo(loweredParameters: descriptor.importParams) } } struct LiftingReturnInfo { let valueToLift: WasmCoreType? - - static let bool = LiftingReturnInfo(valueToLift: .i32) - static let int = LiftingReturnInfo(valueToLift: .i32) - static let float = LiftingReturnInfo(valueToLift: .f32) - static let double = LiftingReturnInfo(valueToLift: .f64) - static let string = LiftingReturnInfo(valueToLift: .i32) - static let jsObject = LiftingReturnInfo(valueToLift: .i32) - static let jsValue = LiftingReturnInfo(valueToLift: nil) - static let void = LiftingReturnInfo(valueToLift: nil) } func liftingReturnInfo( context: BridgeContext = .importTS ) throws -> LiftingReturnInfo { switch self { - case .bool: return .bool - case .int, .uint: return .int - case .float: return .float - case .double: return .double - case .string: return .string - case .jsObject: return .jsObject - case .jsValue: return .jsValue - case .void: return .void - case .closure: - // JS returns a callback ID for closures, which Swift lifts to a typed closure. - return LiftingReturnInfo(valueToLift: .i32) - case .unsafePointer: - return LiftingReturnInfo(valueToLift: .pointer) - case .swiftHeapObject: - return LiftingReturnInfo(valueToLift: .pointer) + case .nullable(let wrappedType, _): + let wrappedInfo = try wrappedType.liftingReturnInfo(context: context) + return LiftingReturnInfo(valueToLift: wrappedInfo.valueToLift) + case .namespaceEnum: + throw BridgeJSCoreError("Namespace enums cannot be used as return values") case .swiftProtocol: throw BridgeJSCoreError("swiftProtocol is not supported in imported signatures") - case .caseEnum: - switch context { - case .importTS: - throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") - case .exportSwift: - return LiftingReturnInfo(valueToLift: .i32) - } - case .rawValueEnum(_, let rawType): - let wasmType = rawType.wasmCoreType ?? .i32 - return LiftingReturnInfo(valueToLift: wasmType) - case .associatedValueEnum: - switch context { - case .importTS: + case .caseEnum, .associatedValueEnum: + if context == .importTS { throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") - case .exportSwift: - return LiftingReturnInfo(valueToLift: .i32) - } - case .swiftStruct: - switch context { - case .importTS: - // Swift structs are bridged as JS objects (object IDs) in imported signatures. - return LiftingReturnInfo(valueToLift: .i32) - case .exportSwift: - return LiftingReturnInfo(valueToLift: nil) } - case .namespaceEnum: - throw BridgeJSCoreError("Namespace enums cannot be used as return values") - case .nullable(let wrappedType, _): - let wrappedInfo = try wrappedType.liftingReturnInfo(context: context) - return LiftingReturnInfo(valueToLift: wrappedInfo.valueToLift) - case .array, .dictionary: - return LiftingReturnInfo(valueToLift: nil) + fallthrough + default: + return LiftingReturnInfo(valueToLift: descriptor.importReturnType) } } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 44b6f0c38..ce844b7bb 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -1534,16 +1534,8 @@ public struct BridgeJSLink { let enumValuesName = enumDefinition.valuesName switch enumDefinition.enumType { - case .simple: - let fragment = IntrinsicJSFragment.simpleEnumHelper(enumDefinition: enumDefinition) - _ = try fragment.printCode([enumValuesName], context) - jsTopLevelLines.append(contentsOf: printer.lines) - case .rawValue: - guard enumDefinition.rawType != nil else { - throw BridgeJSLinkError(message: "Raw value enum \(enumDefinition.name) is missing rawType") - } - - let fragment = IntrinsicJSFragment.rawValueEnumHelper(enumDefinition: enumDefinition) + case .simple, .rawValue: + let fragment = IntrinsicJSFragment.caseEnumHelper(enumDefinition: enumDefinition) _ = try fragment.printCode([enumValuesName], context) jsTopLevelLines.append(contentsOf: printer.lines) case .associatedValue: @@ -2172,7 +2164,7 @@ extension BridgeJSLink { printer.write("} catch (error) {") printer.indent { printer.write("setException(error);") - if let abiReturnType = returnType.abiReturnType { + if !returnType.isOptional, let abiReturnType = returnType.descriptor.wasmReturnType { printer.write("return \(abiReturnType.placeholderValue)") } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index ffb50c7a8..869043255 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -84,6 +84,12 @@ final class JSGlueVariableScope { return suffixedName } + func reserveNames(_ names: [String]) { + for name in names { + variables.insert(name) + } + } + func registerIntrinsic(_ name: String, build: (CodeFragmentPrinter) throws -> Void) rethrows { try intrinsicRegistry.register(name: name, build: build) } @@ -109,17 +115,7 @@ extension JSGlueVariableScope { printer.write("\(JSGlueVariableScope.reservedPointerStack).push(\(value));") } - // MARK: Return - - func emitPushI32Return(_ value: String, printer: CodeFragmentPrinter) { - printer.write("\(JSGlueVariableScope.reservedI32Stack).push(\(value));") - } - func emitPushF64Return(_ value: String, printer: CodeFragmentPrinter) { - printer.write("\(JSGlueVariableScope.reservedF64Stack).push(\(value));") - } - func emitPushPointerReturn(_ value: String, printer: CodeFragmentPrinter) { - printer.write("\(JSGlueVariableScope.reservedPointerStack).push(\(value));") - } + // MARK: Pop func popString() -> String { return "\(JSGlueVariableScope.reservedStringStack).pop()" } @@ -160,6 +156,10 @@ struct IntrinsicJSFragment: Sendable { } } + private static func emitCleanupCall(_ cleanupExpr: String, into printer: CodeFragmentPrinter) { + printer.write("\(cleanupExpr) && \(cleanupExpr)();") + } + /// A function that prints the fragment code. /// /// - Parameters: @@ -204,36 +204,7 @@ struct IntrinsicJSFragment: Sendable { } ) - /// NOTE: JavaScript engine itself converts booleans to integers when passing them to - /// Wasm functions, so we don't need to do anything here - static let boolLowerParameter = identity - static let boolLiftReturn = IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, _ in - return ["\(arguments[0]) !== 0"] - } - ) - static let boolLiftParameter = IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, _ in - return ["\(arguments[0]) !== 0"] - } - ) - static let boolLowerReturn = IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, _ in - return ["\(arguments[0]) ? 1 : 0"] - } - ) - - /// Convert signed Int32 to unsigned for UInt values - static let uintLiftReturn = IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, _ in - return ["\(arguments[0]) >>> 0"] - } - ) - static let uintLiftParameter = uintLiftReturn + // MARK: - String Fragments static let stringLowerParameter = IntrinsicJSFragment( parameters: ["value"], @@ -242,8 +213,12 @@ struct IntrinsicJSFragment: Sendable { let argument = arguments[0] let bytesLabel = scope.variable("\(argument)Bytes") let bytesIdLabel = scope.variable("\(argument)Id") - printer.write("const \(bytesLabel) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(argument));") - printer.write("const \(bytesIdLabel) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesLabel));") + printer.write( + "const \(bytesLabel) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(argument));" + ) + printer.write( + "const \(bytesIdLabel) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesLabel));" + ) cleanupCode.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(bytesIdLabel));") return [bytesIdLabel, "\(bytesLabel).length"] } @@ -265,7 +240,9 @@ struct IntrinsicJSFragment: Sendable { let objectId = arguments[0] let objectLabel = scope.variable("\(objectId)Object") // TODO: Implement "take" operation - printer.write("const \(objectLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(objectId));") + printer.write( + "const \(objectLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(objectId));" + ) printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(objectId));") return [objectLabel] } @@ -281,37 +258,32 @@ struct IntrinsicJSFragment: Sendable { } ) + // MARK: - JSObject Fragments + static let jsObjectLowerParameter = IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, _ in return ["swift.memory.retain(\(arguments[0]))"] } ) - static let jsObjectLiftReturn = IntrinsicJSFragment( - parameters: ["retId"], - printCode: { arguments, context in - let (scope, printer, cleanupCode) = (context.scope, context.printer, context.cleanupCode) - // TODO: Implement "take" operation - let resultLabel = scope.variable("ret") - let retId = arguments[0] - printer.write("const \(resultLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(retId));") - printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(retId));") - return [resultLabel] - } - ) - static let jsObjectLiftRetainedObjectId = IntrinsicJSFragment( - parameters: ["objectId"], - printCode: { arguments, context in - let (scope, printer, cleanupCode) = (context.scope, context.printer, context.cleanupCode) - let resultLabel = scope.variable("value") - let objectId = arguments[0] - printer.write( - "const \(resultLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(objectId));" - ) - printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(objectId));") - return [resultLabel] - } - ) + private static func jsObjectTakeRetained(hint: String = "ret") -> IntrinsicJSFragment { + IntrinsicJSFragment( + parameters: ["objectId"], + printCode: { arguments, context in + let (scope, printer, _) = (context.scope, context.printer, context.cleanupCode) + // TODO: Implement "take" operation + let resultLabel = scope.variable(hint) + let objectId = arguments[0] + printer.write( + "const \(resultLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(objectId));" + ) + printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(objectId));") + return [resultLabel] + } + ) + } + static let jsObjectLiftReturn = jsObjectTakeRetained(hint: "ret") + static let jsObjectLiftRetainedObjectId = jsObjectTakeRetained(hint: "value") static let jsObjectLiftParameter = IntrinsicJSFragment( parameters: ["objectId"], printCode: { arguments, _ in @@ -325,6 +297,8 @@ struct IntrinsicJSFragment: Sendable { } ) + // MARK: - JSValue Fragments + private static let jsValueLowerHelperName = "__bjs_jsValueLower" private static let jsValueLiftHelperName = "__bjs_jsValueLift" @@ -395,7 +369,9 @@ struct IntrinsicJSFragment: Sendable { printer.write("case \"string\":") printer.indent { printer.write("\(kindVar) = 1;") - printer.write("\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") + printer.write( + "\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));" + ) printer.write("\(payload2Var) = 0;") printer.write("break;") } @@ -409,28 +385,36 @@ struct IntrinsicJSFragment: Sendable { printer.write("case \"object\":") printer.indent { printer.write("\(kindVar) = 3;") - printer.write("\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") + printer.write( + "\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));" + ) printer.write("\(payload2Var) = 0;") printer.write("break;") } printer.write("case \"function\":") printer.indent { printer.write("\(kindVar) = 3;") - printer.write("\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") + printer.write( + "\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));" + ) printer.write("\(payload2Var) = 0;") printer.write("break;") } printer.write("case \"symbol\":") printer.indent { printer.write("\(kindVar) = 7;") - printer.write("\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") + printer.write( + "\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));" + ) printer.write("\(payload2Var) = 0;") printer.write("break;") } printer.write("case \"bigint\":") printer.indent { printer.write("\(kindVar) = 8;") - printer.write("\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") + printer.write( + "\(payload1Var) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));" + ) printer.write("\(payload2Var) = 0;") printer.write("break;") } @@ -529,45 +513,20 @@ struct IntrinsicJSFragment: Sendable { } static func jsValueLowerReturn(context: BridgeContext) -> IntrinsicJSFragment { - switch context { - case .importTS: - // Return values from imported JS functions should be delivered to the Swift side - // via the parameter stacks that `_swift_js_pop_*` read from. - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let lowered = try jsValueLower.printCode(arguments, context) - let kindVar = lowered[0] - let payload1Var = lowered[1] - let payload2Var = lowered[2] - scope.emitPushI32Parameter(kindVar, printer: printer) - scope.emitPushI32Parameter(payload1Var, printer: printer) - scope.emitPushF64Parameter(payload2Var, printer: printer) - return [] - } - ) - case .exportSwift: - // Kept for symmetry, though JSValue return for export currently relies on Swift pushing - // to tmpRet stacks directly. - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let lowered = try jsValueLower.printCode( - arguments, - context - ) - let kindVar = lowered[0] - let payload1Var = lowered[1] - let payload2Var = lowered[2] - scope.emitPushI32Parameter(kindVar, printer: printer) - scope.emitPushI32Parameter(payload1Var, printer: printer) - scope.emitPushF64Parameter(payload2Var, printer: printer) - return [] - } - ) - } + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + let (scope, printer) = (context.scope, context.printer) + let lowered = try jsValueLower.printCode(arguments, context) + let kindVar = lowered[0] + let payload1Var = lowered[1] + let payload2Var = lowered[2] + scope.emitPushI32Parameter(kindVar, printer: printer) + scope.emitPushI32Parameter(payload1Var, printer: printer) + scope.emitPushF64Parameter(payload2Var, printer: printer) + return [] + } + ) } static let jsValueLift = IntrinsicJSFragment( @@ -601,12 +560,17 @@ struct IntrinsicJSFragment: Sendable { } ) - static let swiftHeapObjectLowerParameter = IntrinsicJSFragment( + // MARK: - SwiftHeapObject Fragments + + private static let swiftHeapObjectExtractPointer = IntrinsicJSFragment( parameters: ["value"], - printCode: { arguments, context in + printCode: { arguments, _ in return ["\(arguments[0]).pointer"] } ) + static let swiftHeapObjectLowerParameter = swiftHeapObjectExtractPointer + static let swiftHeapObjectLowerReturn = swiftHeapObjectExtractPointer + static func swiftHeapObjectLiftReturn(_ name: String) -> IntrinsicJSFragment { return IntrinsicJSFragment( parameters: ["value"], @@ -625,25 +589,21 @@ struct IntrinsicJSFragment: Sendable { } ) } - static let swiftHeapObjectLowerReturn = IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - return ["\(arguments[0]).pointer"] - } - ) + + // MARK: - Associated Enum Fragments static func associatedEnumLowerParameter(enumBase: String) -> IntrinsicJSFragment { IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in - let (printer, cleanup) = (context.printer, context.cleanupCode) + let (scope, printer, cleanup) = (context.scope, context.printer, context.cleanupCode) let value = arguments[0] - let caseIdName = "\(value)CaseId" - let cleanupName = "\(value)Cleanup" + let caseIdName = scope.variable("\(value)CaseId") + let cleanupName = scope.variable("\(value)Cleanup") printer.write( "const { caseId: \(caseIdName), cleanup: \(cleanupName) } = \(JSGlueVariableScope.reservedEnumHelpers).\(enumBase).lower(\(value));" ) - cleanup.write("if (\(cleanupName)) { \(cleanupName)(); }") + emitCleanupCall(cleanupName, into: cleanup) return [caseIdName] } ) @@ -663,446 +623,476 @@ struct IntrinsicJSFragment: Sendable { ) } - static func optionalLiftParameter(wrappedType: BridgeType, kind: JSOptionalKind) throws -> IntrinsicJSFragment { - if case .jsValue = wrappedType { + // MARK: - Optional Handling + + static func optionalLiftParameter( + wrappedType: BridgeType, + kind: JSOptionalKind, + context bridgeContext: BridgeContext = .importTS + ) throws -> IntrinsicJSFragment { + let desc = wrappedType.descriptor + if let glue = desc.jsCoercion, desc.wasmParams.count == 1 { return IntrinsicJSFragment( - parameters: ["isSome", "kind", "payload1", "payload2"], - printCode: { arguments, context in + parameters: ["isSome", "wrappedValue"], + printCode: { arguments, _ in let isSome = arguments[0] - let lifted = try jsValueLiftParameter.printCode( - [arguments[1], arguments[2], arguments[3]], - context - ) - let valueExpr = lifted.first ?? "undefined" - return ["\(isSome) ? \(valueExpr) : null"] + let wrappedValue = arguments[1] + let absenceLiteral = kind.absenceLiteral + if let coerce = glue.liftCoerce { + let coerced = coerce.replacingOccurrences(of: "$0", with: wrappedValue) + return ["\(isSome) ? \(coerced) : \(absenceLiteral)"] + } + return ["\(isSome) ? \(wrappedValue) : \(absenceLiteral)"] } ) } + let innerFragment = try liftParameter(type: wrappedType, context: bridgeContext) + return compositeOptionalLiftParameter( + wrappedType: wrappedType, + kind: kind, + innerFragment: innerFragment + ) + } + + private static func compositeOptionalLiftParameter( + wrappedType: BridgeType, + kind: JSOptionalKind, + innerFragment: IntrinsicJSFragment + ) -> IntrinsicJSFragment { + let desc = wrappedType.descriptor + let isStackConvention = desc.optionalConvention == .stackABI + let absenceLiteral = kind.absenceLiteral + + let outerParams: [String] + if isStackConvention { + outerParams = ["isSome"] + } else { + outerParams = ["isSome"] + innerFragment.parameters + } + return IntrinsicJSFragment( - parameters: ["isSome", "wrappedValue"], + parameters: outerParams, printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) let isSome = arguments[0] - let wrappedValue = arguments[1] - let resultExpr: String - let absenceLiteral = kind.absenceLiteral - - switch wrappedType { - case .int, .float, .double, .caseEnum: - resultExpr = "\(isSome) ? \(wrappedValue) : \(absenceLiteral)" - case .bool: - resultExpr = "\(isSome) ? \(wrappedValue) !== 0 : \(absenceLiteral)" - case .string: - let objectLabel = scope.variable("obj") - printer.write("let \(objectLabel);") - printer.write("if (\(isSome)) {") - printer.indent { - printer.write( - "\(objectLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(wrappedValue));" - ) - printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(wrappedValue));") - } - printer.write("}") - resultExpr = "\(isSome) ? \(objectLabel) : \(absenceLiteral)" - case .closure: - resultExpr = - "\(isSome) ? \(JSGlueVariableScope.reservedSwift).memory.getObject(\(wrappedValue)) : \(absenceLiteral)" - case .swiftHeapObject(let name): - resultExpr = "\(isSome) ? _exports['\(name)'].__construct(\(wrappedValue)) : \(absenceLiteral)" - case .jsObject: - resultExpr = - "\(isSome) ? \(JSGlueVariableScope.reservedSwift).memory.getObject(\(wrappedValue)) : \(absenceLiteral)" - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - let objectLabel = scope.variable("obj") - printer.write("let \(objectLabel);") - printer.write("if (\(isSome)) {") - printer.indent { - printer.write( - "\(objectLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(wrappedValue));" - ) - printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(wrappedValue));") - } - printer.write("}") - resultExpr = "\(isSome) ? \(objectLabel) : \(absenceLiteral)" - case .bool: - resultExpr = "\(isSome) ? \(wrappedValue) !== 0 : \(absenceLiteral)" - default: - resultExpr = "\(isSome) ? \(wrappedValue) : \(absenceLiteral)" - } - case .associatedValueEnum(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName - let enumVar = scope.variable("enumValue") - printer.write("let \(enumVar);") - printer.write("if (\(isSome)) {") - printer.indent { - printer.write( - "\(enumVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(wrappedValue));" - ) - } - printer.write("}") - resultExpr = "\(isSome) ? \(enumVar) : \(absenceLiteral)" - case .swiftStruct(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName - let structVar = scope.variable("structValue") - printer.write("let \(structVar);") + let innerArgs = isStackConvention ? [] : Array(arguments.dropFirst()) + + let bufferPrinter = CodeFragmentPrinter() + let innerResults = try innerFragment.printCode( + innerArgs, + context.with(\.printer, bufferPrinter) + ) + + let hasSideEffects = !bufferPrinter.lines.isEmpty + let innerExpr = innerResults.first ?? "undefined" + + if hasSideEffects { + let resultVar = scope.variable("optResult") + printer.write("let \(resultVar);") printer.write("if (\(isSome)) {") printer.indent { - printer.write( - "\(structVar) = \(JSGlueVariableScope.reservedStructHelpers).\(base).lift();" - ) - } - printer.write("} else {") - printer.indent { - printer.write("\(structVar) = \(absenceLiteral);") - } - printer.write("}") - resultExpr = structVar - case .array(let elementType): - let arrayVar = scope.variable("arrayValue") - printer.write("let \(arrayVar);") - printer.write("if (\(isSome)) {") - try printer.indent { - let arrayLiftFragment = try arrayLift(elementType: elementType) - let liftResults = try arrayLiftFragment.printCode([], context) - if let liftResult = liftResults.first { - printer.write("\(arrayVar) = \(liftResult);") + for line in bufferPrinter.lines { + printer.write(line) } + printer.write("\(resultVar) = \(innerExpr);") } printer.write("} else {") printer.indent { - printer.write("\(arrayVar) = \(absenceLiteral);") + printer.write("\(resultVar) = \(absenceLiteral);") } printer.write("}") - resultExpr = arrayVar - case .dictionary(let valueType): - let dictVar = scope.variable("dictValue") - printer.write("let \(dictVar);") - printer.write("if (\(isSome)) {") - try printer.indent { - let dictLiftFragment = try dictionaryLift(valueType: valueType) - let liftResults = try dictLiftFragment.printCode([], context) - if let liftResult = liftResults.first { - printer.write("\(dictVar) = \(liftResult);") - } - } - printer.write("} else {") - printer.indent { - printer.write("\(dictVar) = \(absenceLiteral);") + return [resultVar] + } else { + return ["\(isSome) ? \(innerExpr) : \(absenceLiteral)"] + } + } + ) + } + + static func optionalLowerParameter( + wrappedType: BridgeType, + kind: JSOptionalKind + ) throws -> IntrinsicJSFragment { + let desc = wrappedType.descriptor + if let glue = desc.jsCoercion, desc.wasmParams.count == 1 { + let wasmType = desc.wasmParams[0].type + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + let (scope, printer) = (context.scope, context.printer) + let value = arguments[0] + let isSomeVar = scope.variable("isSome") + let presenceExpr = kind.presenceCheck(value: value) + printer.write("const \(isSomeVar) = \(presenceExpr);") + let coerced: String + if let coerce = glue.lowerCoerce { + coerced = coerce.replacingOccurrences(of: "$0", with: value) + } else { + coerced = value } - printer.write("}") - resultExpr = dictVar - default: - resultExpr = "\(isSome) ? \(wrappedValue) : \(absenceLiteral)" + return ["+\(isSomeVar)", "\(isSomeVar) ? \(coerced) : \(wasmType.jsZeroLiteral)"] } + ) + } - return [resultExpr] - } + let innerFragment = try lowerParameter(type: wrappedType) + return try compositeOptionalLowerParameter( + wrappedType: wrappedType, + kind: kind, + innerFragment: innerFragment ) } - static func optionalLowerParameter(wrappedType: BridgeType) throws -> IntrinsicJSFragment { + private static func compositeOptionalLowerParameter( + wrappedType: BridgeType, + kind: JSOptionalKind, + innerFragment: IntrinsicJSFragment + ) throws -> IntrinsicJSFragment { + let desc = wrappedType.descriptor + let isStackConvention = desc.optionalConvention == .stackABI + return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in let (scope, printer, cleanupCode) = (context.scope, context.printer, context.cleanupCode) let value = arguments[0] + scope.reserveNames(arguments) let isSomeVar = scope.variable("isSome") - printer.write("const \(isSomeVar) = \(value) != null;") + let presenceExpr = kind.presenceCheck(value: value) + printer.write("const \(isSomeVar) = \(presenceExpr);") - switch wrappedType { - case .swiftStruct(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName - let cleanupVar = scope.variable("\(value)Cleanup") - printer.write("let \(cleanupVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let resultVar = scope.variable("structResult") - printer.write( - "const \(resultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(base).lower(\(value));" - ) - printer.write("\(cleanupVar) = \(resultVar).cleanup;") - } - printer.write("}") - cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") - printer.write("\(JSGlueVariableScope.reservedI32Stack).push(+\(isSomeVar));") - return [] - case .string, .rawValueEnum(_, .string): - let bytesVar = scope.variable("\(value)Bytes") - let idVar = scope.variable("\(value)Id") + let innerCleanup = CodeFragmentPrinter() + var resultVars: [String] = [] - printer.write("let \(idVar), \(bytesVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - printer.write("\(bytesVar) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(value));") - printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));") + if !isStackConvention { + for param in desc.wasmParams { + resultVars.append(scope.variable(param.name)) } - printer.write("}") - cleanupCode.write("if (\(idVar) != undefined) {") - cleanupCode.indent { - cleanupCode.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") + if !resultVars.isEmpty { + printer.write("let \(resultVars.joined(separator: ", "));") } - cleanupCode.write("}") - - return ["+\(isSomeVar)", "\(isSomeVar) ? \(idVar) : 0", "\(isSomeVar) ? \(bytesVar).length : 0"] - case .jsValue: - let lowered = try jsValueLower.printCode([value], context) - return ["+\(isSomeVar)"] + lowered - case .associatedValueEnum(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName - let caseIdVar = scope.variable("\(value)CaseId") - let cleanupVar = scope.variable("\(value)Cleanup") + } - printer.write("let \(caseIdVar), \(cleanupVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let resultVar = scope.variable("enumResult") - printer.write( - "const \(resultVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lower(\(value));" - ) - printer.write("\(caseIdVar) = \(resultVar).caseId;") - printer.write("\(cleanupVar) = \(resultVar).cleanup;") - } - printer.write("}") - cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") - - return ["+\(isSomeVar)", "\(isSomeVar) ? \(caseIdVar) : 0"] - case .rawValueEnum: - // Raw value enums with optional - falls through to handle based on raw type - return ["+\(isSomeVar)", "\(isSomeVar) ? \(value) : 0"] - case .array(let elementType): - let cleanupArrayVar = scope.variable("\(value)Cleanups") - printer.write("const \(cleanupArrayVar) = [];") - printer.write("if (\(isSomeVar)) {") - try printer.indent { - let arrayLowerFragment = try arrayLower(elementType: elementType) - let arrayCleanup = CodeFragmentPrinter() - let _ = try arrayLowerFragment.printCode( - [value], - context.with(\.cleanupCode, arrayCleanup) - ) - if !arrayCleanup.lines.isEmpty { - for line in arrayCleanup.lines { - printer.write("\(cleanupArrayVar).push(() => { \(line) });") - } + let ifBodyPrinter = CodeFragmentPrinter() + let innerResults = try innerFragment.printCode( + [value], + context.with(\.printer, ifBodyPrinter).with(\.cleanupCode, innerCleanup) + ) + + let innerCleanupLines = innerCleanup.lines.filter { + !$0.trimmingCharacters(in: .whitespaces).isEmpty + } + let releasePrefix = "\(JSGlueVariableScope.reservedSwift).memory.release(" + let inlineCleanupResultIndex: Int? = { + guard innerCleanupLines.count == 1 else { return nil } + let cleanupLine = innerCleanupLines[0].trimmingCharacters(in: .whitespacesAndNewlines) + for (index, innerResult) in innerResults.enumerated() where index < resultVars.count { + if cleanupLine == "\(releasePrefix)\(innerResult));" { + return index } } - printer.write("}") - cleanupCode.write("for (const cleanup of \(cleanupArrayVar)) { cleanup(); }") - printer.write("\(JSGlueVariableScope.reservedI32Stack).push(+\(isSomeVar));") - return [] - case .dictionary(let valueType): - let cleanupArrayVar = scope.variable("\(value)Cleanups") - printer.write("const \(cleanupArrayVar) = [];") - printer.write("if (\(isSomeVar)) {") - try printer.indent { - let dictLowerFragment = try dictionaryLower(valueType: valueType) - let dictCleanup = CodeFragmentPrinter() - let _ = try dictLowerFragment.printCode( - [value], - context.with(\.cleanupCode, dictCleanup) - ) - if !dictCleanup.lines.isEmpty { - for line in dictCleanup.lines { - printer.write("\(cleanupArrayVar).push(() => { \(line) });") - } + return nil + }() + let hasCleanup = !innerCleanupLines.isEmpty + let cleanupVar = hasCleanup && inlineCleanupResultIndex == nil ? scope.variable("\(value)Cleanup") : nil + + if let cleanupVar { + printer.write("let \(cleanupVar);") + } + printer.write("if (\(isSomeVar)) {") + printer.indent { + for line in ifBodyPrinter.lines { + printer.write(line) + } + + if !isStackConvention { + for (resultVar, innerResult) in zip(resultVars, innerResults) { + printer.write("\(resultVar) = \(innerResult);") } } - printer.write("}") - cleanupCode.write("for (const cleanup of \(cleanupArrayVar)) { cleanup(); }") - printer.write("\(JSGlueVariableScope.reservedI32Stack).push(+\(isSomeVar));") - return [] - default: - switch wrappedType { - case .swiftHeapObject: - return ["+\(isSomeVar)", "\(isSomeVar) ? \(value).pointer : 0"] - case .swiftProtocol: - return [ - "+\(isSomeVar)", - "\(isSomeVar) ? \(JSGlueVariableScope.reservedSwift).memory.retain(\(value)) : 0", - ] - case .jsObject: - let idVar = scope.variable("id") - printer.write("let \(idVar);") - printer.write("if (\(isSomeVar)) {") + + if let cleanupVar { + printer.write("\(cleanupVar) = () => {") printer.indent { - printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") - } - printer.write("}") - cleanupCode.write("if (\(idVar) !== undefined) {") - cleanupCode.indent { - cleanupCode.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") + for line in innerCleanupLines { + printer.write(line) + } } - cleanupCode.write("}") - return ["+\(isSomeVar)", "\(isSomeVar) ? \(idVar) : 0"] - default: - return ["+\(isSomeVar)", "\(isSomeVar) ? \(value) : 0"] + printer.write("};") } } - } - ) - } - static func optionalLiftReturn( - wrappedType: BridgeType, - kind: JSOptionalKind - ) -> IntrinsicJSFragment { - let absenceLiteral = kind.absenceLiteral + let hasPlaceholders = !isStackConvention && !desc.wasmParams.isEmpty + if hasPlaceholders { + printer.write("} else {") + printer.indent { + for (resultVar, param) in zip(resultVars, desc.wasmParams) { + printer.write("\(resultVar) = \(param.type.jsZeroLiteral);") + } + } + } + printer.write("}") + if let inlineCleanupResultIndex { + let retainedVar = resultVars[inlineCleanupResultIndex] + cleanupCode.write("if (\(isSomeVar)) { \(releasePrefix)\(retainedVar)); }") + } else if let cleanupVar { + emitCleanupCall(cleanupVar, into: cleanupCode) + } + + if isStackConvention { + scope.emitPushI32Parameter("+\(isSomeVar)", printer: printer) + return [] + } else { + return ["+\(isSomeVar)"] + resultVars + } + } + ) + } + + private static func optionalLiftReturnFromStorage(storage: String) -> IntrinsicJSFragment { + IntrinsicJSFragment( + parameters: [], + printCode: { _, context in + let (scope, printer) = (context.scope, context.printer) + let resultVar = scope.variable("optResult") + printer.write("const \(resultVar) = \(storage);") + printer.write("\(storage) = undefined;") + return [resultVar] + } + ) + } + + private static func optionalLiftReturnWithPresenceFlag( + wrappedType: BridgeType, + kind: JSOptionalKind + ) -> IntrinsicJSFragment { + let absenceLiteral = kind.absenceLiteral return IntrinsicJSFragment( parameters: [], - printCode: { arguments, context in + printCode: { _, context in let (scope, printer) = (context.scope, context.printer) + let isSomeVar = scope.variable("isSome") + printer.write("const \(isSomeVar) = \(scope.popI32());") + + let innerFragment = try liftReturn(type: wrappedType) + + let innerPrinter = CodeFragmentPrinter() + let innerResults = try innerFragment.printCode([], context.with(\.printer, innerPrinter)) + let innerExpr = innerResults.first ?? "undefined" + + if innerPrinter.lines.isEmpty { + return ["\(isSomeVar) ? \(innerExpr) : \(absenceLiteral)"] + } + let resultVar = scope.variable("optResult") - switch wrappedType { - case .bool: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalBool);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalBool) = undefined;") - case .int, .uint: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalInt);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalInt) = undefined;") - case .float: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalFloat);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalFloat) = undefined;") - case .double: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalDouble);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalDouble) = undefined;") - case .string: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnString);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;") - case .jsObject, .swiftProtocol: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnString);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;") - case .swiftHeapObject(let className): - let pointerVar = scope.variable("pointer") - printer.write( - "const \(pointerVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalHeapObject);" - ) - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalHeapObject) = undefined;") - let constructExpr = - context.hasDirectAccessToSwiftClass - ? "\(className).__construct(\(pointerVar))" - : "_exports['\(className)'].__construct(\(pointerVar))" + printer.write("let \(resultVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + for line in innerPrinter.lines { + printer.write(line) + } + printer.write("\(resultVar) = \(innerExpr);") + } + printer.write("} else {") + printer.indent { + printer.write("\(resultVar) = \(absenceLiteral);") + } + printer.write("}") + return [resultVar] + } + ) + } + + private static func optionalLiftReturnAssociatedEnum( + fullName: String, + kind: JSOptionalKind + ) -> IntrinsicJSFragment { + let base = fullName.components(separatedBy: ".").last ?? fullName + let absenceLiteral = kind.absenceLiteral + return IntrinsicJSFragment( + parameters: [], + printCode: { _, context in + let (scope, printer) = (context.scope, context.printer) + let resultVar = scope.variable("optResult") + let tagVar = scope.variable("tag") + printer.write("const \(tagVar) = \(scope.popI32());") + printer.write( + "const \(resultVar) = \(tagVar) === -1 ? \(absenceLiteral) : \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(tagVar));" + ) + return [resultVar] + } + ) + } + + private static func optionalLiftReturnHeapObject( + className: String, + kind: JSOptionalKind + ) -> IntrinsicJSFragment { + let absenceLiteral = kind.absenceLiteral + return IntrinsicJSFragment( + parameters: [], + printCode: { _, context in + let (scope, printer) = (context.scope, context.printer) + let resultVar = scope.variable("optResult") + let pointerVar = scope.variable("pointer") + printer.write( + "const \(pointerVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalHeapObject);" + ) + printer.write( + "\(JSGlueVariableScope.reservedStorageToReturnOptionalHeapObject) = undefined;" + ) + let constructExpr = + context.hasDirectAccessToSwiftClass + ? "\(className).__construct(\(pointerVar))" + : "_exports['\(className)'].__construct(\(pointerVar))" + printer.write( + "const \(resultVar) = \(pointerVar) === null ? \(absenceLiteral) : \(constructExpr);" + ) + return [resultVar] + } + ) + } + + private static func optionalLiftReturnStruct( + fullName: String, + kind: JSOptionalKind + ) -> IntrinsicJSFragment { + let base = fullName.components(separatedBy: ".").last ?? fullName + let absenceLiteral = kind.absenceLiteral + return IntrinsicJSFragment( + parameters: [], + printCode: { _, context in + let (scope, printer) = (context.scope, context.printer) + let isSomeVar = scope.variable("isSome") + let resultVar = scope.variable("optResult") + printer.write("const \(isSomeVar) = \(scope.popI32());") + printer.write( + "const \(resultVar) = \(isSomeVar) ? \(JSGlueVariableScope.reservedStructHelpers).\(base).lift() : \(absenceLiteral);" + ) + return [resultVar] + } + ) + } + + static func optionalLiftReturn( + wrappedType: BridgeType, + kind: JSOptionalKind + ) -> IntrinsicJSFragment { + let desc = wrappedType.descriptor + if let glue = desc.jsCoercion, let kind = glue.optionalScalarKind { + return optionalLiftReturnFromStorage(storage: kind.storageName) + } + if case .sideChannelReturn(let mode) = desc.optionalConvention, mode != .none { + return optionalLiftReturnFromStorage(storage: JSGlueVariableScope.reservedStorageToReturnString) + } + + if case .swiftHeapObject(let className) = wrappedType { + return optionalLiftReturnHeapObject(className: className, kind: kind) + } + + if case .swiftStruct(let fullName) = wrappedType { + return optionalLiftReturnStruct(fullName: fullName, kind: kind) + } + + if desc.nilSentinel.hasSentinel, case .associatedValueEnum(let fullName) = wrappedType { + return optionalLiftReturnAssociatedEnum(fullName: fullName, kind: kind) + } + + return optionalLiftReturnWithPresenceFlag(wrappedType: wrappedType, kind: kind) + } + + private static func optionalLowerReturnToSideChannel( + mode: OptionalSideChannel, + kind: JSOptionalKind + ) -> IntrinsicJSFragment { + IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + let (scope, printer) = (context.scope, context.printer) + let value = arguments[0] + let isSomeVar = scope.variable("isSome") + let presenceExpr = kind.presenceCheck(value: value) + printer.write("const \(isSomeVar) = \(presenceExpr);") + + if mode == .storage { printer.write( - "const \(resultVar) = \(pointerVar) === null ? \(absenceLiteral) : \(constructExpr);" + "\(JSGlueVariableScope.reservedStorageToReturnString) = \(isSomeVar) ? \(value) : \(kind.absenceLiteral);" ) - case .caseEnum: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalInt);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalInt) = undefined;") - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnString);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;") - case .bool: - printer.write( - "const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalBool);" - ) - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalBool) = undefined;") - case .float: - printer.write( - "const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalFloat);" - ) - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalFloat) = undefined;") - case .double: - printer.write( - "const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalDouble);" - ) - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalDouble) = undefined;") - default: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnOptionalInt);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalInt) = undefined;") - } - case .associatedValueEnum(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName - let tagVar = scope.variable("tag") - printer.write("const \(tagVar) = \(scope.popI32());") - let isNullVar = scope.variable("isNull") - printer.write("const \(isNullVar) = (\(tagVar) === -1);") - printer.write("let \(resultVar);") - printer.write("if (\(isNullVar)) {") - printer.indent { - printer.write("\(resultVar) = \(absenceLiteral);") - } - printer.write("} else {") - printer.indent { - printer.write( - "\(resultVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(tagVar));" - ) - } - printer.write("}") - case .swiftStruct(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName - let isSomeVar = scope.variable("isSome") - printer.write("const \(isSomeVar) = \(scope.popI32());") - printer.write("let \(resultVar);") + } else { + let idVar = scope.variable("id") + printer.write("let \(idVar) = 0;") printer.write("if (\(isSomeVar)) {") printer.indent { printer.write( - "\(resultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(base).lift();" + "\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));" ) } - printer.write("} else {") - printer.indent { - printer.write("\(resultVar) = \(absenceLiteral);") - } - printer.write("}") - case .array(let elementType): - let isSomeVar = scope.variable("isSome") - printer.write("const \(isSomeVar) = \(scope.popI32());") - printer.write("let \(resultVar);") - printer.write("if (\(isSomeVar)) {") - try printer.indent { - let arrayLiftFragment = try arrayLift(elementType: elementType) - let liftResults = try arrayLiftFragment.printCode([], context) - if let liftResult = liftResults.first { - printer.write("\(resultVar) = \(liftResult);") - } - } - printer.write("} else {") - printer.indent { - printer.write("\(resultVar) = \(absenceLiteral);") - } printer.write("}") - case .dictionary(let valueType): - let isSomeVar = scope.variable("isSome") - printer.write("const \(isSomeVar) = \(scope.popI32());") - printer.write("let \(resultVar);") - printer.write("if (\(isSomeVar)) {") - try printer.indent { - let dictLiftFragment = try dictionaryLift(valueType: valueType) - let liftResults = try dictLiftFragment.printCode([], context) - if let liftResult = liftResults.first { - printer.write("\(resultVar) = \(liftResult);") - } - } - printer.write("} else {") - printer.indent { - printer.write("\(resultVar) = \(absenceLiteral);") + printer.write("bjs[\"swift_js_return_optional_object\"](\(isSomeVar) ? 1 : 0, \(idVar));") + } + + return [] + } + ) + } + + private static func optionalLowerReturnWithPresenceFlag( + wrappedType: BridgeType, + kind: JSOptionalKind, + innerFragment: IntrinsicJSFragment + ) -> IntrinsicJSFragment { + IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + let (scope, printer, cleanupCode) = (context.scope, context.printer, context.cleanupCode) + let value = arguments[0] + let isSomeVar = scope.variable("isSome") + let presenceExpr = kind.presenceCheck(value: value) + printer.write("const \(isSomeVar) = \(presenceExpr);") + + let innerPrinter = CodeFragmentPrinter() + let innerCleanup = CodeFragmentPrinter() + let innerResults = try innerFragment.printCode( + [value], + context.with(\.printer, innerPrinter).with(\.cleanupCode, innerCleanup) + ) + if !innerResults.isEmpty { + throw BridgeJSLinkError( + message: "Unsupported wrapped type for returning from JS function: \(wrappedType)" + ) + } + + let innerCleanupLines = innerCleanup.lines.filter { + !$0.trimmingCharacters(in: .whitespaces).isEmpty + } + let cleanupVar = innerCleanupLines.isEmpty ? nil : scope.variable("\(value)Cleanup") + if let cleanupVar { + printer.write("let \(cleanupVar);") + } + + printer.write("if (\(isSomeVar)) {") + printer.indent { + for line in innerPrinter.lines { + printer.write(line) } - printer.write("}") - case .jsValue: - let isSomeVar = scope.variable("isSome") - printer.write("const \(isSomeVar) = \(scope.popI32());") - printer.write("let \(resultVar);") - printer.write("if (\(isSomeVar)) {") - try printer.indent { - let lifted = try jsValueLift.printCode([], context) - if let liftedValue = lifted.first { - printer.write("\(resultVar) = \(liftedValue);") + if let cleanupVar { + printer.write("\(cleanupVar) = () => {") + printer.indent { + for line in innerCleanupLines { + printer.write(line) + } } + printer.write("};") } - printer.write("} else {") - printer.indent { - printer.write("\(resultVar) = null;") - } - printer.write("}") - default: - printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnString);") - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;") } - return [resultVar] + printer.write("}") + if let cleanupVar { + emitCleanupCall(cleanupVar, into: cleanupCode) + } + + scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) + return [] } ) } @@ -1114,6 +1104,71 @@ struct IntrinsicJSFragment: Sendable { default: break } + let desc = wrappedType.descriptor + if let glue = desc.jsCoercion, let scalarKind = glue.optionalScalarKind, + !desc.nilSentinel.hasSentinel, desc.wasmParams.count == 1 + { + let wasmType = desc.wasmParams[0].type + let funcName = scalarKind.funcName + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + let (scope, printer) = (context.scope, context.printer) + let value = arguments[0] + let isSomeVar = scope.variable("isSome") + let presenceExpr = kind.presenceCheck(value: value) + printer.write("const \(isSomeVar) = \(presenceExpr);") + var coerced: String + if let coerce = glue.effectiveStackLowerCoerce { + coerced = coerce.replacingOccurrences(of: "$0", with: value) + if coerced.contains("?") && !coerced.hasPrefix("(") { + coerced = "(\(coerced))" + } + } else { + coerced = value + } + printer.write( + "bjs[\"\(funcName)\"](\(isSomeVar) ? 1 : 0, \(isSomeVar) ? \(coerced) : \(wasmType.jsZeroLiteral));" + ) + return [] + } + ) + } + + if case .sideChannelReturn(let mode) = desc.optionalConvention { + if mode == .none { + throw BridgeJSLinkError( + message: "Unsupported wrapped type for returning from JS function: \(wrappedType)" + ) + } + return optionalLowerReturnToSideChannel(mode: mode, kind: kind) + } + + if desc.nilSentinel.hasSentinel { + let innerFragment = try lowerReturn(type: wrappedType, context: .exportSwift) + return sentinelOptionalLowerReturn( + wrappedType: wrappedType, + kind: kind, + innerFragment: innerFragment + ) + } + + let innerFragment = try lowerReturn(type: wrappedType, context: .exportSwift) + return optionalLowerReturnWithPresenceFlag( + wrappedType: wrappedType, + kind: kind, + innerFragment: innerFragment + ) + } + + private static func sentinelOptionalLowerReturn( + wrappedType: BridgeType, + kind: JSOptionalKind, + innerFragment: IntrinsicJSFragment + ) -> IntrinsicJSFragment { + let desc = wrappedType.descriptor + let sentinelLiteral = desc.nilSentinel.jsLiteral + return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in @@ -1123,179 +1178,57 @@ struct IntrinsicJSFragment: Sendable { let presenceExpr = kind.presenceCheck(value: value) printer.write("const \(isSomeVar) = \(presenceExpr);") - switch wrappedType { - case .bool: - printer.write( - "bjs[\"swift_js_return_optional_bool\"](\(isSomeVar) ? 1 : 0, \(isSomeVar) ? (\(value) ? 1 : 0) : 0);" - ) - case .int, .uint: - printer.write( - "bjs[\"swift_js_return_optional_int\"](\(isSomeVar) ? 1 : 0, \(isSomeVar) ? (\(value) | 0) : 0);" - ) - case .caseEnum: - printer.write("return \(isSomeVar) ? (\(value) | 0) : -1;") - case .float: - printer.write( - "bjs[\"swift_js_return_optional_float\"](\(isSomeVar) ? 1 : 0, \(isSomeVar) ? Math.fround(\(value)) : 0.0);" - ) - case .double: - printer.write( - "bjs[\"swift_js_return_optional_double\"](\(isSomeVar) ? 1 : 0, \(isSomeVar) ? \(value) : 0.0);" - ) - case .string: - printer.write("if (\(isSomeVar)) {") - printer.indent { - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = \(value);") - } - printer.write("} else {") - printer.indent { - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = null;") - } - printer.write("}") - case .jsObject, .swiftProtocol: - let idVar = scope.variable("id") - printer.write("let \(idVar) = 0;") + let bufferPrinter = CodeFragmentPrinter() + let innerCleanup = CodeFragmentPrinter() + let innerResults = try innerFragment.printCode( + [value], + context.with(\.printer, bufferPrinter).with(\.cleanupCode, innerCleanup) + ) + + let hasSideEffects = !bufferPrinter.lines.isEmpty + let innerExpr = innerResults.first + + let innerCleanupLines = innerCleanup.lines.filter { + !$0.trimmingCharacters(in: .whitespaces).isEmpty + } + if !innerCleanupLines.isEmpty { + let cleanupVar = scope.variable("\(value)Cleanup") + printer.write("let \(cleanupVar);") printer.write("if (\(isSomeVar)) {") printer.indent { - printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") - } - printer.write("}") - printer.write("bjs[\"swift_js_return_optional_object\"](\(isSomeVar) ? 1 : 0, \(idVar));") - case .jsValue: - if value != "undefined" { - let lowered = try jsValueLower.printCode([value], context) - let kindVar = lowered[0] - let payload1Var = lowered[1] - let payload2Var = lowered[2] - scope.emitPushI32Parameter(kindVar, printer: printer) - scope.emitPushI32Parameter(payload1Var, printer: printer) - scope.emitPushF64Parameter(payload2Var, printer: printer) - } - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .swiftHeapObject: - printer.write("return \(isSomeVar) ? \(value).pointer : 0;") - case .array(let elementType): - printer.write("if (\(isSomeVar)) {") - try printer.indent { - let arrayLowerFragment = try arrayLower(elementType: elementType) - let arrayCleanup = CodeFragmentPrinter() - let _ = try arrayLowerFragment.printCode( - [value], - context.with(\.cleanupCode, arrayCleanup) - ) - if !arrayCleanup.lines.isEmpty { - for line in arrayCleanup.lines { + for line in bufferPrinter.lines { + printer.write(line) + } + printer.write("\(cleanupVar) = () => {") + printer.indent { + for line in innerCleanupLines { printer.write(line) } } + printer.write("};") } printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - printer.write("if (\(isSomeVar)) {") - printer.indent { - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = \(value);") - } - printer.write("} else {") - printer.indent { - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = null;") - } - printer.write("}") - default: - switch rawType { - case .bool: - printer.write( - "bjs[\"swift_js_return_optional_bool\"](\(isSomeVar) ? 1 : 0, \(isSomeVar) ? (\(value) ? 1 : 0) : 0);" - ) - case .float: - printer.write( - "bjs[\"swift_js_return_optional_float\"](\(isSomeVar) ? 1 : 0, \(isSomeVar) ? Math.fround(\(value)) : 0.0);" - ) - case .double: - printer.write( - "bjs[\"swift_js_return_optional_double\"](\(isSomeVar) ? 1 : 0, \(isSomeVar) ? \(value) : 0.0);" - ) - default: - printer.write( - "bjs[\"swift_js_return_optional_int\"](\(isSomeVar) ? 1 : 0, \(isSomeVar) ? (\(value) | 0) : 0);" - ) - } + if let expr = innerExpr { + printer.write("return \(isSomeVar) ? \(expr) : \(sentinelLiteral);") } - case .associatedValueEnum(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName - let caseIdVar = scope.variable("caseId") - let cleanupVar = scope.variable("cleanup") + emitCleanupCall(cleanupVar, into: cleanupCode) + } else if hasSideEffects { printer.write("if (\(isSomeVar)) {") printer.indent { - printer.write( - "const { caseId: \(caseIdVar), cleanup: \(cleanupVar) } = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lower(\(value));" - ) - printer.write("return \(caseIdVar);") + for line in bufferPrinter.lines { + printer.write(line) + } + if let expr = innerExpr { + printer.write("return \(expr);") + } } printer.write("} else {") printer.indent { - printer.write("return -1;") - } - printer.write("}") - cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") - case .dictionary(let valueType): - printer.write("if (\(isSomeVar)) {") - try printer.indent { - let cleanupArrayVar = scope.variable("arrayCleanups") - let entriesVar = scope.variable("entries") - let entryVar = scope.variable("entry") - printer.write("const \(cleanupArrayVar) = [];") - printer.write("const \(entriesVar) = Object.entries(\(value));") - printer.write("for (const \(entryVar) of \(entriesVar)) {") - try printer.indent { - let keyVar = scope.variable("key") - let valueVar = scope.variable("value") - printer.write("const [\(keyVar), \(valueVar)] = \(entryVar);") - - let keyFragment = try stackLowerFragment(elementType: .string) - let keyCleanup = CodeFragmentPrinter() - let _ = try keyFragment.printCode( - [keyVar], - context.with(\.cleanupCode, keyCleanup) - ) - if !keyCleanup.lines.isEmpty { - printer.write("\(cleanupArrayVar).push(() => {") - printer.indent { - for line in keyCleanup.lines { - printer.write(line) - } - } - printer.write("});") - } - - let valueFragment = try stackLowerFragment(elementType: valueType) - let valueCleanup = CodeFragmentPrinter() - let _ = try valueFragment.printCode( - [valueVar], - context.with(\.cleanupCode, valueCleanup) - ) - if !valueCleanup.lines.isEmpty { - printer.write("\(cleanupArrayVar).push(() => {") - printer.indent { - for line in valueCleanup.lines { - printer.write(line) - } - } - printer.write("});") - } - } - printer.write("}") - scope.emitPushI32Parameter("\(entriesVar).length", printer: printer) - cleanupCode.write("for (const cleanup of \(cleanupArrayVar)) { cleanup(); }") + printer.write("return \(sentinelLiteral);") } printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - default: - throw BridgeJSLinkError( - message: "Unsupported wrapped type for returning from JS function: \(wrappedType)" - ) + } else if let expr = innerExpr { + printer.write("return \(isSomeVar) ? \(expr) : \(sentinelLiteral);") } return [] @@ -1306,71 +1239,210 @@ struct IntrinsicJSFragment: Sendable { // MARK: - Protocol Support static func protocolPropertyOptionalToSideChannel(wrappedType: BridgeType) throws -> IntrinsicJSFragment { - switch wrappedType { - case .string, .int, .float, .double, .jsObject, .swiftProtocol: - break - case .rawValueEnum(_, let rawType): - switch rawType { - case .string, .int, .float, .double: - break - default: - throw BridgeJSLinkError( - message: "Unsupported raw value enum type for protocol property side channel: \(rawType)" + let desc = wrappedType.descriptor + if let glue = desc.jsCoercion, let scalarKind = glue.optionalScalarKind { + let storage = scalarKind.storageName + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + context.printer.write("\(storage) = \(arguments[0]);") + return [] + } + ) + } + + if case .sideChannelReturn(let mode) = desc.optionalConvention, + mode != .none + { + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + let printer = context.printer + let value = arguments[0] + + printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = \(value);") + + return [] + } + ) + } + + throw BridgeJSLinkError( + message: "Type \(wrappedType) does not use side channel for protocol property returns" + ) + } + + // MARK: - JS Glue Descriptor Helpers + + private static func popExpression(for wasmType: WasmCoreType, scope: JSGlueVariableScope) -> String { + switch wasmType { + case .i32: return scope.popI32() + case .f32: return scope.popF32() + case .f64: return scope.popF64() + case .pointer: return scope.popPointer() + case .i64: return scope.popI32() + } + } + + private static func emitPush( + for wasmType: WasmCoreType, + value: String, + scope: JSGlueVariableScope, + printer: CodeFragmentPrinter + ) { + switch wasmType { + case .i32: scope.emitPushI32Parameter(value, printer: printer) + case .f32: scope.emitPushF32Parameter(value, printer: printer) + case .f64: scope.emitPushF64Parameter(value, printer: printer) + case .pointer: scope.emitPushPointerParameter(value, printer: printer) + case .i64: scope.emitPushI32Parameter(value, printer: printer) + } + } + + @discardableResult + private static func emitOptionalPlaceholders( + for wrappedType: BridgeType, + scope: JSGlueVariableScope, + printer: CodeFragmentPrinter + ) -> Bool { + let desc = wrappedType.descriptor + let params = desc.wasmParams + if params.isEmpty { + return false + } + for param in params { + emitPush(for: param.type, value: param.type.jsZeroLiteral, scope: scope, printer: printer) + } + return true + } + + private static func stackOptionalLowerWithCleanup( + wrappedType: BridgeType, + kind: JSOptionalKind, + innerFragment: IntrinsicJSFragment + ) -> IntrinsicJSFragment { + IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + let (scope, printer, cleanup) = (context.scope, context.printer, context.cleanupCode) + let value = arguments[0] + let isSomeVar = scope.variable("isSome") + printer.write("const \(isSomeVar) = \(kind.presenceCheck(value: value));") + + let localCleanup = CodeFragmentPrinter() + let ifBodyPrinter = CodeFragmentPrinter() + try ifBodyPrinter.indent { + let _ = try innerFragment.printCode( + [value], + context.with(\.printer, ifBodyPrinter).with(\.cleanupCode, localCleanup) + ) + } + let cleanupLines = localCleanup.lines.filter { + !$0.trimmingCharacters(in: .whitespaces).isEmpty + } + let innerCleanupVar = cleanupLines.isEmpty ? nil : scope.variable("innerCleanup") + if let innerCleanupVar { + printer.write("let \(innerCleanupVar);") + } + printer.write("if (\(isSomeVar)) {") + for line in ifBodyPrinter.lines { + printer.write(line) + } + if let innerCleanupVar { + printer.indent { + printer.write("\(innerCleanupVar) = () => {") + printer.indent { + for line in cleanupLines { + printer.write(line) + } + } + printer.write("};") + } + } + let placeholderPrinter = CodeFragmentPrinter() + let hasPlaceholders = emitOptionalPlaceholders( + for: wrappedType, + scope: scope, + printer: placeholderPrinter ) + if hasPlaceholders { + printer.write("} else {") + printer.indent { + for line in placeholderPrinter.lines { + printer.write(line) + } + } + printer.write("}") + } else { + printer.write("}") + } + scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) + if let innerCleanupVar { + emitCleanupCall(innerCleanupVar, into: cleanup) + } + return [] } - default: - throw BridgeJSLinkError( - message: "Type \(wrappedType) does not use side channel for protocol property returns" + ) + } + + /// Builds lift/lower fragments from a type's scalar coercion info. + /// Returns `nil` when the type has no scalar coercion (complex types) or when + /// the type is nullable/indirect (optionals need dedicated handling). + /// + /// For scalar types, `lowerParameter` is always `.identity` (JS auto-coerces) + /// and `liftReturn`/`liftParameter` share the same lift fragment. + private static func scalarFragments( + for type: BridgeType + ) -> ( + lift: IntrinsicJSFragment, + lower: IntrinsicJSFragment + )? { + if case .nullable = type { return nil } + let desc = type.descriptor + guard let coercion = desc.jsCoercion else { return nil } + + let liftFragment: IntrinsicJSFragment + if let coerce = coercion.liftCoerce { + liftFragment = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, _ in + return [coerce.replacingOccurrences(of: "$0", with: arguments[0])] + } ) + } else { + liftFragment = .identity } - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let printer = context.printer - let value = arguments[0] - - switch wrappedType { - case .string, .rawValueEnum(_, .string): - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = \(value);") - case .int, .rawValueEnum(_, .int): - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalInt) = \(value);") - case .float, .rawValueEnum(_, .float): - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalFloat) = \(value);") - case .double, .rawValueEnum(_, .double): - printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalDouble) = \(value);") - case .jsObject, .swiftProtocol: - printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = \(value);") - default: - fatalError("Unsupported type in protocolPropertyOptionalToSideChannel: \(wrappedType)") + let lowerFragment: IntrinsicJSFragment + if let coerce = coercion.lowerCoerce { + lowerFragment = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, _ in + return [coerce.replacingOccurrences(of: "$0", with: arguments[0])] } + ) + } else { + lowerFragment = .identity + } - return [] - } - ) + return (lift: liftFragment, lower: lowerFragment) } // MARK: - ExportSwift /// Returns a fragment that lowers a JS value to Wasm core values for parameters static func lowerParameter(type: BridgeType) throws -> IntrinsicJSFragment { + if scalarFragments(for: type) != nil { return .identity } switch type { - case .int, .uint, .float, .double, .bool, .unsafePointer: return .identity case .string: return .stringLowerParameter case .jsObject: return .jsObjectLowerParameter case .jsValue: return .jsValueLower - case .swiftHeapObject: - return .swiftHeapObjectLowerParameter + case .swiftHeapObject: return .swiftHeapObjectLowerParameter case .swiftProtocol: return .jsObjectLowerParameter case .void: return .void - case .nullable(let wrappedType, _): - return try .optionalLowerParameter(wrappedType: wrappedType) - case .caseEnum: return .identity - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: return .stringLowerParameter - default: return .identity - } + case .nullable(let wrappedType, let kind): + return try .optionalLowerParameter(wrappedType: wrappedType, kind: kind) + case .rawValueEnum(_, .string): return .stringLowerParameter case .associatedValueEnum(let fullName): let base = fullName.components(separatedBy: ".").last ?? fullName return .associatedEnumLowerParameter(enumBase: base) @@ -1396,31 +1468,24 @@ struct IntrinsicJSFragment: Sendable { return try arrayLower(elementType: elementType) case .dictionary(let valueType): return try dictionaryLower(valueType: valueType) + default: + throw BridgeJSLinkError(message: "Unhandled type in lowerParameter: \(type)") } } /// Returns a fragment that lifts a Wasm core value to a JS value for return values static func liftReturn(type: BridgeType) throws -> IntrinsicJSFragment { + if let scalar = scalarFragments(for: type) { return scalar.lift } switch type { - case .int, .float, .double: return .identity - case .uint: return .uintLiftReturn - case .bool: return .boolLiftReturn case .string: return .stringLiftReturn case .jsObject: return .jsObjectLiftReturn case .jsValue: return .jsValueLift case .swiftHeapObject(let name): return .swiftHeapObjectLiftReturn(name) - case .unsafePointer: return .identity case .swiftProtocol: return .jsObjectLiftReturn case .void: return .void case .nullable(let wrappedType, let kind): return .optionalLiftReturn(wrappedType: wrappedType, kind: kind) - case .caseEnum: return .identity - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: return .stringLiftReturn - case .bool: return .boolLiftReturn - default: return .identity - } + case .rawValueEnum(_, .string): return .stringLiftReturn case .associatedValueEnum(let fullName): let base = fullName.components(separatedBy: ".").last ?? fullName return .associatedEnumLiftReturn(enumBase: base) @@ -1443,6 +1508,8 @@ struct IntrinsicJSFragment: Sendable { return try arrayLift(elementType: elementType) case .dictionary(let valueType): return try dictionaryLift(valueType: valueType) + default: + throw BridgeJSLinkError(message: "Unhandled type in liftReturn: \(type)") } } @@ -1450,14 +1517,11 @@ struct IntrinsicJSFragment: Sendable { /// Returns a fragment that lifts Wasm core values to JS values for parameters static func liftParameter(type: BridgeType, context: BridgeContext = .importTS) throws -> IntrinsicJSFragment { + if let scalar = scalarFragments(for: type) { return scalar.lift } switch type { - case .int, .float, .double: return .identity - case .uint: return .uintLiftParameter - case .bool: return .boolLiftParameter case .string: return .stringLiftParameter case .jsObject: return .jsObjectLiftParameter case .jsValue: return .jsValueLiftParameter - case .unsafePointer: return .identity case .swiftHeapObject(let name): return .swiftHeapObjectLiftParameter(name) case .swiftProtocol: return .jsObjectLiftParameter @@ -1466,14 +1530,8 @@ struct IntrinsicJSFragment: Sendable { message: "Void can't appear in parameters of imported JS functions" ) case .nullable(let wrappedType, let kind): - return try .optionalLiftParameter(wrappedType: wrappedType, kind: kind) - case .caseEnum: return .identity - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: return .stringLiftParameter - case .bool: return .boolLiftParameter - default: return .identity - } + return try .optionalLiftParameter(wrappedType: wrappedType, kind: kind, context: context) + case .rawValueEnum(_, .string): return .stringLiftParameter case .associatedValueEnum(let fullName): switch context { case .importTS: @@ -1531,31 +1589,24 @@ struct IntrinsicJSFragment: Sendable { return try arrayLift(elementType: elementType) case .dictionary(let valueType): return try dictionaryLift(valueType: valueType) + default: + throw BridgeJSLinkError(message: "Unhandled type in liftParameter: \(type)") } } /// Returns a fragment that lowers a JS value to Wasm core values for return values static func lowerReturn(type: BridgeType, context: BridgeContext = .importTS) throws -> IntrinsicJSFragment { + if let scalar = scalarFragments(for: type) { return scalar.lower } switch type { - case .int, .uint, .float, .double: return .identity - case .bool: return .boolLowerReturn case .string: return .stringLowerReturn case .jsObject: return .jsObjectLowerReturn case .jsValue: return .jsValueLowerReturn(context: context) - case .unsafePointer: return .identity - case .swiftHeapObject: - return .swiftHeapObjectLowerReturn + case .swiftHeapObject: return .swiftHeapObjectLowerReturn case .swiftProtocol: return .jsObjectLowerReturn case .void: return .void case .nullable(let wrappedType, let kind): return try .optionalLowerReturn(wrappedType: wrappedType, kind: kind) - case .caseEnum: return .identity - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: return .stringLowerReturn - case .bool: return .boolLowerReturn - default: return .identity - } + case .rawValueEnum(_, .string): return .stringLowerReturn case .associatedValueEnum(let fullName): switch context { case .importTS: @@ -1569,7 +1620,6 @@ struct IntrinsicJSFragment: Sendable { case .swiftStruct(let fullName): switch context { case .importTS: - // ImportTS expects Swift structs to come back as a retained JS object ID. return .jsObjectLowerReturn case .exportSwift: return swiftStructLowerReturn(fullName: fullName) @@ -1596,6 +1646,8 @@ struct IntrinsicJSFragment: Sendable { return try arrayLower(elementType: elementType) case .dictionary(let valueType): return try dictionaryLower(valueType: valueType) + default: + throw BridgeJSLinkError(message: "Unhandled type in lowerReturn: \(type)") } } @@ -1606,14 +1658,12 @@ struct IntrinsicJSFragment: Sendable { return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in - let (scope, printer, cleanupCode) = (context.scope, context.printer, context.cleanupCode) + let (scope, printer) = (context.scope, context.printer) let value = arguments[0] let caseIdVar = scope.variable("caseId") - let cleanupVar = scope.variable("cleanup") printer.write( - "const { caseId: \(caseIdVar), cleanup: \(cleanupVar) } = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lower(\(value));" + "const { caseId: \(caseIdVar) } = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lower(\(value));" ) - cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") printer.write("return \(caseIdVar);") return [] } @@ -1741,32 +1791,7 @@ struct IntrinsicJSFragment: Sendable { ) } - static func simpleEnumHelper(enumDefinition: ExportedEnum) -> IntrinsicJSFragment { - return IntrinsicJSFragment( - parameters: ["enumName"], - printCode: { arguments, context in - let printer = context.printer - let enumName = arguments[0] - printer.write("const \(enumName) = {") - printer.indent { - for (index, enumCase) in enumDefinition.cases.enumerated() { - let caseName = enumCase.name.capitalizedFirstLetter - let value = enumCase.jsValue( - rawType: enumDefinition.rawType, - index: index - ) - printer.write("\(caseName): \(value),") - } - } - printer.write("};") - printer.nextLine() - - return [] - } - ) - } - - static func rawValueEnumHelper(enumDefinition: ExportedEnum) -> IntrinsicJSFragment { + static func caseEnumHelper(enumDefinition: ExportedEnum) -> IntrinsicJSFragment { return IntrinsicJSFragment( parameters: ["enumName"], printCode: { arguments, context in @@ -1881,7 +1906,7 @@ struct IntrinsicJSFragment: Sendable { private static func associatedValuePushPayload(type: BridgeType) throws -> IntrinsicJSFragment { switch type { case .nullable(let wrappedType, let kind): - return associatedValueOptionalPushPayload(wrappedType: wrappedType, kind: kind) + return try associatedValueOptionalPushPayload(wrappedType: wrappedType, kind: kind) default: return try stackLowerFragment(elementType: type) } @@ -1890,195 +1915,43 @@ struct IntrinsicJSFragment: Sendable { private static func associatedValueOptionalPushPayload( wrappedType: BridgeType, kind: JSOptionalKind - ) -> IntrinsicJSFragment { - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer, cleanup) = (context.scope, context.printer, context.cleanupCode) - let value = arguments[0] - let isSomeVar = scope.variable("isSome") - printer.write("const \(isSomeVar) = \(kind.presenceCheck(value: value));") - - switch wrappedType { - case .string: - let idVar = scope.variable("id") - printer.write("let \(idVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let bytesVar = scope.variable("bytes") - printer.write( - "let \(bytesVar) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(value));" - ) - printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));") - scope.emitPushI32Parameter("\(bytesVar).length", printer: printer) - scope.emitPushI32Parameter(idVar, printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushI32Parameter("0", printer: printer) - scope.emitPushI32Parameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - cleanup.write("if(\(idVar)) {") - cleanup.indent { - cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") - } - cleanup.write("}") - case .int, .uint: - scope.emitPushI32Parameter("\(isSomeVar) ? (\(value) | 0) : 0", printer: printer) - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .bool: - scope.emitPushI32Parameter("\(isSomeVar) ? (\(value) ? 1 : 0) : 0", printer: printer) - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .float: - scope.emitPushF32Parameter("\(isSomeVar) ? Math.fround(\(value)) : 0.0", printer: printer) - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .double: - scope.emitPushF64Parameter("\(isSomeVar) ? \(value) : 0.0", printer: printer) - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .caseEnum: - scope.emitPushI32Parameter("\(isSomeVar) ? (\(value) | 0) : 0", printer: printer) - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - let idVar = scope.variable("id") - printer.write("let \(idVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let bytesVar = scope.variable("bytes") - printer.write( - "let \(bytesVar) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(value));" - ) - printer.write( - "\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));" - ) - scope.emitPushI32Parameter("\(bytesVar).length", printer: printer) - scope.emitPushI32Parameter(idVar, printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushI32Parameter("0", printer: printer) - scope.emitPushI32Parameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - cleanup.write("if(\(idVar)) {") - cleanup.indent { - cleanup.write( - "\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));" - ) - } - cleanup.write("}") - case .float: - scope.emitPushF32Parameter("\(isSomeVar) ? Math.fround(\(value)) : 0.0", printer: printer) - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .double: - scope.emitPushF64Parameter("\(isSomeVar) ? \(value) : 0.0", printer: printer) - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - default: - scope.emitPushI32Parameter("\(isSomeVar) ? (\(value) | 0) : 0", printer: printer) - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - } - case .swiftStruct(let structName): - let structBase = structName.components(separatedBy: ".").last ?? structName - let nestedCleanupVar = scope.variable("nestedCleanup") - printer.write("let \(nestedCleanupVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let structResultVar = scope.variable("structResult") - printer.write( - "const \(structResultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(structBase).lower(\(value));" - ) - printer.write("\(nestedCleanupVar) = \(structResultVar).cleanup;") - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - cleanup.write("if (\(nestedCleanupVar)) { \(nestedCleanupVar)(); }") - case .swiftHeapObject: - printer.write("if (\(isSomeVar)) {") - printer.indent { - scope.emitPushPointerParameter("\(value).pointer", printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushPointerParameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .jsObject: - let idVar = scope.variable("id") - printer.write("let \(idVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") - scope.emitPushI32Parameter(idVar, printer: printer) - } - printer.write("} else {") - printer.indent { - printer.write("\(idVar) = undefined;") - scope.emitPushI32Parameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - case .associatedValueEnum(let enumName): - let base = enumName.components(separatedBy: ".").last ?? enumName - let caseIdVar = scope.variable("enumCaseId") - let enumCleanupVar = scope.variable("enumCleanup") - printer.write("let \(caseIdVar), \(enumCleanupVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let enumResultVar = scope.variable("enumResult") - printer.write( - "const \(enumResultVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lower(\(value));" - ) - printer.write("\(caseIdVar) = \(enumResultVar).caseId;") - printer.write("\(enumCleanupVar) = \(enumResultVar).cleanup;") - scope.emitPushI32Parameter(caseIdVar, printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushI32Parameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - cleanup.write("if (\(enumCleanupVar)) { \(enumCleanupVar)(); }") - case .array(let elementType): - // Array cleanup references variables declared inside the if block, - // so capture cleanup into a variable declared at the outer scope. - let arrCleanupVar = scope.variable("arrCleanup") - printer.write("let \(arrCleanupVar);") - printer.write("if (\(isSomeVar)) {") - try printer.indent { - let localCleanup = CodeFragmentPrinter() - let arrFragment = try arrayLower(elementType: elementType) - _ = try arrFragment.printCode( - [value], - context.with(\.cleanupCode, localCleanup) - ) - let cleanupLines = localCleanup.lines.filter { - !$0.trimmingCharacters(in: .whitespaces).isEmpty - } - if !cleanupLines.isEmpty { - printer.write("\(arrCleanupVar) = () => {") - printer.indent { - for line in cleanupLines { - printer.write(line) - } - } - printer.write("};") + ) throws -> IntrinsicJSFragment { + let desc = wrappedType.descriptor + if let glue = desc.jsCoercion, desc.wasmParams.count == 1 { + let wasmType = desc.wasmParams[0].type + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + let (scope, printer) = (context.scope, context.printer) + let value = arguments[0] + let isSomeVar = scope.variable("isSome") + printer.write("const \(isSomeVar) = \(kind.presenceCheck(value: value));") + var coerced: String + if let coerce = glue.effectiveStackLowerCoerce { + coerced = coerce.replacingOccurrences(of: "$0", with: value) + if coerced.contains("?") && !coerced.hasPrefix("(") { + coerced = "(\(coerced))" } + } else { + coerced = value } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - cleanup.write("if (\(arrCleanupVar)) { \(arrCleanupVar)(); }") - default: + emitPush( + for: wasmType, + value: "\(isSomeVar) ? \(coerced) : \(wasmType.jsZeroLiteral)", + scope: scope, + printer: printer + ) scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) + return [] } + ) + } - return [] - } + let innerFragment = try stackLowerFragment(elementType: wrappedType) + return stackOptionalLowerWithCleanup( + wrappedType: wrappedType, + kind: kind, + innerFragment: innerFragment ) } @@ -2136,16 +2009,15 @@ struct IntrinsicJSFragment: Sendable { ) } - static func swiftStructLowerReturn(fullName: String) -> IntrinsicJSFragment { - let base = fullName.components(separatedBy: ".").last ?? fullName - return IntrinsicJSFragment( + private static func swiftStructLower(structBase: String) -> IntrinsicJSFragment { + IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in let (scope, printer, cleanupCode) = (context.scope, context.printer, context.cleanupCode) let value = arguments[0] let cleanupVar = scope.variable("cleanup") printer.write( - "const { cleanup: \(cleanupVar) } = \(JSGlueVariableScope.reservedStructHelpers).\(base).lower(\(value));" + "const { cleanup: \(cleanupVar) } = \(JSGlueVariableScope.reservedStructHelpers).\(structBase).lower(\(value));" ) cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") return [] @@ -2153,20 +2025,12 @@ struct IntrinsicJSFragment: Sendable { ) } + static func swiftStructLowerReturn(fullName: String) -> IntrinsicJSFragment { + swiftStructLower(structBase: fullName.components(separatedBy: ".").last ?? fullName) + } + static func swiftStructLowerParameter(structBase: String) -> IntrinsicJSFragment { - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer, cleanupCode) = (context.scope, context.printer, context.cleanupCode) - let value = arguments[0] - let cleanupVar = scope.variable("cleanup") - printer.write( - "const { cleanup: \(cleanupVar) } = \(JSGlueVariableScope.reservedStructHelpers).\(structBase).lower(\(value));" - ) - cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") - return [] - } - ) + swiftStructLower(structBase: structBase) } static func swiftStructLiftReturn(structBase: String) -> IntrinsicJSFragment { @@ -2337,6 +2201,28 @@ struct IntrinsicJSFragment: Sendable { } private static func stackLiftFragment(elementType: BridgeType) throws -> IntrinsicJSFragment { + if case .nullable(let wrappedType, let kind) = elementType { + return try optionalElementRaiseFragment(wrappedType: wrappedType, kind: kind) + } + let desc = elementType.descriptor + if let glue = desc.jsCoercion, desc.wasmParams.count == 1 { + let wasmType = desc.wasmParams[0].type + return IntrinsicJSFragment( + parameters: [], + printCode: { _, context in + let (scope, printer) = (context.scope, context.printer) + let popExpr = popExpression(for: wasmType, scope: scope) + let varName = scope.variable(glue.varHint) + if let transform = glue.liftCoerce { + let inlined = transform.replacingOccurrences(of: "$0", with: popExpr) + printer.write("const \(varName) = \(inlined);") + } else { + printer.write("const \(varName) = \(popExpr);") + } + return [varName] + } + ) + } switch elementType { case .jsValue: return IntrinsicJSFragment( @@ -2367,44 +2253,14 @@ struct IntrinsicJSFragment: Sendable { return [strVar] } ) - case .bool: - return IntrinsicJSFragment( - parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let bVar = scope.variable("bool") - printer.write("const \(bVar) = \(scope.popI32()) !== 0;") - return [bVar] - } - ) - case .int, .uint: - return IntrinsicJSFragment( - parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let iVar = scope.variable("int") - printer.write("const \(iVar) = \(scope.popI32());") - return [iVar] - } - ) - case .float: + case .rawValueEnum(_, .string): return IntrinsicJSFragment( parameters: [], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) - let fVar = scope.variable("f32") - printer.write("const \(fVar) = \(scope.popF32());") - return [fVar] - } - ) - case .double: - return IntrinsicJSFragment( - parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let dVar = scope.variable("f64") - printer.write("const \(dVar) = \(scope.popF64());") - return [dVar] + let varName = scope.variable("rawValue") + printer.write("const \(varName) = \(scope.popString());") + return [varName] } ) case .swiftStruct(let fullName): @@ -2420,59 +2276,6 @@ struct IntrinsicJSFragment: Sendable { return [resultVar] } ) - case .caseEnum: - return IntrinsicJSFragment( - parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let varName = scope.variable("caseId") - printer.write("const \(varName) = \(scope.popI32());") - return [varName] - } - ) - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - return IntrinsicJSFragment( - parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let varName = scope.variable("rawValue") - printer.write("const \(varName) = \(scope.popString());") - return [varName] - } - ) - case .float: - return IntrinsicJSFragment( - parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let varName = scope.variable("rawValue") - printer.write("const \(varName) = \(scope.popF32());") - return [varName] - } - ) - case .double: - return IntrinsicJSFragment( - parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let varName = scope.variable("rawValue") - printer.write("const \(varName) = \(scope.popF64());") - return [varName] - } - ) - default: - return IntrinsicJSFragment( - parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let varName = scope.variable("rawValue") - printer.write("const \(varName) = \(scope.popI32());") - return [varName] - } - ) - } case .associatedValueEnum(let fullName): let base = fullName.components(separatedBy: ".").last ?? fullName return IntrinsicJSFragment( @@ -2481,7 +2284,7 @@ struct IntrinsicJSFragment: Sendable { let (scope, printer) = (context.scope, context.printer) let resultVar = scope.variable("enumValue") printer.write( - "const \(resultVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(scope.popI32()), );" + "const \(resultVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(scope.popI32()));" ) return [resultVar] } @@ -2506,7 +2309,9 @@ struct IntrinsicJSFragment: Sendable { let idVar = scope.variable("objId") let objVar = scope.variable("obj") printer.write("const \(idVar) = \(scope.popI32());") - printer.write("const \(objVar) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(idVar));") + printer.write( + "const \(objVar) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(idVar));" + ) printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") return [objVar] } @@ -2515,24 +2320,34 @@ struct IntrinsicJSFragment: Sendable { return try arrayLift(elementType: innerElementType) case .dictionary(let valueType): return try dictionaryLift(valueType: valueType) - case .nullable(let wrappedType, let kind): - return try optionalElementRaiseFragment(wrappedType: wrappedType, kind: kind) - case .unsafePointer: + default: + throw BridgeJSLinkError(message: "Unsupported array element type: \(elementType)") + } + } + + private static func stackLowerFragment(elementType: BridgeType) throws -> IntrinsicJSFragment { + if case .nullable(let wrappedType, let kind) = elementType { + return try optionalElementLowerFragment(wrappedType: wrappedType, kind: kind) + } + let desc = elementType.descriptor + if let glue = desc.jsCoercion, desc.wasmParams.count == 1 { + let wasmType = desc.wasmParams[0].type return IntrinsicJSFragment( - parameters: [], + parameters: ["value"], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) - let pVar = scope.variable("pointer") - printer.write("const \(pVar) = \(scope.popPointer());") - return [pVar] + let value = arguments[0] + let pushExpr: String + if let coerce = glue.effectiveStackLowerCoerce { + pushExpr = coerce.replacingOccurrences(of: "$0", with: value) + } else { + pushExpr = value + } + emitPush(for: wasmType, value: pushExpr, scope: scope, printer: printer) + return [] } ) - case .void, .closure, .namespaceEnum: - throw BridgeJSLinkError(message: "Unsupported array element type: \(elementType)") } - } - - private static func stackLowerFragment(elementType: BridgeType) throws -> IntrinsicJSFragment { switch elementType { case .jsValue: return IntrinsicJSFragment( @@ -2550,7 +2365,7 @@ struct IntrinsicJSFragment: Sendable { return [] } ) - case .string: + case .string, .rawValueEnum(_, .string): return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in @@ -2558,50 +2373,18 @@ struct IntrinsicJSFragment: Sendable { let value = arguments[0] let bytesVar = scope.variable("bytes") let idVar = scope.variable("id") - printer.write("const \(bytesVar) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(value));") - printer.write("const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));") + printer.write( + "const \(bytesVar) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(value));" + ) + printer.write( + "const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));" + ) scope.emitPushI32Parameter("\(bytesVar).length", printer: printer) scope.emitPushI32Parameter(idVar, printer: printer) cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") return [] } ) - case .bool: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - scope.emitPushI32Parameter("\(arguments[0]) ? 1 : 0", printer: printer) - return [] - } - ) - case .int, .uint: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - scope.emitPushI32Parameter("(\(arguments[0]) | 0)", printer: printer) - return [] - } - ) - case .float: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - scope.emitPushF32Parameter("Math.fround(\(arguments[0]))", printer: printer) - return [] - } - ) - case .double: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - scope.emitPushF64Parameter("\(arguments[0])", printer: printer) - return [] - } - ) case .swiftStruct(let fullName): let structBase = fullName.components(separatedBy: ".").last ?? fullName return IntrinsicJSFragment( @@ -2613,69 +2396,10 @@ struct IntrinsicJSFragment: Sendable { printer.write( "const { cleanup: \(cleanupVar) } = \(JSGlueVariableScope.reservedStructHelpers).\(structBase).lower(\(value));" ) - cleanup.write("if (\(cleanupVar)) { \(cleanupVar)(); }") - return [] - } - ) - case .caseEnum: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - scope.emitPushI32Parameter("(\(arguments[0]) | 0)", printer: printer) + emitCleanupCall(cleanupVar, into: cleanup) return [] } ) - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer, cleanup) = (context.scope, context.printer, context.cleanupCode) - let value = arguments[0] - let bytesVar = scope.variable("bytes") - let idVar = scope.variable("id") - printer.write( - "const \(bytesVar) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(value));" - ) - printer.write( - "const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));" - ) - scope.emitPushI32Parameter("\(bytesVar).length", printer: printer) - scope.emitPushI32Parameter(idVar, printer: printer) - cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") - return [] - } - ) - case .float: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - scope.emitPushF32Parameter("Math.fround(\(arguments[0]))", printer: printer) - return [] - } - ) - case .double: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - scope.emitPushF64Parameter("\(arguments[0])", printer: printer) - return [] - } - ) - default: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - scope.emitPushI32Parameter("(\(arguments[0]) | 0)", printer: printer) - return [] - } - ) - } case .associatedValueEnum(let fullName): let base = fullName.components(separatedBy: ".").last ?? fullName return IntrinsicJSFragment( @@ -2689,7 +2413,7 @@ struct IntrinsicJSFragment: Sendable { "const { caseId: \(caseIdVar), cleanup: \(cleanupVar) } = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lower(\(value));" ) scope.emitPushI32Parameter(caseIdVar, printer: printer) - cleanup.write("if (\(cleanupVar)) { \(cleanupVar)(); }") + emitCleanupCall(cleanupVar, into: cleanup) return [] } ) @@ -2698,18 +2422,21 @@ struct IntrinsicJSFragment: Sendable { parameters: ["value"], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) - scope.emitPushPointerParameter("\(arguments[0]).pointer", printer: printer) + let value = arguments[0] + scope.emitPushPointerParameter("\(value).pointer", printer: printer) return [] } ) - case .jsObject: + case .jsObject, .swiftProtocol: return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) let value = arguments[0] let idVar = scope.variable("objId") - printer.write("const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") + printer.write( + "const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));" + ) scope.emitPushI32Parameter(idVar, printer: printer) return [] } @@ -2718,34 +2445,7 @@ struct IntrinsicJSFragment: Sendable { return try arrayLower(elementType: innerElementType) case .dictionary(let valueType): return try dictionaryLower(valueType: valueType) - case .nullable(let wrappedType, let kind): - return try optionalElementLowerFragment( - wrappedType: wrappedType, - kind: kind - ) - case .swiftProtocol: - // Same as jsObject but no cleanup — Swift's AnyProtocol wrapper releases via deinit - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let value = arguments[0] - let idVar = scope.variable("objId") - printer.write("const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") - scope.emitPushI32Parameter(idVar, printer: printer) - return [] - } - ) - case .unsafePointer: - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - scope.emitPushPointerParameter("(\(arguments[0]) | 0)", printer: printer) - return [] - } - ) - case .void, .closure, .namespaceEnum: + default: throw BridgeJSLinkError(message: "Unsupported array element type for lowering: \(elementType)") } } @@ -2815,27 +2515,23 @@ struct IntrinsicJSFragment: Sendable { printer.write("arrayCleanups.push(() => { \(localCleanupCode) });") } } - printer.write("} else {") - printer.indent { - // Push placeholders so Swift can unconditionally pop value slots - switch wrappedType { - case .float: - scope.emitPushF32Parameter("0.0", printer: printer) - case .double: - scope.emitPushF64Parameter("0.0", printer: printer) - case .swiftStruct: - // No placeholder — Swift only pops struct fields when isSome=1 - break - case .string, .rawValueEnum(_, .string): - scope.emitPushI32Parameter("0", printer: printer) - scope.emitPushI32Parameter("0", printer: printer) - case .swiftHeapObject: - scope.emitPushPointerParameter("0", printer: printer) - default: - scope.emitPushI32Parameter("0", printer: printer) + let placeholderPrinter = CodeFragmentPrinter() + let hasPlaceholders = emitOptionalPlaceholders( + for: wrappedType, + scope: scope, + printer: placeholderPrinter + ) + if hasPlaceholders { + printer.write("} else {") + printer.indent { + for line in placeholderPrinter.lines { + printer.write(line) + } } + printer.write("}") + } else { + printer.write("}") } - printer.write("}") scope.emitPushI32Parameter(isSomeVar, printer: printer) return [] @@ -2843,6 +2539,8 @@ struct IntrinsicJSFragment: Sendable { ) } + // MARK: - Struct Helpers + static func structHelper(structDefinition: ExportedStruct, allStructs: [ExportedStruct]) -> IntrinsicJSFragment { return IntrinsicJSFragment( parameters: ["structName"], @@ -2891,10 +2589,6 @@ struct IntrinsicJSFragment: Sendable { ) } - private static func findStruct(name: String, structs: [ExportedStruct]) -> ExportedStruct? { - return structs.first(where: { $0.swiftCallName == name || $0.name == name }) - } - private static func generateStructLowerCode( structDef: ExportedStruct, allStructs: [ExportedStruct], @@ -2908,7 +2602,11 @@ struct IntrinsicJSFragment: Sendable { let instanceProps = structDef.properties.filter { !$0.isStatic } for property in instanceProps { - let fragment = try structFieldLowerFragment(field: property, allStructs: allStructs) + let fragment = try structFieldLowerFragment( + type: property.type, + fieldName: property.name, + allStructs: allStructs + ) let fieldValue = "value.\(property.name)" _ = try fragment.printCode( [fieldValue], @@ -2993,7 +2691,7 @@ struct IntrinsicJSFragment: Sendable { } // Cleanup - printer.write("if (\(structCleanupVar)) { \(structCleanupVar)(); }") + emitCleanupCall(structCleanupVar, into: printer) printer.write(contentsOf: methodCleanup) // Lift return value if needed @@ -3016,23 +2714,26 @@ struct IntrinsicJSFragment: Sendable { } private static func structFieldLowerFragment( - field: ExportedProperty, + type: BridgeType, + fieldName: String, allStructs: [ExportedStruct] ) throws -> IntrinsicJSFragment { - switch field.type { + switch type { case .jsValue: preconditionFailure("Struct field of JSValue is not supported yet") case .jsObject: return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) + let (scope, printer, cleanup) = (context.scope, context.printer, context.cleanupCode) let value = arguments[0] let idVar = scope.variable("id") printer.write("let \(idVar);") printer.write("if (\(value) != null) {") printer.indent { - printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") + printer.write( + "\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));" + ) } printer.write("} else {") printer.indent { @@ -3040,279 +2741,60 @@ struct IntrinsicJSFragment: Sendable { } printer.write("}") scope.emitPushI32Parameter("\(idVar) !== undefined ? \(idVar) : 0", printer: printer) + cleanup.write("if(\(idVar) !== undefined && \(idVar) !== 0) {") + cleanup.indent { + cleanup.write("try {") + cleanup.indent { + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.getObject(\(idVar));") + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") + } + cleanup.write("} catch(e) { console.warn('BridgeJS: cleanup failed for retained object', e); }") + } + cleanup.write("}") return [idVar] } ) case .nullable(let wrappedType, let kind): - return IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer, cleanup) = (context.scope, context.printer, context.cleanupCode) - let value = arguments[0] - let isSomeVar = scope.variable("isSome") - printer.write("const \(isSomeVar) = \(kind.presenceCheck(value: value));") - - if case .caseEnum = wrappedType { - printer.write("if (\(isSomeVar)) {") - printer.indent { - scope.emitPushI32Parameter("\(value) | 0", printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushI32Parameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - return [] - } else if case .rawValueEnum(_, let rawType) = wrappedType { - switch rawType { - case .string: - let idVar = scope.variable("id") - printer.write("let \(idVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let bytesVar = scope.variable("bytes") - printer.write( - "const \(bytesVar) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(value));" - ) - printer.write( - "\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));" - ) - scope.emitPushI32Parameter("\(bytesVar).length", printer: printer) - scope.emitPushI32Parameter(idVar, printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushI32Parameter("0", printer: printer) - scope.emitPushI32Parameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - cleanup.write( - "if(\(idVar) !== undefined) { \(JSGlueVariableScope.reservedSwift).memory.release(\(idVar)); }" - ) - return [idVar] - case .float: - printer.write("if (\(isSomeVar)) {") - printer.indent { - scope.emitPushF32Parameter("Math.fround(\(value))", printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushF32Parameter("0.0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - return [] - case .double: - printer.write("if (\(isSomeVar)) {") - printer.indent { - scope.emitPushF64Parameter("\(value)", printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushF64Parameter("0.0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - return [] - default: - printer.write("if (\(isSomeVar)) {") - printer.indent { - scope.emitPushI32Parameter("\(value) | 0", printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushI32Parameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - return [] - } - } else if case .swiftHeapObject = wrappedType { - let ptrVar = scope.variable("ptr") - printer.write("let \(ptrVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - printer.write("\(ptrVar) = \(value).pointer;") - scope.emitPushPointerParameter("\(ptrVar)", printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushPointerParameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - return [] - } else if case .swiftStruct(let structName) = wrappedType { - let nestedCleanupVar = scope.variable("nestedCleanup") - printer.write("let \(nestedCleanupVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let structResultVar = scope.variable("structResult") - printer.write( - "const \(structResultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(structName).lower(\(value));" - ) - printer.write("\(nestedCleanupVar) = \(structResultVar).cleanup;") - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - cleanup.write("if (\(nestedCleanupVar)) { \(nestedCleanupVar)(); }") - return [] - } else if case .string = wrappedType { - let idVar = scope.variable("id") - printer.write("let \(idVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let bytesVar = scope.variable("bytes") - printer.write( - "const \(bytesVar) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(value));" - ) - printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));") - scope.emitPushI32Parameter("\(bytesVar).length", printer: printer) - scope.emitPushI32Parameter(idVar, printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushI32Parameter("0", printer: printer) - scope.emitPushI32Parameter("0", printer: printer) + let wrappedDesc = wrappedType.descriptor + if let glue = wrappedDesc.jsCoercion, wrappedDesc.wasmParams.count == 1 { + let wasmType = wrappedDesc.wasmParams[0].type + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, context in + let (scope, printer) = (context.scope, context.printer) + let value = arguments[0] + let isSomeVar = scope.variable("isSome") + printer.write("const \(isSomeVar) = \(kind.presenceCheck(value: value));") + let coerced: String + if let coerce = glue.effectiveStackLowerCoerce { + coerced = coerce.replacingOccurrences(of: "$0", with: value) + } else { + coerced = value } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - cleanup.write( - "if(\(idVar) !== undefined) { \(JSGlueVariableScope.reservedSwift).memory.release(\(idVar)); }" - ) - return [idVar] - } else if case .jsObject = wrappedType { - let idVar = scope.variable("id") - printer.write("let \(idVar);") printer.write("if (\(isSomeVar)) {") printer.indent { - printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") - scope.emitPushI32Parameter(idVar, printer: printer) + emitPush(for: wasmType, value: coerced, scope: scope, printer: printer) } printer.write("} else {") printer.indent { - printer.write("\(idVar) = undefined;") - scope.emitPushI32Parameter("0", printer: printer) + emitPush(for: wasmType, value: wasmType.jsZeroLiteral, scope: scope, printer: printer) } printer.write("}") scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - return [idVar] - } else { - switch wrappedType { - case .int, .uint: - pushOptionalPrimitive( - value: value, - isSomeVar: isSomeVar, - stack: .i32Stack, - convert: "| 0", - zeroValue: "0", - printer: printer, - scope: scope - ) - case .bool: - pushOptionalPrimitive( - value: value, - isSomeVar: isSomeVar, - stack: .i32Stack, - convert: "? 1 : 0", - zeroValue: "0", - printer: printer, - scope: scope - ) - case .float: - pushOptionalPrimitive( - value: value, - isSomeVar: isSomeVar, - stack: .f32Stack, - convert: "Math.fround", - zeroValue: "0.0", - printer: printer, - scope: scope - ) - case .double: - pushOptionalPrimitive( - value: value, - isSomeVar: isSomeVar, - stack: .f64Stack, - convert: nil, - zeroValue: "0.0", - printer: printer, - scope: scope - ) - case .associatedValueEnum(let enumName): - let base = enumName.components(separatedBy: ".").last ?? enumName - let caseIdVar = scope.variable("enumCaseId") - let enumCleanupVar = scope.variable("enumCleanup") - printer.write("let \(caseIdVar), \(enumCleanupVar);") - printer.write("if (\(isSomeVar)) {") - printer.indent { - let enumResultVar = scope.variable("enumResult") - printer.write( - "const \(enumResultVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lower(\(value));" - ) - printer.write("\(caseIdVar) = \(enumResultVar).caseId;") - printer.write("\(enumCleanupVar) = \(enumResultVar).cleanup;") - scope.emitPushI32Parameter(caseIdVar, printer: printer) - } - printer.write("} else {") - printer.indent { - scope.emitPushI32Parameter("0", printer: printer) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - cleanup.write("if (\(enumCleanupVar)) { \(enumCleanupVar)(); }") - default: - let wrappedFragment = try structFieldLowerFragment( - field: ExportedProperty( - name: field.name, - type: wrappedType, - isReadonly: true, - isStatic: false - ), - allStructs: allStructs - ) - let guardedPrinter = CodeFragmentPrinter() - let guardedCleanup = CodeFragmentPrinter() - _ = try wrappedFragment.printCode( - [value], - context.with(\.printer, guardedPrinter).with(\.cleanupCode, guardedCleanup) - ) - var loweredLines = guardedPrinter.lines - var hoistedCleanupVar: String? - if let first = loweredLines.first { - let trimmed = first.trimmingCharacters(in: .whitespaces) - if trimmed.hasPrefix("const "), - let namePart = trimmed.split(separator: " ").dropFirst().first, - trimmed.contains("= []") - { - hoistedCleanupVar = String(namePart) - loweredLines[0] = "\(hoistedCleanupVar!) = [];" - } - } - if let hoistedName = hoistedCleanupVar { - printer.write("let \(hoistedName);") - } - printer.write("if (\(isSomeVar)) {") - printer.indent { - for line in loweredLines { - printer.write(line) - } - if !guardedCleanup.lines.isEmpty { - cleanup.write("if (\(isSomeVar)) {") - cleanup.indent { - cleanup.write(contentsOf: guardedCleanup) - } - cleanup.write("}") - } - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - } return [] } - } + ) + } + + let innerFragment = try structFieldLowerFragment( + type: wrappedType, + fieldName: fieldName, + allStructs: allStructs + ) + return stackOptionalLowerWithCleanup( + wrappedType: wrappedType, + kind: kind, + innerFragment: innerFragment ) case .swiftStruct(let nestedName): return IntrinsicJSFragment( @@ -3324,7 +2806,7 @@ struct IntrinsicJSFragment: Sendable { printer.write( "const \(structResultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(nestedName).lower(\(value));" ) - context.cleanupCode.write("if (\(structResultVar).cleanup) { \(structResultVar).cleanup(); }") + emitCleanupCall("\(structResultVar).cleanup", into: context.cleanupCode) return [] } ) @@ -3334,61 +2816,17 @@ struct IntrinsicJSFragment: Sendable { parameters: ["value"], printCode: { arguments, context in context.printer.write( - "throw new Error(\"Unsupported struct field type for lowering: \(field.type)\");" + "throw new Error(\"Unsupported struct field type for lowering: \(type)\");" ) return [] } ) default: - return try stackLowerFragment(elementType: field.type) + return try stackLowerFragment(elementType: type) } } /// Helper to push optional primitive values to stack-based parameters - private static func pushOptionalPrimitive( - value: String, - isSomeVar: String, - stack: StackType, - convert: String?, - zeroValue: String, - printer: CodeFragmentPrinter, - scope: JSGlueVariableScope - ) { - let stackName: String - switch stack { - case .i32Stack: stackName = JSGlueVariableScope.reservedI32Stack - case .f32Stack: stackName = JSGlueVariableScope.reservedF32Stack - case .f64Stack: stackName = JSGlueVariableScope.reservedF64Stack - } - - printer.write("if (\(isSomeVar)) {") - printer.indent { - let converted: String - if let convert = convert { - if convert.starts(with: "Math.") { - converted = "\(convert)(\(value))" - } else { - converted = "\(value) \(convert)" - } - } else { - converted = value - } - printer.write("\(stackName).push(\(converted));") - } - printer.write("} else {") - printer.indent { - printer.write("\(stackName).push(\(zeroValue));") - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - } - - private enum StackType { - case i32Stack - case f32Stack - case f64Stack - } - private static func structFieldLiftFragment( field: ExportedProperty, allStructs: [ExportedStruct] @@ -3413,7 +2851,7 @@ struct IntrinsicJSFragment: Sendable { let caseIdVar = scope.variable("enumCaseId") printer.write("const \(caseIdVar) = \(scope.popI32());") printer.write( - "\(optVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(caseIdVar), );" + "\(optVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(caseIdVar));" ) } else { let wrappedFragment = try structFieldLiftFragment( diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index ba25b6ff9..7a783d640 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -148,6 +148,28 @@ public enum JSOptionalKind: String, Codable, Equatable, Hashable, Sendable { } } +public enum SwiftEnumRawType: String, CaseIterable, Codable, Sendable { + case string = "String" + case bool = "Bool" + case int = "Int" + case int32 = "Int32" + case int64 = "Int64" + case uint = "UInt" + case uint32 = "UInt32" + case uint64 = "UInt64" + case float = "Float" + case double = "Double" + + public init?(_ rawTypeString: String?) { + guard let rawTypeString = rawTypeString, + let match = Self.allCases.first(where: { $0.rawValue == rawTypeString }) + else { + return nil + } + self = match + } +} + public enum BridgeType: Codable, Equatable, Hashable, Sendable { case int, uint, float, double, string, bool, jsObject(String?), jsValue, swiftHeapObject(String), void case unsafePointer(UnsafePointerType) @@ -165,46 +187,25 @@ public enum BridgeType: Codable, Equatable, Hashable, Sendable { public enum WasmCoreType: String, Codable, Sendable { case i32, i64, f32, f64, pointer -} -public enum SwiftEnumRawType: String, CaseIterable, Codable, Sendable { - case string = "String" - case bool = "Bool" - case int = "Int" - case int32 = "Int32" - case int64 = "Int64" - case uint = "UInt" - case uint32 = "UInt32" - case uint64 = "UInt64" - case float = "Float" - case double = "Double" - - public var wasmCoreType: WasmCoreType? { + public var jsZeroLiteral: String { switch self { - case .string: - return nil - case .bool, .int, .int32, .uint, .uint32: - return .i32 - case .int64, .uint64: - return .i64 - case .float: - return .f32 - case .double: - return .f64 + case .f32, .f64: return "0.0" + case .i32, .i64, .pointer: return "0" } } - public init?(_ rawTypeString: String?) { - guard let rawTypeString = rawTypeString, - let match = Self.allCases.first(where: { $0.rawValue == rawTypeString }) - else { - return nil + public var swiftReturnPlaceholderStmt: String { + switch self { + case .i32: return "return 0" + case .i64: return "return 0" + case .f32: return "return 0.0" + case .f64: return "return 0.0" + case .pointer: return "return UnsafeMutableRawPointer(bitPattern: -1).unsafelyUnwrapped" } - self = match } } -/// Represents a struct field with name and default value for default parameter values public struct DefaultValueField: Codable, Equatable, Sendable { public let name: String public let value: DefaultValue @@ -222,11 +223,11 @@ public enum DefaultValue: Codable, Equatable, Sendable { case double(Double) case bool(Bool) case null - case enumCase(String, String) // enumName, caseName - case object(String) // className for parameterless constructor - case objectWithArguments(String, [DefaultValue]) // className, constructor argument values - case structLiteral(String, [DefaultValueField]) // structName, field name/value pairs - indirect case array([DefaultValue]) // array literal with element values + case enumCase(String, String) + case object(String) + case objectWithArguments(String, [DefaultValue]) + case structLiteral(String, [DefaultValueField]) + indirect case array([DefaultValue]) } public struct Parameter: Codable, Equatable, Sendable { @@ -247,6 +248,504 @@ public struct Parameter: Codable, Equatable, Sendable { } } +public struct Effects: Codable, Equatable, Sendable { + public var isAsync: Bool + public var isThrows: Bool + public var isStatic: Bool + + public init(isAsync: Bool, isThrows: Bool, isStatic: Bool = false) { + self.isAsync = isAsync + self.isThrows = isThrows + self.isStatic = isStatic + } +} + +public enum StaticContext: Codable, Equatable, Sendable { + case className(String) + case structName(String) + case enumName(String) + case namespaceEnum(String) +} + +// MARK: - ABI Descriptor + +/// How `Optional` is represented at the WASM ABI boundary. +/// +/// The convention is derived from T's descriptor and determines how codegen +/// handles nullable values in both the parameter and return directions. +/// Stack ABI is the general-purpose fallback that works for any T. +public enum OptionalConvention: Sendable, Equatable { + /// Everything goes through the stack (isSome flag + payload pushed/popped). + /// Used for types whose base representation already uses the stack (struct, array, dictionary). + /// This is also the default fallback for unknown types. + case stackABI + + /// isSome is passed as an inline WASM parameter alongside T's normal parameters. + /// For returns, T's return type carries the value (no side channel needed). + /// Used for types with compact representations where the value space has no sentinel + /// (bool, jsValue, closure, caseEnum, associatedValueEnum). + case inlineFlag + + /// Return value goes through a side-channel storage variable; WASM function returns void. + /// For parameters, behaves like `.inlineFlag` (isSome + T's params as direct WASM params). + /// Used for scalar types where Optional return needs disambiguation (int, string, jsObject, etc.). + case sideChannelReturn(OptionalSideChannel) +} + +public enum OptionalSideChannel: Sendable, Equatable { + case none + case storage + case retainedObject +} + +/// A bit pattern that is never a valid value for a type, usable to represent `nil` +/// without an extra `isSome` flag. Inspired by Swift's "extra inhabitant" concept. +/// +/// Types with a nil sentinel can encode Optional in T's own return slot: +/// the sentinel value means absent, any other value means present. +/// Types without a sentinel need either an inline isSome flag or a side channel. +public enum NilSentinel: Sendable, Equatable { + /// No sentinel exists - all bit patterns are valid values. + case none + /// A specific i32 value is never valid (e.g. 0 for object IDs, -1 for enum tags). + case i32(Int32) + /// A null pointer (0) is the sentinel. + case pointer + + public var jsLiteral: String { + switch self { + case .none: fatalError("No sentinel value for .none") + case .i32(let value): return "\(value)" + case .pointer: return "0" + } + } + + public var hasSentinel: Bool { + self != .none + } +} + +/// Identifies which typed optional-return storage slot a scalar type uses. +/// +/// On the Swift -> JS path, Swift calls `swift_js_return_optional_(isSome, value)` +/// which writes to the corresponding `tmpRetOptional` variable in JS. +/// On the JS -> Swift path (protocol returns), the same storage and intrinsic are used +/// unless the type has a nil sentinel (in which case the sentinel path is taken instead). +public enum OptionalScalarKind: String, Sendable { + case bool, int, float, double + + public var storageName: String { "tmpRetOptional\(rawValue.prefix(1).uppercased())\(rawValue.dropFirst())" } + public var funcName: String { "swift_js_return_optional_\(rawValue)" } +} + +/// JS-side coercion info for simple single-value ABI types. +/// Coercion strings use `$0` as a placeholder for the value expression. +/// Grouped within the type descriptor to avoid duplicate per-BridgeType switching. +public struct JSCoercion: Sendable { + public let liftCoerce: String? + public let lowerCoerce: String? + public let stackLowerCoerce: String? + public let varHint: String + public let optionalScalarKind: OptionalScalarKind? + + public init( + liftCoerce: String? = nil, + lowerCoerce: String? = nil, + stackLowerCoerce: String? = nil, + varHint: String, + optionalScalarKind: OptionalScalarKind? = nil + ) { + self.liftCoerce = liftCoerce + self.lowerCoerce = lowerCoerce + self.stackLowerCoerce = stackLowerCoerce + self.varHint = varHint + self.optionalScalarKind = optionalScalarKind + } + + public var effectiveStackLowerCoerce: String? { + stackLowerCoerce ?? lowerCoerce + } +} + +/// Captures the WASM ABI shape for a ``BridgeType`` so codegen can read descriptor fields +/// instead of switching on every concrete type. +/// +/// `wasmReturnType` is for the export direction (Swift->JS), `importReturnType` for import +/// (JS->Swift). They differ for types like `string` and `associatedValueEnum` where the +/// import side returns a pointer/ID while the export side uses the stack. +public struct BridgeTypeDescriptor: Sendable { + public let wasmParams: [(name: String, type: WasmCoreType)] + public let importParams: [(name: String, type: WasmCoreType)] + public let wasmReturnType: WasmCoreType? + public let importReturnType: WasmCoreType? + public let optionalConvention: OptionalConvention + public let nilSentinel: NilSentinel + public let usesStackLifting: Bool + public let accessorTransform: AccessorTransform + public let lowerMethod: LowerMethod + public let jsCoercion: JSCoercion? + + public init( + wasmParams: [(name: String, type: WasmCoreType)], + importParams: [(name: String, type: WasmCoreType)]? = nil, + wasmReturnType: WasmCoreType?, + importReturnType: WasmCoreType? = nil, + optionalConvention: OptionalConvention, + nilSentinel: NilSentinel = .none, + usesStackLifting: Bool = false, + accessorTransform: AccessorTransform, + lowerMethod: LowerMethod, + jsCoercion: JSCoercion? = nil + ) { + self.wasmParams = wasmParams + self.importParams = importParams ?? wasmParams + self.wasmReturnType = wasmReturnType + self.importReturnType = importReturnType ?? wasmReturnType + self.optionalConvention = optionalConvention + self.nilSentinel = nilSentinel + self.usesStackLifting = usesStackLifting + self.accessorTransform = accessorTransform + self.lowerMethod = lowerMethod + self.jsCoercion = jsCoercion + } +} + +/// Describes how to transform a Swift accessor expression before passing it to bridge methods. +/// +/// Most types use `.identity` - the value is passed as-is. Two patterns require transformation: +/// - `@JSClass` types expose their underlying `JSObject` via a `.jsObject` member. +/// - `@JS protocol` wrapper types require a downcast from the existential to the concrete wrapper. +/// +/// Codegen uses this instead of switching on `jsObject(className)` / `swiftProtocol` cases. +public enum AccessorTransform: Sendable, Equatable { + /// Pass the value unchanged (e.g. `ret`). + case identity + /// Access a member on the value (e.g. `ret.jsObject`). Used by `@JSClass` types. + case member(String) + /// Downcast the value (e.g. `(ret as! AnyDrawable)`). Used by `@JS protocol` types. + case cast(String) + + /// Applies the transform to an accessor expression string. + public func apply(_ accessor: String) -> String { + switch self { + case .identity: return accessor + case .member(let member): return "\(accessor).\(member)" + case .cast(let typeName): return "(\(accessor) as! \(typeName))" + } + } + + public var mapClosure: String? { + switch self { + case .identity: return nil + case .member(let member): return "$0.\(member)" + case .cast(let typeName): return "$0 as! \(typeName)" + } + } + + public var flatMapClosure: String? { + switch self { + case .identity: return nil + case .member(let member): return "$0.\(member)" + case .cast(let typeName): return "$0 as? \(typeName)" + } + } + + public func applyToReturnBinding(_ expr: String, isOptional: Bool) -> String { + switch self { + case .cast: + if isOptional { + return "let ret = (\(expr)).flatMap { \(flatMapClosure!) }" + } + return "let ret = \(apply(expr))" + case .identity, .member: + return "let ret = \(expr)" + } + } +} + +/// Which bridge protocol method the codegen calls to lower a value in the export direction. +public enum LowerMethod: Sendable, Equatable { + /// Calls `bridgeJSLowerStackReturn()` - pushes a scalar onto the stack (used within composites like optionals, struct fields). + case stackReturn + /// Calls `bridgeJSLowerReturn()` - serializes the entire value onto the stack (struct, array, dictionary). + case fullReturn + /// Calls `bridgeJSLowerParameter()` and pushes the i32 result. Used by associated value enums (tag + stack payload). + case pushParameter + /// No lowering (void, namespace enums). + case none +} + +extension BridgeType { + /// The ABI descriptor for this type's non-optional representation. + /// + /// This is the single source of truth for how each `BridgeType` maps to WASM ABI. + /// Codegen reads descriptor fields rather than switching on individual types. + /// For `.nullable`, returns the wrapped type's descriptor. + public var descriptor: BridgeTypeDescriptor { + switch self { + case .bool: + return BridgeTypeDescriptor( + wasmParams: [("value", .i32)], + wasmReturnType: .i32, + optionalConvention: .inlineFlag, + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + liftCoerce: "$0 !== 0", + lowerCoerce: "$0 ? 1 : 0", + varHint: "bool", + optionalScalarKind: .bool + ) + ) + case .int: + return BridgeTypeDescriptor( + wasmParams: [("value", .i32)], + wasmReturnType: .i32, + optionalConvention: .sideChannelReturn(.none), + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + stackLowerCoerce: "($0 | 0)", + varHint: "int", + optionalScalarKind: .int + ) + ) + case .uint: + return BridgeTypeDescriptor( + wasmParams: [("value", .i32)], + wasmReturnType: .i32, + optionalConvention: .sideChannelReturn(.none), + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + liftCoerce: "$0 >>> 0", + stackLowerCoerce: "($0 | 0)", + varHint: "int", + optionalScalarKind: .int + ) + ) + case .float: + return BridgeTypeDescriptor( + wasmParams: [("value", .f32)], + wasmReturnType: .f32, + optionalConvention: .sideChannelReturn(.none), + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + stackLowerCoerce: "Math.fround($0)", + varHint: "f32", + optionalScalarKind: .float + ) + ) + case .double: + return BridgeTypeDescriptor( + wasmParams: [("value", .f64)], + wasmReturnType: .f64, + optionalConvention: .sideChannelReturn(.none), + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + varHint: "f64", + optionalScalarKind: .double + ) + ) + case .string: + return BridgeTypeDescriptor( + wasmParams: [("bytes", .i32), ("length", .i32)], + importParams: [("value", .i32)], + wasmReturnType: nil, + importReturnType: .i32, + optionalConvention: .sideChannelReturn(.storage), + accessorTransform: .identity, + lowerMethod: .stackReturn + ) + case .jsObject(let className): + let transform: AccessorTransform = + if let className, className != "JSObject" { .member("jsObject") } else { .identity } + return BridgeTypeDescriptor( + wasmParams: [("value", .i32)], + wasmReturnType: .i32, + optionalConvention: .sideChannelReturn(.retainedObject), + nilSentinel: .i32(0), + accessorTransform: transform, + lowerMethod: .stackReturn + ) + case .jsValue: + return BridgeTypeDescriptor( + wasmParams: [("kind", .i32), ("payload1", .i32), ("payload2", .f64)], + wasmReturnType: nil, + optionalConvention: .inlineFlag, + accessorTransform: .identity, + lowerMethod: .stackReturn + ) + case .swiftHeapObject: + return BridgeTypeDescriptor( + wasmParams: [("pointer", .pointer)], + wasmReturnType: .pointer, + optionalConvention: .inlineFlag, + nilSentinel: .pointer, + accessorTransform: .identity, + lowerMethod: .stackReturn + ) + case .unsafePointer: + return BridgeTypeDescriptor( + wasmParams: [("pointer", .pointer)], + wasmReturnType: .pointer, + optionalConvention: .inlineFlag, + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion(stackLowerCoerce: "($0 | 0)", varHint: "pointer") + ) + case .swiftProtocol(let protocolName): + return BridgeTypeDescriptor( + wasmParams: [("value", .i32)], + wasmReturnType: .i32, + optionalConvention: .sideChannelReturn(.retainedObject), + nilSentinel: .i32(0), + accessorTransform: .cast("Any\(protocolName)"), + lowerMethod: .stackReturn + ) + case .caseEnum: + return BridgeTypeDescriptor( + wasmParams: [("value", .i32)], + wasmReturnType: .i32, + optionalConvention: .inlineFlag, + nilSentinel: .i32(-1), + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + stackLowerCoerce: "($0 | 0)", + varHint: "caseId", + optionalScalarKind: .int + ) + ) + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: + return BridgeTypeDescriptor( + wasmParams: [("bytes", .i32), ("length", .i32)], + importParams: [("value", .i32)], + wasmReturnType: nil, + importReturnType: .i32, + optionalConvention: .sideChannelReturn(.storage), + accessorTransform: .identity, + lowerMethod: .stackReturn + ) + case .float: + return BridgeTypeDescriptor( + wasmParams: [("value", .f32)], + wasmReturnType: .f32, + optionalConvention: .sideChannelReturn(.none), + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + stackLowerCoerce: "Math.fround($0)", + varHint: "rawValue", + optionalScalarKind: .float + ) + ) + case .double: + return BridgeTypeDescriptor( + wasmParams: [("value", .f64)], + wasmReturnType: .f64, + optionalConvention: .sideChannelReturn(.none), + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + varHint: "rawValue", + optionalScalarKind: .double + ) + ) + case .bool: + return BridgeTypeDescriptor( + wasmParams: [("value", .i32)], + wasmReturnType: .i32, + optionalConvention: .inlineFlag, + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + liftCoerce: "$0 !== 0", + lowerCoerce: "$0 ? 1 : 0", + stackLowerCoerce: "$0 ? 1 : 0", + varHint: "rawValue", + optionalScalarKind: .bool + ) + ) + case .int, .int32, .int64, .uint, .uint32, .uint64: + return BridgeTypeDescriptor( + wasmParams: [("value", .i32)], + wasmReturnType: .i32, + optionalConvention: .sideChannelReturn(.none), + accessorTransform: .identity, + lowerMethod: .stackReturn, + jsCoercion: JSCoercion( + stackLowerCoerce: "($0 | 0)", + varHint: "rawValue", + optionalScalarKind: .int + ) + ) + } + case .associatedValueEnum: + return BridgeTypeDescriptor( + wasmParams: [("caseId", .i32)], + wasmReturnType: nil, + importReturnType: .i32, + optionalConvention: .inlineFlag, + nilSentinel: .i32(-1), + usesStackLifting: true, + accessorTransform: .identity, + lowerMethod: .pushParameter + ) + case .closure(_, _): + return BridgeTypeDescriptor( + wasmParams: [("funcRef", .i32)], + wasmReturnType: .i32, + optionalConvention: .inlineFlag, + accessorTransform: .identity, + lowerMethod: .stackReturn + ) + case .swiftStruct: + return BridgeTypeDescriptor( + wasmParams: [], + importParams: [("objectId", .i32)], + wasmReturnType: nil, + importReturnType: .i32, + optionalConvention: .stackABI, + usesStackLifting: true, + accessorTransform: .identity, + lowerMethod: .fullReturn + ) + case .array: + return BridgeTypeDescriptor( + wasmParams: [], + wasmReturnType: nil, + optionalConvention: .stackABI, + usesStackLifting: true, + accessorTransform: .identity, + lowerMethod: .fullReturn + ) + case .dictionary: + return BridgeTypeDescriptor( + wasmParams: [], + wasmReturnType: nil, + optionalConvention: .stackABI, + accessorTransform: .identity, + lowerMethod: .fullReturn + ) + case .void, .namespaceEnum: + return BridgeTypeDescriptor( + wasmParams: [], + wasmReturnType: nil, + optionalConvention: .stackABI, + accessorTransform: .identity, + lowerMethod: .none + ) + case .nullable(let wrapped, _): + return wrapped.descriptor + } + } +} + // MARK: - BridgeType Visitor public protocol BridgeTypeVisitor { @@ -379,39 +878,20 @@ public struct BridgeTypeWalker { } } -public struct Effects: Codable, Equatable, Sendable { - public var isAsync: Bool - public var isThrows: Bool - public var isStatic: Bool +public struct ClosureSignatureCollectorVisitor: BridgeTypeVisitor { + public var signatures: Set = [] - public init(isAsync: Bool, isThrows: Bool, isStatic: Bool = false) { - self.isAsync = isAsync - self.isThrows = isThrows - self.isStatic = isStatic + public init(signatures: Set = []) { + self.signatures = signatures } -} - -// MARK: - Static Function Context -public enum StaticContext: Codable, Equatable, Sendable { - case className(String) - case structName(String) - case enumName(String) - case namespaceEnum(String) + public mutating func visitClosure(_ signature: ClosureSignature, useJSTypedClosure: Bool) { + signatures.insert(signature) + } } // MARK: - Struct Skeleton -public struct StructField: Codable, Equatable, Sendable { - public let name: String - public let type: BridgeType - - public init(name: String, type: BridgeType) { - self.name = name - self.type = type - } -} - public struct ExportedStruct: Codable, Equatable, Sendable { public let name: String public let swiftCallName: String @@ -749,15 +1229,6 @@ public struct ExportedSkeleton: Codable { self.exposeToGlobal = exposeToGlobal } - public mutating func append(_ other: ExportedSkeleton) { - self.functions.append(contentsOf: other.functions) - self.classes.append(contentsOf: other.classes) - self.enums.append(contentsOf: other.enums) - self.structs.append(contentsOf: other.structs) - self.protocols.append(contentsOf: other.protocols) - assert(self.exposeToGlobal == other.exposeToGlobal) - } - public var isEmpty: Bool { functions.isEmpty && classes.isEmpty && enums.isEmpty && structs.isEmpty && protocols.isEmpty } @@ -1005,20 +1476,6 @@ public struct ImportedModuleSkeleton: Codable { } } -// MARK: - Closure signature collection visitor - -public struct ClosureSignatureCollectorVisitor: BridgeTypeVisitor { - public var signatures: Set = [] - - public init(signatures: Set = []) { - self.signatures = signatures - } - - public mutating func visitClosure(_ signature: ClosureSignature, useJSTypedClosure: Bool) { - signatures.insert(signature) - } -} - // MARK: - Unified Skeleton /// Unified skeleton containing both exported and imported API definitions @@ -1034,7 +1491,7 @@ public struct BridgeJSSkeleton: Codable { } } -// MARK: - BridgeType extension +// MARK: - BridgeType Extension extension BridgeType { /// Maps Swift primitive type names to BridgeType. Returns nil for unknown types. @@ -1069,49 +1526,6 @@ extension BridgeType { } } - public var abiReturnType: WasmCoreType? { - switch self { - case .void: return nil - case .bool: return .i32 - case .int, .uint: return .i32 - case .float: return .f32 - case .double: return .f64 - case .string: return nil - case .jsObject: return .i32 - case .jsValue: return nil - case .swiftHeapObject: - // UnsafeMutableRawPointer is returned as an i32 pointer - return .pointer - case .unsafePointer: - return .pointer - case .nullable: - return nil - case .caseEnum: - return .i32 - case .rawValueEnum(_, let rawType): - return rawType.wasmCoreType - case .associatedValueEnum: - return nil - case .namespaceEnum: - return nil - case .swiftProtocol: - // Protocols pass JSObject IDs as Int32 - return .i32 - case .swiftStruct: - // Structs use stack-based return (no direct WASM return type) - return nil - case .closure: - // Closures pass callback ID as Int32 - return .i32 - case .array: - // Arrays use stack-based return with length prefix (no direct WASM return type) - return nil - case .dictionary: - // Dictionaries use stack-based return with entry count (no direct WASM return type) - return nil - } - } - /// Returns true if this type is optional (nullable with null or undefined). public var isOptional: Bool { if case .nullable = self { return true } @@ -1180,42 +1594,13 @@ extension BridgeType { } } - /// Determines if an optional type requires side-channel communication for protocol property returns - /// - /// Side channels are needed when the wrapped type cannot be directly returned via WASM, - /// or when we need to distinguish null from absent value for certain primitives. public func usesSideChannelForOptionalReturn() -> Bool { - guard case .nullable(let wrappedType, _) = self else { + guard case .nullable = self else { return false } - - switch wrappedType { - case .string, .int, .float, .double, .jsObject, .swiftProtocol: + if case .sideChannelReturn = descriptor.optionalConvention { return true - case .rawValueEnum(_, let rawType): - switch rawType { - case .string, .int, .float, .double: - return true - default: - return false - } - case .bool, .caseEnum, .swiftHeapObject, .associatedValueEnum: - return false - default: - return false - } - } -} - -extension WasmCoreType { - /// Returns a Swift statement that returns a placeholder value for this Wasm core type. - public var swiftReturnPlaceholderStmt: String { - switch self { - case .i32: return "return 0" - case .i64: return "return 0" - case .f32: return "return 0.0" - case .f64: return "return 0.0" - case .pointer: return "return UnsafeMutableRawPointer(bitPattern: -1).unsafelyUnwrapped" } + return false } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/CrossFileTypeResolution.ReverseOrder.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/CrossFileTypeResolution.ReverseOrder.swift index 8d697f8ba..1aa97108c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/CrossFileTypeResolution.ReverseOrder.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/CrossFileTypeResolution.ReverseOrder.swift @@ -11,9 +11,9 @@ public func _bjs_ClassA_linkedB_get(_ _self: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_ClassA_linkedB_set") @_cdecl("bjs_ClassA_linkedB_set") -public func _bjs_ClassA_linkedB_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_ClassA_linkedB_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - ClassA.bridgeJSLiftParameter(_self).linkedB = Optional.bridgeJSLiftParameter(valueIsSome, valueValue) + ClassA.bridgeJSLiftParameter(_self).linkedB = Optional.bridgeJSLiftParameter(valueIsSome, valuePointer) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/CrossFileTypeResolution.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/CrossFileTypeResolution.swift index 0a0396075..b523cb4b0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/CrossFileTypeResolution.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/CrossFileTypeResolution.swift @@ -47,9 +47,9 @@ public func _bjs_ClassA_linkedB_get(_ _self: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_ClassA_linkedB_set") @_cdecl("bjs_ClassA_linkedB_set") -public func _bjs_ClassA_linkedB_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_ClassA_linkedB_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - ClassA.bridgeJSLiftParameter(_self).linkedB = Optional.bridgeJSLiftParameter(valueIsSome, valueValue) + ClassA.bridgeJSLiftParameter(_self).linkedB = Optional.bridgeJSLiftParameter(valueIsSome, valuePointer) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Optionals.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Optionals.swift index 471d38a6c..afe88e960 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Optionals.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Optionals.swift @@ -1,8 +1,8 @@ @_expose(wasm, "bjs_roundTripOptionalClass") @_cdecl("bjs_roundTripOptionalClass") -public func _bjs_roundTripOptionalClass(_ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_roundTripOptionalClass(_ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = roundTripOptionalClass(value: Optional.bridgeJSLiftParameter(valueIsSome, valueValue)) + let ret = roundTripOptionalClass(value: Optional.bridgeJSLiftParameter(valueIsSome, valuePointer)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -11,9 +11,9 @@ public func _bjs_roundTripOptionalClass(_ valueIsSome: Int32, _ valueValue: Unsa @_expose(wasm, "bjs_testOptionalPropertyRoundtrip") @_cdecl("bjs_testOptionalPropertyRoundtrip") -public func _bjs_testOptionalPropertyRoundtrip(_ holderIsSome: Int32, _ holderValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_testOptionalPropertyRoundtrip(_ holderIsSome: Int32, _ holderPointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = testOptionalPropertyRoundtrip(_: Optional.bridgeJSLiftParameter(holderIsSome, holderValue)) + let ret = testOptionalPropertyRoundtrip(_: Optional.bridgeJSLiftParameter(holderIsSome, holderPointer)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -307,9 +307,9 @@ public func _bjs_OptionalPropertyHolder_optionalGreeter_get(_ _self: UnsafeMutab @_expose(wasm, "bjs_OptionalPropertyHolder_optionalGreeter_set") @_cdecl("bjs_OptionalPropertyHolder_optionalGreeter_set") -public func _bjs_OptionalPropertyHolder_optionalGreeter_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_OptionalPropertyHolder_optionalGreeter_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - OptionalPropertyHolder.bridgeJSLiftParameter(_self).optionalGreeter = Optional.bridgeJSLiftParameter(valueIsSome, valueValue) + OptionalPropertyHolder.bridgeJSLiftParameter(_self).optionalGreeter = Optional.bridgeJSLiftParameter(valueIsSome, valuePointer) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift index 4051f5c9e..d838f0b54 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift @@ -544,7 +544,7 @@ public func _bjs_MyViewController_sendHelper(_ _self: UnsafeMutableRawPointer, _ @_cdecl("bjs_MyViewController_delegate_get") public func _bjs_MyViewController_delegate_get(_ _self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = MyViewController.bridgeJSLiftParameter(_self).delegate as! AnyMyViewControllerDelegate + let ret = (MyViewController.bridgeJSLiftParameter(_self).delegate as! AnyMyViewControllerDelegate) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift index 57a963637..74bf9a477 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift @@ -729,10 +729,10 @@ extension JSTypedClosure where Signature == (Optional) -> Optional Void { +public func _invoke_swift_closure_TestModule_10TestModuleSq6PersonC_Sq6PersonC(_ boxPtr: UnsafeMutableRawPointer, _ param0IsSome: Int32, _ param0Pointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) let closure = Unmanaged<_BridgeJSTypedClosureBox<(Optional) -> Optional>>.fromOpaque(boxPtr).takeUnretainedValue().closure - let result = closure(Optional.bridgeJSLiftParameter(param0IsSome, param0Value)) + let result = closure(Optional.bridgeJSLiftParameter(param0IsSome, param0Pointer)) return result.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js index 97531d378..00409a713 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js @@ -466,7 +466,7 @@ export async function createInstantiator(options, swift) { for (const elem of points) { const { cleanup: structCleanup } = structHelpers.Point.lower(elem); arrayCleanups.push(() => { - if (structCleanup) { structCleanup(); } + structCleanup && structCleanup(); }); } i32Stack.push(points.length); @@ -530,7 +530,7 @@ export async function createInstantiator(options, swift) { for (const elem of points) { const { cleanup: structCleanup } = structHelpers.Point.lower(elem); arrayCleanups.push(() => { - if (structCleanup) { structCleanup(); } + structCleanup && structCleanup(); }); } i32Stack.push(points.length); @@ -660,14 +660,16 @@ export async function createInstantiator(options, swift) { }, processOptionalArray: function bjs_processOptionalArray(values) { const isSome = values != null; - const valuesCleanups = []; + let valuesCleanup; if (isSome) { const arrayCleanups = []; for (const elem of values) { i32Stack.push((elem | 0)); } i32Stack.push(values.length); - valuesCleanups.push(() => { for (const cleanup of arrayCleanups) { cleanup(); } }); + valuesCleanup = () => { + for (const cleanup of arrayCleanups) { cleanup(); } + }; } i32Stack.push(+isSome); instance.exports.bjs_processOptionalArray(); @@ -685,7 +687,7 @@ export async function createInstantiator(options, swift) { } else { optResult = null; } - for (const cleanup of valuesCleanups) { cleanup(); } + valuesCleanup && valuesCleanup(); return optResult; }, processOptionalPointArray: function bjs_processOptionalPointArray(points) { @@ -694,8 +696,7 @@ export async function createInstantiator(options, swift) { const isSome = elem != null ? 1 : 0; if (isSome) { const { cleanup: structCleanup } = structHelpers.Point.lower(elem); - arrayCleanups.push(() => { if (structCleanup) { structCleanup(); } }); - } else { + arrayCleanups.push(() => { structCleanup && structCleanup(); }); } i32Stack.push(isSome); } @@ -851,7 +852,7 @@ export async function createInstantiator(options, swift) { for (const elem1 of elem) { const { cleanup: structCleanup } = structHelpers.Point.lower(elem1); arrayCleanups1.push(() => { - if (structCleanup) { structCleanup(); } + structCleanup && structCleanup(); }); } i32Stack.push(elem.length); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js index fba9f9c6b..415e0b6f6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js @@ -69,13 +69,13 @@ export async function createInstantiator(options, swift) { instance1.add = function(a, b = 10.0) { const { cleanup: structCleanup } = structHelpers.MathOperations.lower(this); const ret = instance.exports.bjs_MathOperations_add(a, b); - if (structCleanup) { structCleanup(); } + structCleanup && structCleanup(); return ret; }.bind(instance1); instance1.multiply = function(a, b) { const { cleanup: structCleanup } = structHelpers.MathOperations.lower(this); const ret = instance.exports.bjs_MathOperations_multiply(a, b); - if (structCleanup) { structCleanup(); } + structCleanup && structCleanup(); return ret; }.bind(instance1); return instance1; @@ -360,16 +360,19 @@ export async function createInstantiator(options, swift) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); const isSome = tag != null; - let tagId, tagBytes; + let bytes, length; if (isSome) { - tagBytes = textEncoder.encode(tag); - tagId = swift.memory.retain(tagBytes); + const tagBytes = textEncoder.encode(tag); + const tagId = swift.memory.retain(tagBytes); + bytes = tagId; + length = tagBytes.length; + } else { + bytes = 0; + length = 0; } - const ret = instance.exports.bjs_ConstructorDefaults_init(nameId, nameBytes.length, count, enabled, status, +isSome, isSome ? tagId : 0, isSome ? tagBytes.length : 0); + const ret = instance.exports.bjs_ConstructorDefaults_init(nameId, nameBytes.length, count, enabled, status, +isSome, bytes, length); swift.memory.release(nameId); - if (tagId != undefined) { - swift.memory.release(tagId); - } + if (isSome) { swift.memory.release(bytes); } return ConstructorDefaults.__construct(ret); } get name() { @@ -413,15 +416,18 @@ export async function createInstantiator(options, swift) { } set tag(value) { const isSome = value != null; - let valueId, valueBytes; + let bytes, length; if (isSome) { - valueBytes = textEncoder.encode(value); - valueId = swift.memory.retain(valueBytes); - } - instance.exports.bjs_ConstructorDefaults_tag_set(this.pointer, +isSome, isSome ? valueId : 0, isSome ? valueBytes.length : 0); - if (valueId != undefined) { - swift.memory.release(valueId); + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + bytes = valueId; + length = valueBytes.length; + } else { + bytes = 0; + length = 0; } + instance.exports.bjs_ConstructorDefaults_tag_set(this.pointer, +isSome, bytes, length); + if (isSome) { swift.memory.release(bytes); } } } const ConfigHelpers = __bjs_createConfigHelpers()(); @@ -461,32 +467,38 @@ export async function createInstantiator(options, swift) { }, testOptionalDefault: function bjs_testOptionalDefault(name = null) { const isSome = name != null; - let nameId, nameBytes; + let bytes, length; if (isSome) { - nameBytes = textEncoder.encode(name); - nameId = swift.memory.retain(nameBytes); + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + bytes = nameId; + length = nameBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_testOptionalDefault(+isSome, isSome ? nameId : 0, isSome ? nameBytes.length : 0); + instance.exports.bjs_testOptionalDefault(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (nameId != undefined) { - swift.memory.release(nameId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, testOptionalStringDefault: function bjs_testOptionalStringDefault(greeting = "Hi") { const isSome = greeting != null; - let greetingId, greetingBytes; + let bytes, length; if (isSome) { - greetingBytes = textEncoder.encode(greeting); - greetingId = swift.memory.retain(greetingBytes); + const greetingBytes = textEncoder.encode(greeting); + const greetingId = swift.memory.retain(greetingBytes); + bytes = greetingId; + length = greetingBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_testOptionalStringDefault(+isSome, isSome ? greetingId : 0, isSome ? greetingBytes.length : 0); + instance.exports.bjs_testOptionalStringDefault(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (greetingId != undefined) { - swift.memory.release(greetingId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, testMultipleDefaults: function bjs_testMultipleDefaults(title = "Default Title", count = 10, enabled = false) { @@ -514,38 +526,32 @@ export async function createInstantiator(options, swift) { const isSome = point != null; let pointCleanup; if (isSome) { - const structResult = structHelpers.Config.lower(point); - pointCleanup = structResult.cleanup; + const { cleanup: cleanup } = structHelpers.Config.lower(point); + pointCleanup = () => { + if (cleanup) { cleanup(); } + }; } i32Stack.push(+isSome); instance.exports.bjs_testOptionalStructDefault(); const isSome1 = i32Stack.pop(); - let optResult; - if (isSome1) { - optResult = structHelpers.Config.lift(); - } else { - optResult = null; - } - if (pointCleanup) { pointCleanup(); } + const optResult = isSome1 ? structHelpers.Config.lift() : null; + pointCleanup && pointCleanup(); return optResult; }, testOptionalStructWithValueDefault: function bjs_testOptionalStructWithValueDefault(point = { name: "default", value: 42, enabled: true }) { const isSome = point != null; let pointCleanup; if (isSome) { - const structResult = structHelpers.Config.lower(point); - pointCleanup = structResult.cleanup; + const { cleanup: cleanup } = structHelpers.Config.lower(point); + pointCleanup = () => { + if (cleanup) { cleanup(); } + }; } i32Stack.push(+isSome); instance.exports.bjs_testOptionalStructWithValueDefault(); const isSome1 = i32Stack.pop(); - let optResult; - if (isSome1) { - optResult = structHelpers.Config.lift(); - } else { - optResult = null; - } - if (pointCleanup) { pointCleanup(); } + const optResult = isSome1 ? structHelpers.Config.lift() : null; + pointCleanup && pointCleanup(); return optResult; }, testIntArrayDefault: function bjs_testIntArrayDefault(values = [1, 2, 3]) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js index 1f7fe91e1..c6000cd8f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js @@ -299,7 +299,7 @@ export async function createInstantiator(options, swift) { }, optionalDictionary: function bjs_optionalDictionary(values) { const isSome = values != null; - const valuesCleanups = []; + let valuesCleanup; if (isSome) { const arrayCleanups = []; const entries = Object.entries(values); @@ -321,7 +321,9 @@ export async function createInstantiator(options, swift) { }); } i32Stack.push(entries.length); - valuesCleanups.push(() => { for (const cleanup of arrayCleanups) { cleanup(); } }); + valuesCleanup = () => { + for (const cleanup of arrayCleanups) { cleanup(); } + }; } i32Stack.push(+isSome); instance.exports.bjs_optionalDictionary(); @@ -339,7 +341,7 @@ export async function createInstantiator(options, swift) { } else { optResult = null; } - for (const cleanup of valuesCleanups) { cleanup(); } + valuesCleanup && valuesCleanup(); return optResult; }, nestedDictionary: function bjs_nestedDictionary(values) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js index 3a4818463..c84076d0d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js @@ -432,21 +432,22 @@ export async function createInstantiator(options, swift) { switch (enumTag) { case APIOptionalResultValues.Tag.Success: { const isSome = value.param0 != null; - let id; + let innerCleanup; if (isSome) { - let bytes = textEncoder.encode(value.param0); - id = swift.memory.retain(bytes); + const bytes = textEncoder.encode(value.param0); + const id = swift.memory.retain(bytes); i32Stack.push(bytes.length); i32Stack.push(id); + innerCleanup = () => { + swift.memory.release(id); + }; } else { i32Stack.push(0); i32Stack.push(0); } i32Stack.push(isSome ? 1 : 0); const cleanup = () => { - if(id) { - swift.memory.release(id); - } + innerCleanup && innerCleanup(); }; return { caseId: APIOptionalResultValues.Tag.Success, cleanup }; } @@ -462,12 +463,15 @@ export async function createInstantiator(options, swift) { } case APIOptionalResultValues.Tag.Status: { const isSome = value.param2 != null; - let id; + let innerCleanup; if (isSome) { - let bytes = textEncoder.encode(value.param2); - id = swift.memory.retain(bytes); + const bytes = textEncoder.encode(value.param2); + const id = swift.memory.retain(bytes); i32Stack.push(bytes.length); i32Stack.push(id); + innerCleanup = () => { + swift.memory.release(id); + }; } else { i32Stack.push(0); i32Stack.push(0); @@ -480,9 +484,7 @@ export async function createInstantiator(options, swift) { i32Stack.push(isSome2 ? (value.param0 ? 1 : 0) : 0); i32Stack.push(isSome2 ? 1 : 0); const cleanup = () => { - if(id) { - swift.memory.release(id); - } + innerCleanup && innerCleanup(); }; return { caseId: APIOptionalResultValues.Tag.Status, cleanup }; } @@ -637,7 +639,7 @@ export async function createInstantiator(options, swift) { case AllTypesResultValues.Tag.StructPayload: { const { cleanup: structCleanup } = structHelpers.Point.lower(value.param0); const cleanup = () => { - if (structCleanup) { structCleanup(); } + structCleanup && structCleanup(); }; return { caseId: AllTypesResultValues.Tag.StructPayload, cleanup }; } @@ -656,7 +658,7 @@ export async function createInstantiator(options, swift) { const { caseId: caseId, cleanup: enumCleanup } = enumHelpers.APIResult.lower(value.param0); i32Stack.push(caseId); const cleanup = () => { - if (enumCleanup) { enumCleanup(); } + enumCleanup && enumCleanup(); }; return { caseId: AllTypesResultValues.Tag.NestedEnum, cleanup }; } @@ -697,7 +699,7 @@ export async function createInstantiator(options, swift) { return { tag: AllTypesResultValues.Tag.JsObjectPayload, param0: obj }; } case AllTypesResultValues.Tag.NestedEnum: { - const enumValue = enumHelpers.APIResult.lift(i32Stack.pop(), ); + const enumValue = enumHelpers.APIResult.lift(i32Stack.pop()); return { tag: AllTypesResultValues.Tag.NestedEnum, param0: enumValue }; } case AllTypesResultValues.Tag.ArrayPayload: { @@ -723,14 +725,16 @@ export async function createInstantiator(options, swift) { switch (enumTag) { case OptionalAllTypesResultValues.Tag.OptStruct: { const isSome = value.param0 != null; - let nestedCleanup; + let innerCleanup; if (isSome) { - const structResult = structHelpers.Point.lower(value.param0); - nestedCleanup = structResult.cleanup; + const { cleanup: structCleanup } = structHelpers.Point.lower(value.param0); + innerCleanup = () => { + structCleanup && structCleanup(); + }; } i32Stack.push(isSome ? 1 : 0); const cleanup = () => { - if (nestedCleanup) { nestedCleanup(); } + innerCleanup && innerCleanup(); }; return { caseId: OptionalAllTypesResultValues.Tag.OptStruct, cleanup }; } @@ -747,12 +751,10 @@ export async function createInstantiator(options, swift) { } case OptionalAllTypesResultValues.Tag.OptJSObject: { const isSome = value.param0 != null; - let id; if (isSome) { - id = swift.memory.retain(value.param0); - i32Stack.push(id); + const objId = swift.memory.retain(value.param0); + i32Stack.push(objId); } else { - id = undefined; i32Stack.push(0); } i32Stack.push(isSome ? 1 : 0); @@ -761,37 +763,38 @@ export async function createInstantiator(options, swift) { } case OptionalAllTypesResultValues.Tag.OptNestedEnum: { const isSome = value.param0 != null; - let enumCaseId, enumCleanup; + let innerCleanup; if (isSome) { - const enumResult = enumHelpers.APIResult.lower(value.param0); - enumCaseId = enumResult.caseId; - enumCleanup = enumResult.cleanup; - i32Stack.push(enumCaseId); + const { caseId: caseId, cleanup: enumCleanup } = enumHelpers.APIResult.lower(value.param0); + i32Stack.push(caseId); + innerCleanup = () => { + enumCleanup && enumCleanup(); + }; } else { i32Stack.push(0); } i32Stack.push(isSome ? 1 : 0); const cleanup = () => { - if (enumCleanup) { enumCleanup(); } + innerCleanup && innerCleanup(); }; return { caseId: OptionalAllTypesResultValues.Tag.OptNestedEnum, cleanup }; } case OptionalAllTypesResultValues.Tag.OptArray: { const isSome = value.param0 != null; - let arrCleanup; + let innerCleanup; if (isSome) { const arrayCleanups = []; for (const elem of value.param0) { i32Stack.push((elem | 0)); } i32Stack.push(value.param0.length); - arrCleanup = () => { + innerCleanup = () => { for (const cleanup of arrayCleanups) { cleanup(); } }; } i32Stack.push(isSome ? 1 : 0); const cleanup = () => { - if (arrCleanup) { arrCleanup(); } + innerCleanup && innerCleanup(); }; return { caseId: OptionalAllTypesResultValues.Tag.OptArray, cleanup }; } @@ -1128,7 +1131,7 @@ export async function createInstantiator(options, swift) { handle: function bjs_handle(result) { const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.APIResult.lower(result); instance.exports.bjs_handle(resultCaseId); - if (resultCleanup) { resultCleanup(); } + resultCleanup && resultCleanup(); }, getResult: function bjs_getResult() { instance.exports.bjs_getResult(); @@ -1139,33 +1142,32 @@ export async function createInstantiator(options, swift) { const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.APIResult.lower(result); instance.exports.bjs_roundtripAPIResult(resultCaseId); const ret = enumHelpers.APIResult.lift(i32Stack.pop()); - if (resultCleanup) { resultCleanup(); } + resultCleanup && resultCleanup(); return ret; }, roundTripOptionalAPIResult: function bjs_roundTripOptionalAPIResult(result) { const isSome = result != null; - let resultCaseId, resultCleanup; + let caseId; + let resultCleanup1; if (isSome) { - const enumResult = enumHelpers.APIResult.lower(result); - resultCaseId = enumResult.caseId; - resultCleanup = enumResult.cleanup; - } - instance.exports.bjs_roundTripOptionalAPIResult(+isSome, isSome ? resultCaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.APIResult.lower(result); + caseId = resultCaseId; + resultCleanup1 = () => { + resultCleanup && resultCleanup(); + }; } else { - optResult = enumHelpers.APIResult.lift(tag); + caseId = 0; } - if (resultCleanup) { resultCleanup(); } + instance.exports.bjs_roundTripOptionalAPIResult(+isSome, caseId); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.APIResult.lift(tag); + resultCleanup1 && resultCleanup1(); return optResult; }, handleComplex: function bjs_handleComplex(result) { const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.ComplexResult.lower(result); instance.exports.bjs_handleComplex(resultCaseId); - if (resultCleanup) { resultCleanup(); } + resultCleanup && resultCleanup(); }, getComplexResult: function bjs_getComplexResult() { instance.exports.bjs_getComplexResult(); @@ -1176,196 +1178,193 @@ export async function createInstantiator(options, swift) { const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.ComplexResult.lower(result); instance.exports.bjs_roundtripComplexResult(resultCaseId); const ret = enumHelpers.ComplexResult.lift(i32Stack.pop()); - if (resultCleanup) { resultCleanup(); } + resultCleanup && resultCleanup(); return ret; }, roundTripOptionalComplexResult: function bjs_roundTripOptionalComplexResult(result) { const isSome = result != null; - let resultCaseId, resultCleanup; + let caseId; + let resultCleanup1; if (isSome) { - const enumResult = enumHelpers.ComplexResult.lower(result); - resultCaseId = enumResult.caseId; - resultCleanup = enumResult.cleanup; - } - instance.exports.bjs_roundTripOptionalComplexResult(+isSome, isSome ? resultCaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.ComplexResult.lower(result); + caseId = resultCaseId; + resultCleanup1 = () => { + resultCleanup && resultCleanup(); + }; } else { - optResult = enumHelpers.ComplexResult.lift(tag); + caseId = 0; } - if (resultCleanup) { resultCleanup(); } + instance.exports.bjs_roundTripOptionalComplexResult(+isSome, caseId); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.ComplexResult.lift(tag); + resultCleanup1 && resultCleanup1(); return optResult; }, roundTripOptionalUtilitiesResult: function bjs_roundTripOptionalUtilitiesResult(result) { const isSome = result != null; - let resultCaseId, resultCleanup; + let caseId; + let resultCleanup1; if (isSome) { - const enumResult = enumHelpers.Result.lower(result); - resultCaseId = enumResult.caseId; - resultCleanup = enumResult.cleanup; - } - instance.exports.bjs_roundTripOptionalUtilitiesResult(+isSome, isSome ? resultCaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.Result.lower(result); + caseId = resultCaseId; + resultCleanup1 = () => { + resultCleanup && resultCleanup(); + }; } else { - optResult = enumHelpers.Result.lift(tag); + caseId = 0; } - if (resultCleanup) { resultCleanup(); } + instance.exports.bjs_roundTripOptionalUtilitiesResult(+isSome, caseId); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.Result.lift(tag); + resultCleanup1 && resultCleanup1(); return optResult; }, roundTripOptionalNetworkingResult: function bjs_roundTripOptionalNetworkingResult(result) { const isSome = result != null; - let resultCaseId, resultCleanup; + let caseId; + let resultCleanup1; if (isSome) { - const enumResult = enumHelpers.NetworkingResult.lower(result); - resultCaseId = enumResult.caseId; - resultCleanup = enumResult.cleanup; - } - instance.exports.bjs_roundTripOptionalNetworkingResult(+isSome, isSome ? resultCaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.NetworkingResult.lower(result); + caseId = resultCaseId; + resultCleanup1 = () => { + resultCleanup && resultCleanup(); + }; } else { - optResult = enumHelpers.NetworkingResult.lift(tag); + caseId = 0; } - if (resultCleanup) { resultCleanup(); } + instance.exports.bjs_roundTripOptionalNetworkingResult(+isSome, caseId); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.NetworkingResult.lift(tag); + resultCleanup1 && resultCleanup1(); return optResult; }, roundTripOptionalAPIOptionalResult: function bjs_roundTripOptionalAPIOptionalResult(result) { const isSome = result != null; - let resultCaseId, resultCleanup; + let caseId; + let resultCleanup1; if (isSome) { - const enumResult = enumHelpers.APIOptionalResult.lower(result); - resultCaseId = enumResult.caseId; - resultCleanup = enumResult.cleanup; - } - instance.exports.bjs_roundTripOptionalAPIOptionalResult(+isSome, isSome ? resultCaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.APIOptionalResult.lower(result); + caseId = resultCaseId; + resultCleanup1 = () => { + resultCleanup && resultCleanup(); + }; } else { - optResult = enumHelpers.APIOptionalResult.lift(tag); + caseId = 0; } - if (resultCleanup) { resultCleanup(); } + instance.exports.bjs_roundTripOptionalAPIOptionalResult(+isSome, caseId); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.APIOptionalResult.lift(tag); + resultCleanup1 && resultCleanup1(); return optResult; }, compareAPIResults: function bjs_compareAPIResults(result1, result2) { const isSome = result1 != null; - let result1CaseId, result1Cleanup; + let caseId; + let result1Cleanup1; if (isSome) { - const enumResult = enumHelpers.APIOptionalResult.lower(result1); - result1CaseId = enumResult.caseId; - result1Cleanup = enumResult.cleanup; + const { caseId: result1CaseId, cleanup: result1Cleanup } = enumHelpers.APIOptionalResult.lower(result1); + caseId = result1CaseId; + result1Cleanup1 = () => { + result1Cleanup && result1Cleanup(); + }; + } else { + caseId = 0; } const isSome1 = result2 != null; - let result2CaseId, result2Cleanup; + let caseId1; + let result2Cleanup1; if (isSome1) { - const enumResult1 = enumHelpers.APIOptionalResult.lower(result2); - result2CaseId = enumResult1.caseId; - result2Cleanup = enumResult1.cleanup; - } - instance.exports.bjs_compareAPIResults(+isSome, isSome ? result1CaseId : 0, +isSome1, isSome1 ? result2CaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: result2CaseId, cleanup: result2Cleanup } = enumHelpers.APIOptionalResult.lower(result2); + caseId1 = result2CaseId; + result2Cleanup1 = () => { + result2Cleanup && result2Cleanup(); + }; } else { - optResult = enumHelpers.APIOptionalResult.lift(tag); + caseId1 = 0; } - if (result1Cleanup) { result1Cleanup(); } - if (result2Cleanup) { result2Cleanup(); } + instance.exports.bjs_compareAPIResults(+isSome, caseId, +isSome1, caseId1); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.APIOptionalResult.lift(tag); + result1Cleanup1 && result1Cleanup1(); + result2Cleanup1 && result2Cleanup1(); return optResult; }, roundTripTypedPayloadResult: function bjs_roundTripTypedPayloadResult(result) { const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.TypedPayloadResult.lower(result); instance.exports.bjs_roundTripTypedPayloadResult(resultCaseId); const ret = enumHelpers.TypedPayloadResult.lift(i32Stack.pop()); - if (resultCleanup) { resultCleanup(); } + resultCleanup && resultCleanup(); return ret; }, roundTripOptionalTypedPayloadResult: function bjs_roundTripOptionalTypedPayloadResult(result) { const isSome = result != null; - let resultCaseId, resultCleanup; + let caseId; + let resultCleanup1; if (isSome) { - const enumResult = enumHelpers.TypedPayloadResult.lower(result); - resultCaseId = enumResult.caseId; - resultCleanup = enumResult.cleanup; - } - instance.exports.bjs_roundTripOptionalTypedPayloadResult(+isSome, isSome ? resultCaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.TypedPayloadResult.lower(result); + caseId = resultCaseId; + resultCleanup1 = () => { + resultCleanup && resultCleanup(); + }; } else { - optResult = enumHelpers.TypedPayloadResult.lift(tag); + caseId = 0; } - if (resultCleanup) { resultCleanup(); } + instance.exports.bjs_roundTripOptionalTypedPayloadResult(+isSome, caseId); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.TypedPayloadResult.lift(tag); + resultCleanup1 && resultCleanup1(); return optResult; }, roundTripAllTypesResult: function bjs_roundTripAllTypesResult(result) { const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.AllTypesResult.lower(result); instance.exports.bjs_roundTripAllTypesResult(resultCaseId); const ret = enumHelpers.AllTypesResult.lift(i32Stack.pop()); - if (resultCleanup) { resultCleanup(); } + resultCleanup && resultCleanup(); return ret; }, roundTripOptionalAllTypesResult: function bjs_roundTripOptionalAllTypesResult(result) { const isSome = result != null; - let resultCaseId, resultCleanup; + let caseId; + let resultCleanup1; if (isSome) { - const enumResult = enumHelpers.AllTypesResult.lower(result); - resultCaseId = enumResult.caseId; - resultCleanup = enumResult.cleanup; - } - instance.exports.bjs_roundTripOptionalAllTypesResult(+isSome, isSome ? resultCaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.AllTypesResult.lower(result); + caseId = resultCaseId; + resultCleanup1 = () => { + resultCleanup && resultCleanup(); + }; } else { - optResult = enumHelpers.AllTypesResult.lift(tag); + caseId = 0; } - if (resultCleanup) { resultCleanup(); } + instance.exports.bjs_roundTripOptionalAllTypesResult(+isSome, caseId); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.AllTypesResult.lift(tag); + resultCleanup1 && resultCleanup1(); return optResult; }, roundTripOptionalPayloadResult: function bjs_roundTripOptionalPayloadResult(result) { const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.OptionalAllTypesResult.lower(result); instance.exports.bjs_roundTripOptionalPayloadResult(resultCaseId); const ret = enumHelpers.OptionalAllTypesResult.lift(i32Stack.pop()); - if (resultCleanup) { resultCleanup(); } + resultCleanup && resultCleanup(); return ret; }, roundTripOptionalPayloadResultOpt: function bjs_roundTripOptionalPayloadResultOpt(result) { const isSome = result != null; - let resultCaseId, resultCleanup; + let caseId; + let resultCleanup1; if (isSome) { - const enumResult = enumHelpers.OptionalAllTypesResult.lower(result); - resultCaseId = enumResult.caseId; - resultCleanup = enumResult.cleanup; - } - instance.exports.bjs_roundTripOptionalPayloadResultOpt(+isSome, isSome ? resultCaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: resultCaseId, cleanup: resultCleanup } = enumHelpers.OptionalAllTypesResult.lower(result); + caseId = resultCaseId; + resultCleanup1 = () => { + resultCleanup && resultCleanup(); + }; } else { - optResult = enumHelpers.OptionalAllTypesResult.lift(tag); + caseId = 0; } - if (resultCleanup) { resultCleanup(); } + instance.exports.bjs_roundTripOptionalPayloadResultOpt(+isSome, caseId); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.OptionalAllTypesResult.lift(tag); + resultCleanup1 && resultCleanup1(); return optResult; }, APIResult: APIResultValues, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js index 04d667b8e..593e87186 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js @@ -316,17 +316,20 @@ export async function createInstantiator(options, swift) { }, roundTripOptionalTheme: function bjs_roundTripOptionalTheme(input) { const isSome = input != null; - let inputId, inputBytes; + let bytes, length; if (isSome) { - inputBytes = textEncoder.encode(input); - inputId = swift.memory.retain(inputBytes); + const inputBytes = textEncoder.encode(input); + const inputId = swift.memory.retain(inputBytes); + bytes = inputId; + length = inputBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_roundTripOptionalTheme(+isSome, isSome ? inputId : 0, isSome ? inputBytes.length : 0); + instance.exports.bjs_roundTripOptionalTheme(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (inputId != undefined) { - swift.memory.release(inputId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, setTSTheme: function bjs_setTSTheme(theme) { @@ -343,17 +346,20 @@ export async function createInstantiator(options, swift) { }, roundTripOptionalTSTheme: function bjs_roundTripOptionalTSTheme(input) { const isSome = input != null; - let inputId, inputBytes; + let bytes, length; if (isSome) { - inputBytes = textEncoder.encode(input); - inputId = swift.memory.retain(inputBytes); + const inputBytes = textEncoder.encode(input); + const inputId = swift.memory.retain(inputBytes); + bytes = inputId; + length = inputBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_roundTripOptionalTSTheme(+isSome, isSome ? inputId : 0, isSome ? inputBytes.length : 0); + instance.exports.bjs_roundTripOptionalTSTheme(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (inputId != undefined) { - swift.memory.release(inputId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, setFeatureFlag: function bjs_setFeatureFlag(flag) { @@ -370,17 +376,20 @@ export async function createInstantiator(options, swift) { }, roundTripOptionalFeatureFlag: function bjs_roundTripOptionalFeatureFlag(input) { const isSome = input != null; - let inputId, inputBytes; + let bytes, length; if (isSome) { - inputBytes = textEncoder.encode(input); - inputId = swift.memory.retain(inputBytes); + const inputBytes = textEncoder.encode(input); + const inputId = swift.memory.retain(inputBytes); + bytes = inputId; + length = inputBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_roundTripOptionalFeatureFlag(+isSome, isSome ? inputId : 0, isSome ? inputBytes.length : 0); + instance.exports.bjs_roundTripOptionalFeatureFlag(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (inputId != undefined) { - swift.memory.release(inputId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, setHttpStatus: function bjs_setHttpStatus(status) { @@ -490,7 +499,7 @@ export async function createInstantiator(options, swift) { }, roundTripOptionalPrecision: function bjs_roundTripOptionalPrecision(input) { const isSome = input != null; - instance.exports.bjs_roundTripOptionalPrecision(+isSome, isSome ? input : 0); + instance.exports.bjs_roundTripOptionalPrecision(+isSome, isSome ? input : 0.0); const optResult = tmpRetOptionalFloat; tmpRetOptionalFloat = undefined; return optResult; @@ -504,7 +513,7 @@ export async function createInstantiator(options, swift) { }, roundTripOptionalRatio: function bjs_roundTripOptionalRatio(input) { const isSome = input != null; - instance.exports.bjs_roundTripOptionalRatio(+isSome, isSome ? input : 0); + instance.exports.bjs_roundTripOptionalRatio(+isSome, isSome ? input : 0.0); const optResult = tmpRetOptionalDouble; tmpRetOptionalDouble = undefined; return optResult; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js index 48aab8fbb..50643a331 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js @@ -40,16 +40,37 @@ export async function createInstantiator(options, swift) { } i32Stack.push(id !== undefined ? id : 0); const isSome = value.optionalFoo != null; - let id1; + let innerCleanup; if (isSome) { - id1 = swift.memory.retain(value.optionalFoo); - i32Stack.push(id1); + let id1; + if (value.optionalFoo != null) { + id1 = swift.memory.retain(value.optionalFoo); + } else { + id1 = undefined; + } + i32Stack.push(id1 !== undefined ? id1 : 0); + innerCleanup = () => { + if(id1 !== undefined && id1 !== 0) { + try { + swift.memory.getObject(id1); + swift.memory.release(id1); + } catch(e) { console.warn('BridgeJS: cleanup failed for retained object', e); } + } + }; } else { - id1 = undefined; i32Stack.push(0); } i32Stack.push(isSome ? 1 : 0); - return { cleanup: undefined }; + const cleanup = () => { + if(id !== undefined && id !== 0) { + try { + swift.memory.getObject(id); + swift.memory.release(id); + } catch(e) { console.warn('BridgeJS: cleanup failed for retained object', e); } + } + innerCleanup && innerCleanup(); + }; + return { cleanup }; }, lift: () => { const isSome = i32Stack.pop(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js index 426fd8c1a..08fad6f2b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js @@ -369,15 +369,35 @@ export async function createInstantiator(options, swift) { constructor(value, optionalValue) { const [valueKind, valuePayload1, valuePayload2] = __bjs_jsValueLower(value); const isSome = optionalValue != null; - const [optionalValueKind, optionalValuePayload1, optionalValuePayload2] = __bjs_jsValueLower(optionalValue); - const ret = instance.exports.bjs_JSValueHolder_init(valueKind, valuePayload1, valuePayload2, +isSome, optionalValueKind, optionalValuePayload1, optionalValuePayload2); + let kind, payload1, payload2; + if (isSome) { + const [optionalValueKind, optionalValuePayload1, optionalValuePayload2] = __bjs_jsValueLower(optionalValue); + kind = optionalValueKind; + payload1 = optionalValuePayload1; + payload2 = optionalValuePayload2; + } else { + kind = 0; + payload1 = 0; + payload2 = 0.0; + } + const ret = instance.exports.bjs_JSValueHolder_init(valueKind, valuePayload1, valuePayload2, +isSome, kind, payload1, payload2); return JSValueHolder.__construct(ret); } update(value, optionalValue) { const [valueKind, valuePayload1, valuePayload2] = __bjs_jsValueLower(value); const isSome = optionalValue != null; - const [optionalValueKind, optionalValuePayload1, optionalValuePayload2] = __bjs_jsValueLower(optionalValue); - instance.exports.bjs_JSValueHolder_update(this.pointer, valueKind, valuePayload1, valuePayload2, +isSome, optionalValueKind, optionalValuePayload1, optionalValuePayload2); + let kind, payload1, payload2; + if (isSome) { + const [optionalValueKind, optionalValuePayload1, optionalValuePayload2] = __bjs_jsValueLower(optionalValue); + kind = optionalValueKind; + payload1 = optionalValuePayload1; + payload2 = optionalValuePayload2; + } else { + kind = 0; + payload1 = 0; + payload2 = 0.0; + } + instance.exports.bjs_JSValueHolder_update(this.pointer, valueKind, valuePayload1, valuePayload2, +isSome, kind, payload1, payload2); } echo(value) { const [valueKind, valuePayload1, valuePayload2] = __bjs_jsValueLower(value); @@ -390,8 +410,18 @@ export async function createInstantiator(options, swift) { } echoOptional(value) { const isSome = value != null; - const [valueKind, valuePayload1, valuePayload2] = __bjs_jsValueLower(value); - instance.exports.bjs_JSValueHolder_echoOptional(this.pointer, +isSome, valueKind, valuePayload1, valuePayload2); + let kind, payload1, payload2; + if (isSome) { + const [valueKind, valuePayload1, valuePayload2] = __bjs_jsValueLower(value); + kind = valueKind; + payload1 = valuePayload1; + payload2 = valuePayload2; + } else { + kind = 0; + payload1 = 0; + payload2 = 0.0; + } + instance.exports.bjs_JSValueHolder_echoOptional(this.pointer, +isSome, kind, payload1, payload2); const isSome1 = i32Stack.pop(); let optResult; if (isSome1) { @@ -434,8 +464,18 @@ export async function createInstantiator(options, swift) { } set optionalValue(value) { const isSome = value != null; - const [valueKind, valuePayload1, valuePayload2] = __bjs_jsValueLower(value); - instance.exports.bjs_JSValueHolder_optionalValue_set(this.pointer, +isSome, valueKind, valuePayload1, valuePayload2); + let kind, payload1, payload2; + if (isSome) { + const [valueKind, valuePayload1, valuePayload2] = __bjs_jsValueLower(value); + kind = valueKind; + payload1 = valuePayload1; + payload2 = valuePayload2; + } else { + kind = 0; + payload1 = 0; + payload2 = 0.0; + } + instance.exports.bjs_JSValueHolder_optionalValue_set(this.pointer, +isSome, kind, payload1, payload2); } } const exports = { @@ -451,8 +491,18 @@ export async function createInstantiator(options, swift) { }, roundTripOptionalJSValue: function bjs_roundTripOptionalJSValue(value) { const isSome = value != null; - const [valueKind, valuePayload1, valuePayload2] = __bjs_jsValueLower(value); - instance.exports.bjs_roundTripOptionalJSValue(+isSome, valueKind, valuePayload1, valuePayload2); + let kind, payload1, payload2; + if (isSome) { + const [valueKind, valuePayload1, valuePayload2] = __bjs_jsValueLower(value); + kind = valueKind; + payload1 = valuePayload1; + payload2 = valuePayload2; + } else { + kind = 0; + payload1 = 0; + payload2 = 0.0; + } + instance.exports.bjs_roundTripOptionalJSValue(+isSome, kind, payload1, payload2); const isSome1 = i32Stack.pop(); let optResult; if (isSome1) { @@ -491,7 +541,7 @@ export async function createInstantiator(options, swift) { }, roundTripOptionalJSValueArray: function bjs_roundTripOptionalJSValueArray(values) { const isSome = values != null; - const valuesCleanups = []; + let valuesCleanup; if (isSome) { const arrayCleanups = []; for (const elem of values) { @@ -501,7 +551,9 @@ export async function createInstantiator(options, swift) { f64Stack.push(elemPayload2); } i32Stack.push(values.length); - valuesCleanups.push(() => { for (const cleanup of arrayCleanups) { cleanup(); } }); + valuesCleanup = () => { + for (const cleanup of arrayCleanups) { cleanup(); } + }; } i32Stack.push(+isSome); instance.exports.bjs_roundTripOptionalJSValueArray(); @@ -522,7 +574,7 @@ export async function createInstantiator(options, swift) { } else { optResult = null; } - for (const cleanup of valuesCleanups) { cleanup(); } + valuesCleanup && valuesCleanup(); return optResult; }, }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js index f637af601..6be505648 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js @@ -208,19 +208,25 @@ export async function createInstantiator(options, swift) { return swift.memory.retain(obj); }; const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; - TestModule["bjs_WithOptionalJSClass_init"] = function bjs_WithOptionalJSClass_init(valueOrNullIsSome, valueOrNullWrappedValue, valueOrUndefinedIsSome, valueOrUndefinedWrappedValue) { + TestModule["bjs_WithOptionalJSClass_init"] = function bjs_WithOptionalJSClass_init(valueOrNullIsSome, valueOrNullObjectId, valueOrUndefinedIsSome, valueOrUndefinedObjectId) { try { - let obj; + let optResult; if (valueOrNullIsSome) { - obj = swift.memory.getObject(valueOrNullWrappedValue); - swift.memory.release(valueOrNullWrappedValue); + const valueOrNullObjectIdObject = swift.memory.getObject(valueOrNullObjectId); + swift.memory.release(valueOrNullObjectId); + optResult = valueOrNullObjectIdObject; + } else { + optResult = null; } - let obj1; + let optResult1; if (valueOrUndefinedIsSome) { - obj1 = swift.memory.getObject(valueOrUndefinedWrappedValue); - swift.memory.release(valueOrUndefinedWrappedValue); + const valueOrUndefinedObjectIdObject = swift.memory.getObject(valueOrUndefinedObjectId); + swift.memory.release(valueOrUndefinedObjectId); + optResult1 = valueOrUndefinedObjectIdObject; + } else { + optResult1 = undefined; } - return swift.memory.retain(new imports.WithOptionalJSClass(valueOrNullIsSome ? obj : null, valueOrUndefinedIsSome ? obj1 : undefined)); + return swift.memory.retain(new imports.WithOptionalJSClass(optResult, optResult1)); } catch (error) { setException(error); return 0 @@ -230,11 +236,7 @@ export async function createInstantiator(options, swift) { try { let ret = swift.memory.getObject(self).stringOrNull; const isSome = ret != null; - if (isSome) { - tmpRetString = ret; - } else { - tmpRetString = null; - } + tmpRetString = isSome ? ret : null; } catch (error) { setException(error); } @@ -243,11 +245,7 @@ export async function createInstantiator(options, swift) { try { let ret = swift.memory.getObject(self).stringOrUndefined; const isSome = ret !== undefined; - if (isSome) { - tmpRetString = ret; - } else { - tmpRetString = null; - } + tmpRetString = isSome ? ret : undefined; } catch (error) { setException(error); } @@ -306,26 +304,32 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_WithOptionalJSClass_stringOrNull_set"] = function bjs_WithOptionalJSClass_stringOrNull_set(self, newValueIsSome, newValueWrappedValue) { + TestModule["bjs_WithOptionalJSClass_stringOrNull_set"] = function bjs_WithOptionalJSClass_stringOrNull_set(self, newValueIsSome, newValueObjectId) { try { - let obj; + let optResult; if (newValueIsSome) { - obj = swift.memory.getObject(newValueWrappedValue); - swift.memory.release(newValueWrappedValue); + const newValueObjectIdObject = swift.memory.getObject(newValueObjectId); + swift.memory.release(newValueObjectId); + optResult = newValueObjectIdObject; + } else { + optResult = null; } - swift.memory.getObject(self).stringOrNull = newValueIsSome ? obj : null; + swift.memory.getObject(self).stringOrNull = optResult; } catch (error) { setException(error); } } - TestModule["bjs_WithOptionalJSClass_stringOrUndefined_set"] = function bjs_WithOptionalJSClass_stringOrUndefined_set(self, newValueIsSome, newValueWrappedValue) { + TestModule["bjs_WithOptionalJSClass_stringOrUndefined_set"] = function bjs_WithOptionalJSClass_stringOrUndefined_set(self, newValueIsSome, newValueObjectId) { try { - let obj; + let optResult; if (newValueIsSome) { - obj = swift.memory.getObject(newValueWrappedValue); - swift.memory.release(newValueWrappedValue); + const newValueObjectIdObject = swift.memory.getObject(newValueObjectId); + swift.memory.release(newValueObjectId); + optResult = newValueObjectIdObject; + } else { + optResult = undefined; } - swift.memory.getObject(self).stringOrUndefined = newValueIsSome ? obj : undefined; + swift.memory.getObject(self).stringOrUndefined = optResult; } catch (error) { setException(error); } @@ -372,38 +376,36 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_WithOptionalJSClass_roundTripStringOrNull"] = function bjs_WithOptionalJSClass_roundTripStringOrNull(self, valueIsSome, valueWrappedValue) { + TestModule["bjs_WithOptionalJSClass_roundTripStringOrNull"] = function bjs_WithOptionalJSClass_roundTripStringOrNull(self, valueIsSome, valueObjectId) { try { - let obj; + let optResult; if (valueIsSome) { - obj = swift.memory.getObject(valueWrappedValue); - swift.memory.release(valueWrappedValue); - } - let ret = swift.memory.getObject(self).roundTripStringOrNull(valueIsSome ? obj : null); - const isSome = ret != null; - if (isSome) { - tmpRetString = ret; + const valueObjectIdObject = swift.memory.getObject(valueObjectId); + swift.memory.release(valueObjectId); + optResult = valueObjectIdObject; } else { - tmpRetString = null; + optResult = null; } + let ret = swift.memory.getObject(self).roundTripStringOrNull(optResult); + const isSome = ret != null; + tmpRetString = isSome ? ret : null; } catch (error) { setException(error); } } - TestModule["bjs_WithOptionalJSClass_roundTripStringOrUndefined"] = function bjs_WithOptionalJSClass_roundTripStringOrUndefined(self, valueIsSome, valueWrappedValue) { + TestModule["bjs_WithOptionalJSClass_roundTripStringOrUndefined"] = function bjs_WithOptionalJSClass_roundTripStringOrUndefined(self, valueIsSome, valueObjectId) { try { - let obj; + let optResult; if (valueIsSome) { - obj = swift.memory.getObject(valueWrappedValue); - swift.memory.release(valueWrappedValue); - } - let ret = swift.memory.getObject(self).roundTripStringOrUndefined(valueIsSome ? obj : undefined); - const isSome = ret !== undefined; - if (isSome) { - tmpRetString = ret; + const valueObjectIdObject = swift.memory.getObject(valueObjectId); + swift.memory.release(valueObjectId); + optResult = valueObjectIdObject; } else { - tmpRetString = null; + optResult = undefined; } + let ret = swift.memory.getObject(self).roundTripStringOrUndefined(optResult); + const isSome = ret !== undefined; + tmpRetString = isSome ? ret : undefined; } catch (error) { setException(error); } @@ -500,15 +502,18 @@ export async function createInstantiator(options, swift) { constructor(name) { const isSome = name != null; - let nameId, nameBytes; + let bytes, length; if (isSome) { - nameBytes = textEncoder.encode(name); - nameId = swift.memory.retain(nameBytes); - } - const ret = instance.exports.bjs_Greeter_init(+isSome, isSome ? nameId : 0, isSome ? nameBytes.length : 0); - if (nameId != undefined) { - swift.memory.release(nameId); + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + bytes = nameId; + length = nameBytes.length; + } else { + bytes = 0; + length = 0; } + const ret = instance.exports.bjs_Greeter_init(+isSome, bytes, length); + if (isSome) { swift.memory.release(bytes); } return Greeter.__construct(ret); } greet() { @@ -519,15 +524,18 @@ export async function createInstantiator(options, swift) { } changeName(name) { const isSome = name != null; - let nameId, nameBytes; + let bytes, length; if (isSome) { - nameBytes = textEncoder.encode(name); - nameId = swift.memory.retain(nameBytes); - } - instance.exports.bjs_Greeter_changeName(this.pointer, +isSome, isSome ? nameId : 0, isSome ? nameBytes.length : 0); - if (nameId != undefined) { - swift.memory.release(nameId); + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + bytes = nameId; + length = nameBytes.length; + } else { + bytes = 0; + length = 0; } + instance.exports.bjs_Greeter_changeName(this.pointer, +isSome, bytes, length); + if (isSome) { swift.memory.release(bytes); } } get name() { instance.exports.bjs_Greeter_name_get(this.pointer); @@ -537,15 +545,18 @@ export async function createInstantiator(options, swift) { } set name(value) { const isSome = value != null; - let valueId, valueBytes; + let bytes, length; if (isSome) { - valueBytes = textEncoder.encode(value); - valueId = swift.memory.retain(valueBytes); - } - instance.exports.bjs_Greeter_name_set(this.pointer, +isSome, isSome ? valueId : 0, isSome ? valueBytes.length : 0); - if (valueId != undefined) { - swift.memory.release(valueId); + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + bytes = valueId; + length = valueBytes.length; + } else { + bytes = 0; + length = 0; } + instance.exports.bjs_Greeter_name_set(this.pointer, +isSome, bytes, length); + if (isSome) { swift.memory.release(bytes); } } } class OptionalPropertyHolder extends SwiftHeapObject { @@ -565,15 +576,18 @@ export async function createInstantiator(options, swift) { } set optionalName(value) { const isSome = value != null; - let valueId, valueBytes; + let bytes, length; if (isSome) { - valueBytes = textEncoder.encode(value); - valueId = swift.memory.retain(valueBytes); - } - instance.exports.bjs_OptionalPropertyHolder_optionalName_set(this.pointer, +isSome, isSome ? valueId : 0, isSome ? valueBytes.length : 0); - if (valueId != undefined) { - swift.memory.release(valueId); + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + bytes = valueId; + length = valueBytes.length; + } else { + bytes = 0; + length = 0; } + instance.exports.bjs_OptionalPropertyHolder_optionalName_set(this.pointer, +isSome, bytes, length); + if (isSome) { swift.memory.release(bytes); } } get optionalAge() { instance.exports.bjs_OptionalPropertyHolder_optionalAge_get(this.pointer); @@ -594,7 +608,13 @@ export async function createInstantiator(options, swift) { } set optionalGreeter(value) { const isSome = value != null; - instance.exports.bjs_OptionalPropertyHolder_optionalGreeter_set(this.pointer, +isSome, isSome ? value.pointer : 0); + let pointer; + if (isSome) { + pointer = value.pointer; + } else { + pointer = 0; + } + instance.exports.bjs_OptionalPropertyHolder_optionalGreeter_set(this.pointer, +isSome, pointer); } } const exports = { @@ -602,33 +622,48 @@ export async function createInstantiator(options, swift) { OptionalPropertyHolder, roundTripOptionalClass: function bjs_roundTripOptionalClass(value) { const isSome = value != null; - instance.exports.bjs_roundTripOptionalClass(+isSome, isSome ? value.pointer : 0); - const pointer = tmpRetOptionalHeapObject; + let pointer; + if (isSome) { + pointer = value.pointer; + } else { + pointer = 0; + } + instance.exports.bjs_roundTripOptionalClass(+isSome, pointer); + const pointer1 = tmpRetOptionalHeapObject; tmpRetOptionalHeapObject = undefined; - const optResult = pointer === null ? null : Greeter.__construct(pointer); + const optResult = pointer1 === null ? null : Greeter.__construct(pointer1); return optResult; }, testOptionalPropertyRoundtrip: function bjs_testOptionalPropertyRoundtrip(holder) { const isSome = holder != null; - instance.exports.bjs_testOptionalPropertyRoundtrip(+isSome, isSome ? holder.pointer : 0); - const pointer = tmpRetOptionalHeapObject; + let pointer; + if (isSome) { + pointer = holder.pointer; + } else { + pointer = 0; + } + instance.exports.bjs_testOptionalPropertyRoundtrip(+isSome, pointer); + const pointer1 = tmpRetOptionalHeapObject; tmpRetOptionalHeapObject = undefined; - const optResult = pointer === null ? null : OptionalPropertyHolder.__construct(pointer); + const optResult = pointer1 === null ? null : OptionalPropertyHolder.__construct(pointer1); return optResult; }, roundTripString: function bjs_roundTripString(name) { const isSome = name != null; - let nameId, nameBytes; + let bytes, length; if (isSome) { - nameBytes = textEncoder.encode(name); - nameId = swift.memory.retain(nameBytes); + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + bytes = nameId; + length = nameBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_roundTripString(+isSome, isSome ? nameId : 0, isSome ? nameBytes.length : 0); + instance.exports.bjs_roundTripString(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (nameId != undefined) { - swift.memory.release(nameId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, roundTripInt: function bjs_roundTripInt(value) { @@ -640,88 +675,100 @@ export async function createInstantiator(options, swift) { }, roundTripBool: function bjs_roundTripBool(flag) { const isSome = flag != null; - instance.exports.bjs_roundTripBool(+isSome, isSome ? flag : 0); + instance.exports.bjs_roundTripBool(+isSome, isSome ? flag ? 1 : 0 : 0); const optResult = tmpRetOptionalBool; tmpRetOptionalBool = undefined; return optResult; }, roundTripFloat: function bjs_roundTripFloat(number) { const isSome = number != null; - instance.exports.bjs_roundTripFloat(+isSome, isSome ? number : 0); + instance.exports.bjs_roundTripFloat(+isSome, isSome ? number : 0.0); const optResult = tmpRetOptionalFloat; tmpRetOptionalFloat = undefined; return optResult; }, roundTripDouble: function bjs_roundTripDouble(precision) { const isSome = precision != null; - instance.exports.bjs_roundTripDouble(+isSome, isSome ? precision : 0); + instance.exports.bjs_roundTripDouble(+isSome, isSome ? precision : 0.0); const optResult = tmpRetOptionalDouble; tmpRetOptionalDouble = undefined; return optResult; }, roundTripSyntax: function bjs_roundTripSyntax(name) { const isSome = name != null; - let nameId, nameBytes; + let bytes, length; if (isSome) { - nameBytes = textEncoder.encode(name); - nameId = swift.memory.retain(nameBytes); + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + bytes = nameId; + length = nameBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_roundTripSyntax(+isSome, isSome ? nameId : 0, isSome ? nameBytes.length : 0); + instance.exports.bjs_roundTripSyntax(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (nameId != undefined) { - swift.memory.release(nameId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, roundTripMixSyntax: function bjs_roundTripMixSyntax(name) { const isSome = name != null; - let nameId, nameBytes; + let bytes, length; if (isSome) { - nameBytes = textEncoder.encode(name); - nameId = swift.memory.retain(nameBytes); + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + bytes = nameId; + length = nameBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_roundTripMixSyntax(+isSome, isSome ? nameId : 0, isSome ? nameBytes.length : 0); + instance.exports.bjs_roundTripMixSyntax(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (nameId != undefined) { - swift.memory.release(nameId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, roundTripSwiftSyntax: function bjs_roundTripSwiftSyntax(name) { const isSome = name != null; - let nameId, nameBytes; + let bytes, length; if (isSome) { - nameBytes = textEncoder.encode(name); - nameId = swift.memory.retain(nameBytes); + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + bytes = nameId; + length = nameBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_roundTripSwiftSyntax(+isSome, isSome ? nameId : 0, isSome ? nameBytes.length : 0); + instance.exports.bjs_roundTripSwiftSyntax(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (nameId != undefined) { - swift.memory.release(nameId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, roundTripMixedSwiftSyntax: function bjs_roundTripMixedSwiftSyntax(name) { const isSome = name != null; - let nameId, nameBytes; + let bytes, length; if (isSome) { - nameBytes = textEncoder.encode(name); - nameId = swift.memory.retain(nameBytes); + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + bytes = nameId; + length = nameBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_roundTripMixedSwiftSyntax(+isSome, isSome ? nameId : 0, isSome ? nameBytes.length : 0); + instance.exports.bjs_roundTripMixedSwiftSyntax(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (nameId != undefined) { - swift.memory.release(nameId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, roundTripWithSpaces: function bjs_roundTripWithSpaces(value) { const isSome = value != null; - instance.exports.bjs_roundTripWithSpaces(+isSome, isSome ? value : 0); + instance.exports.bjs_roundTripWithSpaces(+isSome, isSome ? value : 0.0); const optResult = tmpRetOptionalDouble; tmpRetOptionalDouble = undefined; return optResult; @@ -735,42 +782,51 @@ export async function createInstantiator(options, swift) { }, roundTripOptionalAlias: function bjs_roundTripOptionalAlias(name) { const isSome = name != null; - let nameId, nameBytes; + let bytes, length; if (isSome) { - nameBytes = textEncoder.encode(name); - nameId = swift.memory.retain(nameBytes); + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + bytes = nameId; + length = nameBytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.bjs_roundTripOptionalAlias(+isSome, isSome ? nameId : 0, isSome ? nameBytes.length : 0); + instance.exports.bjs_roundTripOptionalAlias(+isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (nameId != undefined) { - swift.memory.release(nameId); - } + if (isSome) { swift.memory.release(bytes); } return optResult; }, testMixedOptionals: function bjs_testMixedOptionals(firstName, lastName, age, active) { const isSome = firstName != null; - let firstNameId, firstNameBytes; + let bytes, length; if (isSome) { - firstNameBytes = textEncoder.encode(firstName); - firstNameId = swift.memory.retain(firstNameBytes); + const firstNameBytes = textEncoder.encode(firstName); + const firstNameId = swift.memory.retain(firstNameBytes); + bytes = firstNameId; + length = firstNameBytes.length; + } else { + bytes = 0; + length = 0; } const isSome1 = lastName != null; - let lastNameId, lastNameBytes; + let bytes1, length1; if (isSome1) { - lastNameBytes = textEncoder.encode(lastName); - lastNameId = swift.memory.retain(lastNameBytes); + const lastNameBytes = textEncoder.encode(lastName); + const lastNameId = swift.memory.retain(lastNameBytes); + bytes1 = lastNameId; + length1 = lastNameBytes.length; + } else { + bytes1 = 0; + length1 = 0; } const isSome2 = age != null; - instance.exports.bjs_testMixedOptionals(+isSome, isSome ? firstNameId : 0, isSome ? firstNameBytes.length : 0, +isSome1, isSome1 ? lastNameId : 0, isSome1 ? lastNameBytes.length : 0, +isSome2, isSome2 ? age : 0, active); + instance.exports.bjs_testMixedOptionals(+isSome, bytes, length, +isSome1, bytes1, length1, +isSome2, isSome2 ? age : 0, active); const optResult = tmpRetString; tmpRetString = undefined; - if (firstNameId != undefined) { - swift.memory.release(firstNameId); - } - if (lastNameId != undefined) { - swift.memory.release(lastNameId); - } + if (isSome) { swift.memory.release(bytes); } + if (isSome1) { swift.memory.release(bytes1); } return optResult; }, }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js index d783e8968..b79ac7507 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js @@ -307,14 +307,17 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_MyViewControllerDelegate_optionalName_set"] = function bjs_MyViewControllerDelegate_optionalName_set(self, valueIsSome, valueWrappedValue) { + TestModule["bjs_MyViewControllerDelegate_optionalName_set"] = function bjs_MyViewControllerDelegate_optionalName_set(self, valueIsSome, valueObjectId) { try { - let obj; + let optResult; if (valueIsSome) { - obj = swift.memory.getObject(valueWrappedValue); - swift.memory.release(valueWrappedValue); + const valueObjectIdObject = swift.memory.getObject(valueObjectId); + swift.memory.release(valueObjectId); + optResult = valueObjectIdObject; + } else { + optResult = null; } - swift.memory.getObject(self).optionalName = valueIsSome ? obj : null; + swift.memory.getObject(self).optionalName = optResult; } catch (error) { setException(error); } @@ -327,14 +330,17 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_MyViewControllerDelegate_optionalRawEnum_set"] = function bjs_MyViewControllerDelegate_optionalRawEnum_set(self, valueIsSome, valueWrappedValue) { + TestModule["bjs_MyViewControllerDelegate_optionalRawEnum_set"] = function bjs_MyViewControllerDelegate_optionalRawEnum_set(self, valueIsSome, valueObjectId) { try { - let obj; + let optResult; if (valueIsSome) { - obj = swift.memory.getObject(valueWrappedValue); - swift.memory.release(valueWrappedValue); + const valueObjectIdObject = swift.memory.getObject(valueObjectId); + swift.memory.release(valueObjectId); + optResult = valueObjectIdObject; + } else { + optResult = null; } - swift.memory.getObject(self).optionalRawEnum = valueIsSome ? obj : null; + swift.memory.getObject(self).optionalRawEnum = optResult; } catch (error) { setException(error); } @@ -360,7 +366,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_MyViewControllerDelegate_result_get"] = function bjs_MyViewControllerDelegate_result_get(self) { try { let ret = swift.memory.getObject(self).result; - const { caseId: caseId, cleanup: cleanup } = enumHelpers.Result.lower(ret); + const { caseId: caseId } = enumHelpers.Result.lower(ret); return caseId; } catch (error) { setException(error); @@ -379,7 +385,7 @@ export async function createInstantiator(options, swift) { let ret = swift.memory.getObject(self).optionalResult; const isSome = ret != null; if (isSome) { - const { caseId: caseId, cleanup: cleanup } = enumHelpers.Result.lower(ret); + const { caseId: caseId } = enumHelpers.Result.lower(ret); return caseId; } else { return -1; @@ -388,13 +394,16 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_MyViewControllerDelegate_optionalResult_set"] = function bjs_MyViewControllerDelegate_optionalResult_set(self, valueIsSome, valueWrappedValue) { + TestModule["bjs_MyViewControllerDelegate_optionalResult_set"] = function bjs_MyViewControllerDelegate_optionalResult_set(self, valueIsSome, valueCaseId) { try { - let enumValue; + let optResult; if (valueIsSome) { - enumValue = enumHelpers.Result.lift(valueWrappedValue); + const enumValue = enumHelpers.Result.lift(valueCaseId); + optResult = enumValue; + } else { + optResult = null; } - swift.memory.getObject(self).optionalResult = valueIsSome ? enumValue : null; + swift.memory.getObject(self).optionalResult = optResult; } catch (error) { setException(error); } @@ -419,7 +428,7 @@ export async function createInstantiator(options, swift) { try { let ret = swift.memory.getObject(self).directionOptional; const isSome = ret != null; - return isSome ? (ret | 0) : -1; + return isSome ? ret : -1; } catch (error) { setException(error); } @@ -523,9 +532,9 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_MyViewControllerDelegate_onOptionalHelperUpdated"] = function bjs_MyViewControllerDelegate_onOptionalHelperUpdated(self, helperIsSome, helperWrappedValue) { + TestModule["bjs_MyViewControllerDelegate_onOptionalHelperUpdated"] = function bjs_MyViewControllerDelegate_onOptionalHelperUpdated(self, helperIsSome, helperPointer) { try { - swift.memory.getObject(self).onOptionalHelperUpdated(helperIsSome ? _exports['Helper'].__construct(helperWrappedValue) : null); + swift.memory.getObject(self).onOptionalHelperUpdated(helperIsSome ? _exports['Helper'].__construct(helperPointer) : null); } catch (error) { setException(error); } @@ -559,7 +568,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_MyViewControllerDelegate_getResult"] = function bjs_MyViewControllerDelegate_getResult(self) { try { let ret = swift.memory.getObject(self).getResult(); - const { caseId: caseId, cleanup: cleanup } = enumHelpers.Result.lower(ret); + const { caseId: caseId } = enumHelpers.Result.lower(ret); return caseId; } catch (error) { setException(error); @@ -671,7 +680,13 @@ export async function createInstantiator(options, swift) { } set secondDelegate(value) { const isSome = value != null; - instance.exports.bjs_MyViewController_secondDelegate_set(this.pointer, +isSome, isSome ? swift.memory.retain(value) : 0); + let value1; + if (isSome) { + value1 = swift.memory.retain(value); + } else { + value1 = 0; + } + instance.exports.bjs_MyViewController_secondDelegate_set(this.pointer, +isSome, value1); } } class DelegateManager extends SwiftHeapObject { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js index 892110aca..dce27c8a1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js @@ -329,7 +329,7 @@ export async function createInstantiator(options, swift) { const { caseId: valueCaseId, cleanup: valueCleanup } = enumHelpers.APIResult.lower(value); instance.exports.bjs_APIResult_static_roundtrip(valueCaseId); const ret = enumHelpers.APIResult.lift(i32Stack.pop()); - if (valueCleanup) { valueCleanup(); } + valueCleanup && valueCleanup(); return ret; } }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js index f2c93adae..9a7a560b1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js @@ -323,7 +323,7 @@ export async function createInstantiator(options, swift) { const { caseId: valueCaseId, cleanup: valueCleanup } = enumHelpers.APIResult.lower(value); instance.exports.bjs_APIResult_static_roundtrip(valueCaseId); const ret = enumHelpers.APIResult.lift(i32Stack.pop()); - if (valueCleanup) { valueCleanup(); } + valueCleanup && valueCleanup(); return ret; } }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js index adb809f6f..8f3df8eac 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js @@ -305,15 +305,18 @@ export async function createInstantiator(options, swift) { } static set optionalProperty(value) { const isSome = value != null; - let valueId, valueBytes; + let bytes, length; if (isSome) { - valueBytes = textEncoder.encode(value); - valueId = swift.memory.retain(valueBytes); - } - instance.exports.bjs_PropertyClass_static_optionalProperty_set(+isSome, isSome ? valueId : 0, isSome ? valueBytes.length : 0); - if (valueId != undefined) { - swift.memory.release(valueId); + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + bytes = valueId; + length = valueBytes.length; + } else { + bytes = 0; + length = 0; } + instance.exports.bjs_PropertyClass_static_optionalProperty_set(+isSome, bytes, length); + if (isSome) { swift.memory.release(bytes); } } } if (typeof globalThis.PropertyNamespace === 'undefined') { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js index f1273821a..17ba15a4b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js @@ -305,15 +305,18 @@ export async function createInstantiator(options, swift) { } static set optionalProperty(value) { const isSome = value != null; - let valueId, valueBytes; + let bytes, length; if (isSome) { - valueBytes = textEncoder.encode(value); - valueId = swift.memory.retain(valueBytes); - } - instance.exports.bjs_PropertyClass_static_optionalProperty_set(+isSome, isSome ? valueId : 0, isSome ? valueBytes.length : 0); - if (valueId != undefined) { - swift.memory.release(valueId); + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + bytes = valueId; + length = valueBytes.length; + } else { + bytes = 0; + length = 0; } + instance.exports.bjs_PropertyClass_static_optionalProperty_set(+isSome, bytes, length); + if (isSome) { swift.memory.release(bytes); } } } const exports = { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js index fc2fc58c6..b7dcfcacf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js @@ -221,9 +221,9 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_jsRoundTripOptionalGreeter"] = function bjs_jsRoundTripOptionalGreeter(greeterIsSome, greeterWrappedValue) { + TestModule["bjs_jsRoundTripOptionalGreeter"] = function bjs_jsRoundTripOptionalGreeter(greeterIsSome, greeterPointer) { try { - let ret = imports.jsRoundTripOptionalGreeter(greeterIsSome ? _exports['Greeter'].__construct(greeterWrappedValue) : null); + let ret = imports.jsRoundTripOptionalGreeter(greeterIsSome ? _exports['Greeter'].__construct(greeterPointer) : null); const isSome = ret != null; return isSome ? ret.pointer : 0; } catch (error) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js index 4df3d5018..1d1b5910c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js @@ -405,7 +405,7 @@ export async function createInstantiator(options, swift) { const callback = swift.memory.getObject(callbackId); const enumValue = enumHelpers.APIResult.lift(param0); let ret = callback(enumValue); - const { caseId: caseId, cleanup: cleanup } = enumHelpers.APIResult.lower(ret); + const { caseId: caseId } = enumHelpers.APIResult.lower(ret); return caseId; } catch (error) { setException(error); @@ -416,7 +416,7 @@ export async function createInstantiator(options, swift) { const { caseId: param0CaseId, cleanup: param0Cleanup } = enumHelpers.APIResult.lower(param0); instance.exports.invoke_swift_closure_TestModule_10TestModule9APIResultO_9APIResultO(boxPtr, param0CaseId); const ret = enumHelpers.APIResult.lift(i32Stack.pop()); - if (param0Cleanup) { param0Cleanup(); } + param0Cleanup && param0Cleanup(); if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); swift.memory.release(tmpRetException); @@ -598,21 +598,20 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleSq10HttpStatusO_Sq10HttpStatusO); } - bjs["invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO"] = function(callbackId, param0IsSome, param0WrappedValue) { + bjs["invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO"] = function(callbackId, param0IsSome, param0ObjectId) { try { const callback = swift.memory.getObject(callbackId); - let obj; + let optResult; if (param0IsSome) { - obj = swift.memory.getObject(param0WrappedValue); - swift.memory.release(param0WrappedValue); - } - let ret = callback(param0IsSome ? obj : null); - const isSome = ret != null; - if (isSome) { - tmpRetString = ret; + const param0ObjectIdObject = swift.memory.getObject(param0ObjectId); + swift.memory.release(param0ObjectId); + optResult = param0ObjectIdObject; } else { - tmpRetString = null; + optResult = null; } + let ret = callback(optResult); + const isSome = ret != null; + tmpRetString = isSome ? ret : null; } catch (error) { setException(error); } @@ -620,17 +619,20 @@ export async function createInstantiator(options, swift) { bjs["make_swift_closure_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO = function(param0) { const isSome = param0 != null; - let param0Id, param0Bytes; + let bytes, length; if (isSome) { - param0Bytes = textEncoder.encode(param0); - param0Id = swift.memory.retain(param0Bytes); + const param0Bytes = textEncoder.encode(param0); + const param0Id = swift.memory.retain(param0Bytes); + bytes = param0Id; + length = param0Bytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.invoke_swift_closure_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO(boxPtr, +isSome, isSome ? param0Id : 0, isSome ? param0Bytes.length : 0); + instance.exports.invoke_swift_closure_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO(boxPtr, +isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (param0Id != undefined) { - swift.memory.release(param0Id); - } + if (isSome) { swift.memory.release(bytes); } if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); swift.memory.release(tmpRetException); @@ -641,10 +643,10 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO); } - bjs["invoke_js_callback_TestModule_10TestModuleSq6PersonC_Sq6PersonC"] = function(callbackId, param0IsSome, param0WrappedValue) { + bjs["invoke_js_callback_TestModule_10TestModuleSq6PersonC_Sq6PersonC"] = function(callbackId, param0IsSome, param0Pointer) { try { const callback = swift.memory.getObject(callbackId); - let ret = callback(param0IsSome ? _exports['Person'].__construct(param0WrappedValue) : null); + let ret = callback(param0IsSome ? _exports['Person'].__construct(param0Pointer) : null); const isSome = ret != null; return isSome ? ret.pointer : 0; } catch (error) { @@ -654,10 +656,16 @@ export async function createInstantiator(options, swift) { bjs["make_swift_closure_TestModule_10TestModuleSq6PersonC_Sq6PersonC"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleSq6PersonC_Sq6PersonC = function(param0) { const isSome = param0 != null; - instance.exports.invoke_swift_closure_TestModule_10TestModuleSq6PersonC_Sq6PersonC(boxPtr, +isSome, isSome ? param0.pointer : 0); - const pointer = tmpRetOptionalHeapObject; + let pointer; + if (isSome) { + pointer = param0.pointer; + } else { + pointer = 0; + } + instance.exports.invoke_swift_closure_TestModule_10TestModuleSq6PersonC_Sq6PersonC(boxPtr, +isSome, pointer); + const pointer1 = tmpRetOptionalHeapObject; tmpRetOptionalHeapObject = undefined; - const optResult = pointer === null ? null : _exports['Person'].__construct(pointer); + const optResult = pointer1 === null ? null : _exports['Person'].__construct(pointer1); if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); swift.memory.release(tmpRetException); @@ -668,17 +676,20 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleSq6PersonC_Sq6PersonC); } - bjs["invoke_js_callback_TestModule_10TestModuleSq9APIResultO_Sq9APIResultO"] = function(callbackId, param0IsSome, param0WrappedValue) { + bjs["invoke_js_callback_TestModule_10TestModuleSq9APIResultO_Sq9APIResultO"] = function(callbackId, param0IsSome, param0CaseId) { try { const callback = swift.memory.getObject(callbackId); - let enumValue; + let optResult; if (param0IsSome) { - enumValue = enumHelpers.APIResult.lift(param0WrappedValue); + const enumValue = enumHelpers.APIResult.lift(param0CaseId); + optResult = enumValue; + } else { + optResult = null; } - let ret = callback(param0IsSome ? enumValue : null); + let ret = callback(optResult); const isSome = ret != null; if (isSome) { - const { caseId: caseId, cleanup: cleanup } = enumHelpers.APIResult.lower(ret); + const { caseId: caseId } = enumHelpers.APIResult.lower(ret); return caseId; } else { return -1; @@ -690,22 +701,21 @@ export async function createInstantiator(options, swift) { bjs["make_swift_closure_TestModule_10TestModuleSq9APIResultO_Sq9APIResultO"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleSq9APIResultO_Sq9APIResultO = function(param0) { const isSome = param0 != null; - let param0CaseId, param0Cleanup; + let caseId; + let param0Cleanup1; if (isSome) { - const enumResult = enumHelpers.APIResult.lower(param0); - param0CaseId = enumResult.caseId; - param0Cleanup = enumResult.cleanup; - } - instance.exports.invoke_swift_closure_TestModule_10TestModuleSq9APIResultO_Sq9APIResultO(boxPtr, +isSome, isSome ? param0CaseId : 0); - const tag = i32Stack.pop(); - const isNull = (tag === -1); - let optResult; - if (isNull) { - optResult = null; + const { caseId: param0CaseId, cleanup: param0Cleanup } = enumHelpers.APIResult.lower(param0); + caseId = param0CaseId; + param0Cleanup1 = () => { + param0Cleanup && param0Cleanup(); + }; } else { - optResult = enumHelpers.APIResult.lift(tag); + caseId = 0; } - if (param0Cleanup) { param0Cleanup(); } + instance.exports.invoke_swift_closure_TestModule_10TestModuleSq9APIResultO_Sq9APIResultO(boxPtr, +isSome, caseId); + const tag = i32Stack.pop(); + const optResult = tag === -1 ? null : enumHelpers.APIResult.lift(tag); + param0Cleanup1 && param0Cleanup1(); if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); swift.memory.release(tmpRetException); @@ -721,7 +731,7 @@ export async function createInstantiator(options, swift) { const callback = swift.memory.getObject(callbackId); let ret = callback(param0IsSome ? param0WrappedValue : null); const isSome = ret != null; - return isSome ? (ret | 0) : -1; + return isSome ? ret : -1; } catch (error) { setException(error); } @@ -742,21 +752,20 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleSq9DirectionO_Sq9DirectionO); } - bjs["invoke_js_callback_TestModule_10TestModuleSqSS_SqSS"] = function(callbackId, param0IsSome, param0WrappedValue) { + bjs["invoke_js_callback_TestModule_10TestModuleSqSS_SqSS"] = function(callbackId, param0IsSome, param0ObjectId) { try { const callback = swift.memory.getObject(callbackId); - let obj; + let optResult; if (param0IsSome) { - obj = swift.memory.getObject(param0WrappedValue); - swift.memory.release(param0WrappedValue); - } - let ret = callback(param0IsSome ? obj : null); - const isSome = ret != null; - if (isSome) { - tmpRetString = ret; + const param0ObjectIdObject = swift.memory.getObject(param0ObjectId); + swift.memory.release(param0ObjectId); + optResult = param0ObjectIdObject; } else { - tmpRetString = null; + optResult = null; } + let ret = callback(optResult); + const isSome = ret != null; + tmpRetString = isSome ? ret : null; } catch (error) { setException(error); } @@ -764,17 +773,20 @@ export async function createInstantiator(options, swift) { bjs["make_swift_closure_TestModule_10TestModuleSqSS_SqSS"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleSqSS_SqSS = function(param0) { const isSome = param0 != null; - let param0Id, param0Bytes; + let bytes, length; if (isSome) { - param0Bytes = textEncoder.encode(param0); - param0Id = swift.memory.retain(param0Bytes); + const param0Bytes = textEncoder.encode(param0); + const param0Id = swift.memory.retain(param0Bytes); + bytes = param0Id; + length = param0Bytes.length; + } else { + bytes = 0; + length = 0; } - instance.exports.invoke_swift_closure_TestModule_10TestModuleSqSS_SqSS(boxPtr, +isSome, isSome ? param0Id : 0, isSome ? param0Bytes.length : 0); + instance.exports.invoke_swift_closure_TestModule_10TestModuleSqSS_SqSS(boxPtr, +isSome, bytes, length); const optResult = tmpRetString; tmpRetString = undefined; - if (param0Id != undefined) { - swift.memory.release(param0Id); - } + if (isSome) { swift.memory.release(bytes); } if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); swift.memory.release(tmpRetException); @@ -798,7 +810,7 @@ export async function createInstantiator(options, swift) { bjs["make_swift_closure_TestModule_10TestModuleSqSb_SqSb"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleSqSb_SqSb = function(param0) { const isSome = param0 != null; - instance.exports.invoke_swift_closure_TestModule_10TestModuleSqSb_SqSb(boxPtr, +isSome, isSome ? param0 : 0); + instance.exports.invoke_swift_closure_TestModule_10TestModuleSqSb_SqSb(boxPtr, +isSome, isSome ? param0 ? 1 : 0 : 0); const optResult = tmpRetOptionalBool; tmpRetOptionalBool = undefined; if (tmpRetException) { @@ -824,7 +836,7 @@ export async function createInstantiator(options, swift) { bjs["make_swift_closure_TestModule_10TestModuleSqSd_SqSd"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleSqSd_SqSd = function(param0) { const isSome = param0 != null; - instance.exports.invoke_swift_closure_TestModule_10TestModuleSqSd_SqSd(boxPtr, +isSome, isSome ? param0 : 0); + instance.exports.invoke_swift_closure_TestModule_10TestModuleSqSd_SqSd(boxPtr, +isSome, isSome ? param0 : 0.0); const optResult = tmpRetOptionalDouble; tmpRetOptionalDouble = undefined; if (tmpRetException) { @@ -850,7 +862,7 @@ export async function createInstantiator(options, swift) { bjs["make_swift_closure_TestModule_10TestModuleSqSf_SqSf"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleSqSf_SqSf = function(param0) { const isSome = param0 != null; - instance.exports.invoke_swift_closure_TestModule_10TestModuleSqSf_SqSf(boxPtr, +isSome, isSome ? param0 : 0); + instance.exports.invoke_swift_closure_TestModule_10TestModuleSqSf_SqSf(boxPtr, +isSome, isSome ? param0 : 0.0); const optResult = tmpRetOptionalFloat; tmpRetOptionalFloat = undefined; if (tmpRetException) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js index dca01b33c..3b547d671 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js @@ -45,7 +45,7 @@ export async function createInstantiator(options, swift) { i32Stack.push(id); const isSome = value.optCount != null; if (isSome) { - i32Stack.push(value.optCount | 0); + i32Stack.push((value.optCount | 0)); } else { i32Stack.push(0); } @@ -99,7 +99,7 @@ export async function createInstantiator(options, swift) { i32Stack.push(id1); const isSome = value.zipCode != null; if (isSome) { - i32Stack.push(value.zipCode | 0); + i32Stack.push((value.zipCode | 0)); } else { i32Stack.push(0); } @@ -135,12 +135,15 @@ export async function createInstantiator(options, swift) { i32Stack.push((value.age | 0)); const structResult = structHelpers.Address.lower(value.address); const isSome = value.email != null; - let id1; + let innerCleanup; if (isSome) { const bytes1 = textEncoder.encode(value.email); - id1 = swift.memory.retain(bytes1); + const id1 = swift.memory.retain(bytes1); i32Stack.push(bytes1.length); i32Stack.push(id1); + innerCleanup = () => { + swift.memory.release(id1); + }; } else { i32Stack.push(0); i32Stack.push(0); @@ -148,8 +151,8 @@ export async function createInstantiator(options, swift) { i32Stack.push(isSome ? 1 : 0); const cleanup = () => { swift.memory.release(id); - if (structResult.cleanup) { structResult.cleanup(); } - if(id1 !== undefined) { swift.memory.release(id1); } + structResult.cleanup && structResult.cleanup(); + innerCleanup && innerCleanup(); }; return { cleanup }; }, @@ -234,16 +237,37 @@ export async function createInstantiator(options, swift) { } i32Stack.push(id !== undefined ? id : 0); const isSome = value.optionalObject != null; - let id1; + let innerCleanup; if (isSome) { - id1 = swift.memory.retain(value.optionalObject); - i32Stack.push(id1); + let id1; + if (value.optionalObject != null) { + id1 = swift.memory.retain(value.optionalObject); + } else { + id1 = undefined; + } + i32Stack.push(id1 !== undefined ? id1 : 0); + innerCleanup = () => { + if(id1 !== undefined && id1 !== 0) { + try { + swift.memory.getObject(id1); + swift.memory.release(id1); + } catch(e) { console.warn('BridgeJS: cleanup failed for retained object', e); } + } + }; } else { - id1 = undefined; i32Stack.push(0); } i32Stack.push(isSome ? 1 : 0); - return { cleanup: undefined }; + const cleanup = () => { + if(id !== undefined && id !== 0) { + try { + swift.memory.getObject(id); + swift.memory.release(id); + } catch(e) { console.warn('BridgeJS: cleanup failed for retained object', e); } + } + innerCleanup && innerCleanup(); + }; + return { cleanup }; }, lift: () => { const isSome = i32Stack.pop(); @@ -629,7 +653,7 @@ export async function createInstantiator(options, swift) { const labelId = swift.memory.retain(labelBytes); const isSome = optCount != null; const isSome1 = optFlag != null; - instance.exports.bjs_DataPoint_init(x, y, labelId, labelBytes.length, +isSome, isSome ? optCount : 0, +isSome1, isSome1 ? optFlag : 0); + instance.exports.bjs_DataPoint_init(x, y, labelId, labelBytes.length, +isSome, isSome ? optCount : 0, +isSome1, isSome1 ? optFlag ? 1 : 0 : 0); const structValue = structHelpers.DataPoint.lift(); swift.memory.release(labelId); return structValue; diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index e24775f43..20508fb82 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -1026,10 +1026,10 @@ extension JSTypedClosure where Signature == (Optional) -> String { @_expose(wasm, "invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq7GreeterC_SS") @_cdecl("invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq7GreeterC_SS") -public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq7GreeterC_SS(_ boxPtr: UnsafeMutableRawPointer, _ param0IsSome: Int32, _ param0Value: UnsafeMutableRawPointer) -> Void { +public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq7GreeterC_SS(_ boxPtr: UnsafeMutableRawPointer, _ param0IsSome: Int32, _ param0Pointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) let closure = Unmanaged<_BridgeJSTypedClosureBox<(Optional) -> String>>.fromOpaque(boxPtr).takeUnretainedValue().closure - let result = closure(Optional.bridgeJSLiftParameter(param0IsSome, param0Value)) + let result = closure(Optional.bridgeJSLiftParameter(param0IsSome, param0Pointer)) return result.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -1083,10 +1083,10 @@ extension JSTypedClosure where Signature == (Optional) -> Optional Void { +public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq7GreeterC_Sq7GreeterC(_ boxPtr: UnsafeMutableRawPointer, _ param0IsSome: Int32, _ param0Pointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) let closure = Unmanaged<_BridgeJSTypedClosureBox<(Optional) -> Optional>>.fromOpaque(boxPtr).takeUnretainedValue().closure - let result = closure(Optional.bridgeJSLiftParameter(param0IsSome, param0Value)) + let result = closure(Optional.bridgeJSLiftParameter(param0IsSome, param0Pointer)) return result.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -5900,11 +5900,24 @@ public func _bjs_roundTripOptionalPayloadResultOpt(_ resultIsSome: Int32, _ resu #endif } +@_expose(wasm, "bjs_roundTripTwoOptionalPayloadResults") +@_cdecl("bjs_roundTripTwoOptionalPayloadResults") +public func _bjs_roundTripTwoOptionalPayloadResults(_ a: Int32, _ b: Int32) -> Void { + #if arch(wasm32) + let _tmp_b = OptionalAllTypesResult.bridgeJSLiftParameter(b) + let _tmp_a = OptionalAllTypesResult.bridgeJSLiftParameter(a) + let ret = roundTripTwoOptionalPayloadResults(_: _tmp_a, _: _tmp_b) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripOptionalClass") @_cdecl("bjs_roundTripOptionalClass") -public func _bjs_roundTripOptionalClass(_ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_roundTripOptionalClass(_ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = roundTripOptionalClass(value: Optional.bridgeJSLiftParameter(valueIsSome, valueValue)) + let ret = roundTripOptionalClass(value: Optional.bridgeJSLiftParameter(valueIsSome, valuePointer)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -5913,9 +5926,9 @@ public func _bjs_roundTripOptionalClass(_ valueIsSome: Int32, _ valueValue: Unsa @_expose(wasm, "bjs_roundTripOptionalGreeter") @_cdecl("bjs_roundTripOptionalGreeter") -public func _bjs_roundTripOptionalGreeter(_ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_roundTripOptionalGreeter(_ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = roundTripOptionalGreeter(_: Optional.bridgeJSLiftParameter(valueIsSome, valueValue)) + let ret = roundTripOptionalGreeter(_: Optional.bridgeJSLiftParameter(valueIsSome, valuePointer)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -5924,9 +5937,9 @@ public func _bjs_roundTripOptionalGreeter(_ valueIsSome: Int32, _ valueValue: Un @_expose(wasm, "bjs_applyOptionalGreeter") @_cdecl("bjs_applyOptionalGreeter") -public func _bjs_applyOptionalGreeter(_ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer, _ transform: Int32) -> Void { +public func _bjs_applyOptionalGreeter(_ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer, _ transform: Int32) -> Void { #if arch(wasm32) - let ret = applyOptionalGreeter(_: Optional.bridgeJSLiftParameter(valueIsSome, valueValue), _: _BJS_Closure_20BridgeJSRuntimeTestsSq7GreeterC_Sq7GreeterC.bridgeJSLift(transform)) + let ret = applyOptionalGreeter(_: Optional.bridgeJSLiftParameter(valueIsSome, valuePointer), _: _BJS_Closure_20BridgeJSRuntimeTestsSq7GreeterC_Sq7GreeterC.bridgeJSLift(transform)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -5935,9 +5948,9 @@ public func _bjs_applyOptionalGreeter(_ valueIsSome: Int32, _ valueValue: Unsafe @_expose(wasm, "bjs_makeOptionalHolder") @_cdecl("bjs_makeOptionalHolder") -public func _bjs_makeOptionalHolder(_ nullableGreeterIsSome: Int32, _ nullableGreeterValue: UnsafeMutableRawPointer, _ undefinedNumberIsSome: Int32, _ undefinedNumberValue: Float64) -> UnsafeMutableRawPointer { +public func _bjs_makeOptionalHolder(_ nullableGreeterIsSome: Int32, _ nullableGreeterPointer: UnsafeMutableRawPointer, _ undefinedNumberIsSome: Int32, _ undefinedNumberValue: Float64) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = makeOptionalHolder(nullableGreeter: Optional.bridgeJSLiftParameter(nullableGreeterIsSome, nullableGreeterValue), undefinedNumber: JSUndefinedOr.bridgeJSLiftParameter(undefinedNumberIsSome, undefinedNumberValue)) + let ret = makeOptionalHolder(nullableGreeter: Optional.bridgeJSLiftParameter(nullableGreeterIsSome, nullableGreeterPointer), undefinedNumber: JSUndefinedOr.bridgeJSLiftParameter(undefinedNumberIsSome, undefinedNumberValue)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -7707,7 +7720,7 @@ public func _bjs_DataProcessorManager_setProcessorAPIResult(_ _self: UnsafeMutab @_cdecl("bjs_DataProcessorManager_processor_get") public func _bjs_DataProcessorManager_processor_get(_ _self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = DataProcessorManager.bridgeJSLiftParameter(_self).processor as! AnyDataProcessor + let ret = (DataProcessorManager.bridgeJSLiftParameter(_self).processor as! AnyDataProcessor) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -7858,9 +7871,9 @@ public func _bjs_SwiftDataProcessor_createGreeter(_ _self: UnsafeMutableRawPoint @_expose(wasm, "bjs_SwiftDataProcessor_processOptionalGreeter") @_cdecl("bjs_SwiftDataProcessor_processOptionalGreeter") -public func _bjs_SwiftDataProcessor_processOptionalGreeter(_ _self: UnsafeMutableRawPointer, _ greeterIsSome: Int32, _ greeterValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_SwiftDataProcessor_processOptionalGreeter(_ _self: UnsafeMutableRawPointer, _ greeterIsSome: Int32, _ greeterPointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = SwiftDataProcessor.bridgeJSLiftParameter(_self).processOptionalGreeter(_: Optional.bridgeJSLiftParameter(greeterIsSome, greeterValue)) + let ret = SwiftDataProcessor.bridgeJSLiftParameter(_self).processOptionalGreeter(_: Optional.bridgeJSLiftParameter(greeterIsSome, greeterPointer)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -8091,9 +8104,9 @@ public func _bjs_SwiftDataProcessor_optionalHelper_get(_ _self: UnsafeMutableRaw @_expose(wasm, "bjs_SwiftDataProcessor_optionalHelper_set") @_cdecl("bjs_SwiftDataProcessor_optionalHelper_set") -public func _bjs_SwiftDataProcessor_optionalHelper_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_SwiftDataProcessor_optionalHelper_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - SwiftDataProcessor.bridgeJSLiftParameter(_self).optionalHelper = Optional.bridgeJSLiftParameter(valueIsSome, valueValue) + SwiftDataProcessor.bridgeJSLiftParameter(_self).optionalHelper = Optional.bridgeJSLiftParameter(valueIsSome, valuePointer) #else fatalError("Only available on WebAssembly") #endif @@ -8382,9 +8395,9 @@ fileprivate func _bjs_TextProcessor_wrap(_ pointer: UnsafeMutableRawPointer) -> @_expose(wasm, "bjs_OptionalHolder_init") @_cdecl("bjs_OptionalHolder_init") -public func _bjs_OptionalHolder_init(_ nullableGreeterIsSome: Int32, _ nullableGreeterValue: UnsafeMutableRawPointer, _ undefinedNumberIsSome: Int32, _ undefinedNumberValue: Float64) -> UnsafeMutableRawPointer { +public func _bjs_OptionalHolder_init(_ nullableGreeterIsSome: Int32, _ nullableGreeterPointer: UnsafeMutableRawPointer, _ undefinedNumberIsSome: Int32, _ undefinedNumberValue: Float64) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = OptionalHolder(nullableGreeter: Optional.bridgeJSLiftParameter(nullableGreeterIsSome, nullableGreeterValue), undefinedNumber: JSUndefinedOr.bridgeJSLiftParameter(undefinedNumberIsSome, undefinedNumberValue)) + let ret = OptionalHolder(nullableGreeter: Optional.bridgeJSLiftParameter(nullableGreeterIsSome, nullableGreeterPointer), undefinedNumber: JSUndefinedOr.bridgeJSLiftParameter(undefinedNumberIsSome, undefinedNumberValue)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -8404,9 +8417,9 @@ public func _bjs_OptionalHolder_nullableGreeter_get(_ _self: UnsafeMutableRawPoi @_expose(wasm, "bjs_OptionalHolder_nullableGreeter_set") @_cdecl("bjs_OptionalHolder_nullableGreeter_set") -public func _bjs_OptionalHolder_nullableGreeter_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_OptionalHolder_nullableGreeter_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - OptionalHolder.bridgeJSLiftParameter(_self).nullableGreeter = Optional.bridgeJSLiftParameter(valueIsSome, valueValue) + OptionalHolder.bridgeJSLiftParameter(_self).nullableGreeter = Optional.bridgeJSLiftParameter(valueIsSome, valuePointer) #else fatalError("Only available on WebAssembly") #endif @@ -8524,9 +8537,9 @@ public func _bjs_OptionalPropertyHolder_optionalGreeter_get(_ _self: UnsafeMutab @_expose(wasm, "bjs_OptionalPropertyHolder_optionalGreeter_set") @_cdecl("bjs_OptionalPropertyHolder_optionalGreeter_set") -public func _bjs_OptionalPropertyHolder_optionalGreeter_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valueValue: UnsafeMutableRawPointer) -> Void { +public func _bjs_OptionalPropertyHolder_optionalGreeter_set(_ _self: UnsafeMutableRawPointer, _ valueIsSome: Int32, _ valuePointer: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - OptionalPropertyHolder.bridgeJSLiftParameter(_self).optionalGreeter = Optional.bridgeJSLiftParameter(valueIsSome, valueValue) + OptionalPropertyHolder.bridgeJSLiftParameter(_self).optionalGreeter = Optional.bridgeJSLiftParameter(valueIsSome, valuePointer) #else fatalError("Only available on WebAssembly") #endif diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index fc650b37d..7cde36d82 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -10738,6 +10738,40 @@ } } }, + { + "abiName" : "bjs_roundTripTwoOptionalPayloadResults", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripTwoOptionalPayloadResults", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "associatedValueEnum" : { + "_0" : "OptionalAllTypesResult" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "associatedValueEnum" : { + "_0" : "OptionalAllTypesResult" + } + } + } + ], + "returnType" : { + "associatedValueEnum" : { + "_0" : "OptionalAllTypesResult" + } + } + }, { "abiName" : "bjs_roundTripOptionalClass", "effects" : { diff --git a/Tests/BridgeJSRuntimeTests/JavaScript/OptionalSupportTests.mjs b/Tests/BridgeJSRuntimeTests/JavaScript/OptionalSupportTests.mjs index 8a856582d..2ad4c2200 100644 --- a/Tests/BridgeJSRuntimeTests/JavaScript/OptionalSupportTests.mjs +++ b/Tests/BridgeJSRuntimeTests/JavaScript/OptionalSupportTests.mjs @@ -210,6 +210,11 @@ export function runJsOptionalSupportTests(exports) { const oatr_empty = { tag: OptionalAllTypesResultValues.Tag.Empty }; assert.deepEqual(exports.roundTripOptionalPayloadResult(oatr_empty), oatr_empty); + const twoResult = exports.roundTripTwoOptionalPayloadResults(oatr_arrayNone, oatr_nestedEnumSome); + assert.deepEqual(twoResult, oatr_nestedEnumSome); + const twoResult2 = exports.roundTripTwoOptionalPayloadResults(oatr_arrayNone, oatr_arraySome); + assert.deepEqual(twoResult2, oatr_arraySome); + // Optional OptionalAllTypesResult roundtrip assert.deepEqual(exports.roundTripOptionalPayloadResultOpt(oatr_structSome), oatr_structSome); assert.deepEqual(exports.roundTripOptionalPayloadResultOpt(oatr_structNone), oatr_structNone); diff --git a/Tests/BridgeJSRuntimeTests/OptionalSupportTests.swift b/Tests/BridgeJSRuntimeTests/OptionalSupportTests.swift index 998f8b90f..a7b4f78cd 100644 --- a/Tests/BridgeJSRuntimeTests/OptionalSupportTests.swift +++ b/Tests/BridgeJSRuntimeTests/OptionalSupportTests.swift @@ -183,6 +183,13 @@ enum OptionalAllTypesResult { result } +@JS func roundTripTwoOptionalPayloadResults( + _ a: OptionalAllTypesResult, + _ b: OptionalAllTypesResult +) -> OptionalAllTypesResult { + b +} + @JS func roundTripOptionalClass(value: Greeter?) -> Greeter? { value }