diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 85aeee13..33c45564 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -794,8 +794,10 @@ struct StackCodegen { func liftExpression(for type: BridgeType) -> ExprSyntax { switch type { case .string, .int, .uint, .bool, .float, .double, - .jsObject, .swiftStruct, .swiftHeapObject: + .jsObject(nil), .swiftStruct, .swiftHeapObject: return "\(raw: type.swiftType).bridgeJSLiftParameter()" + case .jsObject(let className?): + return "\(raw: className)(unsafelyWrapping: JSObject.bridgeJSLiftParameter())" case .unsafePointer: return "\(raw: type.swiftType).bridgeJSLiftParameter()" case .swiftProtocol(let protocolName): @@ -830,9 +832,11 @@ struct StackCodegen { func liftArrayExpression(elementType: BridgeType) -> ExprSyntax { switch elementType { case .int, .uint, .float, .double, .string, .bool, - .jsObject, .swiftStruct, .caseEnum, .swiftHeapObject, + .jsObject(nil), .swiftStruct, .caseEnum, .swiftHeapObject, .unsafePointer, .rawValueEnum, .associatedValueEnum: return "[\(raw: elementType.swiftType)].bridgeJSLiftParameter()" + case .jsObject(_?): + return liftArrayExpressionInline(elementType: elementType) case .swiftProtocol(let protocolName): return "[Any\(raw: protocolName)].bridgeJSLiftParameter()" case .optional, .array, .closure: @@ -861,9 +865,11 @@ struct StackCodegen { private func liftOptionalExpression(wrappedType: BridgeType) -> ExprSyntax { switch wrappedType { - case .string, .int, .uint, .bool, .float, .double, .jsObject, + case .string, .int, .uint, .bool, .float, .double, .jsObject(nil), .swiftStruct, .swiftHeapObject, .caseEnum, .associatedValueEnum, .rawValueEnum: return "Optional<\(raw: wrappedType.swiftType)>.bridgeJSLiftParameter()" + case .jsObject(let className?): + return "Optional.bridgeJSLiftParameter().map { \(raw: className)(unsafelyWrapping: $0) }" case .array(let elementType): let arrayLift = liftArrayExpression(elementType: elementType) let swiftTypeName = elementType.swiftType @@ -896,8 +902,10 @@ struct StackCodegen { switch type { case .string, .int, .uint, .bool, .float, .double: return ["\(raw: accessor).bridgeJSLowerStackReturn()"] - case .jsObject: + case .jsObject(nil): return ["\(raw: accessor).bridgeJSLowerStackReturn()"] + case .jsObject(_?): + return ["\(raw: accessor).jsObject.bridgeJSLowerStackReturn()"] case .swiftHeapObject, .unsafePointer, .closure: return ["\(raw: accessor).bridgeJSLowerStackReturn()"] case .swiftProtocol(let protocolName): @@ -923,9 +931,11 @@ struct StackCodegen { ) -> [CodeBlockItemSyntax] { switch elementType { case .int, .uint, .float, .double, .string, .bool, - .jsObject, .swiftStruct, .caseEnum, .swiftHeapObject, + .jsObject(nil), .swiftStruct, .caseEnum, .swiftHeapObject, .unsafePointer, .rawValueEnum, .associatedValueEnum: return ["\(raw: accessor).bridgeJSLowerReturn()"] + case .jsObject(_?): + return ["\(raw: accessor).map { $0.jsObject }.bridgeJSLowerReturn()"] case .swiftProtocol(let protocolName): return ["\(raw: accessor).map { $0 as! Any\(raw: protocolName) }.bridgeJSLowerReturn()"] case .optional, .array, .closure: @@ -1003,8 +1013,10 @@ struct StackCodegen { case .associatedValueEnum: // Push payloads via bridgeJSLowerParameter(), then push the returned case ID return ["_swift_js_push_i32(\(raw: unwrappedVar).bridgeJSLowerParameter())"] - case .jsObject: + case .jsObject(nil): return ["\(raw: unwrappedVar).bridgeJSLowerStackReturn()"] + case .jsObject(_?): + return ["\(raw: unwrappedVar).jsObject.bridgeJSLowerStackReturn()"] case .array(let elementType): return lowerArrayStatements(elementType: elementType, accessor: unwrappedVar, varPrefix: varPrefix) default: diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 9a197949..791bb8a8 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -2474,7 +2474,6 @@ struct IntrinsicJSFragment: Sendable { let idVar = scope.variable("objId") printer.write("const \(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));") printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(idVar));") - cleanup.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));") return [] } ) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ArrayTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ArrayTypes.swift index 678bc369..c1980cbc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ArrayTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ArrayTypes.swift @@ -55,6 +55,9 @@ @JS func processItemArray(_ items: [Item]) -> [Item] @JS func processNestedItemArray(_ items: [[Item]]) -> [[Item]] +@JS func processJSObjectArray(_ objects: [JSObject]) -> [JSObject] +@JS func processOptionalJSObjectArray(_ objects: [JSObject?]) -> [JSObject?] +@JS func processNestedJSObjectArray(_ objects: [[JSObject]]) -> [[JSObject]] + @JSFunction func checkArray(_ a: JSObject) throws(JSException) -> Void @JSFunction func checkArrayWithLength(_ a: JSObject, _ b: Double) throws(JSException) -> Void -@JSFunction func checkArray(_ a: JSObject) throws(JSException) -> Void diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ImportedTypeInExportedInterface.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ImportedTypeInExportedInterface.swift index 9e352428..db167c5a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ImportedTypeInExportedInterface.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ImportedTypeInExportedInterface.swift @@ -5,3 +5,13 @@ @JS func makeFoo() throws(JSException) -> Foo { return try Foo() } + +@JS func processFooArray(_ foos: [Foo]) -> [Foo] +@JS func processOptionalFooArray(_ foos: [Foo?]) -> [Foo?] + +@JS struct FooContainer { + var foo: Foo + var optionalFoo: Foo? +} + +@JS func roundtripFooContainer(_ container: FooContainer) -> FooContainer diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftStruct.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftStruct.swift index ac316a05..0d84f473 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftStruct.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/SwiftStruct.swift @@ -53,3 +53,10 @@ @JS static var computedSetting: String { "Config: \(defaultConfig)" } @JS static func update(_ timeout: Double) -> Double } + +@JS struct Container { + var object: JSObject + var optionalObject: JSObject? +} + +@JS func roundtripContainer(_ container: Container) -> Container diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.json index c9034717..7950cb66 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.json @@ -945,6 +945,121 @@ } } } + }, + { + "abiName" : "bjs_processJSObjectArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "processJSObjectArray", + "parameters" : [ + { + "label" : "_", + "name" : "objects", + "type" : { + "array" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "jsObject" : { + + } + } + } + } + }, + { + "abiName" : "bjs_processOptionalJSObjectArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "processOptionalJSObjectArray", + "parameters" : [ + { + "label" : "_", + "name" : "objects", + "type" : { + "array" : { + "_0" : { + "optional" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "optional" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + } + }, + { + "abiName" : "bjs_processNestedJSObjectArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "processNestedJSObjectArray", + "parameters" : [ + { + "label" : "_", + "name" : "objects", + "type" : { + "array" : { + "_0" : { + "array" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "array" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + } } ], "protocols" : [ @@ -1027,24 +1142,6 @@ "returnType" : { "void" : { - } - } - }, - { - "name" : "checkArray", - "parameters" : [ - { - "name" : "a", - "type" : { - "jsObject" : { - - } - } - } - ], - "returnType" : { - "void" : { - } } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift index 4452446b..c7c80c3b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift @@ -465,6 +465,64 @@ public func _bjs_processNestedItemArray() -> Void { #endif } +@_expose(wasm, "bjs_processJSObjectArray") +@_cdecl("bjs_processJSObjectArray") +public func _bjs_processJSObjectArray() -> Void { + #if arch(wasm32) + let ret = processJSObjectArray(_: [JSObject].bridgeJSLiftParameter()) + ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processOptionalJSObjectArray") +@_cdecl("bjs_processOptionalJSObjectArray") +public func _bjs_processOptionalJSObjectArray() -> Void { + #if arch(wasm32) + let ret = processOptionalJSObjectArray(_: { + let __count = Int(_swift_js_pop_i32()) + var __result: [Optional] = [] + __result.reserveCapacity(__count) + for _ in 0 ..< __count { + __result.append(Optional.bridgeJSLiftParameter()) + } + __result.reverse() + return __result + }()) + for __bjs_elem_ret in ret { + let __bjs_isSome_ret_elem = __bjs_elem_ret != nil + if let __bjs_unwrapped_ret_elem = __bjs_elem_ret { + __bjs_unwrapped_ret_elem.bridgeJSLowerStackReturn()} + _swift_js_push_i32(__bjs_isSome_ret_elem ? 1 : 0)} + _swift_js_push_i32(Int32(ret.count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processNestedJSObjectArray") +@_cdecl("bjs_processNestedJSObjectArray") +public func _bjs_processNestedJSObjectArray() -> Void { + #if arch(wasm32) + let ret = processNestedJSObjectArray(_: { + let __count = Int(_swift_js_pop_i32()) + var __result: [[JSObject]] = [] + __result.reserveCapacity(__count) + for _ in 0 ..< __count { + __result.append([JSObject].bridgeJSLiftParameter()) + } + __result.reverse() + return __result + }()) + for __bjs_elem_ret in ret { + __bjs_elem_ret.bridgeJSLowerReturn()} + _swift_js_push_i32(Int32(ret.count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Item_deinit") @_cdecl("bjs_Item_deinit") public func _bjs_Item_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { @@ -523,21 +581,4 @@ func _$checkArrayWithLength(_ a: JSObject, _ b: Double) throws(JSException) -> V if let error = _swift_js_take_exception() { throw error } -} - -#if arch(wasm32) -@_extern(wasm, module: "TestModule", name: "bjs_checkArray") -fileprivate func bjs_checkArray(_ a: Int32) -> Void -#else -fileprivate func bjs_checkArray(_ a: Int32) -> Void { - fatalError("Only available on WebAssembly") -} -#endif - -func _$checkArray(_ a: JSObject) throws(JSException) -> Void { - let aValue = a.bridgeJSLowerParameter() - bjs_checkArray(aValue) - if let error = _swift_js_take_exception() { - throw error - } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.json index 10a18983..c40f0dc8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.json @@ -24,13 +24,144 @@ "_0" : "Foo" } } + }, + { + "abiName" : "bjs_processFooArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "processFooArray", + "parameters" : [ + { + "label" : "_", + "name" : "foos", + "type" : { + "array" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + }, + { + "abiName" : "bjs_processOptionalFooArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "processOptionalFooArray", + "parameters" : [ + { + "label" : "_", + "name" : "foos", + "type" : { + "array" : { + "_0" : { + "optional" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "optional" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + } + } + }, + { + "abiName" : "bjs_roundtripFooContainer", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripFooContainer", + "parameters" : [ + { + "label" : "_", + "name" : "container", + "type" : { + "swiftStruct" : { + "_0" : "FooContainer" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "FooContainer" + } + } } ], "protocols" : [ ], "structs" : [ + { + "methods" : [ + ], + "name" : "FooContainer", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "foo", + "type" : { + "jsObject" : { + "_0" : "Foo" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optionalFoo", + "type" : { + "optional" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + } + ], + "swiftCallName" : "FooContainer" + } ] }, "imported" : { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.swift index 29b615b0..6d9bee08 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.swift @@ -1,3 +1,54 @@ +extension FooContainer: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> FooContainer { + let optionalFoo = Optional.bridgeJSLiftParameter().map { + Foo(unsafelyWrapping: $0) + } + let foo = Foo(unsafelyWrapping: JSObject.bridgeJSLiftParameter()) + return FooContainer(foo: foo, optionalFoo: optionalFoo) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + self.foo.jsObject.bridgeJSLowerStackReturn() + let __bjs_isSome_optionalFoo = self.optionalFoo != nil + if let __bjs_unwrapped_optionalFoo = self.optionalFoo { + __bjs_unwrapped_optionalFoo.jsObject.bridgeJSLowerStackReturn() + } + _swift_js_push_i32(__bjs_isSome_optionalFoo ? 1 : 0) + } + + init(unsafelyCopying jsObject: JSObject) { + let __bjs_cleanupId = _bjs_struct_lower_FooContainer(jsObject.bridgeJSLowerParameter()) + defer { + _swift_js_struct_cleanup(__bjs_cleanupId) + } + self = Self.bridgeJSLiftParameter() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSLowerReturn() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_FooContainer())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_FooContainer") +fileprivate func _bjs_struct_lower_FooContainer(_ objectId: Int32) -> Int32 +#else +fileprivate func _bjs_struct_lower_FooContainer(_ objectId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_FooContainer") +fileprivate func _bjs_struct_lift_FooContainer() -> Int32 +#else +fileprivate func _bjs_struct_lift_FooContainer() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + @_expose(wasm, "bjs_makeFoo") @_cdecl("bjs_makeFoo") public func _bjs_makeFoo() -> Int32 { @@ -23,6 +74,66 @@ public func _bjs_makeFoo() -> Int32 { #endif } +@_expose(wasm, "bjs_processFooArray") +@_cdecl("bjs_processFooArray") +public func _bjs_processFooArray() -> Void { + #if arch(wasm32) + let ret = processFooArray(_: { + let __count = Int(_swift_js_pop_i32()) + var __result: [Foo] = [] + __result.reserveCapacity(__count) + for _ in 0 ..< __count { + __result.append(Foo(unsafelyWrapping: JSObject.bridgeJSLiftParameter())) + } + __result.reverse() + return __result + }()) + ret.map { + $0.jsObject + } .bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processOptionalFooArray") +@_cdecl("bjs_processOptionalFooArray") +public func _bjs_processOptionalFooArray() -> Void { + #if arch(wasm32) + let ret = processOptionalFooArray(_: { + let __count = Int(_swift_js_pop_i32()) + var __result: [Optional] = [] + __result.reserveCapacity(__count) + for _ in 0 ..< __count { + __result.append(Optional.bridgeJSLiftParameter().map { + Foo(unsafelyWrapping: $0) + }) + } + __result.reverse() + return __result + }()) + for __bjs_elem_ret in ret { + let __bjs_isSome_ret_elem = __bjs_elem_ret != nil + if let __bjs_unwrapped_ret_elem = __bjs_elem_ret { + __bjs_unwrapped_ret_elem.jsObject.bridgeJSLowerStackReturn()} + _swift_js_push_i32(__bjs_isSome_ret_elem ? 1 : 0)} + _swift_js_push_i32(Int32(ret.count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundtripFooContainer") +@_cdecl("bjs_roundtripFooContainer") +public func _bjs_roundtripFooContainer() -> Void { + #if arch(wasm32) + let ret = roundtripFooContainer(_: FooContainer.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_Foo_init") fileprivate func bjs_Foo_init() -> Int32 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.json index fa5f333f..1b2dc531 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.json @@ -113,6 +113,31 @@ "_0" : "Person" } } + }, + { + "abiName" : "bjs_roundtripContainer", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundtripContainer", + "parameters" : [ + { + "label" : "_", + "name" : "container", + "type" : { + "swiftStruct" : { + "_0" : "Container" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "Container" + } + } } ], "protocols" : [ @@ -516,6 +541,39 @@ } ], "swiftCallName" : "ConfigStruct" + }, + { + "methods" : [ + + ], + "name" : "Container", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "object", + "type" : { + "jsObject" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optionalObject", + "type" : { + "optional" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + ], + "swiftCallName" : "Container" } ] }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.swift index 9b7ddd02..ca246724 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.swift @@ -388,6 +388,55 @@ public func _bjs_ConfigStruct_static_update(_ timeout: Float64) -> Float64 { #endif } +extension Container: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> Container { + let optionalObject = Optional.bridgeJSLiftParameter() + let object = JSObject.bridgeJSLiftParameter() + return Container(object: object, optionalObject: optionalObject) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + self.object.bridgeJSLowerStackReturn() + let __bjs_isSome_optionalObject = self.optionalObject != nil + if let __bjs_unwrapped_optionalObject = self.optionalObject { + __bjs_unwrapped_optionalObject.bridgeJSLowerStackReturn() + } + _swift_js_push_i32(__bjs_isSome_optionalObject ? 1 : 0) + } + + init(unsafelyCopying jsObject: JSObject) { + let __bjs_cleanupId = _bjs_struct_lower_Container(jsObject.bridgeJSLowerParameter()) + defer { + _swift_js_struct_cleanup(__bjs_cleanupId) + } + self = Self.bridgeJSLiftParameter() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSLowerReturn() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_Container())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_Container") +fileprivate func _bjs_struct_lower_Container(_ objectId: Int32) -> Int32 +#else +fileprivate func _bjs_struct_lower_Container(_ objectId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_Container") +fileprivate func _bjs_struct_lift_Container() -> Int32 +#else +fileprivate func _bjs_struct_lift_Container() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + @_expose(wasm, "bjs_roundtrip") @_cdecl("bjs_roundtrip") public func _bjs_roundtrip() -> Void { @@ -399,6 +448,17 @@ public func _bjs_roundtrip() -> Void { #endif } +@_expose(wasm, "bjs_roundtripContainer") +@_cdecl("bjs_roundtripContainer") +public func _bjs_roundtripContainer() -> Void { + #if arch(wasm32) + let ret = roundtripContainer(_: Container.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 { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts index 1bb9ff18..3316cd7c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts @@ -62,13 +62,15 @@ export type Exports = { processNestedPointArray(points: Point[][]): Point[][]; processItemArray(items: Item[]): Item[]; processNestedItemArray(items: Item[][]): Item[][]; + processJSObjectArray(objects: any[]): any[]; + processOptionalJSObjectArray(objects: (any | null)[]): (any | null)[]; + processNestedJSObjectArray(objects: any[][]): any[][]; Direction: DirectionObject Status: StatusObject } export type Imports = { checkArray(a: any): void; checkArrayWithLength(a: any, b: number): void; - checkArray(a: any): void; } export function createInstantiator(options: { imports: Imports; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js index 3cb11c79..da592066 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js @@ -263,13 +263,6 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_checkArray"] = function bjs_checkArray(a) { - try { - imports.checkArray(swift.memory.getObject(a)); - } catch (error) { - setException(error); - } - } }, setInstance: (i) => { instance = i; @@ -850,6 +843,92 @@ export async function createInstantiator(options, swift) { for (const cleanup of arrayCleanups) { cleanup(); } return arrayResult; }, + processJSObjectArray: function bjs_processJSObjectArray(objects) { + const arrayCleanups = []; + for (const elem of objects) { + const objId = swift.memory.retain(elem); + tmpParamInts.push(objId); + } + tmpParamInts.push(objects.length); + instance.exports.bjs_processJSObjectArray(); + const arrayLen = tmpRetInts.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const objId1 = tmpRetInts.pop(); + const obj = swift.memory.getObject(objId1); + swift.memory.release(objId1); + arrayResult.push(obj); + } + arrayResult.reverse(); + for (const cleanup of arrayCleanups) { cleanup(); } + return arrayResult; + }, + processOptionalJSObjectArray: function bjs_processOptionalJSObjectArray(objects) { + const arrayCleanups = []; + for (const elem of objects) { + const isSome = elem != null ? 1 : 0; + if (isSome) { + const objId = swift.memory.retain(elem); + tmpParamInts.push(objId); + } else { + tmpParamInts.push(0); + } + tmpParamInts.push(isSome); + } + tmpParamInts.push(objects.length); + instance.exports.bjs_processOptionalJSObjectArray(); + const arrayLen = tmpRetInts.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const isSome1 = tmpRetInts.pop(); + let optValue; + if (isSome1 === 0) { + optValue = null; + } else { + const objId1 = tmpRetInts.pop(); + const obj = swift.memory.getObject(objId1); + swift.memory.release(objId1); + optValue = obj; + } + arrayResult.push(optValue); + } + arrayResult.reverse(); + for (const cleanup of arrayCleanups) { cleanup(); } + return arrayResult; + }, + processNestedJSObjectArray: function bjs_processNestedJSObjectArray(objects) { + const arrayCleanups = []; + for (const elem of objects) { + const arrayCleanups1 = []; + for (const elem1 of elem) { + const objId = swift.memory.retain(elem1); + tmpParamInts.push(objId); + } + tmpParamInts.push(elem.length); + arrayCleanups.push(() => { + for (const cleanup of arrayCleanups1) { cleanup(); } + }); + } + tmpParamInts.push(objects.length); + instance.exports.bjs_processNestedJSObjectArray(); + const arrayLen = tmpRetInts.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const arrayLen1 = tmpRetInts.pop(); + const arrayResult1 = []; + for (let i1 = 0; i1 < arrayLen1; i1++) { + const objId1 = tmpRetInts.pop(); + const obj = swift.memory.getObject(objId1); + swift.memory.release(objId1); + arrayResult1.push(obj); + } + arrayResult1.reverse(); + arrayResult.push(arrayResult1); + } + arrayResult.reverse(); + for (const cleanup of arrayCleanups) { cleanup(); } + return arrayResult; + }, Direction: DirectionValues, Status: StatusValues, }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.d.ts index 654b4bc0..22b4e6a1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.d.ts @@ -4,10 +4,17 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export interface FooContainer { + foo: Foo; + optionalFoo: Foo | null; +} export interface Foo { } export type Exports = { makeFoo(): Foo; + processFooArray(foos: Foo[]): Foo[]; + processOptionalFooArray(foos: (Foo | null)[]): (Foo | null)[]; + roundtripFooContainer(container: FooContainer): FooContainer; } export type Imports = { Foo: { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js index 094c2241..e4ce9e8f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js @@ -34,6 +34,70 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_createFooContainerHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + let id; + if (value.foo != null) { + id = swift.memory.retain(value.foo); + } else { + id = undefined; + } + tmpParamInts.push(id !== undefined ? id : 0); + const isSome = value.optionalFoo != null; + let id1; + if (isSome) { + id1 = swift.memory.retain(value.optionalFoo); + tmpParamInts.push(id1); + } else { + id1 = undefined; + tmpParamInts.push(0); + } + tmpParamInts.push(isSome ? 1 : 0); + const cleanup = () => { + if(id !== undefined && id !== 0) { + try { + swift.memory.getObject(id); + swift.memory.release(id); + } catch(e) {} + } + if(id1 !== undefined && id1 !== 0) { + try { + swift.memory.getObject(id1); + swift.memory.release(id1); + } catch(e) {} + } + }; + return { cleanup }; + }, + lift: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const isSome = tmpRetInts.pop(); + let optional; + if (isSome) { + const objectId = tmpRetInts.pop(); + let value; + if (objectId !== 0) { + value = swift.memory.getObject(objectId); + swift.memory.release(objectId); + } else { + value = null; + } + optional = value; + } else { + optional = null; + } + const objectId1 = tmpRetInts.pop(); + let value1; + if (objectId1 !== 0) { + value1 = swift.memory.getObject(objectId1); + swift.memory.release(objectId1); + } else { + value1 = null; + } + return { foo: value1, optionalFoo: optional }; + } + }); + }; return { /** @@ -112,6 +176,17 @@ export async function createInstantiator(options, swift) { tmpStructCleanups.pop(); } } + bjs["swift_js_struct_lower_FooContainer"] = function(objectId) { + const { cleanup: cleanup } = structHelpers.FooContainer.lower(swift.memory.getObject(objectId)); + if (cleanup) { + return tmpStructCleanups.push(cleanup); + } + return 0; + } + bjs["swift_js_struct_lift_FooContainer"] = function() { + const value = structHelpers.FooContainer.lift(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + return swift.memory.retain(value); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; @@ -223,6 +298,9 @@ export async function createInstantiator(options, swift) { /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { const js = swift.memory.heap; + const FooContainerHelpers = __bjs_createFooContainerHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.FooContainer = FooContainerHelpers; + const exports = { makeFoo: function bjs_makeFoo() { const ret = instance.exports.bjs_makeFoo(); @@ -236,6 +314,66 @@ export async function createInstantiator(options, swift) { } return ret1; }, + processFooArray: function bjs_processFooArray(foos) { + const arrayCleanups = []; + for (const elem of foos) { + const objId = swift.memory.retain(elem); + tmpParamInts.push(objId); + } + tmpParamInts.push(foos.length); + instance.exports.bjs_processFooArray(); + const arrayLen = tmpRetInts.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const objId1 = tmpRetInts.pop(); + const obj = swift.memory.getObject(objId1); + swift.memory.release(objId1); + arrayResult.push(obj); + } + arrayResult.reverse(); + for (const cleanup of arrayCleanups) { cleanup(); } + return arrayResult; + }, + processOptionalFooArray: function bjs_processOptionalFooArray(foos) { + const arrayCleanups = []; + for (const elem of foos) { + const isSome = elem != null ? 1 : 0; + if (isSome) { + const objId = swift.memory.retain(elem); + tmpParamInts.push(objId); + } else { + tmpParamInts.push(0); + } + tmpParamInts.push(isSome); + } + tmpParamInts.push(foos.length); + instance.exports.bjs_processOptionalFooArray(); + const arrayLen = tmpRetInts.pop(); + const arrayResult = []; + for (let i = 0; i < arrayLen; i++) { + const isSome1 = tmpRetInts.pop(); + let optValue; + if (isSome1 === 0) { + optValue = null; + } else { + const objId1 = tmpRetInts.pop(); + const obj = swift.memory.getObject(objId1); + swift.memory.release(objId1); + optValue = obj; + } + arrayResult.push(optValue); + } + arrayResult.reverse(); + for (const cleanup of arrayCleanups) { cleanup(); } + return arrayResult; + }, + roundtripFooContainer: function bjs_roundtripFooContainer(container) { + const { cleanup: cleanup } = structHelpers.FooContainer.lower(container); + instance.exports.bjs_roundtripFooContainer(); + const structValue = structHelpers.FooContainer.lift(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + if (cleanup) { cleanup(); } + return structValue; + }, }; _exports = exports; return exports; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.d.ts index 7769ebcb..4a61a26e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.d.ts @@ -39,6 +39,10 @@ export interface Measurement { } export interface ConfigStruct { } +export interface Container { + object: any; + optionalObject: any | null; +} export type PrecisionObject = typeof PrecisionValues; /// Represents a Swift heap object like a class instance or an actor instance. @@ -57,6 +61,7 @@ export type Exports = { new(name: string): Greeter; } roundtrip(session: Person): Person; + roundtripContainer(container: Container): Container; Precision: PrecisionObject DataPoint: { init(x: number, y: number, label: string, optCount: number | null, optFlag: boolean | null): DataPoint; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js index a3d60d0b..dbeb70e6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js @@ -228,6 +228,70 @@ export async function createInstantiator(options, swift) { } }); }; + const __bjs_createContainerHelpers = () => { + return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({ + lower: (value) => { + let id; + if (value.object != null) { + id = swift.memory.retain(value.object); + } else { + id = undefined; + } + tmpParamInts.push(id !== undefined ? id : 0); + const isSome = value.optionalObject != null; + let id1; + if (isSome) { + id1 = swift.memory.retain(value.optionalObject); + tmpParamInts.push(id1); + } else { + id1 = undefined; + tmpParamInts.push(0); + } + tmpParamInts.push(isSome ? 1 : 0); + const cleanup = () => { + if(id !== undefined && id !== 0) { + try { + swift.memory.getObject(id); + swift.memory.release(id); + } catch(e) {} + } + if(id1 !== undefined && id1 !== 0) { + try { + swift.memory.getObject(id1); + swift.memory.release(id1); + } catch(e) {} + } + }; + return { cleanup }; + }, + lift: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => { + const isSome = tmpRetInts.pop(); + let optional; + if (isSome) { + const objectId = tmpRetInts.pop(); + let value; + if (objectId !== 0) { + value = swift.memory.getObject(objectId); + swift.memory.release(objectId); + } else { + value = null; + } + optional = value; + } else { + optional = null; + } + const objectId1 = tmpRetInts.pop(); + let value1; + if (objectId1 !== 0) { + value1 = swift.memory.getObject(objectId1); + swift.memory.release(objectId1); + } else { + value1 = null; + } + return { object: value1, optionalObject: optional }; + } + }); + }; return { /** @@ -371,6 +435,17 @@ export async function createInstantiator(options, swift) { const value = structHelpers.ConfigStruct.lift(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); return swift.memory.retain(value); } + bjs["swift_js_struct_lower_Container"] = function(objectId) { + const { cleanup: cleanup } = structHelpers.Container.lower(swift.memory.getObject(objectId)); + if (cleanup) { + return tmpStructCleanups.push(cleanup); + } + return 0; + } + bjs["swift_js_struct_lift_Container"] = function() { + const value = structHelpers.Container.lift(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + return swift.memory.retain(value); + } bjs["swift_js_return_optional_bool"] = function(isSome, value) { if (isSome === 0) { tmpRetOptionalBool = null; @@ -549,6 +624,9 @@ export async function createInstantiator(options, swift) { const ConfigStructHelpers = __bjs_createConfigStructHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); structHelpers.ConfigStruct = ConfigStructHelpers; + const ContainerHelpers = __bjs_createContainerHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers); + structHelpers.Container = ContainerHelpers; + const exports = { Greeter, roundtrip: function bjs_roundtrip(session) { @@ -558,6 +636,13 @@ export async function createInstantiator(options, swift) { if (cleanup) { cleanup(); } return structValue; }, + roundtripContainer: function bjs_roundtripContainer(container) { + const { cleanup: cleanup } = structHelpers.Container.lower(container); + instance.exports.bjs_roundtripContainer(); + const structValue = structHelpers.Container.lift(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers); + if (cleanup) { cleanup(); } + return structValue; + }, Precision: PrecisionValues, DataPoint: { init: function(x, y, label, optCount, optFlag) { diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Array.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Array.md index fed56618..53d00806 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Array.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Array.md @@ -119,6 +119,8 @@ const result = exports.processNumbers(original); | Struct arrays: `[MyStruct]` | ✅ | | Class arrays: `[MyClass]` | ✅ | | Enum arrays (case, raw value, associated value) | ✅ | +| `JSObject` arrays: `[JSObject]` | ✅ | +| `@JSClass struct` arrays: `[Foo]` | ✅ | | Nested arrays: `[[Int]]` | ✅ | | Optional arrays: `[Int]?` | ✅ | | Arrays of optionals: `[Int?]` | ✅ | 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 index 236b822a..10babd9b 100644 --- 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 @@ -160,6 +160,8 @@ This differs from classes, which use reference semantics and share state across | Stored fields with supported types | ✅ | | Optional fields | ✅ | | Nested structs | ✅ | +| `JSObject` fields | ✅ | +| `@JSClass struct` fields | ✅ | | Instance methods | ✅ | | Static methods | ✅ | | Static properties | ✅ | diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 520fe829..b482fcdb 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -1443,6 +1443,22 @@ enum GraphOperations { return processors } +@JS func roundTripJSObjectArray(_ objects: [JSObject]) -> [JSObject] { + return objects +} + +@JS func roundTripOptionalJSObjectArray(_ objects: [JSObject?]) -> [JSObject?] { + return objects +} + +@JS func roundTripFooArray(_ foos: [Foo]) -> [Foo] { + return foos +} + +@JS func roundTripOptionalFooArray(_ foos: [Foo?]) -> [Foo?] { + return foos +} + class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index b9b7dc1d..fbd913c7 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -3152,6 +3152,106 @@ public func _bjs_ConfigStruct_static_computedSetting_get() -> Void { #endif } +extension JSObjectContainer: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> JSObjectContainer { + let optionalObject = Optional.bridgeJSLiftParameter() + let object = JSObject.bridgeJSLiftParameter() + return JSObjectContainer(object: object, optionalObject: optionalObject) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + self.object.bridgeJSLowerStackReturn() + let __bjs_isSome_optionalObject = self.optionalObject != nil + if let __bjs_unwrapped_optionalObject = self.optionalObject { + __bjs_unwrapped_optionalObject.bridgeJSLowerStackReturn() + } + _swift_js_push_i32(__bjs_isSome_optionalObject ? 1 : 0) + } + + init(unsafelyCopying jsObject: JSObject) { + let __bjs_cleanupId = _bjs_struct_lower_JSObjectContainer(jsObject.bridgeJSLowerParameter()) + defer { + _swift_js_struct_cleanup(__bjs_cleanupId) + } + self = Self.bridgeJSLiftParameter() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSLowerReturn() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_JSObjectContainer())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_JSObjectContainer") +fileprivate func _bjs_struct_lower_JSObjectContainer(_ objectId: Int32) -> Int32 +#else +fileprivate func _bjs_struct_lower_JSObjectContainer(_ objectId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_JSObjectContainer") +fileprivate func _bjs_struct_lift_JSObjectContainer() -> Int32 +#else +fileprivate func _bjs_struct_lift_JSObjectContainer() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +extension FooContainer: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter() -> FooContainer { + let optionalFoo = Optional.bridgeJSLiftParameter().map { + Foo(unsafelyWrapping: $0) + } + let foo = Foo(unsafelyWrapping: JSObject.bridgeJSLiftParameter()) + return FooContainer(foo: foo, optionalFoo: optionalFoo) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() { + self.foo.jsObject.bridgeJSLowerStackReturn() + let __bjs_isSome_optionalFoo = self.optionalFoo != nil + if let __bjs_unwrapped_optionalFoo = self.optionalFoo { + __bjs_unwrapped_optionalFoo.jsObject.bridgeJSLowerStackReturn() + } + _swift_js_push_i32(__bjs_isSome_optionalFoo ? 1 : 0) + } + + init(unsafelyCopying jsObject: JSObject) { + let __bjs_cleanupId = _bjs_struct_lower_FooContainer(jsObject.bridgeJSLowerParameter()) + defer { + _swift_js_struct_cleanup(__bjs_cleanupId) + } + self = Self.bridgeJSLiftParameter() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSLowerReturn() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_FooContainer())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_FooContainer") +fileprivate func _bjs_struct_lower_FooContainer(_ objectId: Int32) -> Int32 +#else +fileprivate func _bjs_struct_lower_FooContainer(_ objectId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_FooContainer") +fileprivate func _bjs_struct_lift_FooContainer() -> Int32 +#else +fileprivate func _bjs_struct_lift_FooContainer() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif + @_expose(wasm, "bjs_roundTripVoid") @_cdecl("bjs_roundTripVoid") public func _bjs_roundTripVoid() -> Void { @@ -5113,6 +5213,91 @@ public func _bjs_roundTripDataProcessorArrayType() -> Void { #endif } +@_expose(wasm, "bjs_roundTripJSObjectArray") +@_cdecl("bjs_roundTripJSObjectArray") +public func _bjs_roundTripJSObjectArray() -> Void { + #if arch(wasm32) + let ret = roundTripJSObjectArray(_: [JSObject].bridgeJSLiftParameter()) + ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripOptionalJSObjectArray") +@_cdecl("bjs_roundTripOptionalJSObjectArray") +public func _bjs_roundTripOptionalJSObjectArray() -> Void { + #if arch(wasm32) + let ret = roundTripOptionalJSObjectArray(_: { + let __count = Int(_swift_js_pop_i32()) + var __result: [Optional] = [] + __result.reserveCapacity(__count) + for _ in 0 ..< __count { + __result.append(Optional.bridgeJSLiftParameter()) + } + __result.reverse() + return __result + }()) + for __bjs_elem_ret in ret { + let __bjs_isSome_ret_elem = __bjs_elem_ret != nil + if let __bjs_unwrapped_ret_elem = __bjs_elem_ret { + __bjs_unwrapped_ret_elem.bridgeJSLowerStackReturn()} + _swift_js_push_i32(__bjs_isSome_ret_elem ? 1 : 0)} + _swift_js_push_i32(Int32(ret.count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripFooArray") +@_cdecl("bjs_roundTripFooArray") +public func _bjs_roundTripFooArray() -> Void { + #if arch(wasm32) + let ret = roundTripFooArray(_: { + let __count = Int(_swift_js_pop_i32()) + var __result: [Foo] = [] + __result.reserveCapacity(__count) + for _ in 0 ..< __count { + __result.append(Foo(unsafelyWrapping: JSObject.bridgeJSLiftParameter())) + } + __result.reverse() + return __result + }()) + ret.map { + $0.jsObject + } .bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripOptionalFooArray") +@_cdecl("bjs_roundTripOptionalFooArray") +public func _bjs_roundTripOptionalFooArray() -> Void { + #if arch(wasm32) + let ret = roundTripOptionalFooArray(_: { + let __count = Int(_swift_js_pop_i32()) + var __result: [Optional] = [] + __result.reserveCapacity(__count) + for _ in 0 ..< __count { + __result.append(Optional.bridgeJSLiftParameter().map { + Foo(unsafelyWrapping: $0) + }) + } + __result.reverse() + return __result + }()) + for __bjs_elem_ret in ret { + let __bjs_isSome_ret_elem = __bjs_elem_ret != nil + if let __bjs_unwrapped_ret_elem = __bjs_elem_ret { + __bjs_unwrapped_ret_elem.jsObject.bridgeJSLowerStackReturn()} + _swift_js_push_i32(__bjs_isSome_ret_elem ? 1 : 0)} + _swift_js_push_i32(Int32(ret.count)) + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripPointerFields") @_cdecl("bjs_roundTripPointerFields") public func _bjs_roundTripPointerFields() -> Void { @@ -5258,6 +5443,28 @@ public func _bjs_testContainerWithStruct() -> UnsafeMutableRawPointer { #endif } +@_expose(wasm, "bjs_roundTripJSObjectContainer") +@_cdecl("bjs_roundTripJSObjectContainer") +public func _bjs_roundTripJSObjectContainer() -> Void { + #if arch(wasm32) + let ret = roundTripJSObjectContainer(_: JSObjectContainer.bridgeJSLiftParameter()) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_roundTripFooContainer") +@_cdecl("bjs_roundTripFooContainer") +public func _bjs_roundTripFooContainer() -> Void { + #if arch(wasm32) + let ret = roundTripFooContainer(_: FooContainer.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 { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 83845dd4..64637477 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -9450,6 +9450,154 @@ } } }, + { + "abiName" : "bjs_roundTripJSObjectArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripJSObjectArray", + "parameters" : [ + { + "label" : "_", + "name" : "objects", + "type" : { + "array" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "jsObject" : { + + } + } + } + } + }, + { + "abiName" : "bjs_roundTripOptionalJSObjectArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripOptionalJSObjectArray", + "parameters" : [ + { + "label" : "_", + "name" : "objects", + "type" : { + "array" : { + "_0" : { + "optional" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "optional" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + } + }, + { + "abiName" : "bjs_roundTripFooArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripFooArray", + "parameters" : [ + { + "label" : "_", + "name" : "foos", + "type" : { + "array" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + }, + { + "abiName" : "bjs_roundTripOptionalFooArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripOptionalFooArray", + "parameters" : [ + { + "label" : "_", + "name" : "foos", + "type" : { + "array" : { + "_0" : { + "optional" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "optional" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + } + } + }, { "abiName" : "bjs_roundTripPointerFields", "effects" : { @@ -9834,6 +9982,56 @@ "_0" : "Container" } } + }, + { + "abiName" : "bjs_roundTripJSObjectContainer", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripJSObjectContainer", + "parameters" : [ + { + "label" : "_", + "name" : "container", + "type" : { + "swiftStruct" : { + "_0" : "JSObjectContainer" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "JSObjectContainer" + } + } + }, + { + "abiName" : "bjs_roundTripFooContainer", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripFooContainer", + "parameters" : [ + { + "label" : "_", + "name" : "container", + "type" : { + "swiftStruct" : { + "_0" : "FooContainer" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "FooContainer" + } + } } ], "protocols" : [ @@ -11365,6 +11563,72 @@ } ], "swiftCallName" : "ConfigStruct" + }, + { + "methods" : [ + + ], + "name" : "JSObjectContainer", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "object", + "type" : { + "jsObject" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optionalObject", + "type" : { + "optional" : { + "_0" : { + "jsObject" : { + + } + } + } + } + } + ], + "swiftCallName" : "JSObjectContainer" + }, + { + "methods" : [ + + ], + "name" : "FooContainer", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "foo", + "type" : { + "jsObject" : { + "_0" : "Foo" + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "optionalFoo", + "type" : { + "optional" : { + "_0" : { + "jsObject" : { + "_0" : "Foo" + } + } + } + } + } + ], + "swiftCallName" : "FooContainer" } ] }, diff --git a/Tests/BridgeJSRuntimeTests/StructAPIs.swift b/Tests/BridgeJSRuntimeTests/StructAPIs.swift index 5c9fed51..c7de5d13 100644 --- a/Tests/BridgeJSRuntimeTests/StructAPIs.swift +++ b/Tests/BridgeJSRuntimeTests/StructAPIs.swift @@ -214,3 +214,23 @@ @JS func testContainerWithStruct(_ point: DataPoint) -> Container { return Container(location: point, config: nil) } + +// Struct with JSObject fields +@JS struct JSObjectContainer { + var object: JSObject + var optionalObject: JSObject? +} + +@JS func roundTripJSObjectContainer(_ container: JSObjectContainer) -> JSObjectContainer { + return container +} + +// Struct with @JSClass fields (Foo is defined in ExportAPITests.swift) +@JS struct FooContainer { + var foo: Foo + var optionalFoo: Foo? +} + +@JS func roundTripFooContainer(_ container: FooContainer) -> FooContainer { + return container +} diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 684ff02f..850b31b6 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -1164,6 +1164,32 @@ function testStructSupport(exports) { optionalRatio: null }; assert.deepEqual(exports.roundTripMeasurementConfig(mc2), mc2); + + // Struct with JSObject field + const containerObj1 = { value: "hello", nested: { x: 1 } }; + const containerObj2 = { items: [1, 2, 3] }; + const container1 = { object: containerObj1, optionalObject: containerObj2 }; + const containerResult1 = exports.roundTripJSObjectContainer(container1); + assert.equal(containerResult1.object, containerObj1); + assert.equal(containerResult1.optionalObject, containerObj2); + + const container2 = { object: containerObj1, optionalObject: null }; + const containerResult2 = exports.roundTripJSObjectContainer(container2); + assert.equal(containerResult2.object, containerObj1); + assert.equal(containerResult2.optionalObject, null); + + // Struct with @JSClass field + const foo1 = new ImportedFoo("first"); + const foo2 = new ImportedFoo("second"); + const fooContainer1 = { foo: foo1, optionalFoo: foo2 }; + const fooContainerResult1 = exports.roundTripFooContainer(fooContainer1); + assert.equal(fooContainerResult1.foo.value, "first"); + assert.equal(fooContainerResult1.optionalFoo.value, "second"); + + const fooContainer2 = { foo: foo1, optionalFoo: null }; + const fooContainerResult2 = exports.roundTripFooContainer(fooContainer2); + assert.equal(fooContainerResult2.foo.value, "first"); + assert.equal(fooContainerResult2.optionalFoo, null); } /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ @@ -1437,6 +1463,36 @@ function testArraySupport(exports) { assert.equal(result[0].count, 1); helper1.release(); + + // JSObject arrays + const jsObj1 = { a: 1, b: "hello" }; + const jsObj2 = { x: [1, 2, 3], y: { nested: true } }; + const jsObjResult = exports.roundTripJSObjectArray([jsObj1, jsObj2]); + assert.equal(jsObjResult.length, 2); + assert.equal(jsObjResult[0], jsObj1); + assert.equal(jsObjResult[1], jsObj2); + assert.deepEqual(exports.roundTripJSObjectArray([]), []); + + const optJsResult = exports.roundTripOptionalJSObjectArray([jsObj1, null, jsObj2]); + assert.equal(optJsResult.length, 3); + assert.equal(optJsResult[0], jsObj1); + assert.equal(optJsResult[1], null); + assert.equal(optJsResult[2], jsObj2); + + // @JSClass struct arrays + const foo1 = new ImportedFoo("first"); + const foo2 = new ImportedFoo("second"); + const fooResult = exports.roundTripFooArray([foo1, foo2]); + assert.equal(fooResult.length, 2); + assert.equal(fooResult[0].value, "first"); + assert.equal(fooResult[1].value, "second"); + assert.deepEqual(exports.roundTripFooArray([]), []); + + const optFooResult = exports.roundTripOptionalFooArray([foo1, null, foo2]); + assert.equal(optFooResult.length, 3); + assert.equal(optFooResult[0].value, "first"); + assert.equal(optFooResult[1], null); + assert.equal(optFooResult[2].value, "second"); } /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */