diff --git a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json index 9094df26..5ba46ff6 100644 --- a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -726,5 +726,8 @@ "moduleName" : "Benchmarks", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json index f4897068..a536a23f 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -142,5 +142,8 @@ "moduleName" : "PlayBridgeJS", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index ebcf7d4c..436e264d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -24,6 +24,7 @@ public class ExportSwift { private var exportedFunctions: [ExportedFunction] = [] private var exportedClasses: [ExportedClass] = [] private var exportedEnums: [ExportedEnum] = [] + private var exportedStructs: [ExportedStruct] = [] private var exportedProtocols: [ExportedProtocol] = [] private var exportedProtocolNameByKey: [String: String] = [:] private var typeDeclResolver: TypeDeclResolver = TypeDeclResolver() @@ -71,6 +72,7 @@ public class ExportSwift { functions: exportedFunctions, classes: exportedClasses, enums: exportedEnums, + structs: exportedStructs, protocols: exportedProtocols, exposeToGlobal: exposeToGlobal ) @@ -88,6 +90,9 @@ public class ExportSwift { /// The names of the exported protocols, in the order they were written in the source file var exportedProtocolNames: [String] = [] var exportedProtocolByName: [String: ExportedProtocol] = [:] + /// The names of the exported structs, in the order they were written in the source file + var exportedStructNames: [String] = [] + var exportedStructByName: [String: ExportedStruct] = [:] var errors: [DiagnosticError] = [] /// Creates a unique key by combining name and namespace @@ -132,6 +137,7 @@ public class ExportSwift { case classBody(name: String, key: String) case enumBody(name: String, key: String) case protocolBody(name: String, key: String) + case structBody(name: String, key: String) } struct StateStack { @@ -516,6 +522,14 @@ public class ExportSwift { case .protocolBody(_, _): // Protocol methods are handled in visitProtocolMethod during protocol parsing return .skipChildren + case .structBody(let structName, let structKey): + if let exportedFunction = visitFunction(node: node, isStatic: isStatic, structName: structName) { + if var currentStruct = exportedStructByName[structKey] { + currentStruct.methods.append(exportedFunction) + exportedStructByName[structKey] = currentStruct + } + } + return .skipChildren } } @@ -524,7 +538,8 @@ public class ExportSwift { isStatic: Bool, className: String? = nil, classKey: String? = nil, - enumName: String? = nil + enumName: String? = nil, + structName: String? = nil ) -> ExportedFunction? { guard let jsAttribute = node.attributes.firstJSAttribute else { return nil @@ -600,12 +615,21 @@ public class ExportSwift { staticContext = isNamespaceEnum ? .namespaceEnum : .enumName(enumName) case .protocolBody(_, _): return nil + case .structBody(let structName, _): + if isStatic { + staticContext = .structName(structName) + } else { + staticContext = nil + } } let classNameForABI: String? - if case .classBody(let className, _) = state { + switch state { + case .classBody(let className, _): classNameForABI = className - } else { + case .structBody(let structName, _): + classNameForABI = structName + default: classNameForABI = nil } abiName = ABINameGenerator.generateABIName( @@ -691,37 +715,59 @@ public class ExportSwift { override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard let jsAttribute = node.attributes.firstJSAttribute else { return .skipChildren } - guard case .classBody(let className, _) = state else { - if case .enumBody(_, _) = state { - diagnose(node: node, message: "Initializers are not supported inside enums") - } else { - diagnose(node: node, message: "@JS init must be inside a @JS class") + + switch state { + case .classBody(let className, let classKey): + if extractNamespace(from: jsAttribute) != nil { + diagnose( + node: jsAttribute, + message: "Namespace is not supported for initializer declarations", + hint: "Remove the namespace from @JS attribute" + ) } - return .skipChildren - } - if extractNamespace(from: jsAttribute) != nil { - diagnose( - node: jsAttribute, - message: "Namespace is not supported for initializer declarations", - hint: "Remove the namespace from @JS attribute" + let parameters = parseParameters(from: node.signature.parameterClause, allowDefaults: true) + + guard let effects = collectEffects(signature: node.signature) else { + return .skipChildren + } + + let constructor = ExportedConstructor( + abiName: "bjs_\(className)_init", + parameters: parameters, + effects: effects ) - } + exportedClassByName[classKey]?.constructor = constructor - let parameters = parseParameters(from: node.signature.parameterClause, allowDefaults: true) + case .structBody(let structName, let structKey): + if extractNamespace(from: jsAttribute) != nil { + diagnose( + node: jsAttribute, + message: "Namespace is not supported for initializer declarations", + hint: "Remove the namespace from @JS attribute" + ) + } - guard let effects = collectEffects(signature: node.signature) else { - return .skipChildren - } + let parameters = parseParameters(from: node.signature.parameterClause, allowDefaults: true) - let constructor = ExportedConstructor( - abiName: "bjs_\(className)_init", - parameters: parameters, - effects: effects - ) - if case .classBody(_, let classKey) = state { - exportedClassByName[classKey]?.constructor = constructor + guard let effects = collectEffects(signature: node.signature) else { + return .skipChildren + } + + let constructor = ExportedConstructor( + abiName: "bjs_\(structName)_init", + parameters: parameters, + effects: effects + ) + exportedStructByName[structKey]?.constructor = constructor + + case .enumBody(_, _): + diagnose(node: node, message: "Initializers are not supported inside enums") + + case .topLevel, .protocolBody(_, _): + diagnose(node: node, message: "@JS init must be inside a @JS class or struct") } + return .skipChildren } @@ -753,27 +799,29 @@ public class ExportSwift { // Determine static context and validate placement let staticContext: StaticContext? - let classKey: String switch state { - case .classBody(let className, let key): - classKey = key + case .classBody(let className, _): staticContext = isStatic ? .className(className) : nil case .enumBody(let enumName, let enumKey): if !isStatic { diagnose(node: node, message: "Only static properties are supported in enums") return .skipChildren } - classKey = enumKey - let isNamespaceEnum = exportedEnumByName[enumKey]?.cases.isEmpty ?? true staticContext = isStatic ? (isNamespaceEnum ? .namespaceEnum : .enumName(enumName)) : nil - case .topLevel: diagnose(node: node, message: "@JS var must be inside a @JS class or enum") return .skipChildren case .protocolBody(let protocolName, let protocolKey): return visitProtocolProperty(node: node, protocolName: protocolName, protocolKey: protocolKey) + case .structBody(let structName, _): + if isStatic { + staticContext = .structName(structName) + } else { + diagnose(node: node, message: "@JS var must be static in structs (instance fields don't need @JS)") + return .skipChildren + } } // Process each binding (variable declaration) @@ -810,13 +858,15 @@ public class ExportSwift { staticContext: staticContext ) - if case .enumBody(_, let enumKey) = state { - if var currentEnum = exportedEnumByName[enumKey] { + if case .enumBody(_, let key) = state { + if var currentEnum = exportedEnumByName[key] { currentEnum.staticProperties.append(exportedProperty) - exportedEnumByName[enumKey] = currentEnum + exportedEnumByName[key] = currentEnum } - } else { - exportedClassByName[classKey]?.properties.append(exportedProperty) + } else if case .structBody(_, let key) = state { + exportedStructByName[key]?.properties.append(exportedProperty) + } else if case .classBody(_, let key) = state { + exportedClassByName[key]?.properties.append(exportedProperty) } } @@ -937,7 +987,6 @@ public class ExportSwift { let emitStyle = exportedEnum.emitStyle if case .tsEnum = emitStyle { - // Check for Bool raw type limitation if exportedEnum.rawType == .bool { diagnose( node: jsAttribute, @@ -945,8 +994,6 @@ public class ExportSwift { hint: "Use enumStyle: .const or change the raw type to String or a numeric type" ) } - - // Check for static functions limitation if !exportedEnum.staticMethods.isEmpty { diagnose( node: jsAttribute, @@ -1055,6 +1102,98 @@ public class ExportSwift { return .skipChildren } + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + guard let jsAttribute = node.attributes.firstJSAttribute else { + return .skipChildren + } + + let name = node.name.text + + let namespaceResult = resolveNamespace(from: jsAttribute, for: node, declarationType: "struct") + guard namespaceResult.isValid else { + return .skipChildren + } + let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: name) + let explicitAccessControl = computeExplicitAtLeastInternalAccessControl( + for: node, + message: "Struct visibility must be at least internal" + ) + + var properties: [ExportedProperty] = [] + + // Process all variables in struct as readonly (value semantics) and don't require @JS + for member in node.memberBlock.members { + if let varDecl = member.decl.as(VariableDeclSyntax.self) { + let isStatic = varDecl.modifiers.contains { modifier in + modifier.name.tokenKind == .keyword(.static) || modifier.name.tokenKind == .keyword(.class) + } + + // Handled with error in visitVariable + if varDecl.attributes.hasJSAttribute() { + continue + } + // Skips static non-@JS properties + if isStatic { + continue + } + + for binding in varDecl.bindings { + guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { + continue + } + + let fieldName = pattern.identifier.text + + guard let typeAnnotation = binding.typeAnnotation else { + diagnose(node: binding, message: "Struct field must have explicit type annotation") + continue + } + + guard let fieldType = self.parent.lookupType(for: typeAnnotation.type) else { + diagnoseUnsupportedType( + node: typeAnnotation.type, + type: typeAnnotation.type.trimmedDescription + ) + continue + } + + let property = ExportedProperty( + name: fieldName, + type: fieldType, + isReadonly: true, + isStatic: false, + namespace: namespaceResult.namespace, + staticContext: nil + ) + properties.append(property) + } + } + } + + let structUniqueKey = makeKey(name: name, namespace: namespaceResult.namespace) + let exportedStruct = ExportedStruct( + name: name, + swiftCallName: swiftCallName, + explicitAccessControl: explicitAccessControl, + properties: properties, + methods: [], + namespace: namespaceResult.namespace + ) + + exportedStructByName[structUniqueKey] = exportedStruct + exportedStructNames.append(structUniqueKey) + + stateStack.push(state: .structBody(name: name, key: structUniqueKey)) + + return .visitChildren + } + + override func visitPost(_ node: StructDeclSyntax) { + if case .structBody(_, _) = stateStack.current { + stateStack.pop() + } + } + private func visitProtocolMethod( node: FunctionDeclSyntax, protocolName: String, @@ -1314,6 +1453,11 @@ public class ExportSwift { collector.exportedProtocolByName[$0]! } ) + exportedStructs.append( + contentsOf: collector.exportedStructNames.map { + collector.exportedStructByName[$0]! + } + ) return collector.errors } @@ -1463,6 +1607,11 @@ public class ExportSwift { } } + if let structDecl = typeDecl.as(StructDeclSyntax.self) { + let swiftCallName = ExportSwift.computeSwiftCallName(for: structDecl, itemName: structDecl.name.text) + return .swiftStruct(swiftCallName) + } + guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else { return nil } @@ -1484,7 +1633,7 @@ public class ExportSwift { var decls: [DeclSyntax] = [] guard exportedFunctions.count > 0 || exportedClasses.count > 0 || exportedEnums.count > 0 - || exportedProtocols.count > 0 + || exportedProtocols.count > 0 || exportedStructs.count > 0 else { return nil } @@ -1543,6 +1692,12 @@ public class ExportSwift { } } + let structCodegen = StructCodegen() + for structDef in exportedStructs { + decls.append(structCodegen.renderStructHelpers(structDef)) + decls.append(contentsOf: try renderSingleExportedStruct(struct: structDef)) + } + for function in exportedFunctions { decls.append(try renderSingleExportedFunction(function: function)) } @@ -1592,6 +1747,9 @@ 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 .optional(let wrappedType): typeNameForIntrinsic = "Optional<\(wrappedType.swiftType)>" liftingExpr = ExprSyntax( @@ -1703,6 +1861,30 @@ public class ExportSwift { append(item) } + /// 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, .optional(.swiftStruct), + .associatedValueEnum, .optional(.associatedValueEnum): + return index + default: + return nil + } + } + + guard stackParamIndices.count > 1 else { return } + + for index in stackParamIndices.reversed() { + let param = parameters[index] + let expr = liftedParameterExprs[index] + let varName = "_tmp_\(param.name)" + + append("let \(raw: varName) = \(expr)") + liftedParameterExprs[index] = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varName))) + } + } + func callPropertyGetter(klassName: String, propertyName: String, returnType: BridgeType) { let (_, selfExpr) = removeFirstLiftedParameter() if returnType == .void { @@ -1830,28 +2012,6 @@ public class ExportSwift { return abiReturnType?.swiftType ?? "Void" } - /// 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 .optional(.associatedValueEnum): - return index - default: - return nil - } - } - - guard stackParamIndices.count > 1 else { return } - - for index in stackParamIndices.reversed() { - let param = parameters[index] - let expr = liftedParameterExprs[index] - let varName = "_tmp_\(param.name)" - - append("let \(raw: varName) = \(expr)") - liftedParameterExprs[index] = ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier(varName))) - } - } } private struct ClosureCodegen { @@ -2051,7 +2211,220 @@ public class ExportSwift { } + /// Helper for stack-based lifting and lowering operations. + private struct StackCodegen { + /// Generates an expression to lift a value from the parameter stack. + /// - Parameter type: The BridgeType to lift + /// - Returns: An ExprSyntax representing the lift expression + func liftExpression(for type: BridgeType) -> ExprSyntax { + switch type { + case .string: + return "String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .int: + return "Int.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .bool: + return "Bool.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .float: + return "Float.bridgeJSLiftParameter(_swift_js_pop_param_f32())" + case .double: + return "Double.bridgeJSLiftParameter(_swift_js_pop_param_f64())" + case .jsObject: + return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .swiftHeapObject(let className): + return "\(raw: className).bridgeJSLiftParameter(_swift_js_pop_param_pointer())" + case .swiftProtocol: + // Protocols are handled via JSObject + return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .caseEnum(let enumName): + return "\(raw: enumName).bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .rawValueEnum(let enumName, let rawType): + switch rawType { + case .string: + return + "\(raw: enumName).bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .bool, .int, .int32, .int64, .uint, .uint32, .uint64, .float, .double: + return "\(raw: enumName).bridgeJSLiftParameter(_swift_js_pop_param_int32())" + } + case .associatedValueEnum(let enumName): + return "\(raw: enumName).bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .swiftStruct(let structName): + return "\(raw: structName).bridgeJSLiftParameter()" + case .optional(let wrappedType): + return liftOptionalExpression(wrappedType: wrappedType) + case .void: + // Void shouldn't be lifted, but return a placeholder + return "()" + case .namespaceEnum: + // Namespace enums are not passed as values + return "()" + case .closure: + return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + } + } + + private func liftOptionalExpression(wrappedType: BridgeType) -> ExprSyntax { + switch wrappedType { + case .string: + return + "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .int: + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .bool: + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .float: + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f32())" + case .double: + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f64())" + case .caseEnum(let enumName): + return + "Optional<\(raw: enumName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .rawValueEnum(let enumName, let rawType): + switch rawType { + case .string: + return + "Optional<\(raw: enumName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .bool, .int, .float, .double, .int32, .int64, .uint, .uint32, .uint64: + return + "Optional<\(raw: enumName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + } + case .swiftStruct(let nestedName): + return "Optional<\(raw: nestedName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + case .swiftHeapObject(let className): + return + "Optional<\(raw: className)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_pointer())" + case .associatedValueEnum(let enumName): + return + "Optional<\(raw: enumName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + case .jsObject: + return + "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + default: + // Fallback for other optional types + return "Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" + } + } + + /// Generates statements to lower/push a value onto the stack. + /// - Parameters: + /// - type: The BridgeType to lower + /// - accessor: The expression to access the value (e.g., "self.name" or "paramName") + /// - varPrefix: A unique prefix for intermediate variables + /// - Returns: An array of CodeBlockItemSyntax representing the lowering statements + func lowerStatements( + for type: BridgeType, + accessor: String, + varPrefix: String + ) -> [CodeBlockItemSyntax] { + switch type { + case .string: + return [ + "var __bjs_\(raw: varPrefix) = \(raw: accessor)", + "__bjs_\(raw: varPrefix).withUTF8 { ptr in _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) }", + ] + case .int: + return ["_swift_js_push_int(Int32(\(raw: accessor)))"] + case .bool: + return ["_swift_js_push_int(\(raw: accessor) ? 1 : 0)"] + case .float: + return ["_swift_js_push_f32(\(raw: accessor))"] + case .double: + return ["_swift_js_push_f64(\(raw: accessor))"] + case .jsObject: + return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerParameter())"] + case .swiftHeapObject: + return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"] + case .swiftProtocol: + return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerParameter())"] + case .caseEnum: + return ["_swift_js_push_int(Int32(\(raw: accessor).bridgeJSLowerParameter()))"] + case .rawValueEnum: + return ["_swift_js_push_int(Int32(\(raw: accessor).bridgeJSLowerParameter()))"] + case .associatedValueEnum: + return ["\(raw: accessor).bridgeJSLowerReturn()"] + case .swiftStruct: + return ["\(raw: accessor).bridgeJSLowerReturn()"] + case .optional(let wrappedType): + return lowerOptionalStatements(wrappedType: wrappedType, accessor: accessor, varPrefix: varPrefix) + case .void: + return [] + case .namespaceEnum: + return [] + case .closure: + return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"] + } + } + + private func lowerOptionalStatements( + wrappedType: BridgeType, + accessor: String, + varPrefix: String + ) -> [CodeBlockItemSyntax] { + var statements: [CodeBlockItemSyntax] = [] + statements.append("let __bjs_isSome_\(raw: varPrefix) = \(raw: accessor) != nil") + statements.append("if let __bjs_unwrapped_\(raw: varPrefix) = \(raw: accessor) {") + + let innerStatements = lowerUnwrappedOptionalStatements( + wrappedType: wrappedType, + unwrappedVar: "__bjs_unwrapped_\(varPrefix)", + varPrefix: varPrefix + ) + for stmt in innerStatements { + statements.append(stmt) + } + + statements.append("}") + statements.append("_swift_js_push_int(__bjs_isSome_\(raw: varPrefix) ? 1 : 0)") + return statements + } + + private func lowerUnwrappedOptionalStatements( + wrappedType: BridgeType, + unwrappedVar: String, + varPrefix: String + ) -> [CodeBlockItemSyntax] { + switch wrappedType { + case .string: + return [ + "var __bjs_str_\(raw: varPrefix) = \(raw: unwrappedVar)", + "__bjs_str_\(raw: varPrefix).withUTF8 { ptr in _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) }", + ] + case .int: + return ["_swift_js_push_int(Int32(\(raw: unwrappedVar)))"] + case .bool: + return ["_swift_js_push_int(\(raw: unwrappedVar) ? 1 : 0)"] + case .float: + return ["_swift_js_push_f32(\(raw: unwrappedVar))"] + case .double: + return ["_swift_js_push_f64(\(raw: unwrappedVar))"] + case .caseEnum: + return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"] + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: + return [ + "var __bjs_str_\(raw: varPrefix) = \(raw: unwrappedVar).rawValue", + "__bjs_str_\(raw: varPrefix).withUTF8 { ptr in _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) }", + ] + default: + return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"] + } + case .swiftStruct: + return ["\(raw: unwrappedVar).bridgeJSLowerReturn()"] + case .swiftHeapObject: + return ["_swift_js_push_pointer(\(raw: unwrappedVar).bridgeJSLowerReturn())"] + case .associatedValueEnum: + return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"] + case .jsObject: + return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"] + default: + return ["preconditionFailure(\"BridgeJS: unsupported optional wrapped type\")"] + } + } + } + private struct EnumCodegen { + private let stackCodegen = StackCodegen() + func renderCaseEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax { let typeName = enumDef.swiftCallName var initCases: [String] = [] @@ -2138,47 +2511,14 @@ public class ExportSwift { var lines: [String] = [] lines.append("case \(caseIndex):") let argList = enumCase.associatedValues.map { associatedValue in - let paramName: String + let labelPrefix: String if let label = associatedValue.label { - paramName = "\(label): " + labelPrefix = "\(label): " } else { - paramName = "" - } - switch associatedValue.type { - case .string: - return - "\(paramName)String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" - case .int: - return "\(paramName)Int.bridgeJSLiftParameter(_swift_js_pop_param_int32())" - case .bool: - return "\(paramName)Bool.bridgeJSLiftParameter(_swift_js_pop_param_int32())" - case .float: - return "\(paramName)Float.bridgeJSLiftParameter(_swift_js_pop_param_f32())" - case .double: - return "\(paramName)Double.bridgeJSLiftParameter(_swift_js_pop_param_f64())" - case .optional(let wrappedType): - switch wrappedType { - case .string: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32())" - case .int: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" - case .bool: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())" - case .float: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f32())" - case .double: - return - "\(paramName)Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f64())" - default: - return "" - } - default: - return "\(paramName)Int.bridgeJSLiftParameter(_swift_js_pop_param_int32())" + labelPrefix = "" } + let liftExpr = stackCodegen.liftExpression(for: associatedValue.type) + return "\(labelPrefix)\(liftExpr)" } lines.append("return .\(enumCase.name)(\(argList.joined(separator: ", ")))") cases.append(lines.joined(separator: "\n")) @@ -2193,48 +2533,13 @@ public class ExportSwift { var bodyLines: [String] = [] for (index, associatedValue) in associatedValues.enumerated() { let paramName = associatedValue.label ?? "param\(index)" - switch associatedValue.type { - case .string: - bodyLines.append("var __bjs_\(paramName) = \(paramName)") - bodyLines.append("__bjs_\(paramName).withUTF8 { ptr in") - bodyLines.append("_swift_js_push_string(ptr.baseAddress, Int32(ptr.count))") - bodyLines.append("}") - case .int: - bodyLines.append("_swift_js_push_int(Int32(\(paramName)))") - case .bool: - bodyLines.append("_swift_js_push_int(\(paramName) ? 1 : 0)") - case .float: - bodyLines.append("_swift_js_push_f32(\(paramName))") - case .double: - bodyLines.append("_swift_js_push_f64(\(paramName))") - case .optional(let wrappedType): - bodyLines.append("let __bjs_isSome_\(paramName) = \(paramName) != nil") - bodyLines.append("if let __bjs_unwrapped_\(paramName) = \(paramName) {") - switch wrappedType { - case .string: - bodyLines.append("var __bjs_str_\(paramName) = __bjs_unwrapped_\(paramName)") - bodyLines.append("__bjs_str_\(paramName).withUTF8 { ptr in") - bodyLines.append("_swift_js_push_string(ptr.baseAddress, Int32(ptr.count))") - bodyLines.append("}") - case .int: - bodyLines.append("_swift_js_push_int(Int32(__bjs_unwrapped_\(paramName)))") - case .bool: - bodyLines.append("_swift_js_push_int(__bjs_unwrapped_\(paramName) ? 1 : 0)") - case .float: - bodyLines.append("_swift_js_push_f32(__bjs_unwrapped_\(paramName))") - case .double: - bodyLines.append("_swift_js_push_f64(__bjs_unwrapped_\(paramName))") - default: - bodyLines.append( - "preconditionFailure(\"BridgeJS: unsupported optional wrapped type in generated code\")" - ) - } - bodyLines.append("}") - bodyLines.append("_swift_js_push_int(__bjs_isSome_\(paramName) ? 1 : 0)") - default: - bodyLines.append( - "preconditionFailure(\"BridgeJS: unsupported associated value type in generated code\")" - ) + let statements = stackCodegen.lowerStatements( + for: associatedValue.type, + accessor: paramName, + varPrefix: paramName + ) + for stmt in statements { + bodyLines.append(stmt.description) } } return bodyLines @@ -2279,11 +2584,69 @@ public class ExportSwift { } } + private struct StructCodegen { + private let stackCodegen = StackCodegen() + + func renderStructHelpers(_ structDef: ExportedStruct) -> DeclSyntax { + let typeName = structDef.swiftCallName + let liftCode = generateStructLiftCode(structDef: structDef) + let lowerCode = generateStructLowerCode(structDef: structDef) + + return """ + extension \(raw: typeName): _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> \(raw: typeName) { + \(raw: liftCode.joined(separator: "\n")) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + \(raw: lowerCode.joined(separator: "\n")) + } + } + """ + } + + private func generateStructLiftCode(structDef: ExportedStruct) -> [String] { + var lines: [String] = [] + let instanceProps = structDef.properties.filter { !$0.isStatic } + + for property in instanceProps.reversed() { + let fieldName = property.name + let liftExpr = stackCodegen.liftExpression(for: property.type) + lines.append("let \(fieldName) = \(liftExpr)") + } + + let initArgs = instanceProps.map { "\($0.name): \($0.name)" }.joined(separator: ", ") + lines.append("return \(structDef.swiftCallName)(\(initArgs))") + + return lines + } + + private func generateStructLowerCode(structDef: ExportedStruct) -> [String] { + var lines: [String] = [] + let instanceProps = structDef.properties.filter { !$0.isStatic } + + for property in instanceProps { + let accessor = "self.\(property.name)" + let statements = stackCodegen.lowerStatements( + for: property.type, + accessor: accessor, + varPrefix: property.name + ) + for stmt in statements { + lines.append(stmt.description) + } + } + + return lines + } + } + /// Context for property rendering that determines call behavior and ABI generation private enum PropertyRenderingContext { case enumStatic(enumDef: ExportedEnum) case classStatic(klass: ExportedClass) case classInstance(klass: ExportedClass) + case structStatic(structDef: ExportedStruct) } /// Renders getter and setter Swift thunk code for a property in any context @@ -2309,6 +2672,10 @@ public class ExportSwift { callName = property.callName() className = klass.name isStatic = false + case .structStatic(let structDef): + callName = property.callName(prefix: structDef.swiftCallName) + className = structDef.name + isStatic = true } let getterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false, isStatic: isStatic)) @@ -2369,7 +2736,7 @@ public class ExportSwift { if function.effects.isStatic, let staticContext = function.staticContext { let callName: String switch staticContext { - case .className(let baseName), .enumName(let baseName): + case .className(let baseName), .enumName(let baseName), .structName(let baseName): callName = "\(baseName).\(function.name)" case .namespaceEnum: if let namespace = function.namespace, !namespace.isEmpty { @@ -2387,6 +2754,57 @@ public class ExportSwift { return builder.render(abiName: function.abiName) } + func renderSingleExportedStruct(struct structDef: ExportedStruct) throws -> [DeclSyntax] { + var decls: [DeclSyntax] = [] + + if let constructor = structDef.constructor { + let builder = ExportedThunkBuilder(effects: constructor.effects) + for param in constructor.parameters { + try builder.liftParameter(param: param) + } + builder.call(name: structDef.swiftCallName, returnType: .swiftStruct(structDef.swiftCallName)) + try builder.lowerReturnValue(returnType: .swiftStruct(structDef.swiftCallName)) + decls.append(builder.render(abiName: constructor.abiName)) + } + + for property in structDef.properties where property.isStatic { + decls.append( + contentsOf: try renderSingleExportedProperty( + property: property, + context: .structStatic(structDef: structDef) + ) + ) + } + + for method in structDef.methods { + let builder = ExportedThunkBuilder(effects: method.effects) + + if method.effects.isStatic { + for param in method.parameters { + try builder.liftParameter(param: param) + } + builder.call(name: "\(structDef.swiftCallName).\(method.name)", returnType: method.returnType) + } else { + try builder.liftParameter( + param: Parameter(label: nil, name: "_self", type: .swiftStruct(structDef.swiftCallName)) + ) + for param in method.parameters { + try builder.liftParameter(param: param) + } + builder.callMethod( + klassName: structDef.swiftCallName, + methodName: method.name, + returnType: method.returnType + ) + } + + try builder.lowerReturnValue(returnType: method.returnType) + decls.append(builder.render(abiName: method.abiName)) + } + + return decls + } + /// # Example /// /// Given the following Swift code: @@ -2879,6 +3297,7 @@ extension BridgeType { case .caseEnum(let name): return name case .rawValueEnum(let name, _): return name case .associatedValueEnum(let name): return name + case .swiftStruct(let name): return name case .namespaceEnum(let name): return name case .closure(let signature): let paramTypes = signature.parameters.map { $0.swiftType }.joined(separator: ", ") @@ -2935,6 +3354,8 @@ extension BridgeType { } case .associatedValueEnum: return .associatedValueEnum + case .swiftStruct: + return LiftingIntrinsicInfo(parameters: []) case .namespaceEnum: throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") case .closure: @@ -2956,6 +3377,7 @@ extension BridgeType { 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) } @@ -2987,6 +3409,8 @@ extension BridgeType { } case .associatedValueEnum: return .associatedValueEnum + case .swiftStruct: + return .swiftStruct case .namespaceEnum: throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") case .closure: diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 00ef5399..a3ae96ab 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -476,6 +476,13 @@ extension BridgeType { case .exportSwift: return LoweringParameterInfo(loweredParameters: [("caseId", .i32)]) } + case .swiftStruct: + switch context { + case .importTS: + throw BridgeJSCoreError("Swift structs are not yet supported in TypeScript imports") + case .exportSwift: + return LoweringParameterInfo(loweredParameters: []) + } case .namespaceEnum: throw BridgeJSCoreError("Namespace enums cannot be used as parameters") case .optional(let wrappedType): @@ -552,6 +559,13 @@ extension BridgeType { case .exportSwift: return LiftingReturnInfo(valueToLift: .i32) } + case .swiftStruct: + switch context { + case .importTS: + throw BridgeJSCoreError("Swift structs are not yet supported in TypeScript imports") + case .exportSwift: + return LiftingReturnInfo(valueToLift: nil) + } case .namespaceEnum: throw BridgeJSCoreError("Namespace enums cannot be used as return values") case .optional(let wrappedType): diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 7ecc2086..3e815b71 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -71,8 +71,8 @@ struct BridgeJSLink { var classLines: [String] = [] var dtsExportLines: [String] = [] var dtsClassLines: [String] = [] - var topLevelEnumLines: [String] = [] - var topLevelDtsEnumLines: [String] = [] + var topLevelTypeLines: [String] = [] + var topLevelDtsTypeLines: [String] = [] var importObjectBuilders: [ImportObjectBuilder] = [] var enumStaticAssignments: [String] = [] } @@ -109,29 +109,33 @@ struct BridgeJSLink { data.dtsClassLines.append(contentsOf: dtsType) } - // Process enums - if !skeleton.enums.isEmpty { - for enumDefinition in skeleton.enums { - let (jsEnum, dtsEnum) = try renderExportedEnum(enumDefinition) + // Process enums - collect top-level definitions and export entries + var enumExportEntries: [(js: [String], dts: [String])] = [] + for enumDefinition in skeleton.enums { + let (jsTopLevel, jsExportEntry, dtsType, dtsExportEntry) = try renderExportedEnum(enumDefinition) - switch enumDefinition.enumType { - case .namespace: - break - case .simple, .rawValue: - var exportedJsEnum = jsEnum - if !exportedJsEnum.isEmpty && exportedJsEnum[0].hasPrefix("const ") { - exportedJsEnum[0] = "export " + exportedJsEnum[0] - } - data.topLevelEnumLines.append(contentsOf: exportedJsEnum) - data.topLevelDtsEnumLines.append(contentsOf: dtsEnum) - case .associatedValue: - var exportedJsEnum = jsEnum - if !exportedJsEnum.isEmpty && exportedJsEnum[0].hasPrefix("const ") { - exportedJsEnum[0] = "export " + exportedJsEnum[0] - } - data.topLevelEnumLines.append(contentsOf: exportedJsEnum) - data.topLevelDtsEnumLines.append(contentsOf: dtsEnum) + // Add top-level JS const definition + if enumDefinition.enumType != .namespace { + var exportedJsEnum = jsTopLevel + if !exportedJsEnum.isEmpty && exportedJsEnum[0].hasPrefix("const ") { + exportedJsEnum[0] = "export " + exportedJsEnum[0] } + data.topLevelTypeLines.append(contentsOf: exportedJsEnum) + data.topLevelDtsTypeLines.append(contentsOf: dtsType) + } + + if !jsExportEntry.isEmpty || !dtsExportEntry.isEmpty { + enumExportEntries.append((js: jsExportEntry, dts: dtsExportEntry)) + } + } + + var structExportEntries: [(js: [String], dts: [String])] = [] + for structDefinition in skeleton.structs { + let (jsStruct, dtsType, dtsExportEntry) = try renderExportedStruct(structDefinition) + data.topLevelDtsTypeLines.append(contentsOf: dtsType) + + if structDefinition.namespace == nil && (!jsStruct.isEmpty || !dtsExportEntry.isEmpty) { + structExportEntries.append((js: jsStruct, dts: dtsExportEntry)) } } @@ -146,107 +150,14 @@ struct BridgeJSLink { } } - for enumDefinition in skeleton.enums - where enumDefinition.enumType != .namespace && enumDefinition.emitStyle != .tsEnum { - if enumDefinition.namespace != nil { - continue - } - - let enumExportPrinter = CodeFragmentPrinter() - let enumValuesName = enumDefinition.valuesName - - for function in enumDefinition.staticMethods { - let thunkBuilder = ExportedThunkBuilder(effects: function.effects) - for param in function.parameters { - try thunkBuilder.lowerParameter(param: param) - } - let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) - - let methodPrinter = CodeFragmentPrinter() - methodPrinter.write( - "\(function.name): function(\(function.parameters.map { $0.name }.joined(separator: ", "))) {" - ) - methodPrinter.indent { - methodPrinter.write(contentsOf: thunkBuilder.body) - methodPrinter.write(contentsOf: thunkBuilder.cleanupCode) - methodPrinter.write(lines: thunkBuilder.checkExceptionLines()) - if let returnExpr = returnExpr { - methodPrinter.write("return \(returnExpr);") - } - } - methodPrinter.write("},") - - enumExportPrinter.write(lines: methodPrinter.lines) - } - - let enumExportLines = enumExportPrinter.lines - - let enumPropertyPrinter = CodeFragmentPrinter() - - for property in enumDefinition.staticProperties { - let getterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) - let getterReturnExpr = try getterThunkBuilder.call( - abiName: property.getterAbiName(), - returnType: property.type - ) - - enumPropertyPrinter.write("get \(property.name)() {") - enumPropertyPrinter.indent { - enumPropertyPrinter.write(contentsOf: getterThunkBuilder.body) - enumPropertyPrinter.write(contentsOf: getterThunkBuilder.cleanupCode) - enumPropertyPrinter.write(lines: getterThunkBuilder.checkExceptionLines()) - if let returnExpr = getterReturnExpr { - enumPropertyPrinter.write("return \(returnExpr);") - } - } - enumPropertyPrinter.write("},") - - if !property.isReadonly { - let setterThunkBuilder = ExportedThunkBuilder( - effects: Effects(isAsync: false, isThrows: false) - ) - try setterThunkBuilder.lowerParameter( - param: Parameter(label: "value", name: "value", type: property.type) - ) - _ = try setterThunkBuilder.call( - abiName: property.setterAbiName(), - returnType: .void - ) - - enumPropertyPrinter.write("set \(property.name)(value) {") - enumPropertyPrinter.indent { - enumPropertyPrinter.write(contentsOf: setterThunkBuilder.body) - enumPropertyPrinter.write(contentsOf: setterThunkBuilder.cleanupCode) - enumPropertyPrinter.write(lines: setterThunkBuilder.checkExceptionLines()) - } - enumPropertyPrinter.write("},") - } - } - - let enumPropertyLines = enumPropertyPrinter.lines - - let exportsPrinter = CodeFragmentPrinter() - let dtsExportsPrinter = CodeFragmentPrinter() - - if !enumExportLines.isEmpty || !enumPropertyLines.isEmpty { - exportsPrinter.write("\(enumDefinition.name): {") - exportsPrinter.indent { - exportsPrinter.write("...\(enumValuesName),") - var allLines = enumExportLines + enumPropertyLines - if let lastLineIndex = allLines.indices.last, allLines[lastLineIndex].hasSuffix(",") { - allLines[lastLineIndex] = String(allLines[lastLineIndex].dropLast()) - } - exportsPrinter.write(lines: allLines) - } - exportsPrinter.write("},") - } else { - exportsPrinter.write("\(enumDefinition.name): \(enumValuesName),") - } - - dtsExportsPrinter.write("\(enumDefinition.name): \(enumDefinition.objectTypeName)") + for entry in enumExportEntries { + data.exportsLines.append(contentsOf: entry.js) + data.dtsExportLines.append(contentsOf: entry.dts) + } - data.exportsLines.append(contentsOf: exportsPrinter.lines) - data.dtsExportLines.append(contentsOf: dtsExportsPrinter.lines) + for entry in structExportEntries { + data.exportsLines.append(contentsOf: entry.js) + data.dtsExportLines.append(contentsOf: entry.dts) } } @@ -324,12 +235,22 @@ struct BridgeJSLink { "let \(JSGlueVariableScope.reservedTmpParamInts) = [];", "let \(JSGlueVariableScope.reservedTmpParamF32s) = [];", "let \(JSGlueVariableScope.reservedTmpParamF64s) = [];", + "let \(JSGlueVariableScope.reservedTmpRetPointers) = [];", + "let \(JSGlueVariableScope.reservedTmpParamPointers) = [];", ] + let hasStructs = exportedSkeletons.contains { skeleton in + !skeleton.structs.isEmpty + } + if hasAssociatedValueEnums { declarations.append("const enumHelpers = {};") } + if hasStructs { + declarations.append("const structHelpers = {};") + } + declarations.append("") declarations.append("let _exports = null;") declarations.append("let bjs = null;") @@ -497,6 +418,16 @@ struct BridgeJSLink { printer.write("return \(JSGlueVariableScope.reservedTmpParamF64s).pop();") } printer.write("}") + printer.write("bjs[\"swift_js_push_pointer\"] = function(pointer) {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpRetPointers).push(pointer);") + } + printer.write("}") + printer.write("bjs[\"swift_js_pop_param_pointer\"] = function() {") + printer.indent { + printer.write("return \(JSGlueVariableScope.reservedTmpParamPointers).pop();") + } + printer.write("}") printer.write("bjs[\"swift_js_return_optional_bool\"] = function(isSome, value) {") printer.indent { printer.write("if (isSome === 0) {") @@ -883,7 +814,7 @@ struct BridgeJSLink { } } - printer.write(lines: data.topLevelDtsEnumLines) + printer.write(lines: data.topLevelDtsTypeLines) // Generate Object types for const-style enums for skeleton in exportedSkeletons { @@ -1002,8 +933,7 @@ struct BridgeJSLink { let printer = CodeFragmentPrinter(header: header) printer.nextLine() - // Top-level enums section - printer.write(lines: data.topLevelEnumLines) + printer.write(lines: data.topLevelTypeLines) let topLevelNamespaceCode = namespaceBuilder.buildTopLevelNamespaceInitialization( exportedSkeletons: exportedSkeletons @@ -1015,6 +945,16 @@ struct BridgeJSLink { printer.indent { printer.write(lines: generateVariableDeclarations()) + + let allStructs = exportedSkeletons.flatMap { $0.structs } + for structDef in allStructs { + let structPrinter = CodeFragmentPrinter() + let structScope = JSGlueVariableScope() + let structCleanup = CodeFragmentPrinter() + let fragment = IntrinsicJSFragment.structHelper(structDefinition: structDef, allStructs: allStructs) + _ = fragment.printCode([structDef.name], structScope, structPrinter, structCleanup) + printer.write(lines: structPrinter.lines) + } printer.nextLine() printer.write(contentsOf: generateAddImports()) } @@ -1068,6 +1008,8 @@ struct BridgeJSLink { printer.write(lines: data.classLines) + // Struct helpers must be initialized AFTER classes are defined (to allow _exports access) + printer.write(contentsOf: structHelperAssignments()) let namespaceInitCode = namespaceBuilder.buildNamespaceInitialization( exportedSkeletons: exportedSkeletons ) @@ -1112,6 +1054,22 @@ struct BridgeJSLink { return printer } + private func structHelperAssignments() -> CodeFragmentPrinter { + let printer = CodeFragmentPrinter() + + for skeleton in exportedSkeletons { + for structDef in skeleton.structs { + printer.write( + "const \(structDef.name)Helpers = __bjs_create\(structDef.name)Helpers()(\(JSGlueVariableScope.reservedTmpParamInts), \(JSGlueVariableScope.reservedTmpParamF32s), \(JSGlueVariableScope.reservedTmpParamF64s), \(JSGlueVariableScope.reservedTmpParamPointers), \(JSGlueVariableScope.reservedTmpRetPointers), \(JSGlueVariableScope.reservedTextEncoder), \(JSGlueVariableScope.reservedSwift), enumHelpers);" + ) + printer.write("structHelpers.\(structDef.name) = \(structDef.name)Helpers;") + printer.nextLine() + } + } + + return printer + } + private func renderSwiftClassWrappers() -> [String] { var wrapperLines: [String] = [] var modulesByName: [String: [ExportedClass]] = [:] @@ -1367,6 +1325,8 @@ struct BridgeJSLink { } } return type.tsType + case .swiftStruct(let name): + return name.components(separatedBy: ".").last ?? name case .optional(let wrapped): return "\(resolveTypeScriptType(wrapped, exportedSkeletons: exportedSkeletons)) | null" default: @@ -1460,9 +1420,109 @@ struct BridgeJSLink { } } - func renderExportedEnum(_ enumDefinition: ExportedEnum) throws -> (js: [String], dts: [String]) { - var jsLines: [String] = [] - var dtsLines: [String] = [] + func renderExportedStruct( + _ structDefinition: ExportedStruct + ) throws -> (js: [String], dtsType: [String], dtsExportEntry: [String]) { + let structName = structDefinition.name + let hasConstructor = structDefinition.constructor != nil + let staticMethods = structDefinition.methods.filter { $0.effects.isStatic } + let staticProperties = structDefinition.properties.filter { $0.isStatic } + + let dtsTypePrinter = CodeFragmentPrinter() + dtsTypePrinter.write("export interface \(structName) {") + let instanceProps = structDefinition.properties.filter { !$0.isStatic } + dtsTypePrinter.indent { + for property in instanceProps { + let tsType = resolveTypeScriptType(property.type) + dtsTypePrinter.write("\(property.name): \(tsType);") + } + for method in structDefinition.methods where !method.effects.isStatic { + let signature = renderTSSignature( + parameters: method.parameters, + returnType: method.returnType, + effects: method.effects + ) + dtsTypePrinter.write("\(method.name)\(signature);") + } + } + dtsTypePrinter.write("}") + + guard hasConstructor || !staticMethods.isEmpty || !staticProperties.isEmpty else { + return (js: [], dtsType: dtsTypePrinter.lines, dtsExportEntry: []) + } + + let jsPrinter = CodeFragmentPrinter() + jsPrinter.write("\(structName): {") + try jsPrinter.indent { + // Constructor as 'init' function + if let constructor = structDefinition.constructor { + let thunkBuilder = ExportedThunkBuilder(effects: constructor.effects) + for param in constructor.parameters { + try thunkBuilder.lowerParameter(param: param) + } + let returnExpr = try thunkBuilder.call( + abiName: constructor.abiName, + returnType: .swiftStruct(structDefinition.swiftCallName) + ) + + let constructorPrinter = CodeFragmentPrinter() + let paramList = thunkBuilder.generateParameterList(parameters: constructor.parameters) + constructorPrinter.write("init: function(\(paramList)) {") + constructorPrinter.indent { + constructorPrinter.write(contentsOf: thunkBuilder.body) + constructorPrinter.write(contentsOf: thunkBuilder.cleanupCode) + constructorPrinter.write(lines: thunkBuilder.checkExceptionLines()) + if let returnExpr = returnExpr { + constructorPrinter.write("return \(returnExpr);") + } + } + constructorPrinter.write("},") + jsPrinter.write(lines: constructorPrinter.lines) + } + + for property in staticProperties { + let propertyLines = try renderStaticPropertyForExportObject( + property: property, + className: structName + ) + jsPrinter.write(lines: propertyLines) + } + + for method in staticMethods { + let methodLines = try renderStaticMethodForExportObject(method: method) + jsPrinter.write(lines: methodLines) + } + } + jsPrinter.write("},") + + let dtsExportEntryPrinter = CodeFragmentPrinter() + dtsExportEntryPrinter.write("\(structName): {") + dtsExportEntryPrinter.indent { + if let constructor = structDefinition.constructor { + dtsExportEntryPrinter.write( + "init\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftStruct(structDefinition.swiftCallName), effects: constructor.effects));" + ) + } + for property in staticProperties { + let readonly = property.isReadonly ? "readonly " : "" + dtsExportEntryPrinter.write("\(readonly)\(property.name): \(resolveTypeScriptType(property.type));") + } + for method in staticMethods { + dtsExportEntryPrinter.write( + "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" + ) + } + } + dtsExportEntryPrinter.write("}") + + return (js: jsPrinter.lines, dtsType: dtsTypePrinter.lines, dtsExportEntry: dtsExportEntryPrinter.lines) + } + + func renderExportedEnum( + _ enumDefinition: ExportedEnum + ) throws -> (jsTopLevel: [String], jsExportEntry: [String], dtsType: [String], dtsExportEntry: [String]) { + var jsTopLevelLines: [String] = [] + var dtsTypeLines: [String] = [] let scope = JSGlueVariableScope() let cleanup = CodeFragmentPrinter() let printer = CodeFragmentPrinter() @@ -1472,7 +1532,7 @@ struct BridgeJSLink { case .simple: let fragment = IntrinsicJSFragment.simpleEnumHelper(enumDefinition: enumDefinition) _ = fragment.printCode([enumValuesName], scope, printer, cleanup) - jsLines.append(contentsOf: printer.lines) + jsTopLevelLines.append(contentsOf: printer.lines) case .rawValue: guard enumDefinition.rawType != nil else { throw BridgeJSLinkError(message: "Raw value enum \(enumDefinition.name) is missing rawType") @@ -1480,20 +1540,63 @@ struct BridgeJSLink { let fragment = IntrinsicJSFragment.rawValueEnumHelper(enumDefinition: enumDefinition) _ = fragment.printCode([enumValuesName], scope, printer, cleanup) - jsLines.append(contentsOf: printer.lines) + jsTopLevelLines.append(contentsOf: printer.lines) case .associatedValue: let fragment = IntrinsicJSFragment.associatedValueEnumHelper(enumDefinition: enumDefinition) _ = fragment.printCode([enumValuesName], scope, printer, cleanup) - jsLines.append(contentsOf: printer.lines) + jsTopLevelLines.append(contentsOf: printer.lines) case .namespace: break } if enumDefinition.namespace == nil { - dtsLines.append(contentsOf: generateDeclarations(enumDefinition: enumDefinition)) + dtsTypeLines.append(contentsOf: generateDeclarations(enumDefinition: enumDefinition)) + } + + var jsExportEntryLines: [String] = [] + var dtsExportEntryLines: [String] = [] + + if enumDefinition.enumType != .namespace + && enumDefinition.emitStyle != .tsEnum + && enumDefinition.namespace == nil + { + var enumMethodLines: [String] = [] + for function in enumDefinition.staticMethods { + let methodLines = try renderStaticMethodForExportObject(method: function) + enumMethodLines.append(contentsOf: methodLines) + } + + var enumPropertyLines: [String] = [] + for property in enumDefinition.staticProperties { + let propertyLines = try renderStaticPropertyForExportObject( + property: property, + className: nil + ) + enumPropertyLines.append(contentsOf: propertyLines) + } + + let exportsPrinter = CodeFragmentPrinter() + + if !enumMethodLines.isEmpty || !enumPropertyLines.isEmpty { + exportsPrinter.write("\(enumDefinition.name): {") + exportsPrinter.indent { + exportsPrinter.write("...\(enumValuesName),") + var allLines = enumMethodLines + enumPropertyLines + if let lastLineIndex = allLines.indices.last, allLines[lastLineIndex].hasSuffix(",") { + allLines[lastLineIndex] = String(allLines[lastLineIndex].dropLast()) + } + exportsPrinter.write(lines: allLines) + } + exportsPrinter.write("},") + } else { + exportsPrinter.write("\(enumDefinition.name): \(enumValuesName),") + } + + jsExportEntryLines = exportsPrinter.lines + dtsExportEntryLines = ["\(enumDefinition.name): \(enumDefinition.objectTypeName)"] } - return (jsLines, dtsLines) + return (jsTopLevelLines, jsExportEntryLines, dtsTypeLines, dtsExportEntryLines) } private func generateDeclarations(enumDefinition: ExportedEnum) -> [String] { @@ -1622,8 +1725,8 @@ extension BridgeJSLink { staticContext: StaticContext ) throws -> (js: [String], dts: [String]) { switch staticContext { - case .className(let className): - return try renderClassStaticFunction(function: function, className: className) + case .className(let name), .structName(let name): + return try renderStaticFunction(function: function, className: name) case .enumName(let enumName): return try renderEnumStaticFunction(function: function, enumName: enumName) case .namespaceEnum: @@ -1635,7 +1738,7 @@ extension BridgeJSLink { } } - private func renderClassStaticFunction( + private func renderStaticFunction( function: ExportedFunction, className: String ) throws -> (js: [String], dts: [String]) { @@ -1718,67 +1821,61 @@ extension BridgeJSLink { return (funcLines, dtsLines) } - private func renderEnumStaticFunctionAssignment( - function: ExportedFunction, - enumName: String + /// Renders a static method for use in an export object + private func renderStaticMethodForExportObject( + method: ExportedFunction ) throws -> [String] { - let thunkBuilder = ExportedThunkBuilder(effects: function.effects) - for param in function.parameters { + let thunkBuilder = ExportedThunkBuilder(effects: method.effects) + for param in method.parameters { try thunkBuilder.lowerParameter(param: param) } - let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) - let paramList = thunkBuilder.generateParameterList(parameters: function.parameters) + let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) - let printer = CodeFragmentPrinter() - printer.write( - "\(enumName).\(function.name) = function(\(paramList)) {" + let methodPrinter = CodeFragmentPrinter() + methodPrinter.write( + "\(method.name): function(\(method.parameters.map { $0.name }.joined(separator: ", "))) {" ) - printer.indent { - printer.write(contentsOf: thunkBuilder.body) - printer.write(contentsOf: thunkBuilder.cleanupCode) - printer.write(lines: thunkBuilder.checkExceptionLines()) + methodPrinter.indent { + methodPrinter.write(contentsOf: thunkBuilder.body) + methodPrinter.write(contentsOf: thunkBuilder.cleanupCode) + methodPrinter.write(lines: thunkBuilder.checkExceptionLines()) if let returnExpr = returnExpr { - printer.write("return \(returnExpr);") + methodPrinter.write("return \(returnExpr);") } } - printer.write("};") - - return printer.lines + methodPrinter.write("},") + return methodPrinter.lines } - /// Renders an enum static property as getter/setter assignments on the enum object - private func renderEnumStaticProperty( + /// Renders a static property getter/setter for use in an export object + private func renderStaticPropertyForExportObject( property: ExportedProperty, - enumName: String - ) throws -> (js: [String], dts: [String]) { - var jsLines: [String] = [] + className: String? + ) throws -> [String] { + let propertyPrinter = CodeFragmentPrinter() - // Generate getter assignment + // Generate getter let getterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) let getterReturnExpr = try getterThunkBuilder.call( - abiName: property.getterAbiName(), + abiName: className != nil + ? property.getterAbiName(className: className!) + : property.getterAbiName(), returnType: property.type ) - let getterLines = getterThunkBuilder.renderFunction( - name: property.name, - parameters: [], - returnExpr: getterReturnExpr, - declarationPrefixKeyword: nil - ) - - // Build Object.defineProperty call - var definePropertyLines: [String] = [] - definePropertyLines.append("Object.defineProperty(\(enumName), '\(property.name)', { get: function() {") - - // Add getter body (skip function declaration and closing brace) - if getterLines.count > 2 { - let bodyLines = Array(getterLines[1.. 2 { - let bodyLines = Array(setterLines[1.. = [ reservedSwift, + reservedInstance, reservedMemory, + reservedSetException, reservedStorageToReturnString, reservedStorageToReturnBytes, reservedStorageToReturnException, @@ -50,6 +54,8 @@ final class JSGlueVariableScope { reservedTmpParamInts, reservedTmpParamF32s, reservedTmpParamF64s, + reservedTmpRetPointers, + reservedTmpParamPointers, ] /// Returns a unique variable name in the scope based on the given name hint. @@ -324,6 +330,22 @@ struct IntrinsicJSFragment: Sendable { } printer.write("}") resultExpr = "\(isSome) ? \(enumVar) : null" + case .swiftStruct(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + let structVar = scope.variable("structValue") + printer.write("let \(structVar);") + printer.write("if (\(isSome)) {") + printer.indent { + printer.write( + "\(structVar) = structHelpers.\(base).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + } + printer.write("} else {") + printer.indent { + printer.write("\(structVar) = null;") + } + printer.write("}") + resultExpr = structVar default: resultExpr = "\(isSome) ? \(wrappedValue) : null" } @@ -342,6 +364,19 @@ struct IntrinsicJSFragment: Sendable { printer.write("const \(isSomeVar) = \(value) != null;") 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) = structHelpers.\(base).lower(\(value));") + printer.write("\(cleanupVar) = \(resultVar).cleanup;") + } + printer.write("}") + cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") + return ["+\(isSomeVar)"] case .string, .rawValueEnum(_, .string): let bytesVar = scope.variable("\(value)Bytes") let idVar = scope.variable("\(value)Id") @@ -494,6 +529,22 @@ struct IntrinsicJSFragment: Sendable { ) } printer.write("}") + case .swiftStruct(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + let isSomeVar = scope.variable("isSome") + printer.write("const \(isSomeVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + printer.write("let \(resultVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + printer.write( + "\(resultVar) = structHelpers.\(base).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + } + printer.write("} else {") + printer.indent { + printer.write("\(resultVar) = null;") + } + printer.write("}") default: printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnString);") printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;") @@ -1259,6 +1310,9 @@ struct IntrinsicJSFragment: Sendable { case .associatedValueEnum(let fullName): let base = fullName.components(separatedBy: ".").last ?? fullName return .associatedEnumLowerParameter(enumBase: base) + case .swiftStruct(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + return swiftStructLowerParameter(structBase: base) case .closure: return IntrinsicJSFragment( parameters: ["closure"], @@ -1297,6 +1351,9 @@ struct IntrinsicJSFragment: Sendable { case .associatedValueEnum(let fullName): let base = fullName.components(separatedBy: ".").last ?? fullName return .associatedEnumLiftReturn(enumBase: base) + case .swiftStruct(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + return swiftStructLiftReturn(structBase: base) case .closure(let signature): let lowerFuncName = "lower_closure_\(signature.moduleName)_\(signature.mangleName)" return IntrinsicJSFragment( @@ -1374,6 +1431,26 @@ struct IntrinsicJSFragment: Sendable { } ) } + case .swiftStruct(let fullName): + switch context { + case .importTS: + throw BridgeJSLinkError( + message: + "Swift structs are not supported to be passed as parameters to imported JS functions: \(fullName)" + ) + case .exportSwift: + let base = fullName.components(separatedBy: ".").last ?? fullName + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanupCode in + let resultVar = scope.variable("structValue") + printer.write( + "const \(resultVar) = structHelpers.\(base).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + return [resultVar] + } + ) + } case .closure: throw BridgeJSLinkError(message: "Closure parameters not yet implemented for imported JS functions") case .namespaceEnum(let string): @@ -1428,6 +1505,16 @@ struct IntrinsicJSFragment: Sendable { case .exportSwift: return associatedValueLowerReturn(fullName: fullName) } + case .swiftStruct(let fullName): + switch context { + case .importTS: + throw BridgeJSLinkError( + message: + "Swift structs are not supported to be returned from imported JS functions: \(fullName)" + ) + case .exportSwift: + return swiftStructLowerReturn(fullName: fullName) + } case .closure: throw BridgeJSLinkError(message: "Closure return values not yet implemented for imported JS functions") case .namespaceEnum(let string): @@ -1886,4 +1973,839 @@ struct IntrinsicJSFragment: Sendable { ) } } + + static func swiftStructLowerReturn(fullName: String) -> IntrinsicJSFragment { + let base = fullName.components(separatedBy: ".").last ?? fullName + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + let value = arguments[0] + let cleanupVar = scope.variable("cleanup") + printer.write( + "const { cleanup: \(cleanupVar) } = structHelpers.\(base).lower(\(value));" + ) + cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") + return [] + } + ) + } + + static func swiftStructLowerParameter(structBase: String) -> IntrinsicJSFragment { + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + let value = arguments[0] + let cleanupVar = scope.variable("cleanup") + printer.write( + "const { cleanup: \(cleanupVar) } = structHelpers.\(structBase).lower(\(value));" + ) + cleanupCode.write("if (\(cleanupVar)) { \(cleanupVar)(); }") + return [] + } + ) + } + + static func swiftStructLiftReturn(structBase: String) -> IntrinsicJSFragment { + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanupCode in + let resultVar = scope.variable("structValue") + printer.write( + "const \(resultVar) = structHelpers.\(structBase).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + return [resultVar] + } + ) + } + + static func structHelper(structDefinition: ExportedStruct, allStructs: [ExportedStruct]) -> IntrinsicJSFragment { + return IntrinsicJSFragment( + parameters: ["structName"], + printCode: { arguments, scope, printer, cleanup in + let structName = arguments[0] + let capturedStructDef = structDefinition + let capturedAllStructs = allStructs + + printer.write("const __bjs_create\(structName)Helpers = () => {") + printer.indent() + printer.write( + "return (\(JSGlueVariableScope.reservedTmpParamInts), \(JSGlueVariableScope.reservedTmpParamF32s), \(JSGlueVariableScope.reservedTmpParamF64s), \(JSGlueVariableScope.reservedTmpParamPointers), \(JSGlueVariableScope.reservedTmpRetPointers), textEncoder, \(JSGlueVariableScope.reservedSwift), enumHelpers) => ({" + ) + printer.indent() + + printer.write("lower: (value) => {") + printer.indent { + generateStructLowerCode( + structDef: capturedStructDef, + allStructs: capturedAllStructs, + printer: printer + ) + } + printer.write("},") + + printer.write( + "raise: (\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers)) => {" + ) + printer.indent { + generateStructRaiseCode( + structDef: capturedStructDef, + allStructs: capturedAllStructs, + printer: printer, + attachMethods: true + ) + } + printer.write("}") + printer.unindent() + printer.write("});") + printer.unindent() + printer.write("};") + + return [] + } + ) + } + + 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], + printer: CodeFragmentPrinter + ) { + let lowerPrinter = CodeFragmentPrinter() + let lowerScope = JSGlueVariableScope() + let lowerCleanup = CodeFragmentPrinter() + lowerCleanup.indent() + + let instanceProps = structDef.properties.filter { !$0.isStatic } + for property in instanceProps { + let fragment = structFieldLowerFragment(field: property, allStructs: allStructs) + let fieldValue = "value.\(property.name)" + _ = fragment.printCode([fieldValue], lowerScope, lowerPrinter, lowerCleanup) + } + + for line in lowerPrinter.lines { + printer.write(line) + } + + if !lowerCleanup.lines.isEmpty { + printer.write("const cleanup = () => {") + printer.write(contentsOf: lowerCleanup) + printer.write("};") + printer.write("return { cleanup };") + } else { + printer.write("return { cleanup: undefined };") + } + } + + private static func generateStructRaiseCode( + structDef: ExportedStruct, + allStructs: [ExportedStruct], + printer: CodeFragmentPrinter, + attachMethods: Bool = false + ) { + let raiseScope = JSGlueVariableScope() + let raiseCleanup = CodeFragmentPrinter() + + var fieldExpressions: [(name: String, expression: String)] = [] + + let instanceProps = structDef.properties.filter { !$0.isStatic } + for property in instanceProps.reversed() { + let fragment = structFieldRaiseFragment(field: property, allStructs: allStructs) + let results = fragment.printCode([], raiseScope, printer, raiseCleanup) + + if let resultExpr = results.first { + fieldExpressions.append((property.name, resultExpr)) + } else { + fieldExpressions.append((property.name, "undefined")) + } + } + + // Construct struct object with fields in original order + let reconstructedFields = instanceProps.map { property in + let expr = fieldExpressions.first(where: { $0.name == property.name })?.expression ?? "undefined" + return "\(property.name): \(expr)" + } + + if attachMethods && !structDef.methods.filter({ !$0.effects.isStatic }).isEmpty { + let instanceVar = raiseScope.variable("instance") + printer.write("const \(instanceVar) = { \(reconstructedFields.joined(separator: ", ")) };") + + // Attach instance methods to the struct instance + for method in structDef.methods where !method.effects.isStatic { + printer.write( + "\(instanceVar).\(method.name) = function(\(method.parameters.map { $0.name }.joined(separator: ", "))) {" + ) + printer.indent { + let methodScope = JSGlueVariableScope() + let methodCleanup = CodeFragmentPrinter() + + // Lower the struct instance (this) using the helper's lower function + let structCleanupVar = methodScope.variable("structCleanup") + printer.write( + "const { cleanup: \(structCleanupVar) } = structHelpers.\(structDef.name).lower(this);" + ) + + // Lower each parameter and collect forwarding expressions + var paramForwardings: [String] = [] + for param in method.parameters { + let fragment = try! IntrinsicJSFragment.lowerParameter(type: param.type) + let loweredValues = fragment.printCode([param.name], methodScope, printer, methodCleanup) + paramForwardings.append(contentsOf: loweredValues) + } + + // Call the Swift function with all lowered parameters + let callExpr = "instance.exports.\(method.abiName)(\(paramForwardings.joined(separator: ", ")))" + if method.returnType == .void { + printer.write("\(callExpr);") + } else { + printer.write("const ret = \(callExpr);") + } + + // Cleanup + printer.write("if (\(structCleanupVar)) { \(structCleanupVar)(); }") + printer.write(contentsOf: methodCleanup) + + // Lift return value if needed + if method.returnType != .void { + let liftFragment = try! IntrinsicJSFragment.liftReturn(type: method.returnType) + if !liftFragment.parameters.isEmpty { + let lifted = liftFragment.printCode(["ret"], methodScope, printer, methodCleanup) + if let liftedValue = lifted.first { + printer.write("return \(liftedValue);") + } + } + } + } + printer.write("}.bind(\(instanceVar));") + } + + printer.write("return \(instanceVar);") + } else { + printer.write("return { \(reconstructedFields.joined(separator: ", ")) };") + } + } + + private static func structFieldLowerFragment( + field: ExportedProperty, + allStructs: [ExportedStruct] + ) -> IntrinsicJSFragment { + switch field.type { + case .string: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let bytesVar = scope.variable("bytes") + let idVar = scope.variable("id") + printer.write("const \(bytesVar) = textEncoder.encode(\(value));") + printer.write("const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(bytesVar).length);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") + return [idVar] + } + ) + case .bool: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(arguments[0]) ? 1 : 0);") + return [] + } + ) + case .int: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(arguments[0]) | 0));") + return [] + } + ) + case .float: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamF32s).push(Math.fround(\(arguments[0])));") + return [] + } + ) + case .double: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamF64s).push(\(arguments[0]));") + return [] + } + ) + case .jsObject: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + 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("} else {") + printer.indent { + printer.write("\(idVar) = undefined;") + } + printer.write("}") + printer.write( + "\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar) !== undefined ? \(idVar) : 0);" + ) + 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) {}") + } + cleanup.write("}") + return [idVar] + } + ) + case .optional(let wrappedType): + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let isSomeVar = scope.variable("isSome") + printer.write("const \(isSomeVar) = \(value) != null;") + + if case .caseEnum = wrappedType { + printer.write("if (\(isSomeVar)) {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(value) | 0));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + 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) = textEncoder.encode(\(value));") + printer.write( + "\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));" + ) + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(bytesVar).length);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + cleanup.write( + "if(\(idVar) !== undefined) { \(JSGlueVariableScope.reservedSwift).memory.release(\(idVar)); }" + ) + return [idVar] + default: + printer.write("if (\(isSomeVar)) {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(value) | 0));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + 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;") + printer.write("\(JSGlueVariableScope.reservedTmpParamPointers).push(\(ptrVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamPointers).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + 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) = structHelpers.\(structName).lower(\(value));") + printer.write("\(nestedCleanupVar) = \(structResultVar).cleanup;") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + 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) = textEncoder.encode(\(value));") + printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(bytesVar).length);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + 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));") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(idVar) = undefined;") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + 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) {}") + } + cleanup.write("}") + return [idVar] + } else { + // Handle optional primitive types using helper + switch wrappedType { + case .int: + pushOptionalPrimitive( + value: value, + isSomeVar: isSomeVar, + stack: .tmpParamInts, + convert: "| 0", + zeroValue: "0", + printer: printer + ) + case .bool: + pushOptionalPrimitive( + value: value, + isSomeVar: isSomeVar, + stack: .tmpParamInts, + convert: "? 1 : 0", + zeroValue: "0", + printer: printer + ) + case .float: + pushOptionalPrimitive( + value: value, + isSomeVar: isSomeVar, + stack: .tmpParamF32s, + convert: "Math.fround", + zeroValue: "0.0", + printer: printer + ) + case .double: + pushOptionalPrimitive( + value: value, + isSomeVar: isSomeVar, + stack: .tmpParamF64s, + convert: nil, + zeroValue: "0.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) = enumHelpers.\(base).lower(\(value));") + printer.write("\(caseIdVar) = \(enumResultVar).caseId;") + printer.write("\(enumCleanupVar) = \(enumResultVar).cleanup;") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(caseIdVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(0);") + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + cleanup.write("if (\(enumCleanupVar)) { \(enumCleanupVar)(); }") + default: + // For other types (nested structs, etc.), original logic applies + let wrappedFragment = structFieldLowerFragment( + field: ExportedProperty( + name: field.name, + type: wrappedType, + isReadonly: true, + isStatic: false + ), + allStructs: allStructs + ) + printer.write("if (\(isSomeVar)) {") + printer.indent { + _ = wrappedFragment.printCode([value], scope, printer, cleanup) + } + printer.write("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + } + return [] + } + } + ) + case .swiftStruct(let nestedName): + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let structResultVar = scope.variable("structResult") + printer.write("const \(structResultVar) = structHelpers.\(nestedName).lower(\(value));") + cleanup.write("if (\(structResultVar).cleanup) { \(structResultVar).cleanup(); }") + return [] + } + ) + case .swiftHeapObject: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + printer.write("\(JSGlueVariableScope.reservedTmpParamPointers).push(\(value).pointer);") + return [] + } + ) + case .associatedValueEnum(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let caseIdVar = scope.variable("caseId") + let cleanupVar = scope.variable("enumCleanup") + printer.write( + "const { caseId: \(caseIdVar), cleanup: \(cleanupVar) } = enumHelpers.\(base).lower(\(value));" + ) + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(caseIdVar));") + cleanup.write("if (\(cleanupVar)) { \(cleanupVar)(); }") + return [cleanupVar] + } + ) + case .caseEnum: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(arguments[0]) | 0));") + return [] + } + ) + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + let value = arguments[0] + let bytesVar = scope.variable("bytes") + let idVar = scope.variable("id") + printer.write("const \(bytesVar) = textEncoder.encode(\(value));") + printer.write( + "const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesVar));" + ) + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(bytesVar).length);") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") + cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") + return [idVar] + } + ) + default: + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push((\(arguments[0]) | 0));") + return [] + } + ) + } + case .void, .swiftProtocol, .namespaceEnum, .closure: + // These types should not appear as struct fields - return error fragment + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanup in + printer.write("throw new Error(\"Unsupported struct field type for lowering: \(field.type)\");") + return [] + } + ) + } + } + + /// 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 + ) { + let stackName: String + switch stack { + case .tmpParamInts: stackName = JSGlueVariableScope.reservedTmpParamInts + case .tmpParamF32s: stackName = JSGlueVariableScope.reservedTmpParamF32s + case .tmpParamF64s: stackName = JSGlueVariableScope.reservedTmpParamF64s + } + + 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("}") + printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);") + } + + private enum StackType { + case tmpParamInts + case tmpParamF32s + case tmpParamF64s + } + + private static func structFieldRaiseFragment( + field: ExportedProperty, + allStructs: [ExportedStruct] + ) -> IntrinsicJSFragment { + switch field.type { + case .string: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let strVar = scope.variable("string") + printer.write("const \(strVar) = \(JSGlueVariableScope.reservedTmpRetStrings).pop();") + return [strVar] + } + ) + case .bool: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let bVar = scope.variable("bool") + printer.write("const \(bVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop() !== 0;") + return [bVar] + } + ) + case .int: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let iVar = scope.variable("int") + printer.write("const \(iVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + return [iVar] + } + ) + case .float: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let fVar = scope.variable("f32") + printer.write("const \(fVar) = \(JSGlueVariableScope.reservedTmpRetF32s).pop();") + return [fVar] + } + ) + case .double: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let dVar = scope.variable("f64") + printer.write("const \(dVar) = \(JSGlueVariableScope.reservedTmpRetF64s).pop();") + return [dVar] + } + ) + case .optional(let wrappedType): + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let isSomeVar = scope.variable("isSome") + let optVar = scope.variable("optional") + printer.write("const \(isSomeVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + printer.write("let \(optVar);") + printer.write("if (\(isSomeVar)) {") + printer.indent { + // Special handling for associated value enum - in struct fields, case ID is pushed to tmpRetInts + if case .associatedValueEnum(let enumName) = wrappedType { + let base = enumName.components(separatedBy: ".").last ?? enumName + let caseIdVar = scope.variable("enumCaseId") + printer.write("const \(caseIdVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + printer.write( + "\(optVar) = enumHelpers.\(base).raise(\(caseIdVar), \(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s));" + ) + } else { + let wrappedFragment = structFieldRaiseFragment( + field: ExportedProperty( + name: field.name, + type: wrappedType, + isReadonly: true, + isStatic: false + ), + allStructs: allStructs + ) + let wrappedResults = wrappedFragment.printCode([], scope, printer, cleanup) + if let wrappedResult = wrappedResults.first { + printer.write("\(optVar) = \(wrappedResult);") + } else { + printer.write("\(optVar) = undefined;") + } + } + } + printer.write("} else {") + printer.indent { + printer.write("\(optVar) = null;") + } + printer.write("}") + return [optVar] + } + ) + case .swiftStruct(let nestedName): + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let structVar = scope.variable("struct") + printer.write( + "const \(structVar) = structHelpers.\(nestedName).raise(\(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s), \(JSGlueVariableScope.reservedTmpRetPointers));" + ) + return [structVar] + } + ) + case .caseEnum: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let varName = scope.variable("value") + printer.write("const \(varName) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + return [varName] + } + ) + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let varName = scope.variable("value") + printer.write("const \(varName) = \(JSGlueVariableScope.reservedTmpRetStrings).pop();") + return [varName] + } + ) + default: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let varName = scope.variable("value") + printer.write("const \(varName) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + return [varName] + } + ) + } + case .swiftHeapObject(let className): + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let ptrVar = scope.variable("ptr") + let varName = scope.variable("value") + printer.write("const \(ptrVar) = \(JSGlueVariableScope.reservedTmpRetPointers).pop();") + printer.write("const \(varName) = _exports['\(className)'].__construct(\(ptrVar));") + return [varName] + } + ) + case .jsObject: + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let objectIdVar = scope.variable("objectId") + let varName = scope.variable("value") + printer.write("const \(objectIdVar) = \(JSGlueVariableScope.reservedTmpRetInts).pop();") + printer.write("let \(varName);") + printer.write("if (\(objectIdVar) !== 0) {") + printer.indent { + printer.write( + "\(varName) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(objectIdVar));" + ) + printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(objectIdVar));") + } + printer.write("} else {") + printer.indent { + printer.write("\(varName) = null;") + } + printer.write("}") + return [varName] + } + ) + case .associatedValueEnum(let fullName): + let base = fullName.components(separatedBy: ".").last ?? fullName + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + let varName = scope.variable("value") + printer.write( + "const \(varName) = enumHelpers.\(base).raise(\(JSGlueVariableScope.reservedTmpRetTag), \(JSGlueVariableScope.reservedTmpRetStrings), \(JSGlueVariableScope.reservedTmpRetInts), \(JSGlueVariableScope.reservedTmpRetF32s), \(JSGlueVariableScope.reservedTmpRetF64s));" + ) + return [varName] + } + ) + case .void, .swiftProtocol, .namespaceEnum, .closure: + // These types should not appear as struct fields + return IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanup in + printer.write("throw new Error(\"Unsupported struct field type: \(field.type)\");") + return [] + } + ) + } + } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 12970b50..e80d1c82 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -25,7 +25,7 @@ public struct ABINameGenerator { let contextPart: String? if let staticContext = staticContext { switch staticContext { - case .className(let name), .enumName(let name): + case .className(let name), .enumName(let name), .structName(let name): contextPart = name case .namespaceEnum: contextPart = namespacePart @@ -115,6 +115,7 @@ public enum BridgeType: Codable, Equatable, Hashable, Sendable { case associatedValueEnum(String) case namespaceEnum(String) case swiftProtocol(String) + case swiftStruct(String) indirect case closure(ClosureSignature) } @@ -205,10 +206,51 @@ public struct Effects: Codable, Equatable, Sendable { public enum StaticContext: Codable, Equatable, Sendable { case className(String) + case structName(String) case enumName(String) case namespaceEnum } +// 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 + public let explicitAccessControl: String? + public var properties: [ExportedProperty] + public var constructor: ExportedConstructor? + public var methods: [ExportedFunction] + public let namespace: [String]? + + public init( + name: String, + swiftCallName: String, + explicitAccessControl: String?, + properties: [ExportedProperty] = [], + constructor: ExportedConstructor? = nil, + methods: [ExportedFunction] = [], + namespace: [String]? + ) { + self.name = name + self.swiftCallName = swiftCallName + self.explicitAccessControl = explicitAccessControl + self.properties = properties + self.constructor = constructor + self.methods = methods + self.namespace = namespace + } +} + // MARK: - Enum Skeleton public struct AssociatedValue: Codable, Equatable, Sendable { @@ -416,7 +458,7 @@ public struct ExportedClass: Codable { } } -public struct ExportedConstructor: Codable { +public struct ExportedConstructor: Codable, Equatable, Sendable { public var abiName: String public var parameters: [Parameter] public var effects: Effects @@ -457,7 +499,7 @@ public struct ExportedProperty: Codable, Equatable, Sendable { public func callName(prefix: String? = nil) -> String { if let staticContext = staticContext { switch staticContext { - case .className(let baseName), .enumName(let baseName): + case .className(let baseName), .enumName(let baseName), .structName(let baseName): return "\(baseName).\(name)" case .namespaceEnum: if let namespace = namespace, !namespace.isEmpty { @@ -498,6 +540,7 @@ public struct ExportedSkeleton: Codable { public let functions: [ExportedFunction] public let classes: [ExportedClass] public let enums: [ExportedEnum] + public let structs: [ExportedStruct] public let protocols: [ExportedProtocol] /// Whether to expose exported APIs to the global namespace. /// @@ -511,6 +554,7 @@ public struct ExportedSkeleton: Codable { functions: [ExportedFunction], classes: [ExportedClass], enums: [ExportedEnum], + structs: [ExportedStruct] = [], protocols: [ExportedProtocol] = [], exposeToGlobal: Bool ) { @@ -518,6 +562,7 @@ public struct ExportedSkeleton: Codable { self.functions = functions self.classes = classes self.enums = enums + self.structs = structs self.protocols = protocols self.exposeToGlobal = exposeToGlobal } @@ -624,6 +669,9 @@ extension BridgeType { 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 @@ -660,6 +708,8 @@ extension BridgeType { return "\(name.count)\(name)O" case .swiftProtocol(let name): return "\(name.count)\(name)P" + case .swiftStruct(let name): + return "\(name.count)\(name)V" case .closure(let signature): let params = signature.parameters.isEmpty diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftStruct.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftStruct.swift new file mode 100644 index 00000000..3415f54a --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftStruct.swift @@ -0,0 +1,44 @@ +@JS struct DataPoint { + let x: Double + let y: Double + var label: String + var optCount: Int? + var optFlag: Bool? + + @JS init(x: Double, y: Double, label: String, optCount: Int?, optFlag: Bool?) +} + +@JS struct Address { + var street: String + var city: String + var zipCode: Int? +} + +@JS struct Person { + var name: String + var age: Int + var address: Address + var email: String? +} + +@JS class Greeter { + @JS var name: String + + @JS init(name: String) + @JS func greet() -> String +} + +@JS struct Session { + var id: Int + var owner: Greeter +} + +@JS func roundtrip(_ session: Person) -> Person + +@JS struct ConfigStruct { + @JS static let maxRetries: Int = 3 + @JS nonisolated(unsafe) static var defaultConfig: String = "production" + @JS nonisolated(unsafe) static var timeout: Double = 30.0 + @JS static var computedSetting: String { "Config: \(defaultConfig)" } + @JS static func update(_ timeout: Double) -> Double +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js index 22709366..e53708aa 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js index 372de6ec..c9c2e484 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js index 873c9d69..c98f1971 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.js index fa7a7c06..ba9eac2f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.Export.js @@ -32,6 +32,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -97,6 +99,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js index e484b72c..f0fbf6f7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.Export.js @@ -501,6 +501,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -567,6 +569,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js index 526e2fc9..4a0ebe84 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js @@ -50,6 +50,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -115,6 +117,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js index 9fed226d..96127eba 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js @@ -51,6 +51,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -116,6 +118,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.Export.js index a38b5093..c766f08e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.Export.js @@ -70,6 +70,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -135,6 +137,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js index 9daa221b..2331d1c7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js @@ -101,6 +101,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -166,6 +168,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js index 6e546119..1bfd0944 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js index 9336edc6..c8b24272 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.Export.js index 453057fa..737a4004 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.Export.js index 0ff8f450..ccf941de 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.Export.js index e422b08b..1e1e9563 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js index 380f1a72..df998b01 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index 24022a43..da556661 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.Export.js index e2f13dd0..c051fcd2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.Export.js index f9c60e06..015d5a1c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js index c8bd621d..5e7ae209 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index 32f5e36f..512b48ac 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index 605a13ea..f2e1bbe0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index 62122a83..4b486f48 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js index e37af60c..4bfa5f69 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js index 58f32ea9..efb6b11e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.Export.js @@ -90,6 +90,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -156,6 +158,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Export.js index 8522168f..782a3860 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Export.js @@ -77,6 +77,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -143,6 +145,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.Export.js index 3e445992..f0ce1032 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.Export.js @@ -77,6 +77,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -143,6 +145,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Export.js index 5d70c39b..d1039d0f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Export.js @@ -31,6 +31,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -96,6 +98,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.Export.js index 09a2b0de..086a2ca3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.Export.js @@ -31,6 +31,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -96,6 +98,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js index a668639d..2f182b63 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js index e13404bb..21caebd0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js index f31ef140..71256344 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js index ae86fd8f..f1ab7907 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index e946b01c..2def00d1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.Export.js index 755d98fd..74b2cfe4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.Export.js @@ -128,6 +128,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; const enumHelpers = {}; let _exports = null; @@ -194,6 +196,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.d.ts new file mode 100644 index 00000000..1d147451 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.d.ts @@ -0,0 +1,66 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export interface DataPoint { + x: number; + y: number; + label: string; + optCount: number | null; + optFlag: boolean | null; +} +export interface Address { + street: string; + city: string; + zipCode: number | null; +} +export interface Person { + name: string; + age: number; + address: Address; + email: string | null; +} +export interface Session { + id: number; + owner: Greeter; +} +export interface ConfigStruct { +} +/// Represents a Swift heap object like a class instance or an actor instance. +export interface SwiftHeapObject { + /// Release the heap object. + /// + /// Note: Calling this method will release the heap object and it will no longer be accessible. + release(): void; +} +export interface Greeter extends SwiftHeapObject { + greet(): string; + name: string; +} +export type Exports = { + Greeter: { + new(name: string): Greeter; + } + roundtrip(session: Person): Person; + DataPoint: { + init(x: number, y: number, label: string, optCount: number | null, optFlag: boolean | null): DataPoint; + } + ConfigStruct: { + readonly maxRetries: number; + defaultConfig: string; + timeout: number; + readonly computedSetting: string; + update(timeout: number): number; + } +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.js new file mode 100644 index 00000000..fe359bcb --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.Export.js @@ -0,0 +1,499 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + let tmpRetOptionalBool; + let tmpRetOptionalInt; + let tmpRetOptionalFloat; + let tmpRetOptionalDouble; + let tmpRetOptionalHeapObject; + let tmpRetTag; + let tmpRetStrings = []; + let tmpRetInts = []; + let tmpRetF32s = []; + let tmpRetF64s = []; + let tmpParamInts = []; + let tmpParamF32s = []; + let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; + const structHelpers = {}; + + let _exports = null; + let bjs = null; + const __bjs_createDataPointHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + tmpParamF64s.push(value.x); + tmpParamF64s.push(value.y); + const bytes = textEncoder.encode(value.label); + const id = swift.memory.retain(bytes); + tmpParamInts.push(bytes.length); + tmpParamInts.push(id); + const isSome = value.optCount != null; + if (isSome) { + tmpParamInts.push(value.optCount | 0); + } else { + tmpParamInts.push(0); + } + tmpParamInts.push(isSome ? 1 : 0); + const isSome1 = value.optFlag != null; + if (isSome1) { + tmpParamInts.push(value.optFlag ? 1 : 0); + } else { + tmpParamInts.push(0); + } + tmpParamInts.push(isSome1 ? 1 : 0); + const cleanup = () => { + swift.memory.release(id); + }; + return { cleanup }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const isSome = tmpRetInts.pop(); + let optional; + if (isSome) { + const bool = tmpRetInts.pop() !== 0; + optional = bool; + } else { + optional = null; + } + const isSome1 = tmpRetInts.pop(); + let optional1; + if (isSome1) { + const int = tmpRetInts.pop(); + optional1 = int; + } else { + optional1 = null; + } + const string = tmpRetStrings.pop(); + const f64 = tmpRetF64s.pop(); + const f641 = tmpRetF64s.pop(); + return { x: f641, y: f64, label: string, optCount: optional1, optFlag: optional }; + } + }); + }; + const __bjs_createAddressHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + const bytes = textEncoder.encode(value.street); + const id = swift.memory.retain(bytes); + tmpParamInts.push(bytes.length); + tmpParamInts.push(id); + const bytes1 = textEncoder.encode(value.city); + const id1 = swift.memory.retain(bytes1); + tmpParamInts.push(bytes1.length); + tmpParamInts.push(id1); + const isSome = value.zipCode != null; + if (isSome) { + tmpParamInts.push(value.zipCode | 0); + } else { + tmpParamInts.push(0); + } + tmpParamInts.push(isSome ? 1 : 0); + const cleanup = () => { + swift.memory.release(id); + swift.memory.release(id1); + }; + return { cleanup }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const isSome = tmpRetInts.pop(); + let optional; + if (isSome) { + const int = tmpRetInts.pop(); + optional = int; + } else { + optional = null; + } + const string = tmpRetStrings.pop(); + const string1 = tmpRetStrings.pop(); + return { street: string1, city: string, zipCode: optional }; + } + }); + }; + const __bjs_createPersonHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + const bytes = textEncoder.encode(value.name); + const id = swift.memory.retain(bytes); + tmpParamInts.push(bytes.length); + tmpParamInts.push(id); + tmpParamInts.push((value.age | 0)); + const structResult = structHelpers.Address.lower(value.address); + const isSome = value.email != null; + let id1; + if (isSome) { + const bytes1 = textEncoder.encode(value.email); + id1 = swift.memory.retain(bytes1); + tmpParamInts.push(bytes1.length); + tmpParamInts.push(id1); + } else { + tmpParamInts.push(0); + tmpParamInts.push(0); + } + tmpParamInts.push(isSome ? 1 : 0); + const cleanup = () => { + swift.memory.release(id); + if (structResult.cleanup) { structResult.cleanup(); } + if(id1 !== undefined) { swift.memory.release(id1); } + }; + return { cleanup }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const isSome = tmpRetInts.pop(); + let optional; + if (isSome) { + const string = tmpRetStrings.pop(); + optional = string; + } else { + optional = null; + } + const struct = structHelpers.Address.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + const int = tmpRetInts.pop(); + const string1 = tmpRetStrings.pop(); + return { name: string1, age: int, address: struct, email: optional }; + } + }); + }; + const __bjs_createSessionHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + tmpParamInts.push((value.id | 0)); + tmpParamPointers.push(value.owner.pointer); + return { cleanup: undefined }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const ptr = tmpRetPointers.pop(); + const value = _exports['Greeter'].__construct(ptr); + const int = tmpRetInts.pop(); + return { id: int, owner: value }; + } + }); + }; + const __bjs_createConfigStructHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + return { cleanup: undefined }; + }, + raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + return { }; + } + }); + }; + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + bjs["swift_js_push_tag"] = function(tag) { + tmpRetTag = tag; + } + bjs["swift_js_push_int"] = function(v) { + tmpRetInts.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + tmpRetF32s.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + tmpRetF64s.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + const value = textDecoder.decode(bytes); + tmpRetStrings.push(value); + } + bjs["swift_js_pop_param_int32"] = function() { + return tmpParamInts.pop(); + } + bjs["swift_js_pop_param_f32"] = function() { + return tmpParamF32s.pop(); + } + bjs["swift_js_pop_param_f64"] = function() { + return tmpParamF64s.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } + bjs["swift_js_return_optional_bool"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalBool = null; + } else { + tmpRetOptionalBool = value !== 0; + } + } + bjs["swift_js_return_optional_int"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalInt = null; + } else { + tmpRetOptionalInt = value | 0; + } + } + bjs["swift_js_return_optional_float"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalFloat = null; + } else { + tmpRetOptionalFloat = Math.fround(value); + } + } + bjs["swift_js_return_optional_double"] = function(isSome, value) { + if (isSome === 0) { + tmpRetOptionalDouble = null; + } else { + tmpRetOptionalDouble = value; + } + } + bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + if (isSome === 0) { + tmpRetString = null; + } else { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + } + bjs["swift_js_return_optional_object"] = function(isSome, objectId) { + if (isSome === 0) { + tmpRetString = null; + } else { + tmpRetString = swift.memory.getObject(objectId); + } + } + bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) { + if (isSome === 0) { + tmpRetOptionalHeapObject = null; + } else { + tmpRetOptionalHeapObject = pointer; + } + } + bjs["swift_js_get_optional_int_presence"] = function() { + return tmpRetOptionalInt != null ? 1 : 0; + } + bjs["swift_js_get_optional_int_value"] = function() { + const value = tmpRetOptionalInt; + tmpRetOptionalInt = undefined; + return value; + } + bjs["swift_js_get_optional_string"] = function() { + const str = tmpRetString; + tmpRetString = undefined; + if (str == null) { + return -1; + } else { + const bytes = textEncoder.encode(str); + tmpRetBytes = bytes; + return bytes.length; + } + } + bjs["swift_js_get_optional_float_presence"] = function() { + return tmpRetOptionalFloat != null ? 1 : 0; + } + bjs["swift_js_get_optional_float_value"] = function() { + const value = tmpRetOptionalFloat; + tmpRetOptionalFloat = undefined; + return value; + } + bjs["swift_js_get_optional_double_presence"] = function() { + return tmpRetOptionalDouble != null ? 1 : 0; + } + bjs["swift_js_get_optional_double_value"] = function() { + const value = tmpRetOptionalDouble; + tmpRetOptionalDouble = undefined; + return value; + } + bjs["swift_js_get_optional_heap_object_pointer"] = function() { + const pointer = tmpRetOptionalHeapObject; + tmpRetOptionalHeapObject = undefined; + return pointer || 0; + } + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_Greeter_wrap"] = function(pointer) { + const obj = Greeter.__construct(pointer); + return swift.memory.retain(obj); + }; + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { + deinit(pointer); + }); + obj.registry.register(this, obj.pointer); + return obj; + } + + release() { + this.registry.unregister(this); + this.deinit(this.pointer); + } + } + class Greeter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); + } + + constructor(name) { + const nameBytes = textEncoder.encode(name); + const nameId = swift.memory.retain(nameBytes); + const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); + swift.memory.release(nameId); + return Greeter.__construct(ret); + } + greet() { + instance.exports.bjs_Greeter_greet(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + get name() { + instance.exports.bjs_Greeter_name_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set name(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_Greeter_name_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } + } + const DataPointHelpers = __bjs_createDataPointHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.DataPoint = DataPointHelpers; + + const AddressHelpers = __bjs_createAddressHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.Address = AddressHelpers; + + const PersonHelpers = __bjs_createPersonHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.Person = PersonHelpers; + + const SessionHelpers = __bjs_createSessionHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.Session = SessionHelpers; + + const ConfigStructHelpers = __bjs_createConfigStructHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.ConfigStruct = ConfigStructHelpers; + + const exports = { + Greeter, + roundtrip: function bjs_roundtrip(session) { + const { cleanup: cleanup } = structHelpers.Person.lower(session); + instance.exports.bjs_roundtrip(); + const structValue = structHelpers.Person.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + if (cleanup) { cleanup(); } + return structValue; + }, + DataPoint: { + init: function(x, y, label, optCount, optFlag) { + const labelBytes = textEncoder.encode(label); + 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); + const structValue = structHelpers.DataPoint.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + swift.memory.release(labelId); + return structValue; + }, + }, + ConfigStruct: { + get maxRetries() { + const ret = instance.exports.bjs_ConfigStruct_static_maxRetries_get(); + return ret; + }, + get defaultConfig() { + instance.exports.bjs_ConfigStruct_static_defaultConfig_get(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + set defaultConfig(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_ConfigStruct_static_defaultConfig_set(valueId, valueBytes.length); + swift.memory.release(valueId); + }, + get timeout() { + const ret = instance.exports.bjs_ConfigStruct_static_timeout_get(); + return ret; + }, + set timeout(value) { + instance.exports.bjs_ConfigStruct_static_timeout_set(value); + }, + get computedSetting() { + instance.exports.bjs_ConfigStruct_static_computedSetting_get(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + update: function(timeout) { + const ret = instance.exports.bjs_ConfigStruct_static_update(timeout); + return ret; + }, + }, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js index 4c3cbc21..b0684f15 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js index 059c9315..928ba595 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js index 320609e1..c2f47724 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index 783884ef..71511ca3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js index b6d7a4ea..b39042bd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js index 627dd140..8f832805 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js @@ -26,6 +26,8 @@ export async function createInstantiator(options, swift) { let tmpParamInts = []; let tmpParamF32s = []; let tmpParamF64s = []; + let tmpRetPointers = []; + let tmpParamPointers = []; let _exports = null; let bjs = null; @@ -91,6 +93,12 @@ export async function createInstantiator(options, swift) { bjs["swift_js_pop_param_f64"] = function() { return tmpParamF64s.pop(); } + bjs["swift_js_push_pointer"] = function(pointer) { + tmpRetPointers.push(pointer); + } + bjs["swift_js_pop_param_pointer"] = function() { + return tmpParamPointers.pop(); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json index 2e295991..4bb84558 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json @@ -178,5 +178,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.json index ccb4cb28..66b4dcc4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/DefaultParameters.json @@ -647,5 +647,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json index fb7271b9..f7e57aab 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumAssociatedValue.json @@ -838,5 +838,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json index 9effaaec..1f4d5504 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json @@ -314,5 +314,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.Global.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.Global.json index 8aeb6c78..98ded1fe 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.Global.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.Global.json @@ -409,5 +409,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json index 94d1bb06..24afb4eb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json @@ -409,5 +409,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json index 4bf1644c..b7ea9cd1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json @@ -1492,5 +1492,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedGlobal.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedGlobal.json index 21e78b12..995c3efc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedGlobal.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedGlobal.json @@ -70,5 +70,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedPrivate.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedPrivate.json index d86712af..c986a903 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedPrivate.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/MixedPrivate.json @@ -70,5 +70,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.Global.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.Global.json index bde86c44..a0b1efea 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.Global.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.Global.json @@ -176,5 +176,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json index 910fcfc2..2420d592 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json @@ -176,5 +176,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Optionals.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Optionals.json index eb92b21d..64aaa295 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Optionals.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Optionals.json @@ -693,5 +693,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json index ccd504f4..d9edf11c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json @@ -63,5 +63,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json index 6210c866..79ce75da 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json @@ -79,5 +79,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json index 14f0cc0f..aa5fcc86 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json @@ -354,5 +354,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Protocol.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Protocol.json index 487533b3..c3107394 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Protocol.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Protocol.json @@ -798,5 +798,8 @@ } ] } + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.Global.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.Global.json index 8a2923a0..4e8d80d5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.Global.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.Global.json @@ -330,5 +330,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.json index 13f707bd..ab399f10 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticFunctions.json @@ -330,5 +330,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.Global.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.Global.json index d7af9918..472406ba 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.Global.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.Global.json @@ -333,5 +333,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.json index fb3a12b6..6ef93798 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StaticProperties.json @@ -333,5 +333,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json index bec03d8f..b7015123 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json @@ -61,5 +61,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json index 86221037..39ec8b7a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json @@ -28,5 +28,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json index 9f1f934e..df4975e1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json @@ -136,5 +136,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClosure.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClosure.json index 2a8a6404..20015ba2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClosure.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClosure.json @@ -1069,5 +1069,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.json new file mode 100644 index 00000000..498231bf --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.json @@ -0,0 +1,448 @@ +{ + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_Greeter_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "name", + "name" : "name", + "type" : { + "string" : { + + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_Greeter_greet", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "greet", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "Greeter", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "Greeter" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_roundtrip", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtrip", + "parameters" : [ + { + "label" : "_", + "name" : "session", + "type" : { + "swiftStruct" : { + "_0" : "Person" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Person" + } + } + } + ], + "moduleName" : "TestModule", + "protocols" : [ + + ], + "structs" : [ + { + "constructor" : { + "abiName" : "bjs_DataPoint_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "x", + "name" : "x", + "type" : { + "double" : { + + } + } + }, + { + "label" : "y", + "name" : "y", + "type" : { + "double" : { + + } + } + }, + { + "label" : "label", + "name" : "label", + "type" : { + "string" : { + + } + } + }, + { + "label" : "optCount", + "name" : "optCount", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "label" : "optFlag", + "name" : "optFlag", + "type" : { + "optional" : { + "_0" : { + "bool" : { + + } + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "DataPoint", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "x", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "y", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "label", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optCount", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optFlag", + "type" : { + "optional" : { + "_0" : { + "bool" : { + + } + } + } + } + } + ], + "swiftCallName" : "DataPoint" + }, + { + "methods" : [ + + ], + "name" : "Address", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "street", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "city", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "zipCode", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + } + ], + "swiftCallName" : "Address" + }, + { + "methods" : [ + + ], + "name" : "Person", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "age", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "address", + "type" : { + "swiftStruct" : { + "_0" : "Address" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "email", + "type" : { + "optional" : { + "_0" : { + "string" : { + + } + } + } + } + } + ], + "swiftCallName" : "Person" + }, + { + "methods" : [ + + ], + "name" : "Session", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "owner", + "type" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + ], + "swiftCallName" : "Session" + }, + { + "methods" : [ + { + "abiName" : "bjs_ConfigStruct_static_update", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "update", + "parameters" : [ + { + "label" : "_", + "name" : "timeout", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + }, + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + } + } + ], + "name" : "ConfigStruct", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : true, + "name" : "maxRetries", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : true, + "name" : "defaultConfig", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : true, + "name" : "timeout", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : true, + "name" : "computedSetting", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "ConfigStruct" + } + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.swift new file mode 100644 index 00000000..57635c62 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.swift @@ -0,0 +1,273 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +extension DataPoint: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> DataPoint { + let optFlag = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let optCount = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let label = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let y = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + let x = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + return DataPoint(x: x, y: y, label: label, optCount: optCount, optFlag: optFlag) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_f64(self.x) + _swift_js_push_f64(self.y) + var __bjs_label = self.label + __bjs_label.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_optCount = self.optCount != nil + if let __bjs_unwrapped_optCount = self.optCount { + _swift_js_push_int(Int32(__bjs_unwrapped_optCount)) + } + _swift_js_push_int(__bjs_isSome_optCount ? 1 : 0) + let __bjs_isSome_optFlag = self.optFlag != nil + if let __bjs_unwrapped_optFlag = self.optFlag { + _swift_js_push_int(__bjs_unwrapped_optFlag ? 1 : 0) + } + _swift_js_push_int(__bjs_isSome_optFlag ? 1 : 0) + } +} + +@_expose(wasm, "bjs_DataPoint_init") +@_cdecl("bjs_DataPoint_init") +public func _bjs_DataPoint_init(x: Float64, y: Float64, labelBytes: Int32, labelLength: Int32, optCountIsSome: Int32, optCountValue: Int32, optFlagIsSome: Int32, optFlagValue: Int32) -> Void { + #if arch(wasm32) + let ret = DataPoint(x: Double.bridgeJSLiftParameter(x), y: Double.bridgeJSLiftParameter(y), label: String.bridgeJSLiftParameter(labelBytes, labelLength), optCount: Optional.bridgeJSLiftParameter(optCountIsSome, optCountValue), optFlag: Optional.bridgeJSLiftParameter(optFlagIsSome, optFlagValue)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension Address: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Address { + let zipCode = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let city = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let street = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Address(street: street, city: city, zipCode: zipCode) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_street = self.street + __bjs_street.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + var __bjs_city = self.city + __bjs_city.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_zipCode = self.zipCode != nil + if let __bjs_unwrapped_zipCode = self.zipCode { + _swift_js_push_int(Int32(__bjs_unwrapped_zipCode)) + } + _swift_js_push_int(__bjs_isSome_zipCode ? 1 : 0) + } +} + +extension Person: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Person { + let email = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let address = Address.bridgeJSLiftParameter() + let age = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Person(name: name, age: age, address: address, email: email) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.age)) + self.address.bridgeJSLowerReturn() + let __bjs_isSome_email = self.email != nil + if let __bjs_unwrapped_email = self.email { + var __bjs_str_email = __bjs_unwrapped_email + __bjs_str_email.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + } + _swift_js_push_int(__bjs_isSome_email ? 1 : 0) + } +} + +extension Session: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Session { + let owner = Greeter.bridgeJSLiftParameter(_swift_js_pop_param_pointer()) + let id = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + return Session(id: id, owner: owner) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_int(Int32(self.id)) + _swift_js_push_pointer(self.owner.bridgeJSLowerReturn()) + } +} + +extension ConfigStruct: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> ConfigStruct { + return ConfigStruct() + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + + } +} + +@_expose(wasm, "bjs_ConfigStruct_static_maxRetries_get") +@_cdecl("bjs_ConfigStruct_static_maxRetries_get") +public func _bjs_ConfigStruct_static_maxRetries_get() -> Int32 { + #if arch(wasm32) + let ret = ConfigStruct.maxRetries + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_defaultConfig_get") +@_cdecl("bjs_ConfigStruct_static_defaultConfig_get") +public func _bjs_ConfigStruct_static_defaultConfig_get() -> Void { + #if arch(wasm32) + let ret = ConfigStruct.defaultConfig + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_defaultConfig_set") +@_cdecl("bjs_ConfigStruct_static_defaultConfig_set") +public func _bjs_ConfigStruct_static_defaultConfig_set(valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + ConfigStruct.defaultConfig = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_timeout_get") +@_cdecl("bjs_ConfigStruct_static_timeout_get") +public func _bjs_ConfigStruct_static_timeout_get() -> Float64 { + #if arch(wasm32) + let ret = ConfigStruct.timeout + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_timeout_set") +@_cdecl("bjs_ConfigStruct_static_timeout_set") +public func _bjs_ConfigStruct_static_timeout_set(value: Float64) -> Void { + #if arch(wasm32) + ConfigStruct.timeout = Double.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_computedSetting_get") +@_cdecl("bjs_ConfigStruct_static_computedSetting_get") +public func _bjs_ConfigStruct_static_computedSetting_get() -> Void { + #if arch(wasm32) + let ret = ConfigStruct.computedSetting + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_update") +@_cdecl("bjs_ConfigStruct_static_update") +public func _bjs_ConfigStruct_static_update(timeout: Float64) -> Float64 { + #if arch(wasm32) + let ret = ConfigStruct.update(_: Double.bridgeJSLiftParameter(timeout)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundtrip") +@_cdecl("bjs_roundtrip") +public func _bjs_roundtrip() -> Void { + #if arch(wasm32) + let ret = roundtrip(_: Person.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_init") +@_cdecl("bjs_Greeter_init") +public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_greet") +@_cdecl("bjs_Greeter_greet") +public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Greeter.bridgeJSLiftParameter(_self).greet() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_get") +@_cdecl("bjs_Greeter_name_get") +public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Greeter.bridgeJSLiftParameter(_self).name + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_set") +@_cdecl("bjs_Greeter_name_set") +public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + Greeter.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_deinit") +@_cdecl("bjs_Greeter_deinit") +public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_Greeter_wrap") +fileprivate func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json index 9b2e6104..ef42dc6c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json @@ -28,5 +28,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json index 508107ee..c80650af 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json @@ -28,5 +28,8 @@ "moduleName" : "TestModule", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Sources/JavaScriptKit/BridgeJSInstrincics.swift b/Sources/JavaScriptKit/BridgeJSInstrincics.swift index b0e5ec39..b3262cb9 100644 --- a/Sources/JavaScriptKit/BridgeJSInstrincics.swift +++ b/Sources/JavaScriptKit/BridgeJSInstrincics.swift @@ -316,6 +316,15 @@ public protocol _BridgedSwiftAssociatedValueEnum: _BridgedSwiftTypeLoweredIntoVo @_spi(BridgeJS) consuming func bridgeJSLowerReturn() -> Void } +/// A protocol that Swift struct types conform to. +/// +/// The conformance is automatically synthesized by the BridgeJS code generator. +public protocol _BridgedSwiftStruct: _BridgedSwiftTypeLoweredIntoVoidType { + // MARK: ExportSwift + @_spi(BridgeJS) static func bridgeJSLiftParameter() -> Self + @_spi(BridgeJS) consuming func bridgeJSLowerReturn() -> Void +} + extension _BridgedSwiftEnumNoPayload where Self: RawRepresentable, RawValue == String { // MARK: ImportTS @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> Int32 { rawValue.bridgeJSLowerParameter() } @@ -618,6 +627,24 @@ func _swift_js_return_optional_double(_ isSome: Int32, _ value: Float64) { } #endif +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_push_pointer") +@_spi(BridgeJS) public func _swift_js_push_pointer(_ pointer: UnsafeMutableRawPointer) +#else +@_spi(BridgeJS) public func _swift_js_push_pointer(_ pointer: UnsafeMutableRawPointer) { + _onlyAvailableOnWasm() +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_pop_param_pointer") +@_spi(BridgeJS) public func _swift_js_pop_param_pointer() -> UnsafeMutableRawPointer +#else +@_spi(BridgeJS) public func _swift_js_pop_param_pointer() -> UnsafeMutableRawPointer { + _onlyAvailableOnWasm() +} +#endif + extension Optional where Wrapped == Bool { // MARK: ImportTS @@ -1225,3 +1252,27 @@ extension Optional where Wrapped: _BridgedSwiftAssociatedValueEnum { } } } + +// MARK: Optional Struct Support + +extension Optional where Wrapped: _BridgedSwiftStruct { + // MARK: ExportSwift + + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ isSome: Int32) -> Wrapped? { + if isSome == 0 { + return nil + } else { + return Wrapped.bridgeJSLiftParameter() + } + } + + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void { + switch consume self { + case .none: + _swift_js_push_int(0) // Push only isSome=0 (no struct fields) + case .some(let value): + value.bridgeJSLowerReturn() // Push all struct fields FIRST + _swift_js_push_int(1) // Then push isSome=1 LAST (so it's popped FIRST by JS) + } + } +} diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md index 44019bf6..c7053305 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md @@ -65,6 +65,7 @@ This command will: - - +- - - - diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md new file mode 100644 index 00000000..e20a324c --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md @@ -0,0 +1,180 @@ +# Exporting Swift Structs to JS + +Learn how to export Swift structs to JavaScript. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +To export a Swift struct, mark it with `@JS`: + +```swift +import JavaScriptKit + +@JS struct Point { + var x: Double + var y: Double + var label: String +} + +@JS struct Address { + var street: String + var city: String + var zipCode: Int? +} + +@JS struct Person { + var name: String + var age: Int + var address: Address + var email: String? +} + +@JS func createPerson(name: String, age: Int, street: String, city: String) -> Person +@JS func updateEmail(person: Person, email: String?) -> Person +``` + +In JavaScript: + +```javascript +import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; +const { exports } = await init({}); + +const person = exports.createPerson("Alice", 30, "123 Main St", "NYC"); +console.log(person.name); // "Alice" +console.log(person.address.street); // "123 Main St" +console.log(person.email); // null + +const updated = exports.updateEmail(person, "alice@example.com"); +console.log(updated.email); // "alice@example.com" +``` + +The generated TypeScript declarations: + +```typescript +export interface Point { + x: number; + y: number; + label: string; +} + +export interface Address { + street: string; + city: string; + zipCode: number | null; +} + +export interface Person { + name: string; + age: number; + address: Address; + email: string | null; +} + +export type Exports = { + createPerson(name: string, age: number, street: string, city: string): Person; + updateEmail(person: Person, email: string | null): Person; +} +``` + +## Instance Fields vs Static Properties + +**Instance fields** (part of the struct value) are automatically exported - no `@JS` needed: +```swift +@JS struct Point { + var x: Double // Auto-exported + var y: Double // Auto-exported +} +``` + +**Static properties** (not part of instance) require `@JS`: +```swift +@JS struct Config { + var name: String + + @JS nonisolated(unsafe) static var defaultTimeout: Double = 30.0 + @JS static let maxRetries: Int = 3 +} +``` + +In JavaScript: +```javascript +console.log(exports.Config.defaultTimeout); // 30.0 +exports.Config.defaultTimeout = 60.0; +console.log(exports.Config.maxRetries); // 3 (readonly) +``` + +## Struct Methods + +Structs can have instance and static methods, both require @JS annotation: + +```swift +@JS struct Calculator { + @JS func add(a: Double, b: Double) -> Double { + return a + b + } + + @JS static func multiply(x: Double, y: Double) -> Double { + return x * y + } +} +``` + +In JavaScript: + +```javascript +const calc = {}; +console.log(exports.useMathOperations(calc, 5.0, 3.0)); // Uses instance methods +console.log(exports.Calculator.multiply(4.0, 5.0)); // Static method +``` + +## Struct Initializers + +Struct initializers are exported as static `init` methods, not constructors: + +```swift +@JS struct Point { + var x: Double + var y: Double + + @JS init(x: Double, y: Double) { + self.x = x + self.y = y + } +} +``` + +In JavaScript: + +```javascript +const point = exports.Point.init(10.0, 20.0); +console.log(point.x); // 10.0 +``` + +This differs from classes, where `@JS init` maps to a JavaScript constructor using `new`: + +```javascript +// Class: uses `new` +const cart = new exports.ShoppingCart(); + +// Struct: uses static `init` method +const point = exports.Point.init(10.0, 20.0); +``` + +## Supported Features + +| Feature | Status | +|:--------|:-------| +| Stored fields with supported types | ✅ | +| Optional fields | ✅ | +| Nested structs | ✅ | +| Instance methods | ✅ | +| Static methods | ✅ | +| Static properties | ✅ | +| Property observers (`willSet`, `didSet`) | ❌ | +| Generics | ❌ | +| Conformances | ❌ | + +## See Also + +- diff --git a/Tests/BridgeJSGlobalTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSGlobalTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 979311c8..955b78f6 100644 --- a/Tests/BridgeJSGlobalTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSGlobalTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -533,5 +533,8 @@ "moduleName" : "BridgeJSGlobalTests", "protocols" : [ + ], + "structs" : [ + ] } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 682e1185..b3aa84c1 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -1232,6 +1232,128 @@ enum APIOptionalResult { } } +// MARK: - Struct Tests + +@JS struct DataPoint { + let x: Double + let y: Double + var label: String + var optCount: Int? + var optFlag: Bool? + + @JS init(x: Double, y: Double, label: String, optCount: Int?, optFlag: Bool?) { + self.x = x + self.y = y + self.label = label + self.optCount = optCount + self.optFlag = optFlag + } +} + +@JS struct Address { + var street: String + var city: String + var zipCode: Int? +} + +@JS struct Contact { + var name: String + var age: Int + var address: Address + var email: String? + var secondaryAddress: Address? +} + +@JS struct Config { + var name: String + var theme: Theme? + var direction: Direction? + var status: Status +} + +@JS struct SessionData { + var id: Int + var owner: Greeter? +} + +@JS struct ValidationReport { + var id: Int + var result: APIResult + var status: Status? + var outcome: APIResult? +} + +@JS struct MathOperations { + @JS init() {} + @JS func add(a: Double, b: Double) -> Double { + return a + b + } + + @JS func multiply(a: Double, b: Double) -> Double { + return a * b + } + + @JS static func subtract(a: Double, b: Double) -> Double { + return a - b + } +} + +@JS struct ConfigStruct { + var name: String + var value: Int + + @JS nonisolated(unsafe) static var defaultConfig: String = "production" + @JS static let maxRetries: Int = 3 + @JS nonisolated(unsafe) static var timeout: Double = 30.0 + + @JS static var computedSetting: String { + return "Config: \(defaultConfig)" + } +} + +@JS func roundTripDataPoint(_ data: DataPoint) -> DataPoint { + return data +} + +@JS func roundTripContact(_ contact: Contact) -> Contact { + return contact +} + +@JS func roundTripConfig(_ config: Config) -> Config { + return config +} + +@JS func roundTripSessionData(_ session: SessionData) -> SessionData { + return session +} + +@JS func roundTripValidationReport(_ report: ValidationReport) -> ValidationReport { + return report +} + +@JS func updateValidationReport(_ newResult: APIResult?, _ report: ValidationReport) -> ValidationReport { + return ValidationReport( + id: report.id, + result: newResult ?? report.result, + status: report.status, + outcome: report.outcome + ) +} + +@JS class Container { + @JS var location: DataPoint + @JS var config: Config? + + @JS init(location: DataPoint, config: Config?) { + self.location = location + self.config = config + } +} + +@JS func testContainerWithStruct(_ point: DataPoint) -> Container { + return Container(location: point, config: nil) +} + class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index bfb10219..dcae4a74 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -2008,6 +2008,312 @@ public func _bjs_StaticPropertyNamespace_NestedProperties_static_nestedDouble_se #endif } +extension DataPoint: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> DataPoint { + let optFlag = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let optCount = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let label = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let y = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + let x = Double.bridgeJSLiftParameter(_swift_js_pop_param_f64()) + return DataPoint(x: x, y: y, label: label, optCount: optCount, optFlag: optFlag) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_f64(self.x) + _swift_js_push_f64(self.y) + var __bjs_label = self.label + __bjs_label.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_optCount = self.optCount != nil + if let __bjs_unwrapped_optCount = self.optCount { + _swift_js_push_int(Int32(__bjs_unwrapped_optCount)) + } + _swift_js_push_int(__bjs_isSome_optCount ? 1 : 0) + let __bjs_isSome_optFlag = self.optFlag != nil + if let __bjs_unwrapped_optFlag = self.optFlag { + _swift_js_push_int(__bjs_unwrapped_optFlag ? 1 : 0) + } + _swift_js_push_int(__bjs_isSome_optFlag ? 1 : 0) + } +} + +@_expose(wasm, "bjs_DataPoint_init") +@_cdecl("bjs_DataPoint_init") +public func _bjs_DataPoint_init(x: Float64, y: Float64, labelBytes: Int32, labelLength: Int32, optCountIsSome: Int32, optCountValue: Int32, optFlagIsSome: Int32, optFlagValue: Int32) -> Void { + #if arch(wasm32) + let ret = DataPoint(x: Double.bridgeJSLiftParameter(x), y: Double.bridgeJSLiftParameter(y), label: String.bridgeJSLiftParameter(labelBytes, labelLength), optCount: Optional.bridgeJSLiftParameter(optCountIsSome, optCountValue), optFlag: Optional.bridgeJSLiftParameter(optFlagIsSome, optFlagValue)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension Address: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Address { + let zipCode = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let city = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let street = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Address(street: street, city: city, zipCode: zipCode) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_street = self.street + __bjs_street.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + var __bjs_city = self.city + __bjs_city.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_zipCode = self.zipCode != nil + if let __bjs_unwrapped_zipCode = self.zipCode { + _swift_js_push_int(Int32(__bjs_unwrapped_zipCode)) + } + _swift_js_push_int(__bjs_isSome_zipCode ? 1 : 0) + } +} + +extension Contact: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Contact { + let secondaryAddress = Optional
.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let email = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let address = Address.bridgeJSLiftParameter() + let age = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Contact(name: name, age: age, address: address, email: email, secondaryAddress: secondaryAddress) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.age)) + self.address.bridgeJSLowerReturn() + let __bjs_isSome_email = self.email != nil + if let __bjs_unwrapped_email = self.email { + var __bjs_str_email = __bjs_unwrapped_email + __bjs_str_email.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + } + _swift_js_push_int(__bjs_isSome_email ? 1 : 0) + let __bjs_isSome_secondaryAddress = self.secondaryAddress != nil + if let __bjs_unwrapped_secondaryAddress = self.secondaryAddress { + __bjs_unwrapped_secondaryAddress.bridgeJSLowerReturn() + } + _swift_js_push_int(__bjs_isSome_secondaryAddress ? 1 : 0) + } +} + +extension Config: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Config { + let status = Status.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let direction = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let theme = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return Config(name: name, theme: theme, direction: direction, status: status) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + let __bjs_isSome_theme = self.theme != nil + if let __bjs_unwrapped_theme = self.theme { + var __bjs_str_theme = __bjs_unwrapped_theme.rawValue + __bjs_str_theme.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + } + _swift_js_push_int(__bjs_isSome_theme ? 1 : 0) + let __bjs_isSome_direction = self.direction != nil + if let __bjs_unwrapped_direction = self.direction { + _swift_js_push_int(__bjs_unwrapped_direction.bridgeJSLowerParameter()) + } + _swift_js_push_int(__bjs_isSome_direction ? 1 : 0) + _swift_js_push_int(Int32(self.status.bridgeJSLowerParameter())) + } +} + +extension SessionData: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> SessionData { + let owner = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_pointer()) + let id = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + return SessionData(id: id, owner: owner) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_int(Int32(self.id)) + let __bjs_isSome_owner = self.owner != nil + if let __bjs_unwrapped_owner = self.owner { + _swift_js_push_pointer(__bjs_unwrapped_owner.bridgeJSLowerReturn()) + } + _swift_js_push_int(__bjs_isSome_owner ? 1 : 0) + } +} + +extension ValidationReport: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> ValidationReport { + let outcome = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let status = Optional.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + let result = APIResult.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let id = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + return ValidationReport(id: id, result: result, status: status, outcome: outcome) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + _swift_js_push_int(Int32(self.id)) + self.result.bridgeJSLowerReturn() + let __bjs_isSome_status = self.status != nil + if let __bjs_unwrapped_status = self.status { + _swift_js_push_int(__bjs_unwrapped_status.bridgeJSLowerParameter()) + } + _swift_js_push_int(__bjs_isSome_status ? 1 : 0) + let __bjs_isSome_outcome = self.outcome != nil + if let __bjs_unwrapped_outcome = self.outcome { + _swift_js_push_int(__bjs_unwrapped_outcome.bridgeJSLowerParameter()) + } + _swift_js_push_int(__bjs_isSome_outcome ? 1 : 0) + } +} + +extension MathOperations: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> MathOperations { + return MathOperations() + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + + } +} + +@_expose(wasm, "bjs_MathOperations_init") +@_cdecl("bjs_MathOperations_init") +public func _bjs_MathOperations_init() -> Void { + #if arch(wasm32) + let ret = MathOperations() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MathOperations_add") +@_cdecl("bjs_MathOperations_add") +public func _bjs_MathOperations_add(a: Float64, b: Float64) -> Float64 { + #if arch(wasm32) + let ret = MathOperations.bridgeJSLiftParameter().add(a: Double.bridgeJSLiftParameter(a), b: Double.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MathOperations_multiply") +@_cdecl("bjs_MathOperations_multiply") +public func _bjs_MathOperations_multiply(a: Float64, b: Float64) -> Float64 { + #if arch(wasm32) + let ret = MathOperations.bridgeJSLiftParameter().multiply(a: Double.bridgeJSLiftParameter(a), b: Double.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_MathOperations_static_subtract") +@_cdecl("bjs_MathOperations_static_subtract") +public func _bjs_MathOperations_static_subtract(a: Float64, b: Float64) -> Float64 { + #if arch(wasm32) + let ret = MathOperations.subtract(a: Double.bridgeJSLiftParameter(a), b: Double.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ConfigStruct: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> ConfigStruct { + let value = Int.bridgeJSLiftParameter(_swift_js_pop_param_int32()) + let name = String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()) + return ConfigStruct(name: name, value: value) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + var __bjs_name = self.name + __bjs_name.withUTF8 { ptr in + _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + } + _swift_js_push_int(Int32(self.value)) + } +} + +@_expose(wasm, "bjs_ConfigStruct_static_defaultConfig_get") +@_cdecl("bjs_ConfigStruct_static_defaultConfig_get") +public func _bjs_ConfigStruct_static_defaultConfig_get() -> Void { + #if arch(wasm32) + let ret = ConfigStruct.defaultConfig + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_defaultConfig_set") +@_cdecl("bjs_ConfigStruct_static_defaultConfig_set") +public func _bjs_ConfigStruct_static_defaultConfig_set(valueBytes: Int32, valueLength: Int32) -> Void { + #if arch(wasm32) + ConfigStruct.defaultConfig = String.bridgeJSLiftParameter(valueBytes, valueLength) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_maxRetries_get") +@_cdecl("bjs_ConfigStruct_static_maxRetries_get") +public func _bjs_ConfigStruct_static_maxRetries_get() -> Int32 { + #if arch(wasm32) + let ret = ConfigStruct.maxRetries + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_timeout_get") +@_cdecl("bjs_ConfigStruct_static_timeout_get") +public func _bjs_ConfigStruct_static_timeout_get() -> Float64 { + #if arch(wasm32) + let ret = ConfigStruct.timeout + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_timeout_set") +@_cdecl("bjs_ConfigStruct_static_timeout_set") +public func _bjs_ConfigStruct_static_timeout_set(value: Float64) -> Void { + #if arch(wasm32) + ConfigStruct.timeout = Double.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ConfigStruct_static_computedSetting_get") +@_cdecl("bjs_ConfigStruct_static_computedSetting_get") +public func _bjs_ConfigStruct_static_computedSetting_get() -> Void { + #if arch(wasm32) + let ret = ConfigStruct.computedSetting + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripVoid") @_cdecl("bjs_roundTripVoid") public func _bjs_roundTripVoid() -> Void { @@ -3311,6 +3617,85 @@ public func _bjs_makeAdder(base: Int32) -> UnsafeMutableRawPointer { #endif } +@_expose(wasm, "bjs_roundTripDataPoint") +@_cdecl("bjs_roundTripDataPoint") +public func _bjs_roundTripDataPoint() -> Void { + #if arch(wasm32) + let ret = roundTripDataPoint(_: DataPoint.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripContact") +@_cdecl("bjs_roundTripContact") +public func _bjs_roundTripContact() -> Void { + #if arch(wasm32) + let ret = roundTripContact(_: Contact.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripConfig") +@_cdecl("bjs_roundTripConfig") +public func _bjs_roundTripConfig() -> Void { + #if arch(wasm32) + let ret = roundTripConfig(_: Config.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripSessionData") +@_cdecl("bjs_roundTripSessionData") +public func _bjs_roundTripSessionData() -> Void { + #if arch(wasm32) + let ret = roundTripSessionData(_: SessionData.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripValidationReport") +@_cdecl("bjs_roundTripValidationReport") +public func _bjs_roundTripValidationReport() -> Void { + #if arch(wasm32) + let ret = roundTripValidationReport(_: ValidationReport.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_updateValidationReport") +@_cdecl("bjs_updateValidationReport") +public func _bjs_updateValidationReport(newResultIsSome: Int32, newResultCaseId: Int32) -> Void { + #if arch(wasm32) + let _tmp_report = ValidationReport.bridgeJSLiftParameter() + let _tmp_newResult = Optional.bridgeJSLiftParameter(newResultIsSome, newResultCaseId) + let ret = updateValidationReport(_: _tmp_newResult, _: _tmp_report) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testContainerWithStruct") +@_cdecl("bjs_testContainerWithStruct") +public func _bjs_testContainerWithStruct() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = testContainerWithStruct(_: DataPoint.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { @@ -5461,4 +5846,80 @@ fileprivate func _bjs_TextProcessor_wrap(_: UnsafeMutableRawPointer) -> Int32 fileprivate func _bjs_TextProcessor_wrap(_: UnsafeMutableRawPointer) -> Int32 { fatalError("Only available on WebAssembly") } +#endif + +@_expose(wasm, "bjs_Container_init") +@_cdecl("bjs_Container_init") +public func _bjs_Container_init(config: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let _tmp_config = Optional.bridgeJSLiftParameter(config) + let _tmp_location = DataPoint.bridgeJSLiftParameter() + let ret = Container(location: _tmp_location, config: _tmp_config) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_location_get") +@_cdecl("bjs_Container_location_get") +public func _bjs_Container_location_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Container.bridgeJSLiftParameter(_self).location + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_location_set") +@_cdecl("bjs_Container_location_set") +public func _bjs_Container_location_set(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Container.bridgeJSLiftParameter(_self).location = DataPoint.bridgeJSLiftParameter() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_config_get") +@_cdecl("bjs_Container_config_get") +public func _bjs_Container_config_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + let ret = Container.bridgeJSLiftParameter(_self).config + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_config_set") +@_cdecl("bjs_Container_config_set") +public func _bjs_Container_config_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Container.bridgeJSLiftParameter(_self).config = Optional.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Container_deinit") +@_cdecl("bjs_Container_deinit") +public func _bjs_Container_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Container: ConvertibleToJSValue, _BridgedSwiftHeapObject { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_Container_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Container_wrap") +fileprivate func _bjs_Container_wrap(_: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_Container_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} #endif \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 50a9236e..751d6519 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -3118,6 +3118,71 @@ ], "swiftCallName" : "TextProcessor" + }, + { + "constructor" : { + "abiName" : "bjs_Container_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "location", + "name" : "location", + "type" : { + "swiftStruct" : { + "_0" : "DataPoint" + } + } + }, + { + "label" : "config", + "name" : "config", + "type" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Config" + } + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "Container", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "location", + "type" : { + "swiftStruct" : { + "_0" : "DataPoint" + } + } + }, + { + "isReadonly" : false, + "isStatic" : false, + "name" : "config", + "type" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Config" + } + } + } + } + } + ], + "swiftCallName" : "Container" } ], "enums" : [ @@ -7578,6 +7643,194 @@ } } } + }, + { + "abiName" : "bjs_roundTripDataPoint", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripDataPoint", + "parameters" : [ + { + "label" : "_", + "name" : "data", + "type" : { + "swiftStruct" : { + "_0" : "DataPoint" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "DataPoint" + } + } + }, + { + "abiName" : "bjs_roundTripContact", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripContact", + "parameters" : [ + { + "label" : "_", + "name" : "contact", + "type" : { + "swiftStruct" : { + "_0" : "Contact" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Contact" + } + } + }, + { + "abiName" : "bjs_roundTripConfig", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripConfig", + "parameters" : [ + { + "label" : "_", + "name" : "config", + "type" : { + "swiftStruct" : { + "_0" : "Config" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Config" + } + } + }, + { + "abiName" : "bjs_roundTripSessionData", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripSessionData", + "parameters" : [ + { + "label" : "_", + "name" : "session", + "type" : { + "swiftStruct" : { + "_0" : "SessionData" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "SessionData" + } + } + }, + { + "abiName" : "bjs_roundTripValidationReport", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripValidationReport", + "parameters" : [ + { + "label" : "_", + "name" : "report", + "type" : { + "swiftStruct" : { + "_0" : "ValidationReport" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "ValidationReport" + } + } + }, + { + "abiName" : "bjs_updateValidationReport", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "updateValidationReport", + "parameters" : [ + { + "label" : "_", + "name" : "newResult", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + } + } + }, + { + "label" : "_", + "name" : "report", + "type" : { + "swiftStruct" : { + "_0" : "ValidationReport" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "ValidationReport" + } + } + }, + { + "abiName" : "bjs_testContainerWithStruct", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "testContainerWithStruct", + "parameters" : [ + { + "label" : "_", + "name" : "point", + "type" : { + "swiftStruct" : { + "_0" : "DataPoint" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Container" + } + } } ], "moduleName" : "BridgeJSRuntimeTests", @@ -7961,5 +8214,611 @@ } ] } + ], + "structs" : [ + { + "constructor" : { + "abiName" : "bjs_DataPoint_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "x", + "name" : "x", + "type" : { + "double" : { + + } + } + }, + { + "label" : "y", + "name" : "y", + "type" : { + "double" : { + + } + } + }, + { + "label" : "label", + "name" : "label", + "type" : { + "string" : { + + } + } + }, + { + "label" : "optCount", + "name" : "optCount", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "label" : "optFlag", + "name" : "optFlag", + "type" : { + "optional" : { + "_0" : { + "bool" : { + + } + } + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "DataPoint", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "x", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "y", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "label", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optCount", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optFlag", + "type" : { + "optional" : { + "_0" : { + "bool" : { + + } + } + } + } + } + ], + "swiftCallName" : "DataPoint" + }, + { + "methods" : [ + + ], + "name" : "Address", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "street", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "city", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "zipCode", + "type" : { + "optional" : { + "_0" : { + "int" : { + + } + } + } + } + } + ], + "swiftCallName" : "Address" + }, + { + "methods" : [ + + ], + "name" : "Contact", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "age", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "address", + "type" : { + "swiftStruct" : { + "_0" : "Address" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "email", + "type" : { + "optional" : { + "_0" : { + "string" : { + + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "secondaryAddress", + "type" : { + "optional" : { + "_0" : { + "swiftStruct" : { + "_0" : "Address" + } + } + } + } + } + ], + "swiftCallName" : "Contact" + }, + { + "methods" : [ + + ], + "name" : "Config", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "theme", + "type" : { + "optional" : { + "_0" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "direction", + "type" : { + "optional" : { + "_0" : { + "caseEnum" : { + "_0" : "Direction" + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "status", + "type" : { + "caseEnum" : { + "_0" : "Status" + } + } + } + ], + "swiftCallName" : "Config" + }, + { + "methods" : [ + + ], + "name" : "SessionData", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "owner", + "type" : { + "optional" : { + "_0" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + } + } + ], + "swiftCallName" : "SessionData" + }, + { + "methods" : [ + + ], + "name" : "ValidationReport", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "result", + "type" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "status", + "type" : { + "optional" : { + "_0" : { + "caseEnum" : { + "_0" : "Status" + } + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "outcome", + "type" : { + "optional" : { + "_0" : { + "associatedValueEnum" : { + "_0" : "APIResult" + } + } + } + } + } + ], + "swiftCallName" : "ValidationReport" + }, + { + "constructor" : { + "abiName" : "bjs_MathOperations_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_MathOperations_add", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "add", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "double" : { + + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_MathOperations_multiply", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "multiply", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "double" : { + + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_MathOperations_static_subtract", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "subtract", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "double" : { + + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + }, + "staticContext" : { + "structName" : { + "_0" : "MathOperations" + } + } + } + ], + "name" : "MathOperations", + "properties" : [ + + ], + "swiftCallName" : "MathOperations" + }, + { + "methods" : [ + + ], + "name" : "ConfigStruct", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "value", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : true, + "name" : "defaultConfig", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : true, + "name" : "maxRetries", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "isStatic" : true, + "name" : "timeout", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : true, + "name" : "computedSetting", + "staticContext" : { + "structName" : { + "_0" : "ConfigStruct" + } + }, + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "ConfigStruct" + } ] } \ No newline at end of file diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 04fa3abd..eaef7ca1 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -710,6 +710,7 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { testProtocolSupport(exports); testClosureSupport(exports); + testStructSupport(exports); } /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ function testClosureSupport(exports) { @@ -905,6 +906,100 @@ function testClosureSupport(exports) { processor.release(); } +/** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ +function testStructSupport(exports) { + const data1 = { x: 1.5, y: 2.5, label: "Point", optCount: 42, optFlag: true }; + assert.deepEqual(exports.roundTripDataPoint(data1), data1); + const data2 = { x: 0.0, y: 0.0, label: "", optCount: null, optFlag: null }; + assert.deepEqual(exports.roundTripDataPoint(data2), data2); + + const contact1 = { + name: "Alice", + age: 30, + address: { street: "123 Main St", city: "NYC", zipCode: 10001 }, + email: "alice@test.com", + secondaryAddress: { street: "456 Oak Ave", city: "LA", zipCode: null } + }; + assert.deepEqual(exports.roundTripContact(contact1), contact1); + const contact2 = { + name: "Bob", + age: 25, + address: { street: "789 Pine Rd", city: "SF", zipCode: null }, + email: null, + secondaryAddress: null + }; + assert.deepEqual(exports.roundTripContact(contact2), contact2); + + const config1 = { + name: "prod", + theme: exports.Theme.Dark, + direction: exports.Direction.North, + status: exports.Status.Success + }; + assert.deepEqual(exports.roundTripConfig(config1), config1); + const config2 = { + name: "dev", + theme: null, + direction: null, + status: exports.Status.Loading + }; + assert.deepEqual(exports.roundTripConfig(config2), config2); + + const owner1 = new exports.Greeter("TestUser"); + const session1 = { id: 123, owner: owner1 }; + const resultSession1 = exports.roundTripSessionData(session1); + assert.equal(resultSession1.id, 123); + assert.equal(resultSession1.owner.greet(), "Hello, TestUser!"); + const session2 = { id: 456, owner: null }; + assert.deepEqual(exports.roundTripSessionData(session2), session2); + owner1.release(); + resultSession1.owner.release(); + + const report1 = { + id: 100, + result: { tag: exports.APIResult.Tag.Success, param0: "ok" }, + status: exports.Status.Success, + outcome: { tag: exports.APIResult.Tag.Info } + }; + assert.deepEqual(exports.roundTripValidationReport(report1), report1); + const report2 = { + id: 200, + result: { tag: exports.APIResult.Tag.Failure, param0: 404 }, + status: null, + outcome: null + }; + assert.deepEqual(exports.roundTripValidationReport(report2), report2); + + const origReport = { + id: 999, + result: { tag: exports.APIResult.Tag.Failure, param0: 500 }, + status: exports.Status.Error, + outcome: { tag: exports.APIResult.Tag.Info } + }; + const updatedReport = exports.updateValidationReport( + { tag: exports.APIResult.Tag.Success, param0: "updated" }, + origReport + ); + assert.deepEqual(updatedReport.result, { tag: exports.APIResult.Tag.Success, param0: "updated" }); + assert.deepEqual(exports.updateValidationReport(null, origReport).result, origReport.result); + + assert.equal(exports.MathOperations.subtract(10.0, 4.0), 6.0); + const mathOps = exports.MathOperations.init(); + assert.equal(mathOps.add(5.0, 3.0), 8.0); + assert.equal(mathOps.multiply(4.0, 7.0), 28.0); + + const container = exports.testContainerWithStruct({ x: 5.0, y: 10.0, label: "test", optCount: null, optFlag: true }); + assert.equal(container.location.x, 5.0); + assert.equal(container.config, null); + container.release(); + + assert.equal(exports.ConfigStruct.defaultConfig, "production"); + assert.equal(exports.ConfigStruct.maxRetries, 3); + assert.equal(exports.ConfigStruct.computedSetting, "Config: production"); + exports.ConfigStruct.defaultConfig = "staging"; + assert.equal(exports.ConfigStruct.computedSetting, "Config: staging"); + exports.ConfigStruct.defaultConfig = "production"; +} /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ async function BridgeJSRuntimeTests_runAsyncWorks(exports) {