diff --git a/Benchmarks/Sources/Generated/BridgeJS.swift b/Benchmarks/Sources/Generated/BridgeJS.swift index 384ca35a2..5e3e11db8 100644 --- a/Benchmarks/Sources/Generated/BridgeJS.swift +++ b/Benchmarks/Sources/Generated/BridgeJS.swift @@ -2179,6 +2179,34 @@ fileprivate func _bjs_ArrayRoundtrip_wrap_extern(_ pointer: UnsafeMutableRawPoin return _bjs_ArrayRoundtrip_wrap_extern(pointer) } +extension SimpleStruct: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = SimpleStruct.bridgeJSMakeTypeHandle() +} + +extension Address: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Address.bridgeJSMakeTypeHandle() +} + +extension Person: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Person.bridgeJSMakeTypeHandle() +} + +extension ComplexStruct: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ComplexStruct.bridgeJSMakeTypeHandle() +} + +extension Point: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Point.bridgeJSMakeTypeHandle() +} + +extension APIResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = APIResult.bridgeJSMakeTypeHandle() +} + +extension ComplexResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ComplexResult.bridgeJSMakeTypeHandle() +} + #if arch(wasm32) @_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkHelperNoop") fileprivate func bjs_benchmarkHelperNoop_extern() -> Void @@ -2238,4 +2266,25 @@ func _$benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> if let error = _swift_js_take_exception() { throw error } -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_Benchmarks_register_type_handles") +fileprivate func _bjs_Benchmarks_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_Benchmarks_register_type_handles") +public func _bjs_Benchmarks_register_type_handles() { + let typeIds: [Int32] = [ + SimpleStruct.bridgeJSTypeID, + Address.bridgeJSTypeID, + Person.bridgeJSTypeID, + ComplexStruct.bridgeJSTypeID, + Point.bridgeJSTypeID, + APIResult.bridgeJSTypeID, + ComplexResult.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_Benchmarks_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Examples/Embedded/Package.swift b/Examples/Embedded/Package.swift index 42702394a..1f88a8947 100644 --- a/Examples/Embedded/Package.swift +++ b/Examples/Embedded/Package.swift @@ -16,6 +16,9 @@ let package = Package( swiftSettings: [ .enableExperimentalFeature("Extern") ], + plugins: [ + .plugin(name: "BridgeJS", package: "JavaScriptKit") + ] ) ], swiftLanguageModes: [.v5] diff --git a/Examples/Embedded/Sources/EmbeddedApp/main.swift b/Examples/Embedded/Sources/EmbeddedApp/main.swift index 5e7f01a3c..c3e0dd3cd 100644 --- a/Examples/Embedded/Sources/EmbeddedApp/main.swift +++ b/Examples/Embedded/Sources/EmbeddedApp/main.swift @@ -1,5 +1,12 @@ import JavaScriptKit +@JS struct CounterLabel { + var count: Int + var text: String +} + +@JSFunction func echoValue(_ value: T) throws(JSException) -> T + let alert = JSObject.global.alert.object! let document = JSObject.global.document @@ -46,6 +53,17 @@ _ = encoderContainer.appendChild(textInputElement) _ = encoderContainer.appendChild(encodeResultElement) _ = document.body.appendChild(encoderContainer) +let genericResultElement = document.createElement("pre") +do { + let number = try echoValue(42) + let text = try echoValue("hello") + let label = try echoValue(CounterLabel(count: number, text: text)) + genericResultElement.innerText = .string("Generic import round-trip: \(label.text) \(label.count)") +} catch { + genericResultElement.innerText = "Generic import round-trip failed" +} +_ = document.body.appendChild(genericResultElement) + func print(_ message: String) { _ = JSObject.global.console.log(message) } diff --git a/Examples/Embedded/index.html b/Examples/Embedded/index.html index 93868214d..d280d7067 100644 --- a/Examples/Embedded/index.html +++ b/Examples/Embedded/index.html @@ -8,7 +8,13 @@ diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift index 10976f793..328ae0610 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift @@ -231,6 +231,18 @@ fileprivate func _bjs_PlayBridgeJS_wrap_extern(_ pointer: UnsafeMutableRawPointe return _bjs_PlayBridgeJS_wrap_extern(pointer) } +extension PlayBridgeJSOutput: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PlayBridgeJSOutput.bridgeJSMakeTypeHandle() +} + +extension PlayBridgeJSDiagnostic: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PlayBridgeJSDiagnostic.bridgeJSMakeTypeHandle() +} + +extension PlayBridgeJSResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PlayBridgeJSResult.bridgeJSMakeTypeHandle() +} + #if arch(wasm32) @_extern(wasm, module: "PlayBridgeJS", name: "bjs_createTS2Swift") fileprivate func bjs_createTS2Swift_extern() -> Int32 @@ -274,4 +286,21 @@ func _$TS2Swift_convert(_ self: JSObject, _ ts: String) throws(JSException) -> S throw error } return String.bridgeJSLiftReturn(ret) -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_PlayBridgeJS_register_type_handles") +fileprivate func _bjs_PlayBridgeJS_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_PlayBridgeJS_register_type_handles") +public func _bjs_PlayBridgeJS_register_type_handles() { + let typeIds: [Int32] = [ + PlayBridgeJSOutput.bridgeJSTypeID, + PlayBridgeJSDiagnostic.bridgeJSTypeID, + PlayBridgeJSResult.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_PlayBridgeJS_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/README.md b/Plugins/BridgeJS/README.md index 9e1e0aa08..0905695c5 100644 --- a/Plugins/BridgeJS/README.md +++ b/Plugins/BridgeJS/README.md @@ -98,7 +98,7 @@ graph LR | `Dictionary` | `Record` | - | [#495](https://github.com/swiftwasm/JavaScriptKit/issues/495) | | `Set` | `Set` | - | [#397](https://github.com/swiftwasm/JavaScriptKit/issues/397) | | `Foundation.URL` | `string` | - | [#496](https://github.com/swiftwasm/JavaScriptKit/issues/496) | -| Generics | - | - | [#398](https://github.com/swiftwasm/JavaScriptKit/issues/398) | +| Generic function or method (`T`, `[T]`, `T?`, `[String: T]`) | `(value: T): T` | Depends on `T` | ✅ imports only ([#398](https://github.com/swiftwasm/JavaScriptKit/issues/398) for exports) | ### Import-specific (TypeScript -> Swift) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 2cc551857..1508363c2 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -91,6 +91,15 @@ public class ExportSwift { } } + withSpan("Render Generic Bridgeable Conformances") { [self] in + // Emitted unconditionally: a module cannot know whether a dependent + // module passes its types to a generic imported function. + let genericConformanceCodegen = GenericConformanceCodegen() + for entry in skeleton.genericBridgeableTypeEntries { + decls.append(contentsOf: genericConformanceCodegen.renderConformance(typeName: entry.swiftName)) + } + } + try withSpan("Render Async Promise Helpers") { [self] in let asyncResolveTypes = skeleton.asyncPromiseResolveReturnTypes if !asyncResolveTypes.isEmpty { @@ -875,6 +884,67 @@ public class ExportSwift { } } +// MARK: - GenericConformanceCodegen + +/// Renders `BridgedSwiftGenericBridgeable` conformances for `@JS` types so they +/// can be used as the generic argument of a generic imported `@JSFunction`. +struct GenericConformanceCodegen { + func renderConformance(typeName: String) -> [DeclSyntax] { + let printer = CodeFragmentPrinter() + printer.write("extension \(typeName): BridgedSwiftGenericBridgeable {") + printer.indent { + printer.write( + "@_spi(BridgeJS) public static let bridgeJSTypeHandle = \(typeName).bridgeJSMakeTypeHandle()" + ) + } + printer.write("}") + return ["\(raw: printer.lines.joined(separator: "\n"))"] + } +} + +// MARK: - GenericTypeRegistrationCodegen + +/// Renders the `bjs__register_type_handles` wasm export: it lowers each +/// registered type's `bridgeJSTypeID` into a buffer, in the canonical order of +/// `BridgeJSSkeleton.typeRegistrationEntries`, and passes it to the JS import +/// hook of the same name, which pairs the IDs with its codec array by index. +/// +/// Only the module's own `@JS` types are listed; the core (primitive) handles are +/// registered once by the JavaScriptKit library itself +/// (`_bjs_core_register_type_handles`). +public struct GenericTypeRegistrationCodegen { + public init() {} + + public func render(for skeleton: BridgeJSSkeleton) -> String? { + guard let entries = skeleton.typeRegistrationEntries else { return nil } + let abiName = ABINameGenerator.typeRegistrationFunctionName(moduleName: skeleton.moduleName) + let printer = CodeFragmentPrinter() + printer.write("#if arch(wasm32)") + printer.write("@_extern(wasm, module: \"bjs\", name: \"\(abiName)\")") + printer.write("fileprivate func _\(abiName)_extern(_ base: UnsafePointer?, _ count: Int32)") + printer.nextLine() + printer.write("@_expose(wasm, \"\(abiName)\")") + printer.write("public func _\(abiName)() {") + printer.indent { + printer.write("let typeIds: [Int32] = [") + printer.indent { + for entry in entries { + printer.write("\(entry.swiftName).bridgeJSTypeID,") + } + } + printer.write("]") + printer.write("typeIds.withUnsafeBufferPointer { buffer in") + printer.indent { + printer.write("_\(abiName)_extern(buffer.baseAddress, Int32(buffer.count))") + } + printer.write("}") + } + printer.write("}") + printer.write("#endif") + return printer.lines.joined(separator: "\n") + } +} + // MARK: - StackCodegen /// Helper for stack-based lifting and lowering operations. @@ -896,6 +966,10 @@ struct StackCodegen { return "JSObject.bridgeJSStackPop()" case .void, .namespaceEnum: return "()" + case .generic: + fatalError( + "Generic parameters are only supported on imported declarations, not exported concrete-type codegen" + ) } } @@ -908,7 +982,7 @@ struct StackCodegen { return "\(raw: typeName)<\(raw: wrappedType.swiftType)>.bridgeJSStackPop()" case .jsObject(let className?): return "\(raw: typeName).bridgeJSStackPop().map { \(raw: className)(unsafelyWrapping: $0) }" - case .nullable, .void, .namespaceEnum, .closure, .unsafePointer, .swiftProtocol: + case .nullable, .void, .namespaceEnum, .closure, .unsafePointer, .swiftProtocol, .generic: fatalError("Invalid nullable wrapped type: \(wrappedType)") } } @@ -941,6 +1015,10 @@ struct StackCodegen { return lowerArrayStatements(elementType: elementType, accessor: accessor, varPrefix: varPrefix) case .dictionary(let valueType): return lowerDictionaryStatements(valueType: valueType, accessor: accessor, varPrefix: varPrefix) + case .generic: + fatalError( + "Generic parameters are only supported on imported declarations, not exported concrete-type codegen" + ) } } @@ -1596,12 +1674,34 @@ extension BridgeType { case .associatedValueEnum: return ["_BridgedSwiftAssociatedValueEnum"] case .rawValueEnum, .void, .unsafePointer, .namespaceEnum, - .swiftProtocol, .closure, .nullable, .array, .dictionary, .alias: + .swiftProtocol, .closure, .nullable, .array, .dictionary, .alias, .generic: // Not supported yet. return nil } } + /// Stack expressions for bare `T` and `T?`, the only generic shapes that + /// cannot reuse the concrete emission: `bridgeJSLowerParameter()` names + /// per-type members that the generic constraint erases to the stack, so + /// `bridgeJSStackPush()`/`bridgeJSStackPop()` is the shared spelling. + /// `[T]` and `[String: T]` go through the ordinary paths via the `Array` + /// and `Dictionary` stack conformances. + var genericStackPopExpression: String? { + switch self { + case .generic(let name): return "\(name).bridgeJSStackPop()" + case .nullable(.generic(let name), _): return "Optional<\(name)>.bridgeJSStackPop()" + default: return nil + } + } + + func genericStackPushStatement(value: String) -> String? { + switch self { + case .generic, .nullable(.generic, _): + return "\(value).bridgeJSStackPush()" + default: return nil + } + } + var swiftType: String { switch self { case .bool: return "Bool" @@ -1631,6 +1731,7 @@ extension BridgeType { let closureType = "(\(paramTypes))\(effectsStr) -> \(signature.returnType.swiftType)" return useJSTypedClosure ? "JSTypedClosure<\(closureType)>" : closureType case .alias(let name, _): return name + case .generic(let name): return name } } @@ -1717,6 +1818,10 @@ extension BridgeType { return LiftingIntrinsicInfo(parameters: []) case .alias(_, let underlying): return try underlying.liftParameterInfo() + case .generic: + throw BridgeJSCoreError( + "Generic parameters are only supported on imported declarations, not exported concrete-type codegen" + ) } } @@ -1770,6 +1875,10 @@ extension BridgeType { return .array case .alias(_, let underlying): return try underlying.loweringReturnInfo() + case .generic: + throw BridgeJSCoreError( + "Generic parameters are only supported on imported declarations, not exported concrete-type codegen" + ) } } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 286352915..cb5a88e93 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -143,6 +143,11 @@ public struct ImportTS { } func lowerParameter(param: Parameter) throws { + if let genericPush = param.type.genericStackPushStatement(value: param.name) { + stackLoweringStmts.insert(genericPush, at: 0) + return + } + let loweringInfo = try param.type.loweringParameterInfo(context: context) switch param.type { @@ -237,6 +242,18 @@ public struct ImportTS { abiParameterForwardings.insert(contentsOf: ["resolveRef", "rejectRef"], at: 0) } + private func appendTypeIDParameter(index: Int, genericParameterName: String) { + let abiParamName = ABINameGenerator.genericTypeIdParameterName(index: index) + abiParameterSignatures.append((abiParamName, .i32)) + abiParameterForwardings.append("\(genericParameterName).bridgeJSTypeID") + } + + func appendTypeIDParameters(_ genericParameterNames: [String]) { + for (index, name) in genericParameterNames.enumerated() { + appendTypeIDParameter(index: index, genericParameterName: name) + } + } + func call() throws { for stmt in stackLoweringStmts { body.write(stmt.description) @@ -293,14 +310,18 @@ public struct ImportTS { body.write("return \(returnType.swiftType).bridgeJSLiftReturnFromSideChannel()") } else { let liftExpr: String - switch returnType { - case .closure(let signature, _): - liftExpr = "_BJS_Closure_\(signature.mangleName).bridgeJSLift(ret)" - default: - if liftingInfo.valueToLift != nil { - liftExpr = "\(returnType.swiftType).bridgeJSLiftReturn(ret)" - } else { - liftExpr = "\(returnType.swiftType).bridgeJSLiftReturn()" + if let genericPop = returnType.genericStackPopExpression { + liftExpr = genericPop + } else { + switch returnType { + case .closure(let signature, _): + liftExpr = "_BJS_Closure_\(signature.mangleName).bridgeJSLift(ret)" + default: + if liftingInfo.valueToLift != nil { + liftExpr = "\(returnType.swiftType).bridgeJSLiftReturn(ret)" + } else { + liftExpr = "\(returnType.swiftType).bridgeJSLiftReturn()" + } } } body.write("return \(liftExpr)") @@ -359,7 +380,8 @@ public struct ImportTS { name: String, parameters: [Parameter], returnType: BridgeType, - effects: Effects + effects: Effects, + genericParameters: [String] = [] ) -> DeclSyntax { let printer = CodeFragmentPrinter() let signature = SwiftSignatureBuilder.buildFunctionSignature( @@ -368,7 +390,12 @@ public struct ImportTS { effects: effects, useWildcardLabels: true ) - printer.write("func \(name.backtickIfNeeded())\(signature) {") + let genericClause = + genericParameters.isEmpty + ? "" + : "<" + genericParameters.map { "\($0): BridgedSwiftGenericBridgeable" }.joined(separator: ", ") + + ">" + printer.write("func \(name.backtickIfNeeded())\(genericClause)\(signature) {") printer.indent { printer.write(lines: body.lines) } @@ -428,6 +455,7 @@ public struct ImportTS { for param in function.parameters { try builder.lowerParameter(param: param) } + builder.appendTypeIDParameters(function.genericParameterNames) try builder.call() try builder.liftReturnValue() topLevelDecls.append(builder.renderImportDecl()) @@ -436,7 +464,8 @@ public struct ImportTS { name: Self.thunkName(function: function), parameters: function.parameters, returnType: function.returnType, - effects: function.effects + effects: function.effects, + genericParameters: function.genericParameterNames ) .with(\.leadingTrivia, Self.renderDocumentation(documentation: function.documentation)) ] @@ -457,6 +486,7 @@ public struct ImportTS { for param in method.parameters { try builder.lowerParameter(param: param) } + builder.appendTypeIDParameters(method.genericParameterNames) try builder.call() try builder.liftReturnValue() topLevelDecls.append(builder.renderImportDecl()) @@ -465,7 +495,8 @@ public struct ImportTS { name: Self.thunkName(type: type, method: method), parameters: [selfParameter] + method.parameters, returnType: method.returnType, - effects: method.effects + effects: method.effects, + genericParameters: method.genericParameterNames ) ] } @@ -481,6 +512,7 @@ public struct ImportTS { for param in method.parameters { try builder.lowerParameter(param: param) } + builder.appendTypeIDParameters(method.genericParameterNames) try builder.call() try builder.liftReturnValue() topLevelDecls.append(builder.renderImportDecl()) @@ -489,7 +521,8 @@ public struct ImportTS { name: Self.thunkName(type: type, method: method), parameters: method.parameters, returnType: method.returnType, - effects: method.effects + effects: method.effects, + genericParameters: method.genericParameterNames ) ] } @@ -505,6 +538,7 @@ public struct ImportTS { for param in constructor.parameters { try builder.lowerParameter(param: param) } + builder.appendTypeIDParameters(constructor.genericParameterNames) try builder.call() try builder.liftReturnValue() topLevelDecls.append(builder.renderImportDecl()) @@ -513,7 +547,8 @@ public struct ImportTS { name: Self.thunkName(type: type), parameters: constructor.parameters, returnType: .jsObject(nil), - effects: effects + effects: effects, + genericParameters: constructor.genericParameterNames ) ] } @@ -932,9 +967,6 @@ extension BridgeType { return LoweringParameterInfo(loweredParameters: [("value", wasmType)]) case .associatedValueEnum: return LoweringParameterInfo(loweredParameters: [("caseId", .i32)]) - case .swiftStruct: - // `@JS struct` parameters always use the stack ABI (same as arrays/dictionaries). - return LoweringParameterInfo(loweredParameters: []) case .namespaceEnum: throw BridgeJSCoreError("Namespace enums cannot be used as parameters") case .nullable(let wrappedType, _): @@ -942,7 +974,10 @@ extension BridgeType { var params = [("isSome", WasmCoreType.i32)] params.append(contentsOf: wrappedInfo.loweredParameters) return LoweringParameterInfo(loweredParameters: params, useBorrowing: wrappedInfo.useBorrowing) - case .array, .dictionary: + case .swiftStruct: + // `@JS struct` parameters always use the stack ABI (same as arrays/dictionaries). + return LoweringParameterInfo(loweredParameters: []) + case .array, .dictionary, .generic: return LoweringParameterInfo(loweredParameters: []) case .alias: preconditionFailure("`.alias` must be resolved by `.unaliased` before reaching loweringParameterInfo") @@ -995,9 +1030,6 @@ extension BridgeType { return LiftingReturnInfo(valueToLift: wasmType) case .associatedValueEnum: return LiftingReturnInfo(valueToLift: .i32) - case .swiftStruct: - // `@JS struct` returns always use the stack ABI (same as arrays/dictionaries). - return LiftingReturnInfo(valueToLift: nil) case .namespaceEnum: throw BridgeJSCoreError("Namespace enums cannot be used as return values") case .nullable(let wrappedType, _): @@ -1008,7 +1040,10 @@ extension BridgeType { } let wrappedInfo = try wrappedType.liftingReturnInfo(context: context) return LiftingReturnInfo(valueToLift: wrappedInfo.valueToLift) - case .array, .dictionary: + case .swiftStruct: + // `@JS struct` returns always use the stack ABI (same as arrays/dictionaries). + return LiftingReturnInfo(valueToLift: nil) + case .array, .dictionary, .generic: return LiftingReturnInfo(valueToLift: nil) case .alias: preconditionFailure("`.alias` must be resolved by `.unaliased` before reaching liftingReturnInfo") diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index bfd639ee6..f37bfb822 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -7,6 +7,79 @@ import BridgeJSUtilities import BridgeJSSkeleton #endif +/// Outcome of attempting to resolve a type as a reference to a generic parameter. +enum GenericParameterResolution { + case resolved(BridgeType) + /// A non-nil message is a hard diagnostic; `nil` means the type isn't generic + /// and the caller should fall back to normal type resolution. + case rejected(String?) +} + +func resolveGenericTypeReference( + for type: TypeSyntax, + genericParameterNames: [String] +) -> GenericParameterResolution { + if let identifier = type.as(IdentifierTypeSyntax.self), + identifier.genericArgumentClause == nil, + genericParameterNames.contains(identifier.name.text) + { + return .resolved(.generic(identifier.name.text)) + } + if let wrapped = wrappedGenericBridgeType(for: type, genericParameterNames: genericParameterNames) { + return .resolved(wrapped) + } + if !genericParameterNames.isEmpty, + let wrapped = wrappedGenericParameter(in: type, genericParameterNames: genericParameterNames) + { + return .rejected( + "Generic parameter '\(wrapped)' may only be used as a bare type; wrapping it beyond 'T?', '[T]' and '[String: T]' is not supported." + ) + } + return .rejected(nil) +} + +private func wrappedGenericParameter( + in type: TypeSyntax, + genericParameterNames: [String] +) -> String? { + for token in type.tokens(viewMode: .sourceAccurate) { + if case .identifier(let text) = token.tokenKind, genericParameterNames.contains(text) { + return text + } + } + return nil +} + +private func wrappedGenericBridgeType( + for type: TypeSyntax, + genericParameterNames: [String] +) -> BridgeType? { + func bareGenericName(_ inner: TypeSyntax) -> String? { + guard let identifier = inner.as(IdentifierTypeSyntax.self), + identifier.genericArgumentClause == nil, + genericParameterNames.contains(identifier.name.text) + else { + return nil + } + return identifier.name.text + } + if let arrayType = type.as(ArrayTypeSyntax.self), let name = bareGenericName(arrayType.element) { + return .array(.generic(name)) + } + if let optionalType = type.as(OptionalTypeSyntax.self), let name = bareGenericName(optionalType.wrappedType) { + return .nullable(.generic(name), .null) + } + if let dictType = type.as(DictionaryTypeSyntax.self), + let keyIdentifier = dictType.key.as(IdentifierTypeSyntax.self), + keyIdentifier.genericArgumentClause == nil, + keyIdentifier.name.text == "String", + let name = bareGenericName(dictType.value) + { + return .dictionary(.generic(name)) + } + return nil +} + /// Builds BridgeJS skeletons from Swift source files using SwiftSyntax walk for API collection. /// /// This is a shared entry point for producing: @@ -748,6 +821,11 @@ public final class SwiftToSkeleton { return name.unicodeScalars.dropFirst().allSatisfy { isIdentifierPart($0, isStart: false) } } + fileprivate static func isBridgeableGenericConstraint(_ constraint: String?) -> Bool { + constraint == "BridgedSwiftGenericBridgeable" + || constraint == "JavaScriptKit.BridgedSwiftGenericBridgeable" + } + } private enum ExportSwiftConstants { @@ -1219,10 +1297,6 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { diagnoseNestedOptional(node: param.type, type: param.type.trimmedDescription) continue } - if case .nullable(let wrappedType, _) = type, wrappedType.isOptional { - diagnoseNestedOptional(node: param.type, type: param.type.trimmedDescription) - continue - } let name = param.secondName?.text ?? param.firstName.text let label = param.firstName.text @@ -1307,6 +1381,15 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { return nil } + if let genericClause = node.genericParameterClause, let firstGenericParam = genericClause.parameters.first { + diagnose( + node: firstGenericParam, + message: + "Generic parameters on exported @JS functions are not supported yet. Generic functions are currently only supported on imported @JSFunction declarations." + ) + return nil + } + let name = node.name.text let jsName = extractValidatedJSName(from: jsAttribute) @@ -1784,6 +1867,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { message: "Class visibility must be at least internal" ) let classIdentityMode = extractIdentityMode(from: jsAttribute) + let isFinal = node.modifiers.contains { $0.name.tokenKind == .keyword(.final) } ? true : nil let exportedClass = ExportedClass( name: name, swiftCallName: swiftCallName, @@ -1793,7 +1877,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { properties: [], namespace: effectiveNamespace, identityMode: classIdentityMode, - documentation: extractDocumentation(from: node) + documentation: extractDocumentation(from: node), + isFinal: isFinal ) let uniqueKey = makeKey(name: name, namespace: effectiveNamespace) @@ -3204,24 +3289,101 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { // MARK: - Parsing Methods + /// Validates and collects the generic parameter names of an imported + /// `@JSFunction` declaration (function, method or initializer). + /// + /// Returns `nil` when a diagnostic was emitted; an empty array when the + /// declaration is not generic. + private func parseGenericParameterNames( + genericParameterClause: GenericParameterClauseSyntax?, + genericWhereClause: GenericWhereClauseSyntax?, + node: Syntax + ) -> [String]? { + var genericParameterNames: [String] = [] + if let genericParameterClause { + for genericParam in genericParameterClause.parameters { + let paramName = genericParam.name.text + let constraintText = genericParam.inheritedType?.trimmedDescription + guard SwiftToSkeleton.isBridgeableGenericConstraint(constraintText) else { + errors.append( + DiagnosticError( + node: Syntax(genericParam), + message: + "Generic parameter '\(paramName)' must be constrained to 'BridgedSwiftGenericBridgeable' to be used with @JSFunction." + ) + ) + return nil + } + genericParameterNames.append(paramName) + } + } + if genericWhereClause != nil { + errors.append( + DiagnosticError( + node: node, + message: "'where' clauses are not supported on @JSFunction declarations." + ) + ) + return nil + } + return genericParameterNames + } + private func parseConstructor( _ initializer: InitializerDeclSyntax, typeName: String ) -> ImportedConstructorSkeleton? { guard - validateEffects(initializer.signature.effectSpecifiers, node: initializer, attributeName: "JSFunction") - != nil + let effects = validateEffects( + initializer.signature.effectSpecifiers, + node: initializer, + attributeName: "JSFunction" + ) + else { + return nil + } + guard + let genericParameterNames = parseGenericParameterNames( + genericParameterClause: initializer.genericParameterClause, + genericWhereClause: initializer.genericWhereClause, + node: Syntax(initializer) + ) else { return nil } + if !genericParameterNames.isEmpty && effects.isAsync { + errors.append( + DiagnosticError( + node: Syntax(initializer), + message: "Generic @JSFunction declarations cannot be 'async' yet." + ) + ) + return nil + } + let parameters = parseParameters( + from: initializer.signature.parameterClause, + genericParameterNames: genericParameterNames + ) + for genericName in genericParameterNames + where !parameters.contains(where: { $0.type.referencedGenericName == genericName }) { + errors.append( + DiagnosticError( + node: Syntax(initializer), + message: + "The generic parameter '\(genericName)' must be used in a parameter of a generic @JSFunction initializer." + ) + ) + return nil + } // Initializers without an explicit modifier inherit access from the // enclosing `@JSClass` (the user's example pattern: `public init(...)` // inside `public struct JSDocument`). let parentLevel = currentType?.accessLevel ?? .internal let accessLevel = Self.bridgeAccessLevel(from: initializer.modifiers, default: parentLevel) return ImportedConstructorSkeleton( - parameters: parseParameters(from: initializer.signature.parameterClause), - accessLevel: accessLevel + parameters: parameters, + accessLevel: accessLevel, + genericParameters: genericParameterNames.isEmpty ? nil : genericParameterNames ) } @@ -3239,6 +3401,16 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { return nil } + guard + let genericParameterNames = parseGenericParameterNames( + genericParameterClause: node.genericParameterClause, + genericWhereClause: node.genericWhereClause, + node: Syntax(node) + ) + else { + return nil + } + let baseName = SwiftToSkeleton.normalizeIdentifier(node.name.text) let extractedJSName = extractJSName(from: jsFunction) let from = extractJSImportFrom(from: jsFunction) @@ -3246,16 +3418,51 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { let jsName = extractedJSName?.memberName let name = baseName - let parameters = parseParameters(from: node.signature.parameterClause) + let parameters = parseParameters( + from: node.signature.parameterClause, + genericParameterNames: genericParameterNames + ) let returnType: BridgeType if let returnTypeSyntax = node.signature.returnClause?.type { - guard let resolved = withLookupErrors({ parent.lookupType(for: returnTypeSyntax, errors: &$0) }) else { + guard + let resolved = lookupTypeWithGenerics( + for: returnTypeSyntax, + genericParameterNames: genericParameterNames + ) + else { return nil } returnType = resolved } else { returnType = .void } + + if !genericParameterNames.isEmpty { + if effects.isAsync { + errors.append( + DiagnosticError( + node: node, + message: "Generic @JSFunction declarations cannot be 'async' yet." + ) + ) + return nil + } + for genericName in genericParameterNames { + let usedInParameter = parameters.contains { $0.type.referencedGenericName == genericName } + let usedInReturn = returnType.referencedGenericName == genericName + if !usedInParameter && !usedInReturn { + errors.append( + DiagnosticError( + node: node, + message: + "The generic parameter '\(genericName)' must be used in a parameter or return type of a generic @JSFunction declaration." + ) + ) + return nil + } + } + } + let accessLevel = Self.bridgeAccessLevel(from: node.modifiers) return ImportedFunctionSkeleton( name: name, @@ -3265,7 +3472,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { returnType: returnType, effects: effects, documentation: nil, - accessLevel: accessLevel + accessLevel: accessLevel, + genericParameters: genericParameterNames.isEmpty ? nil : genericParameterNames ) } @@ -3342,7 +3550,26 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { // MARK: - Type and Parameter Parsing - private func parseParameters(from clause: FunctionParameterClauseSyntax) -> [Parameter] { + private func lookupTypeWithGenerics( + for type: TypeSyntax, + genericParameterNames: [String] + ) -> BridgeType? { + switch resolveGenericTypeReference(for: type, genericParameterNames: genericParameterNames) { + case .resolved(let bridgeType): + return bridgeType + case .rejected(let message): + if let message { + errors.append(DiagnosticError(node: Syntax(type), message: message)) + return nil + } + return withLookupErrors { parent.lookupType(for: type, errors: &$0) } + } + } + + private func parseParameters( + from clause: FunctionParameterClauseSyntax, + genericParameterNames: [String] = [] + ) -> [Parameter] { clause.parameters.compactMap { param in let type = param.type if type.is(MissingTypeSyntax.self) { @@ -3354,7 +3581,8 @@ private final class ImportSwiftMacrosAPICollector: SyntaxAnyVisitor { ) return nil } - guard let bridgeType = withLookupErrors({ parent.lookupType(for: type, errors: &$0) }) else { + guard let bridgeType = lookupTypeWithGenerics(for: type, genericParameterNames: genericParameterNames) + else { return nil } let nameToken = param.secondName ?? param.firstName diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 6043f3cd1..1a3c917e5 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -31,6 +31,10 @@ public struct BridgeJSLink { skeletons.compactMap(\.exported).compactMap(\.identityMode).first ?? "none" } + var hasGenerics: Bool { + skeletons.contains { $0.imported?.hasGenericDeclarations ?? false } + } + /// Whether a class should use identity caching based on its annotation and the config default. private func shouldUseIdentityCache(for klass: ExportedClass) -> Bool { // Per-class annotation takes priority @@ -311,7 +315,7 @@ public struct BridgeJSLink { } private func generateVariableDeclarations() -> [String] { - return [ + var declarations: [String] = [ "let \(JSGlueVariableScope.reservedInstance);", "let \(JSGlueVariableScope.reservedMemory);", "let \(JSGlueVariableScope.reservedSetException);", @@ -335,10 +339,41 @@ public struct BridgeJSLink { "let \(JSGlueVariableScope.reservedTaStack) = [];", "const \(JSGlueVariableScope.reservedEnumHelpers) = {};", "const \(JSGlueVariableScope.reservedStructHelpers) = {};", + ] + if hasGenerics { + declarations.append("const \(JSGlueVariableScope.reservedCodecByTypeId) = new Map();") + declarations.append("let __bjs_typeHandlesRegistered = false;") + // Registration is hybrid eager+lazy. The registration exports + // execute Swift code, which must not happen before WASI + // initialization (setInstance runs earlier than that), so the + // instantiator exposes an `afterInitialize` hook that hosts call + // once the instance is initialized (the `instantiate.js` template + // does). The guard below keeps the lazy path — first generic call + // — as a fallback for instantiation paths that skip the hook. + declarations.append("function __bjs_registerTypeHandles() {") + declarations.append(" if (__bjs_typeHandlesRegistered) {") + declarations.append(" return;") + declarations.append(" }") + declarations.append(" __bjs_typeHandlesRegistered = true;") + // The core (primitive) handles live in the JavaScriptKit library, so + // they are registered once here rather than by every module. + declarations.append( + " \(JSGlueVariableScope.reservedInstance).exports[\"\(ABINameGenerator.coreTypeRegistrationFunctionName)\"]();" + ) + for skeleton in skeletons { + guard skeleton.typeRegistrationEntries != nil else { continue } + let name = ABINameGenerator.typeRegistrationFunctionName(moduleName: skeleton.moduleName) + declarations.append(" \(JSGlueVariableScope.reservedInstance).exports[\"\(name)\"]();") + } + declarations.append("}") + declarations.append(contentsOf: GenericJSCodegen.runtimeHelperDeclarations()) + } + declarations.append(contentsOf: [ "", "let _exports = null;", "let bjs = null;", - ] + ]) + return declarations } /// JS const (in the import glue scope) holding the `Symbol` under which a promise's @@ -375,9 +410,120 @@ public struct BridgeJSLink { printer.write(lines: lines) } + /// A print context detached from any thunk, used for codec literal emission. + private func makeCodecPrintContext(printer: CodeFragmentPrinter) -> IntrinsicJSFragment.PrintCodeContext { + IntrinsicJSFragment.PrintCodeContext( + scope: JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry), + printer: printer, + hasDirectAccessToSwiftClass: false, + classNamespaces: intrinsicRegistry.classNamespaces + ) + } + + /// Returns the module-scope codec helper for one bridgeable type, declaring + /// it if this is the first reference. + /// + /// The registration table and the container combinators' element positions + /// go through the same helper, so a type's stack ABI is described once. + private func genericCodecReference(type: BridgeType, into printer: CodeFragmentPrinter) throws -> String { + try ContainerCodecJS.codecExpression(for: type, context: makeCodecPrintContext(printer: printer)) + } + + /// Writes the body shared by every registration hook: verify the codec array + /// lines up with the buffer Swift pushed, then pair IDs with codecs by index. + /// + /// The count check is the enforcement point of the ordering contract: the + /// Swift side and the JS side derive their lists from the same declaration + /// order, so a divergence shows up here instead of as a silent mismatch. + private func writeTypeHandleRegistrationBody( + into printer: CodeFragmentPrinter, + mismatchDescription: String + ) { + printer.write("if (count !== codecs.length) {") + printer.indent { + printer.write( + "throw new Error(\"BridgeJS: type handle registration mismatch for \(mismatchDescription)\");" + ) + } + printer.write("}") + printer.write( + "const typeIds = new Int32Array(\(JSGlueVariableScope.reservedMemory).buffer, base >>> 0, count >>> 0);" + ) + printer.write("for (let i = 0; i < count; i++) {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedCodecByTypeId).set(typeIds[i], codecs[i]);") + } + printer.write("}") + } + + /// Installs the `bjs_core_register_type_handles` hook. The core handles are + /// owned by the JavaScriptKit library rather than by generated code, so the + /// wasm import exists in every binary that links JavaScriptKit and the hook + /// is always installed; without generics anywhere in the build it is a no-op + /// and the registration export is never called. + private func generateCoreTypeRegistrationHook(into printer: CodeFragmentPrinter) throws { + let hookName = ABINameGenerator.coreTypeRegistrationFunctionName + guard hasGenerics else { + printer.write("bjs[\"\(hookName)\"] = function() {};") + return + } + try ContainerCodecJS.registerPrimitiveCodecs(context: makeCodecPrintContext(printer: printer)) + printer.write("bjs[\"\(hookName)\"] = function(base, count) {") + printer.indent { + // Same canonical order as `_bjs_core_register_type_handles` in the + // JavaScriptKit library. + printer.write("const codecs = [") + printer.indent { + for primitive in BridgeType.genericBridgeablePrimitives { + printer.write("\(JSGlueVariableScope.reservedPrimitiveCodecs).\(primitive.token),") + } + } + printer.write("];") + writeTypeHandleRegistrationBody(into: printer, mismatchDescription: "core types") + } + printer.write("}") + } + + /// Installs the per-module `bjs__register_type_handles` import + /// hooks. A module with a registration function always carries the wasm + /// import, so a hook is always installed; without generics anywhere in the + /// build it is a no-op and the registration export is never called. + private func generateTypeRegistrationHooks(into printer: CodeFragmentPrinter) throws { + try generateCoreTypeRegistrationHook(into: printer) + for skeleton in skeletons { + guard let moduleEntries = skeleton.typeRegistrationEntries else { continue } + let hookName = ABINameGenerator.typeRegistrationFunctionName(moduleName: skeleton.moduleName) + guard hasGenerics else { + printer.write("bjs[\"\(hookName)\"] = function() {};") + continue + } + printer.write("bjs[\"\(hookName)\"] = function(base, count) {") + try printer.indent { + // Same order as the module's Swift registration function. + let codecNames = try moduleEntries.map { + try genericCodecReference(type: $0.bridgeType, into: printer) + } + printer.write("const codecs = [") + printer.indent { + for name in codecNames { + printer.write("\(name),") + } + } + printer.write("];") + writeTypeHandleRegistrationBody( + into: printer, + mismatchDescription: "module '\(skeleton.moduleName)'" + ) + } + printer.write("}") + } + } + private func generateAddImports(needsImportsObject: Bool) throws -> CodeFragmentPrinter { let printer = CodeFragmentPrinter() - let allStructs = skeletons.compactMap { $0.exported?.structs }.flatMap { $0 } + let allStructs = skeletons.flatMap { unified in + (unified.exported?.structs ?? []).map { (moduleName: unified.moduleName, structDef: $0) } + } printer.write("return {") try printer.indent { printer.write(lines: [ @@ -525,11 +671,16 @@ public struct BridgeJSLink { } printer.write("}") if !allStructs.isEmpty { - for structDef in allStructs { + for (moduleName, structDef) in allStructs { + // The `bjs` import names are part of the wasm ABI and are + // minted from the ABI name alone on both sides, so they + // cannot be module-qualified here. `validateNoCrossModuleTypeNameCollisions` + // rejects the inputs that would make them collide. + let key = HelperNaming.qualified(base: structDef.abiName, module: moduleName) printer.write("bjs[\"swift_js_struct_lower_\(structDef.abiName)\"] = function(objectId) {") printer.indent { printer.write( - "\(JSGlueVariableScope.reservedStructHelpers).\(structDef.abiName).lower(\(JSGlueVariableScope.reservedSwift).memory.getObject(objectId));" + "\(JSGlueVariableScope.reservedStructHelpers).\(key).lower(\(JSGlueVariableScope.reservedSwift).memory.getObject(objectId));" ) } printer.write("}") @@ -537,13 +688,14 @@ public struct BridgeJSLink { printer.write("bjs[\"swift_js_struct_lift_\(structDef.abiName)\"] = function() {") printer.indent { printer.write( - "const value = \(JSGlueVariableScope.reservedStructHelpers).\(structDef.abiName).lift();" + "const value = \(JSGlueVariableScope.reservedStructHelpers).\(key).lift();" ) printer.write("return \(JSGlueVariableScope.reservedSwift).memory.retain(value);") } printer.write("}") } } + try generateTypeRegistrationHooks(into: printer) // Always provided: the runtime's `_bjs_makePromise` imports it unconditionally. // The settlers are stored under a Symbol to avoid clashing with promise fields. @@ -1025,7 +1177,7 @@ public struct BridgeJSLink { self.renderExportedStructExportEntry(structDef) }, renderFunctionEntry: { function in - self.renderJSDoc(documentation: function.documentation, parameters: function.parameters) + return self.renderJSDoc(documentation: function.documentation, parameters: function.parameters) + [ "\(function.resolvedJSName)\(self.renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" ] @@ -1062,6 +1214,7 @@ public struct BridgeJSLink { printer.write(lines: [ "addImports: (importObject: WebAssembly.Imports) => void;", "setInstance: (instance: WebAssembly.Instance) => void;", + "afterInitialize?: () => void;", "createExports: (instance: WebAssembly.Instance) => Exports;", ]) } @@ -1109,12 +1262,18 @@ public struct BridgeJSLink { let bodyPrinter = CodeFragmentPrinter() let allStructs = exportedSkeletons.flatMap { $0.structs } - for structDef in allStructs { + for (moduleName, structDef) in skeletons.flatMap({ unified in + (unified.exported?.structs ?? []).map { (unified.moduleName, $0) } + }) { let structPrinter = CodeFragmentPrinter() let structScope = JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry) - let fragment = IntrinsicJSFragment.structHelper(structDefinition: structDef, allStructs: allStructs) + let fragment = IntrinsicJSFragment.structHelper( + structDefinition: structDef, + allStructs: allStructs, + moduleName: moduleName + ) _ = try fragment.printCode( - [structDef.abiName], + [], IntrinsicJSFragment.PrintCodeContext( scope: structScope, printer: structPrinter, @@ -1125,13 +1284,16 @@ public struct BridgeJSLink { bodyPrinter.write(lines: structPrinter.lines) } - let allAssocEnums = exportedSkeletons.flatMap { - $0.enums.filter { $0.enumType == .associatedValue } - } - for enumDef in allAssocEnums { + for (moduleName, enumDef) in skeletons.flatMap({ unified in + (unified.exported?.enums ?? []).filter { $0.enumType == .associatedValue } + .map { (unified.moduleName, $0) } + }) { let enumPrinter = CodeFragmentPrinter() let enumScope = JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry) - let fragment = IntrinsicJSFragment.associatedValueEnumHelperFactory(enumDefinition: enumDef) + let fragment = IntrinsicJSFragment.associatedValueEnumHelperFactory( + enumDefinition: enumDef, + moduleName: moduleName + ) _ = try fragment.printCode( [enumDef.valuesName], IntrinsicJSFragment.PrintCodeContext( @@ -1151,6 +1313,18 @@ public struct BridgeJSLink { printer.nextLine() } + // The named codec helpers come after the intrinsics because they are + // built out of the combinators and the primitive codec table, and + // before everything that uses them: they are hoisted here so that no + // call site ever composes a codec. Helpers that delegate to the + // `structHelpers` / `enumHelpers` tables only read those tables when + // called, so declaring them ahead of the tables being populated is + // fine. + if intrinsicRegistry.hasNamedCodecs { + printer.write(lines: intrinsicRegistry.emitNamedCodecLines()) + printer.nextLine() + } + printer.write(lines: bodyPrinter.lines) } printer.indent() @@ -1200,6 +1374,19 @@ public struct BridgeJSLink { printer.write("},") } + // afterInitialize method: eager type-handle registration. Only emitted + // when the generic runtime exists; otherwise the hook is absent and + // callers use optional chaining. + if hasGenerics { + printer.indent { + printer.write("afterInitialize: () => {") + printer.indent { + printer.write("\(JSGlueVariableScope.reservedRegisterTypeHandles)();") + } + printer.write("},") + } + } + // createExports method printer.indent { printer.write(lines: [ @@ -1231,6 +1418,7 @@ public struct BridgeJSLink { } public func link() throws -> (outputJs: String, outputDts: String) { + try validateNoCrossModuleTypeNameCollisions() intrinsicRegistry.reset() importedModuleRegistry.configure(skeletons: skeletons) intrinsicRegistry.classNamespaces = skeletons.reduce(into: [:]) { result, unified in @@ -1241,21 +1429,132 @@ public struct BridgeJSLink { } } } + intrinsicRegistry.typeOwnerModules = collectTypeOwnerModules() let data = try collectLinkData() let outputJs = try generateJavaScript(data: data) let outputDts = generateTypeScript(data: data) return (outputJs, outputDts) } + /// Rejects skeletons in which two modules declare an exported type under the + /// same name. + /// + /// Declaration-site identifiers are module-qualified, but three things + /// downstream of them are minted from the bare type name and cannot be: + /// + /// - `BridgeType` carries only a type name, so a reference to `Point` from + /// module *B* cannot tell which module's `structHelpers` entry it means. + /// - The top-level `const Values` object and the `class ` + /// declaration are part of the public JS surface, so both modules would + /// emit the same `const` / `class` in one glue module. + /// - `bjs["swift_js_struct_lower_"]` is a wasm import name minted + /// from the ABI name on the Swift side too, so both modules import the + /// same one. + /// + /// Emitting glue anyway would either throw at load time or silently bind one + /// module's values to the other module's helpers, so this fails the link. + func validateNoCrossModuleTypeNameCollisions() throws { + /// The identifier minted for a declaration, and where it comes from. + struct Declaration { + let moduleName: String + let kind: String + } + var declarations: [String: [Declaration]] = [:] + func record(_ mintedName: String, kind: String, module: String) { + // Two declarations from the *same* module colliding is a separate, + // pre-existing issue (`@JS` types of the same name in different + // namespaces); this check is only about cross-module collisions. + guard !(declarations[mintedName] ?? []).contains(where: { $0.moduleName == module }) else { return } + declarations[mintedName, default: []].append(Declaration(moduleName: module, kind: kind)) + } + + for unified in skeletons { + guard let skeleton = unified.exported else { continue } + let module = unified.moduleName + for structDef in skeleton.structs { + record(structDef.abiName, kind: "struct", module: module) + } + for klass in skeleton.classes { + record(klass.name, kind: "class", module: module) + } + // Namespace enums declare no runtime value of their own; they only + // contribute to the merged namespace objects, which already merge + // across modules. + for enumDef in skeleton.enums where enumDef.enumType != .namespace { + record(enumDef.name, kind: "enum", module: module) + } + } + + for (mintedName, declarations) in declarations.sorted(by: { $0.key < $1.key }) where declarations.count > 1 { + let modules = declarations.map(\.moduleName).sorted() + let kinds = Set(declarations.map(\.kind)).sorted().joined(separator: "/") + throw BridgeJSLinkError( + message: """ + Duplicate @JS \(kinds) '\(mintedName)' declared by modules \ + \(modules.map { "'\($0)'" }.joined(separator: " and ")). + + The generated JavaScript glue mints identifiers from the type name \ + (the helper tables, the top-level declarations, and the wasm import names \ + the Swift side derives from the same name), so the two declarations \ + would collide in a single glue module. + + Rename one of them so that each module declares a distinct name. + """ + ) + } + } + + /// Maps every type name a `BridgeType` can carry to the module that declares + /// it, so identifiers minted from type names can be module-qualified. + /// + /// A name declared by two modules is a pre-existing ambiguity in the + /// skeleton format (`BridgeType` carries only the name), so the first + /// declaration wins, which keeps the output deterministic. + private func collectTypeOwnerModules() -> [String: String] { + var result: [String: String] = [:] + func record(_ name: String, _ moduleName: String) { + if result[name] == nil { + result[name] = moduleName + } + } + for unified in skeletons { + let moduleName = unified.moduleName + if let skeleton = unified.exported { + for structDef in skeleton.structs { + record(structDef.name, moduleName) + record(structDef.abiName, moduleName) + } + for klass in skeleton.classes { + record(klass.name, moduleName) + record(klass.abiName, moduleName) + } + for enumDef in skeleton.enums { + record(enumDef.name, moduleName) + record(enumDef.abiName, moduleName) + } + for protocolDef in skeleton.protocols { + record(protocolDef.name, moduleName) + } + } + for file in unified.imported?.children ?? [] { + for type in file.types { + record(type.name, moduleName) + } + } + } + return result + } + private func enumHelperAssignments() -> CodeFragmentPrinter { let printer = CodeFragmentPrinter() - for skeleton in skeletons.compactMap(\.exported) { + for unified in skeletons { + guard let skeleton = unified.exported else { continue } for enumDef in skeleton.enums where enumDef.enumType == .associatedValue { - printer.write( - "const \(enumDef.name)Helpers = __bjs_create\(enumDef.valuesName)Helpers();" - ) - printer.write("\(JSGlueVariableScope.reservedEnumHelpers).\(enumDef.name) = \(enumDef.name)Helpers;") + let key = HelperNaming.qualified(base: enumDef.name, module: unified.moduleName) + let local = HelperNaming.helperConstant(key) + printer.write("const \(local) = \(HelperNaming.enumHelperFactory(key))();") + printer.write("\(JSGlueVariableScope.reservedEnumHelpers).\(key) = \(local);") printer.nextLine() } } @@ -1266,14 +1565,13 @@ public struct BridgeJSLink { private func structHelperAssignments() -> CodeFragmentPrinter { let printer = CodeFragmentPrinter() - for skeleton in skeletons.compactMap(\.exported) { + for unified in skeletons { + guard let skeleton = unified.exported else { continue } for structDef in skeleton.structs { - printer.write( - "const \(structDef.abiName)Helpers = __bjs_create\(structDef.abiName)Helpers();" - ) - printer.write( - "\(JSGlueVariableScope.reservedStructHelpers).\(structDef.abiName) = \(structDef.abiName)Helpers;" - ) + let key = HelperNaming.qualified(base: structDef.abiName, module: unified.moduleName) + let local = HelperNaming.helperConstant(key) + printer.write("const \(local) = \(HelperNaming.structHelperFactory(key))();") + printer.write("\(JSGlueVariableScope.reservedStructHelpers).\(key) = \(local);") printer.nextLine() } } @@ -1373,8 +1671,9 @@ public struct BridgeJSLink { // Add methods for method in type.methods { let methodName = method.resolvedJSName + let genericClause = renderGenericClause(method.genericParameterNames) let methodSignature = - "\(renderTSPropertyName(methodName))\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" + "\(renderTSPropertyName(methodName))\(genericClause)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" printer.write(methodSignature) } @@ -1589,6 +1888,10 @@ public struct BridgeJSLink { return "(\(parameterSignatures.joined(separator: ", "))): \(returnTypeWithEffect)" } + private func renderGenericClause(_ genericParameterNames: [String]) -> String { + genericParameterNames.isEmpty ? "" : "<\(genericParameterNames.joined(separator: ", "))>" + } + private func renderTSPropertyName(_ name: String) -> String { // TypeScript allows quoted property names for keys that aren't valid identifiers. if name.range(of: #"^[$A-Z_][0-9A-Z_$]*$"#, options: [.regularExpression, .caseInsensitive]) != nil { @@ -2385,6 +2688,8 @@ extension BridgeJSLink { var parameterNames: [String] = [] var parameterForwardings: [String] = [] var returnExpr: String? + var genericCodecVariables: [String: String] = [:] + var genericTypeIdParameters: [String: String] = [:] let printContext: IntrinsicJSFragment.PrintCodeContext init( @@ -2410,7 +2715,36 @@ extension BridgeJSLink { parameterNames.append("self") } + func declareGenericCodecs(genericParameters: [String]) { + if !genericParameters.isEmpty { + // Generic call sites instantiate the shared container codec + // combinators with the codecs resolved from type IDs. + ContainerCodecJS.registerCombinators(scope: scope) + } + for genericParam in genericParameters { + let typeIdParam = scope.variable("\(genericParam.lowercased())TypeId") + let codecVar = scope.variable("codec\(genericParam)") + body.write("const \(codecVar) = __bjs_codecForTypeId(\(typeIdParam));") + genericCodecVariables[genericParam] = codecVar + genericTypeIdParameters[genericParam] = typeIdParam + } + } + func liftParameter(param: Parameter) throws { + if let name = param.type.referencedGenericName { + guard let codecVar = genericCodecVariables[name] else { + throw BridgeJSLinkError( + message: "Generic codec for '\(name)' was not declared before lifting parameter '\(param.name)'" + ) + } + let valueVar = scope.variable(param.name) + let liftExpr = + GenericJSCodegen.genericCodecLiftExpression(type: param.type, codec: codecVar) + ?? "\(codecVar).lift()" + body.write("const \(valueVar) = \(liftExpr);") + parameterForwardings.append(valueVar) + return + } let liftingFragment = try IntrinsicJSFragment.liftParameter(type: param.type, context: context) let valuesToLift: [String] if liftingFragment.parameters.count == 0 { @@ -2427,6 +2761,16 @@ extension BridgeJSLink { parameterForwardings.append(contentsOf: liftedValues) } + func liftParametersAndGenericTypeIds(_ parameters: [Parameter], genericParameters: [String]) throws { + declareGenericCodecs(genericParameters: genericParameters) + for param in parameters { + try liftParameter(param: param) + } + for genericParam in genericParameters { + parameterNames.append(genericTypeIdParameters[genericParam] ?? genericParam) + } + } + func renderFunction(name: String?) -> [String] { if effects.isAsync { return renderAsyncFunction(name: name) @@ -2503,6 +2847,25 @@ extension BridgeJSLink { body.write("\(callExpr).then(resolve, reject);") return } + if let name = returnType.referencedGenericName { + guard let codecVar = genericCodecVariables[name] else { + throw BridgeJSLinkError( + message: "Generic codec for return type '\(name)' was not declared before the call" + ) + } + let resultVariable = scope.variable("ret") + body.write("let \(resultVariable) = \(callExpr);") + let lowerStmt = + GenericJSCodegen.genericCodecLowerStatement( + type: returnType, + codec: codecVar, + value: resultVariable + ) + ?? "\(codecVar).lower(\(resultVariable));" + body.write(lowerStmt) + self.returnExpr = nil + return + } let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: returnType, context: context) let returnExpr: String? if loweringFragment.parameters.count == 0 { @@ -3488,9 +3851,11 @@ extension BridgeJSLink { returnType: function.returnType, intrinsicRegistry: intrinsicRegistry ) - for param in function.parameters { - try thunkBuilder.liftParameter(param: param) - } + let genericParameters = function.genericParameterNames + try thunkBuilder.liftParametersAndGenericTypeIds( + function.parameters, + genericParameters: genericParameters + ) let jsName = function.resolvedJSName let calleeExpr = try importedModuleRegistry.memberExpression( swiftModuleName: importObjectBuilder.moduleName, @@ -3501,9 +3866,10 @@ extension BridgeJSLink { try thunkBuilder.call(calleeExpr: calleeExpr) let funcLines = thunkBuilder.renderFunction(name: function.abiName(context: nil)) if function.from == nil { + let genericClause = renderGenericClause(genericParameters) importObjectBuilder.appendDts( [ - "\(renderTSPropertyName(jsName))\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" + "\(renderTSPropertyName(jsName))\(genericClause)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" ] ) } @@ -3592,14 +3958,16 @@ extension BridgeJSLink { dtsPrinter.indent { if let constructor = type.constructor { let returnType = BridgeType.jsObject(type.name) + let genericClause = renderGenericClause(constructor.genericParameterNames) dtsPrinter.write( - "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));" + "new\(genericClause)\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));" ) } for method in type.staticMethods { let methodName = method.resolvedJSName + let genericClause = renderGenericClause(method.genericParameterNames) let signature = - "\(renderTSPropertyName(methodName))\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" + "\(renderTSPropertyName(methodName))\(genericClause)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" dtsPrinter.write(signature) } } @@ -3623,9 +3991,10 @@ extension BridgeJSLink { returnType: BridgeType.jsObject(type.name), intrinsicRegistry: intrinsicRegistry ) - for param in constructor.parameters { - try thunkBuilder.liftParameter(param: param) - } + try thunkBuilder.liftParametersAndGenericTypeIds( + constructor.parameters, + genericParameters: constructor.genericParameterNames + ) let ctorExpr = try importedModuleRegistry.memberExpression( swiftModuleName: importObjectBuilder.moduleName, from: type.from, @@ -3682,9 +4051,10 @@ extension BridgeJSLink { returnType: method.returnType, intrinsicRegistry: intrinsicRegistry ) - for param in method.parameters { - try thunkBuilder.liftParameter(param: param) - } + try thunkBuilder.liftParametersAndGenericTypeIds( + method.parameters, + genericParameters: method.genericParameterNames + ) let constructorExpr = try importedModuleRegistry.memberExpression( swiftModuleName: swiftModuleName, from: context.from, @@ -3706,9 +4076,11 @@ extension BridgeJSLink { intrinsicRegistry: intrinsicRegistry ) thunkBuilder.liftSelf() - for param in method.parameters { - try thunkBuilder.liftParameter(param: param) - } + let genericParameters = method.genericParameterNames + try thunkBuilder.liftParametersAndGenericTypeIds( + method.parameters, + genericParameters: genericParameters + ) try thunkBuilder.callMethod(name: method.resolvedJSName) let funcLines = thunkBuilder.renderFunction(name: method.abiName(context: context)) @@ -3991,8 +4363,10 @@ private struct DocCComment { } } -struct BridgeJSLinkError: Error { +struct BridgeJSLinkError: Error, CustomStringConvertible { let message: String + + var description: String { message } } extension BridgeType { @@ -4052,6 +4426,8 @@ extension BridgeType { return "Record" case .alias(_, let underlying): return underlying.tsType + case .generic(let name): + return name } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 2bf656708..eb7e9ad80 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -35,6 +35,11 @@ final class JSGlueVariableScope { static let reservedSwiftClosureRegistry = "swiftClosureRegistry" static let reservedMakeSwiftClosure = "makeClosure" static let reservedTaStack = "taStack" + static let reservedCodecByTypeId = "__bjs_codecByTypeId" + static let reservedPrimitiveCodecs = "__bjs_primitiveCodecs" + static let reservedStringCodec = "__bjs_stringCodec" + static let reservedTypeHandlesRegistered = "__bjs_typeHandlesRegistered" + static let reservedRegisterTypeHandles = "__bjs_registerTypeHandles" private let intrinsicRegistry: JSIntrinsicRegistry @@ -65,6 +70,11 @@ final class JSGlueVariableScope { reservedSwiftClosureRegistry, reservedMakeSwiftClosure, reservedTaStack, + reservedCodecByTypeId, + reservedPrimitiveCodecs, + reservedStringCodec, + reservedTypeHandlesRegistered, + reservedRegisterTypeHandles, ] init(intrinsicRegistry: JSIntrinsicRegistry) { @@ -92,12 +102,96 @@ final class JSGlueVariableScope { try intrinsicRegistry.register(name: name, build: build) } + /// Registers a module-scope `{ lower, lift }` codec helper shared by every + /// site that needs a codec for the same type shape. + func registerNamedCodec(_ name: String, build: (CodeFragmentPrinter) throws -> Void) rethrows { + try intrinsicRegistry.registerNamedCodec(name: name, build: build) + } + + /// The module declaring `typeName`, when the link step knows it. + func moduleName(declaringType typeName: String) -> String? { + intrinsicRegistry.typeOwnerModules[typeName] + } + func makeChildScope() -> JSGlueVariableScope { JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry) } } +extension JSGlueVariableScope { + // MARK: - Module-qualified names minted from `@JS` type names + + /// Qualifies `base` with the module declaring `typeName`, when the link step + /// resolves it. `base` is left alone when it doesn't, which happens for + /// types the skeletons don't declare (core JavaScriptKit handles, say). + func qualified(base: String, declaringType typeName: String) -> String { + let base = HelperNaming.sanitized(base) + guard let module = moduleName(declaringType: typeName) ?? moduleName(declaringType: base) else { + return base + } + return HelperNaming.qualified(base: base, module: module) + } + + /// The `structHelpers` key for the name carried by `BridgeType.swiftStruct`. + func structHelperKey(forTypeNamed fullName: String) -> String { + qualified(base: HelperNaming.structKeyBase(forTypeNamed: fullName), declaringType: fullName) + } + + /// The `enumHelpers` key for the name carried by `BridgeType.associatedValueEnum`. + func enumHelperKey(forTypeNamed fullName: String) -> String { + qualified(base: HelperNaming.enumKeyBase(forTypeNamed: fullName), declaringType: fullName) + } +} + +/// Naming of the JS identifiers the link step mints from `@JS` type names. +/// +/// Every such identifier — the module-scope helper factories, the `const`s +/// holding their result, and the keys of the shared `structHelpers` / +/// `enumHelpers` tables — is qualified with the declaring module, so two modules +/// declaring the same type name do not mint the same identifier. This mirrors +/// the `__bjs_codec__` naming of the codec helpers. +enum HelperNaming { + /// Replaces anything that can't appear in a JS identifier with `_`. + static func sanitized(_ name: String) -> String { + String(name.map { $0.isLetter || $0.isNumber || $0 == "_" ? $0 : "_" }) + } + + /// `_`. + static func qualified(base: String, module: String) -> String { + "\(sanitized(module))_\(sanitized(base))" + } + + /// The unqualified `structHelpers` key for a `BridgeType.swiftStruct` name. + /// + /// Matches `ExportedStruct.abiName`, which joins the namespace path with `_`. + static func structKeyBase(forTypeNamed fullName: String) -> String { + fullName.replacingOccurrences(of: ".", with: "_") + } + + /// The unqualified `enumHelpers` key for a `BridgeType.associatedValueEnum` + /// name. Matches `ExportedEnum.name`, which drops the namespace path. + static func enumKeyBase(forTypeNamed fullName: String) -> String { + fullName.components(separatedBy: ".").last ?? fullName + } + + /// The factory building the `{ lower, lift }` helpers for a `@JS` struct. + static func structHelperFactory(_ qualifiedKey: String) -> String { + "__bjs_createStructHelpers_\(qualifiedKey)" + } + + /// The factory building the `{ lower, lift }` helpers for a `@JS` + /// associated-value enum. + static func enumHelperFactory(_ qualifiedKey: String) -> String { + "__bjs_createEnumHelpers_\(qualifiedKey)" + } + + /// The `const` holding the result of one of the factories above. + static func helperConstant(_ qualifiedKey: String) -> String { + "__bjs_helpers_\(qualifiedKey)" + } +} + extension JSGlueVariableScope { // MARK: Parameter @@ -138,6 +232,383 @@ extension JSGlueVariableScope { } } +enum GenericJSCodegen { + /// Wraps a bare element codec into the codec for the wrapped form (`[T]`, + /// `T?`, `[String: T]`) used at a generic call site, or `nil` when the type + /// is not a generic reference. + static func genericCodecExpression(type: BridgeType, codec: String) -> String? { + switch type { + case .generic: return codec + case .array(.generic): return "\(ContainerCodecJS.arrayCodec)(\(codec))" + case .nullable(.generic, let kind): + return ContainerCodecJS.optionalCodecExpression(elementCodec: codec, kind: kind) + case .dictionary(.generic): return "\(ContainerCodecJS.dictCodec)(\(codec))" + default: return nil + } + } + + static func genericCodecLowerStatement(type: BridgeType, codec: String, value: String) -> String? { + genericCodecExpression(type: type, codec: codec).map { "\($0).lower(\(value));" } + } + + static func genericCodecLiftExpression(type: BridgeType, codec: String) -> String? { + genericCodecExpression(type: type, codec: codec).map { "\($0).lift()" } + } + + /// Generic-only runtime: resolves a wasm-side type ID to the codec + /// registered for it. The container codec combinators themselves live in + /// `ContainerCodecJS` and are shared with the non-generic bridging paths. + static func runtimeHelperDeclarations() -> [String] { + let codecByTypeId = JSGlueVariableScope.reservedCodecByTypeId + return [ + "function __bjs_codecForTypeId(typeId) {", + " __bjs_registerTypeHandles();", + " const codec = \(codecByTypeId).get(typeId);", + " if (!codec) {", + " throw new Error(\"BridgeJS: no codec registered for type ID \" + typeId);", + " }", + " return codec;", + "}", + ] + } +} + +/// Shared `{ lower, lift }` codec codegen: each container's stack ABI is +/// described once by a combinator and instantiated with an element codec by +/// both the generic and non-generic paths. Emitted lazily via the intrinsic +/// registry, so builds that bridge no containers pay nothing. +enum ContainerCodecJS { + static let arrayCodec = "__bjs_arrayCodec" + static let optionalCodec = "__bjs_optionalCodec" + static let dictCodec = "__bjs_dictCodec" + + /// Prefix of the module-scope codec helper `const`s. + static let namedCodecPrefix = "__bjs_codec_" + + private static let combinatorIntrinsicName = "containerCodecCombinators" + private static let primitiveCodecIntrinsicName = "containerPrimitiveCodecs" + + /// The single description of each container shape's stack ABI. + /// + /// The combinators memoize per element codec object. Statically known + /// compositions are hoisted into module-scope `const`s and so instantiate a + /// combinator only once, but a generic call site resolves its element codec + /// from a runtime type ID and cannot be hoisted; memoizing keeps those call + /// sites from allocating a fresh codec on every call. + static func combinatorDeclarations() -> [String] { + let i32 = JSGlueVariableScope.reservedI32Stack + let stringCodec = JSGlueVariableScope.reservedStringCodec + return [ + "const \(arrayCodec)Cache = new WeakMap();", + "function \(arrayCodec)(elementCodec) {", + " let codec = \(arrayCodec)Cache.get(elementCodec);", + " if (codec !== undefined) {", + " return codec;", + " }", + " codec = {", + " lower(value) {", + " for (let i = 0; i < value.length; i++) {", + " elementCodec.lower(value[i]);", + " }", + " \(i32).push(value.length);", + " },", + " lift() {", + " const count = \(i32).pop();", + " if (count === -1) {", + " return \(JSGlueVariableScope.reservedTaStack).pop();", + " }", + " const result = new Array(count);", + " for (let i = count - 1; i >= 0; i--) {", + " result[i] = elementCodec.lift();", + " }", + " return result;", + " },", + " };", + " \(arrayCodec)Cache.set(elementCodec, codec);", + " return codec;", + "}", + // `isUndefinedOr` selects the `JSUndefinedOr` flavor: `null` is then a + // present value and absence surfaces as `undefined` instead of `null`. + // The two flavors are cached separately because they differ in + // behavior, not just in the element codec. + "const \(optionalCodec)Cache = new WeakMap();", + "const \(optionalCodec)UndefinedOrCache = new WeakMap();", + "function \(optionalCodec)(elementCodec, isUndefinedOr = false) {", + " const cache = isUndefinedOr ? \(optionalCodec)UndefinedOrCache : \(optionalCodec)Cache;", + " let codec = cache.get(elementCodec);", + " if (codec !== undefined) {", + " return codec;", + " }", + " codec = {", + " lower(value) {", + " const isSome = isUndefinedOr ? value !== undefined : value != null;", + " if (isSome) {", + " elementCodec.lower(value);", + " \(i32).push(1);", + " } else {", + " \(i32).push(0);", + " }", + " },", + " lift() {", + " if (\(i32).pop() === 0) {", + " return isUndefinedOr ? undefined : null;", + " }", + " return elementCodec.lift();", + " },", + " };", + " cache.set(elementCodec, codec);", + " return codec;", + "}", + "const \(dictCodec)Cache = new WeakMap();", + "function \(dictCodec)(valueCodec) {", + " let codec = \(dictCodec)Cache.get(valueCodec);", + " if (codec !== undefined) {", + " return codec;", + " }", + " codec = {", + " lower(value) {", + " const keys = Object.keys(value);", + " for (let i = 0; i < keys.length; i++) {", + " \(stringCodec).lower(keys[i]);", + " valueCodec.lower(value[keys[i]]);", + " }", + " \(i32).push(keys.length);", + " },", + " lift() {", + " const count = \(i32).pop();", + " const result = {};", + " for (let i = 0; i < count; i++) {", + " const value = valueCodec.lift();", + " const key = \(stringCodec).lift();", + " result[key] = value;", + " }", + " return result;", + " },", + " };", + " \(dictCodec)Cache.set(valueCodec, codec);", + " return codec;", + "}", + ] + } + + static func optionalCodecExpression(elementCodec: String, kind: JSOptionalKind) -> String { + switch kind { + case .null: return "\(optionalCodec)(\(elementCodec))" + case .undefined: return "\(optionalCodec)(\(elementCodec), true)" + } + } + + static func registerCombinators(scope: JSGlueVariableScope) { + scope.registerIntrinsic(combinatorIntrinsicName) { printer in + printer.write(lines: combinatorDeclarations()) + } + } + + /// Emits `__bjs_stringCodec` and the `__bjs_primitiveCodecs` table shared + /// by combinator instantiations and the generic type-handle registration. + static func registerPrimitiveCodecs(context: IntrinsicJSFragment.PrintCodeContext) throws { + try context.scope.registerIntrinsic(primitiveCodecIntrinsicName) { printer in + let stringCodec = JSGlueVariableScope.reservedStringCodec + // The String codec is named so the dictionary codec combinator can + // lower/lift keys through it. + try writeCodecLiteral( + type: .string, + into: printer, + context: context, + prefix: "const \(stringCodec) = ", + suffix: ";" + ) + printer.write("const \(JSGlueVariableScope.reservedPrimitiveCodecs) = {") + try printer.indent { + for primitive in BridgeType.genericBridgeablePrimitives { + if case .string = primitive.type { + printer.write("\(primitive.token): \(stringCodec),") + } else { + try writeCodecLiteral( + type: primitive.type, + into: printer, + context: context, + prefix: "\(primitive.token): ", + suffix: "," + ) + } + } + } + printer.write("};") + } + } + + /// Emits a `{ lower, lift }` codec literal for one bridgeable type. + /// `prefix` is prepended to the opening brace (e.g. an assignment) and + /// `suffix` is appended to the closing brace (e.g. `","` in an object). + static func writeCodecLiteral( + type: BridgeType, + into printer: CodeFragmentPrinter, + context: IntrinsicJSFragment.PrintCodeContext, + prefix: String = "", + suffix: String = "," + ) throws { + func literalContext() -> IntrinsicJSFragment.PrintCodeContext { + context.with(\.printer, printer).with(\.scope, context.scope.makeChildScope()) + } + let lowerFragment = try IntrinsicJSFragment.stackLowerFragment(elementType: type) + let liftFragment = try IntrinsicJSFragment.stackLiftFragment(elementType: type) + printer.write("\(prefix){") + try printer.indent { + printer.write("lower: (v) => {") + try printer.indent { + _ = try lowerFragment.printCode(["v"], literalContext()) + } + printer.write("},") + printer.write("lift: () => {") + try printer.indent { + let results = try liftFragment.printCode([], literalContext()) + printer.write("return \(results[0]);") + } + printer.write("},") + } + printer.write("}\(suffix)") + } + + /// A codec that is reachable by name from module scope. + /// + /// `token` is the stable, module-qualified spelling of the type shape; codec + /// names for compositions are derived from their elements' tokens, so the + /// whole naming scheme inherits module qualification from its leaves. + struct NamedCodec { + let expression: String + let token: String + } + + /// Returns a JS expression evaluating to the `{ lower, lift }` codec for one + /// element type, registering the shared codec runtime as needed. + /// + /// Every codec is a module-scope `const`, so a call site never builds one: + /// the same type shape resolves to the same helper wherever it appears, + /// including the generic type-handle registration table. + static func codecExpression( + for elementType: BridgeType, + context: IntrinsicJSFragment.PrintCodeContext + ) throws -> String { + try namedCodec(for: elementType, context: context).expression + } + + static func namedCodec( + for elementType: BridgeType, + context: IntrinsicJSFragment.PrintCodeContext + ) throws -> NamedCodec { + registerCombinators(scope: context.scope) + try registerPrimitiveCodecs(context: context) + let type = elementType.unaliased + switch type { + case .array(let element): + let element = try namedCodec(for: element, context: context) + return composedCodec( + token: "Array_\(element.token)", + factory: "\(arrayCodec)(\(element.expression))", + context: context + ) + case .dictionary(let value): + let value = try namedCodec(for: value, context: context) + return composedCodec( + token: "Dict_\(value.token)", + factory: "\(dictCodec)(\(value.expression))", + context: context + ) + case .nullable(let wrapped, let kind): + let wrapped = try namedCodec(for: wrapped, context: context) + let prefix = kind == .null ? "Optional" : "UndefinedOr" + return composedCodec( + token: "\(prefix)_\(wrapped.token)", + factory: optionalCodecExpression(elementCodec: wrapped.expression, kind: kind), + context: context + ) + case .string, .rawValueEnum(_, .string): + // A string-backed raw value enum bridges exactly as its raw value. + return NamedCodec(expression: JSGlueVariableScope.reservedStringCodec, token: "String") + default: + if let token = BridgeType.genericBridgeablePrimitives.first(where: { $0.type == type })?.token { + return NamedCodec( + expression: "\(JSGlueVariableScope.reservedPrimitiveCodecs).\(token)", + token: token + ) + } + return try leafCodec(for: type, context: context) + } + } + + /// Declares (once) a module-scope `const` holding a container combinator + /// instantiated with an already-declared element codec. + private static func composedCodec( + token: String, + factory: String, + context: IntrinsicJSFragment.PrintCodeContext + ) -> NamedCodec { + let name = "\(namedCodecPrefix)\(token)" + context.scope.registerNamedCodec(name) { printer in + printer.write("const \(name) = \(factory);") + } + return NamedCodec(expression: name, token: token) + } + + /// Declares (once) a module-scope `const` holding the codec for a type that + /// is not a container: primitives are handled by the shared table, so this + /// covers `@JS` structs, enums, classes, `JSObject`, protocols and friends. + /// + /// The body comes from ``writeCodecLiteral``, the same emitter the generic + /// type-handle registration uses, so both reference one helper per type. + private static func leafCodec( + for type: BridgeType, + context: IntrinsicJSFragment.PrintCodeContext + ) throws -> NamedCodec { + let token = leafToken(for: type, scope: context.scope) + let name = "\(namedCodecPrefix)\(token)" + // The helper lives at module scope, outside `createExports`, so exported + // Swift classes are not in lexical scope here and must be reached + // through `_exports`. + let hoistedContext = context.with(\.hasDirectAccessToSwiftClass, false) + try context.scope.registerNamedCodec(name) { printer in + try writeCodecLiteral( + type: type, + into: printer, + context: hoistedContext, + prefix: "const \(name) = ", + suffix: ";" + ) + } + return NamedCodec(expression: name, token: token) + } + + /// The module-qualified token identifying a non-container type shape. + /// + /// Types declared by a `@JS` module are qualified with the declaring module + /// so two modules declaring the same type name do not mint the same helper. + private static func leafToken(for type: BridgeType, scope: JSGlueVariableScope) -> String { + func sanitized(_ name: String) -> String { + HelperNaming.sanitized(name) + } + func qualified(_ name: String) -> String { + scope.qualified(base: name, declaringType: name) + } + switch type { + case .jsObject(nil): + return "JSObject" + case .jsObject(let name?): + return qualified(name) + case .swiftStruct(let name), + .swiftHeapObject(let name), + .swiftProtocol(let name), + .caseEnum(let name), + .rawValueEnum(let name, _), + .associatedValueEnum(let name), + .namespaceEnum(let name): + return qualified(name) + default: + return sanitized(type.mangleTypeName) + } + } +} + /// A fragment of JS code used to convert a value between Swift and JS. /// /// See `BridgeJSIntrinsics.swift` in the main JavaScriptKit module for Swift side lowering/lifting implementation. @@ -615,12 +1086,13 @@ struct IntrinsicJSFragment: Sendable { // MARK: - Associated Enum Fragments - static func associatedEnumLowerParameter(enumBase: String) -> IntrinsicJSFragment { + static func associatedEnumLowerParameter(enumName: String) -> IntrinsicJSFragment { IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) let value = arguments[0] + let enumBase = scope.enumHelperKey(forTypeNamed: enumName) let caseIdName = scope.variable("\(value)CaseId") printer.write( "const \(caseIdName) = \(JSGlueVariableScope.reservedEnumHelpers).\(enumBase).lower(\(value));" @@ -630,11 +1102,12 @@ struct IntrinsicJSFragment: Sendable { ) } - static func associatedEnumLiftReturn(enumBase: String) -> IntrinsicJSFragment { + static func associatedEnumLiftReturn(enumName: String) -> IntrinsicJSFragment { IntrinsicJSFragment( parameters: [], printCode: { _, context in let (scope, printer) = (context.scope, context.printer) + let enumBase = scope.enumHelperKey(forTypeNamed: enumName) let retName = scope.variable("ret") printer.write( "const \(retName) = \(JSGlueVariableScope.reservedEnumHelpers).\(enumBase).lift(\(scope.popI32()));" @@ -681,6 +1154,12 @@ struct IntrinsicJSFragment: Sendable { ) } + /// Lift an optional parameter whose presence flag arrives as a wasm + /// parameter (not on the i32 stack), with the payload either in further + /// wasm parameters or on the stacks. The shared optional codec combinator + /// pops its flag from the i32 stack, so this ABI cannot go through it; + /// stack-convention payloads still lift through the shared container + /// codecs via `stackLiftFragment`. private static func compositeOptionalLiftParameter( wrappedType: BridgeType, kind: JSOptionalKind, @@ -761,26 +1240,26 @@ struct IntrinsicJSFragment: Sendable { ) } - let innerFragment = - if wrappedType.optionalParameterUsesStackABI { - try stackLowerFragment(elementType: wrappedType) - } else { - try lowerParameter(type: wrappedType) - } + if wrappedType.optionalParameterUsesStackABI { + // Stack convention: the conditional flag-plus-payload protocol is + // the shared optional codec's stack ABI. + return try optionalElementLowerFragment(wrappedType: wrappedType, kind: kind) + } return try compositeOptionalLowerParameter( wrappedType: wrappedType, kind: kind, - innerFragment: innerFragment + innerFragment: try lowerParameter(type: wrappedType) ) } + /// Lower an optional parameter using the direct `(isSome, ...payload)` wasm + /// parameter ABI with zero placeholders for nil. This is not the container + /// stack ABI, so it cannot go through the shared optional codec combinator. private static func compositeOptionalLowerParameter( wrappedType: BridgeType, kind: JSOptionalKind, innerFragment: IntrinsicJSFragment ) throws -> IntrinsicJSFragment { - let isStackConvention = wrappedType.optionalParameterUsesStackABI - return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in @@ -797,7 +1276,7 @@ struct IntrinsicJSFragment: Sendable { let resultVars = innerResults.map { _ in scope.variable("result") } assert( - isStackConvention || resultVars.count == wrappedType.wasmParams.count, + resultVars.count == wrappedType.wasmParams.count, "Inner fragment result count (\(resultVars.count)) must match wasmParams count (\(wrappedType.wasmParams.count)) for \(wrappedType)" ) if !resultVars.isEmpty { @@ -814,8 +1293,7 @@ struct IntrinsicJSFragment: Sendable { } } - let hasPlaceholders = !isStackConvention && !wrappedType.wasmParams.isEmpty - if hasPlaceholders { + if !wrappedType.wasmParams.isEmpty { printer.write("} else {") printer.indent { for (resultVar, param) in zip(resultVars, wrappedType.wasmParams) { @@ -825,12 +1303,7 @@ struct IntrinsicJSFragment: Sendable { } printer.write("}") - if isStackConvention { - scope.emitPushI32Parameter("+\(isSomeVar)", printer: printer) - return [] - } else { - return ["+\(isSomeVar)"] + resultVars - } + return ["+\(isSomeVar)"] + resultVars } ) } @@ -848,6 +1321,9 @@ struct IntrinsicJSFragment: Sendable { ) } + /// Lift an optional return whose presence flag travels on the i32 stack but + /// whose payload uses the wrapped type's regular (non-stack) return ABI, so + /// it cannot go through the shared optional codec combinator. private static func optionalLiftReturnWithPresenceFlag( wrappedType: BridgeType, kind: JSOptionalKind @@ -860,12 +1336,7 @@ struct IntrinsicJSFragment: Sendable { let isSomeVar = scope.variable("isSome") printer.write("const \(isSomeVar) = \(scope.popI32());") - let innerFragment = - if wrappedType.optionalConvention == .stackABI { - try stackLiftFragment(elementType: wrappedType) - } else { - try liftReturn(type: wrappedType) - } + let innerFragment = try liftReturn(type: wrappedType) let innerPrinter = CodeFragmentPrinter() let innerResults = try innerFragment.printCode([], context.with(\.printer, innerPrinter)) @@ -898,12 +1369,12 @@ struct IntrinsicJSFragment: Sendable { fullName: String, kind: JSOptionalKind ) -> IntrinsicJSFragment { - let base = fullName.components(separatedBy: ".").last ?? fullName let absenceLiteral = kind.absenceLiteral return IntrinsicJSFragment( parameters: [], printCode: { _, context in let (scope, printer) = (context.scope, context.printer) + let base = scope.enumHelperKey(forTypeNamed: fullName) let resultVar = scope.variable("optResult") let tagVar = scope.variable("tag") printer.write("const \(tagVar) = \(scope.popI32());") @@ -942,31 +1413,13 @@ struct IntrinsicJSFragment: Sendable { ) } - private static func optionalLiftReturnStruct( - fullName: String, - kind: JSOptionalKind - ) -> IntrinsicJSFragment { - let base = fullName.replacingOccurrences(of: ".", with: "_") - let absenceLiteral = kind.absenceLiteral - return IntrinsicJSFragment( - parameters: [], - printCode: { _, context in - let (scope, printer) = (context.scope, context.printer) - let isSomeVar = scope.variable("isSome") - let resultVar = scope.variable("optResult") - printer.write("const \(isSomeVar) = \(scope.popI32());") - printer.write( - "const \(resultVar) = \(isSomeVar) ? \(JSGlueVariableScope.reservedStructHelpers).\(base).lift() : \(absenceLiteral);" - ) - return [resultVar] - } - ) - } - static func optionalLiftReturn( wrappedType: BridgeType, kind: JSOptionalKind - ) -> IntrinsicJSFragment { + ) throws -> IntrinsicJSFragment { + // Side-channel optionals deliver their payload through dedicated + // storage/imports instead of the bridge stacks, so they cannot go + // through the shared optional codec combinator. if let scalarKind = wrappedType.optionalScalarKind { return optionalLiftReturnFromStorage(storage: scalarKind.storageName) } @@ -974,18 +1427,21 @@ struct IntrinsicJSFragment: Sendable { return optionalLiftReturnFromStorage(storage: JSGlueVariableScope.reservedStorageToReturnString) } + // Heap object optionals use the tmpRetOptionalHeapObject side channel. if case .swiftHeapObject(let className) = wrappedType { return optionalLiftReturnHeapObject(className: className, kind: kind) } - if case .swiftStruct(let fullName) = wrappedType { - return optionalLiftReturnStruct(fullName: fullName, kind: kind) - } - + // Sentinel optionals encode nil in-band (tag -1), with no presence flag. if wrappedType.nilSentinel.hasSentinel, case .associatedValueEnum(let fullName) = wrappedType { return optionalLiftReturnAssociatedEnum(fullName: fullName, kind: kind) } + if wrappedType.optionalConvention == .stackABI { + // Stack convention: route through the shared optional codec combinator. + return try optionalElementRaiseFragment(wrappedType: wrappedType, kind: kind) + } + return optionalLiftReturnWithPresenceFlag(wrappedType: wrappedType, kind: kind) } @@ -1111,12 +1567,8 @@ struct IntrinsicJSFragment: Sendable { } if wrappedType.optionalConvention == .stackABI { - let innerFragment = try stackLowerFragment(elementType: wrappedType) - return stackOptionalLower( - wrappedType: wrappedType, - kind: kind, - innerFragment: innerFragment - ) + // Stack convention: route through the shared optional codec combinator. + return try optionalElementLowerFragment(wrappedType: wrappedType, kind: kind) } if wrappedType.nilSentinel.hasSentinel { @@ -1248,39 +1700,6 @@ struct IntrinsicJSFragment: Sendable { } } - /// Lower an optional value to the stack using the **conditional** protocol: - /// push isSome flag, then conditionally push the payload (no placeholders for nil). - private static func stackOptionalLower( - wrappedType: BridgeType, - kind: JSOptionalKind, - innerFragment: IntrinsicJSFragment - ) -> IntrinsicJSFragment { - IntrinsicJSFragment( - parameters: ["value"], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let value = arguments[0] - let isSomeVar = scope.variable("isSome") - printer.write("const \(isSomeVar) = \(kind.presenceCheck(value: value));") - - let ifBodyPrinter = CodeFragmentPrinter() - try ifBodyPrinter.indent { - let _ = try innerFragment.printCode( - [value], - context.with(\.printer, ifBodyPrinter) - ) - } - printer.write("if (\(isSomeVar)) {") - for line in ifBodyPrinter.lines { - printer.write(line) - } - printer.write("}") - scope.emitPushI32Parameter("\(isSomeVar) ? 1 : 0", printer: printer) - return [] - } - ) - } - // MARK: - ExportSwift /// Returns a fragment that lowers a JS value to Wasm core values for parameters @@ -1300,11 +1719,9 @@ struct IntrinsicJSFragment: Sendable { return try .optionalLowerParameter(wrappedType: wrappedType, kind: kind) case .rawValueEnum(_, .string): return .stringLowerParameter case .associatedValueEnum(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName - return .associatedEnumLowerParameter(enumBase: base) + return .associatedEnumLowerParameter(enumName: fullName) case .swiftStruct(let fullName): - let base = fullName.replacingOccurrences(of: ".", with: "_") - return swiftStructLowerParameter(structBase: base) + return swiftStructLowerParameter(structName: fullName) case .closure: return IntrinsicJSFragment( parameters: ["closure"], @@ -1357,14 +1774,12 @@ struct IntrinsicJSFragment: Sendable { case .swiftProtocol: return .jsObjectLiftReturn case .void: return .void case .nullable(let wrappedType, let kind): - return .optionalLiftReturn(wrappedType: wrappedType, kind: kind) + return try .optionalLiftReturn(wrappedType: wrappedType, kind: kind) case .rawValueEnum(_, .string): return .stringLiftReturn case .associatedValueEnum(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName - return .associatedEnumLiftReturn(enumBase: base) + return .associatedEnumLiftReturn(enumName: fullName) case .swiftStruct(let fullName): - let base = fullName.replacingOccurrences(of: ".", with: "_") - return swiftStructLiftReturn(structBase: base) + return swiftStructLiftReturn(structName: fullName) case .closure: return IntrinsicJSFragment( parameters: ["funcRef"], @@ -1423,11 +1838,11 @@ struct IntrinsicJSFragment: Sendable { return try .optionalLiftParameter(wrappedType: wrappedType, kind: kind, context: context) case .rawValueEnum(_, .string): return .stringLiftParameter case .associatedValueEnum(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName return IntrinsicJSFragment( parameters: ["caseId"], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) + let base = scope.enumHelperKey(forTypeNamed: fullName) let caseId = arguments[0] let resultVar = scope.variable("enumValue") printer.write( @@ -1437,11 +1852,11 @@ struct IntrinsicJSFragment: Sendable { } ) case .swiftStruct(let fullName): - let base = fullName.replacingOccurrences(of: ".", with: "_") return IntrinsicJSFragment( parameters: [], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) + let base = scope.structHelperKey(forTypeNamed: fullName) let resultVar = scope.variable("structValue") printer.write( "const \(resultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(base).lift();" @@ -1523,11 +1938,11 @@ struct IntrinsicJSFragment: Sendable { // MARK: - Enums Payload Fragments static func associatedValueLowerReturn(fullName: String) -> IntrinsicJSFragment { - let base = fullName.components(separatedBy: ".").last ?? fullName return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) + let base = scope.enumHelperKey(forTypeNamed: fullName) let value = arguments[0] let caseIdVar = scope.variable("caseId") printer.write( @@ -1569,14 +1984,24 @@ struct IntrinsicJSFragment: Sendable { /// Generates the enum helper factory function (lower/lift closures). /// This is placed inside `createInstantiator` alongside struct helpers, /// so it has access to `_exports` for class references. - static func associatedValueEnumHelperFactory(enumDefinition: ExportedEnum) -> IntrinsicJSFragment { + /// + /// - Parameter moduleName: The module declaring the enum. The factory + /// identifier is qualified with it so that two modules declaring an enum + /// of the same name don't mint the same `const`. + static func associatedValueEnumHelperFactory( + enumDefinition: ExportedEnum, + moduleName: String + ) -> IntrinsicJSFragment { + let factoryName = HelperNaming.enumHelperFactory( + HelperNaming.qualified(base: enumDefinition.name, module: moduleName) + ) return IntrinsicJSFragment( parameters: ["enumName"], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) let enumName = arguments[0] - printer.write("const __bjs_create\(enumName)Helpers = () => ({") + printer.write("const \(factoryName) = () => ({") try printer.indent { printer.write("lower: (value) => {") try printer.indent { @@ -1769,11 +2194,12 @@ struct IntrinsicJSFragment: Sendable { } } - private static func swiftStructLower(structBase: String) -> IntrinsicJSFragment { + private static func swiftStructLower(structName: String) -> IntrinsicJSFragment { IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in let printer = context.printer + let structBase = context.scope.structHelperKey(forTypeNamed: structName) let value = arguments[0] printer.write( "\(JSGlueVariableScope.reservedStructHelpers).\(structBase).lower(\(value));" @@ -1784,18 +2210,19 @@ struct IntrinsicJSFragment: Sendable { } static func swiftStructLowerReturn(fullName: String) -> IntrinsicJSFragment { - swiftStructLower(structBase: fullName.replacingOccurrences(of: ".", with: "_")) + swiftStructLower(structName: fullName) } - static func swiftStructLowerParameter(structBase: String) -> IntrinsicJSFragment { - swiftStructLower(structBase: structBase) + static func swiftStructLowerParameter(structName: String) -> IntrinsicJSFragment { + swiftStructLower(structName: structName) } - static func swiftStructLiftReturn(structBase: String) -> IntrinsicJSFragment { + static func swiftStructLiftReturn(structName: String) -> IntrinsicJSFragment { return IntrinsicJSFragment( parameters: [], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) + let structBase = scope.structHelperKey(forTypeNamed: structName) let resultVar = scope.variable("structValue") printer.write( "const \(resultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(structBase).lift();" @@ -1807,133 +2234,57 @@ struct IntrinsicJSFragment: Sendable { // MARK: - Array Helpers - /// Lowers an array from JS to Swift by iterating elements and pushing to stacks + /// Lowers an array from JS to Swift through the shared array codec combinator static func arrayLower(elementType: BridgeType) throws -> IntrinsicJSFragment { return IntrinsicJSFragment( parameters: ["arr"], printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let arr = arguments[0] - - let elemVar = scope.variable("elem") - printer.write("for (const \(elemVar) of \(arr)) {") - try printer.indent { - let elementFragment = try stackLowerFragment(elementType: elementType) - let _ = try elementFragment.printCode( - [elemVar], - context - ) - } - printer.write("}") - scope.emitPushI32Parameter("\(arr).length", printer: printer) + let codec = try ContainerCodecJS.codecExpression(for: .array(elementType), context: context) + context.printer.write("\(codec).lower(\(arguments[0]));") return [] } ) } - /// Lowers a dictionary from JS to Swift by iterating entries and pushing to stacks + /// Lowers a dictionary from JS to Swift through the shared dictionary codec combinator static func dictionaryLower(valueType: BridgeType) throws -> IntrinsicJSFragment { return IntrinsicJSFragment( parameters: ["dict"], printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let dict = arguments[0] - - let entriesVar = scope.variable("entries") - let entryVar = scope.variable("entry") - printer.write("const \(entriesVar) = Object.entries(\(dict));") - printer.write("for (const \(entryVar) of \(entriesVar)) {") - try printer.indent { - let keyVar = scope.variable("key") - let valueVar = scope.variable("value") - printer.write("const [\(keyVar), \(valueVar)] = \(entryVar);") - - let keyFragment = try stackLowerFragment(elementType: .string) - let _ = try keyFragment.printCode( - [keyVar], - context - ) - - let valueFragment = try stackLowerFragment(elementType: valueType) - let _ = try valueFragment.printCode( - [valueVar], - context - ) - } - printer.write("}") - scope.emitPushI32Parameter("\(entriesVar).length", printer: printer) + let codec = try ContainerCodecJS.codecExpression(for: .dictionary(valueType), context: context) + context.printer.write("\(codec).lower(\(arguments[0]));") return [] } ) } - /// Lifts an array from Swift to JS by popping elements from stacks + /// Lifts an array from Swift to JS through the shared array codec combinator static func arrayLift(elementType: BridgeType) throws -> IntrinsicJSFragment { return IntrinsicJSFragment( parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let resultVar = scope.variable("arrayResult") - let lenVar = scope.variable("arrayLen") - - printer.write("const \(lenVar) = \(scope.popI32());") - printer.write("let \(resultVar);") - printer.write("if (\(lenVar) === -1) {") - printer.indent { - // Bulk path: Swift pushed a typed array onto the typed-array stack - printer.write("\(resultVar) = \(JSGlueVariableScope.reservedTaStack).pop();") - } - printer.write("} else {") - try printer.indent { - // Element-by-element path (original behavior) - let iVar = scope.variable("i") - printer.write("\(resultVar) = [];") - printer.write("for (let \(iVar) = 0; \(iVar) < \(lenVar); \(iVar)++) {") - try printer.indent { - let elementFragment = try stackLiftFragment(elementType: elementType) - let elementResults = try elementFragment.printCode([], context) - if let elementExpr = elementResults.first { - printer.write("\(resultVar).push(\(elementExpr));") - } - } - printer.write("}") - printer.write("\(resultVar).reverse();") - } - printer.write("}") + printCode: { _, context in + let codec = try ContainerCodecJS.codecExpression(for: .array(elementType), context: context) + let resultVar = context.scope.variable("arrayResult") + context.printer.write("const \(resultVar) = \(codec).lift();") return [resultVar] } ) } - /// Lifts a dictionary from Swift to JS by popping key/value pairs from stacks + /// Lifts a dictionary from Swift to JS through the shared dictionary codec combinator static func dictionaryLift(valueType: BridgeType) throws -> IntrinsicJSFragment { return IntrinsicJSFragment( parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let resultVar = scope.variable("dictResult") - let lenVar = scope.variable("dictLen") - let iVar = scope.variable("i") - - printer.write("const \(lenVar) = \(scope.popI32());") - printer.write("const \(resultVar) = {};") - printer.write("for (let \(iVar) = 0; \(iVar) < \(lenVar); \(iVar)++) {") - try printer.indent { - let valueFragment = try stackLiftFragment(elementType: valueType) - let valueResults = try valueFragment.printCode([], context) - let keyFragment = try stackLiftFragment(elementType: .string) - let keyResults = try keyFragment.printCode([], context) - if let keyExpr = keyResults.first, let valueExpr = valueResults.first { - printer.write("\(resultVar)[\(keyExpr)] = \(valueExpr);") - } - } - printer.write("}") + printCode: { _, context in + let codec = try ContainerCodecJS.codecExpression(for: .dictionary(valueType), context: context) + let resultVar = context.scope.variable("dictResult") + context.printer.write("const \(resultVar) = \(codec).lift();") return [resultVar] } ) } - private static func stackLiftFragment(elementType: BridgeType) throws -> IntrinsicJSFragment { + static func stackLiftFragment(elementType: BridgeType) throws -> IntrinsicJSFragment { if case .nullable(let wrappedType, let kind) = elementType { return try optionalElementRaiseFragment(wrappedType: wrappedType, kind: kind) } @@ -1998,11 +2349,11 @@ struct IntrinsicJSFragment: Sendable { } ) case .swiftStruct(let fullName): - let structBase = fullName.replacingOccurrences(of: ".", with: "_") return IntrinsicJSFragment( parameters: [], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) + let structBase = scope.structHelperKey(forTypeNamed: fullName) let resultVar = scope.variable("struct") printer.write( "const \(resultVar) = \(JSGlueVariableScope.reservedStructHelpers).\(structBase).lift();" @@ -2011,11 +2362,11 @@ struct IntrinsicJSFragment: Sendable { } ) case .associatedValueEnum(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName return IntrinsicJSFragment( parameters: [], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) + let base = scope.enumHelperKey(forTypeNamed: fullName) let resultVar = scope.variable("enumValue") printer.write( "const \(resultVar) = \(JSGlueVariableScope.reservedEnumHelpers).\(base).lift(\(scope.popI32()));" @@ -2060,7 +2411,7 @@ struct IntrinsicJSFragment: Sendable { } } - private static func stackLowerFragment(elementType: BridgeType) throws -> IntrinsicJSFragment { + static func stackLowerFragment(elementType: BridgeType) throws -> IntrinsicJSFragment { if case .nullable(let wrappedType, let kind) = elementType { return try optionalElementLowerFragment(wrappedType: wrappedType, kind: kind) } @@ -2120,11 +2471,11 @@ struct IntrinsicJSFragment: Sendable { } ) case .swiftStruct(let fullName): - let structBase = fullName.replacingOccurrences(of: ".", with: "_") return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in let printer = context.printer + let structBase = context.scope.structHelperKey(forTypeNamed: fullName) let value = arguments[0] printer.write( "\(JSGlueVariableScope.reservedStructHelpers).\(structBase).lower(\(value));" @@ -2134,11 +2485,11 @@ struct IntrinsicJSFragment: Sendable { ) case .associatedValueEnum(let fullName): - let base = fullName.components(separatedBy: ".").last ?? fullName return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) + let base = scope.enumHelperKey(forTypeNamed: fullName) let value = arguments[0] let caseIdVar = scope.variable("caseId") printer.write( @@ -2181,43 +2532,29 @@ struct IntrinsicJSFragment: Sendable { } } + /// Lift an optional from the stack (isSome flag, then conditional payload) + /// through the shared optional codec combinator. private static func optionalElementRaiseFragment( wrappedType: BridgeType, kind: JSOptionalKind ) throws -> IntrinsicJSFragment { - let absenceLiteral = kind.absenceLiteral return IntrinsicJSFragment( parameters: [], - printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let isSomeVar = scope.variable("isSome") - let resultVar = scope.variable("optValue") - - printer.write("const \(isSomeVar) = \(scope.popI32());") - printer.write("let \(resultVar);") - printer.write("if (\(isSomeVar) === 0) {") - printer.indent { - printer.write("\(resultVar) = \(absenceLiteral);") - } - printer.write("} else {") - try printer.indent { - let innerFragment = try stackLiftFragment(elementType: wrappedType) - let innerResults = try innerFragment.printCode([], context) - if let innerResult = innerResults.first { - printer.write("\(resultVar) = \(innerResult);") - } else { - printer.write("\(resultVar) = undefined;") - } - } - printer.write("}") - + printCode: { _, context in + let codec = try ContainerCodecJS.codecExpression( + for: .nullable(wrappedType, kind), + context: context + ) + let resultVar = context.scope.variable("optValue") + context.printer.write("const \(resultVar) = \(codec).lift();") return [resultVar] } ) } - /// Lower an optional element to the stack using the **conditional** protocol: - /// push isSome flag, then conditionally push the payload (no placeholders for nil). + /// Lower an optional value to the stack using the **conditional** protocol + /// (push isSome flag, then conditionally push the payload) through the + /// shared optional codec combinator. private static func optionalElementLowerFragment( wrappedType: BridgeType, kind: JSOptionalKind @@ -2225,23 +2562,11 @@ struct IntrinsicJSFragment: Sendable { return IntrinsicJSFragment( parameters: ["value"], printCode: { arguments, context in - let (scope, printer) = (context.scope, context.printer) - let value = arguments[0] - let isSomeVar = scope.variable("isSome") - - let presenceExpr = kind.presenceCheck(value: value) - printer.write("const \(isSomeVar) = \(presenceExpr) ? 1 : 0;") - printer.write("if (\(isSomeVar)) {") - try printer.indent { - let innerFragment = try stackLowerFragment(elementType: wrappedType) - let _ = try innerFragment.printCode( - [value], - context - ) - } - printer.write("}") - scope.emitPushI32Parameter(isSomeVar, printer: printer) - + let codec = try ContainerCodecJS.codecExpression( + for: .nullable(wrappedType, kind), + context: context + ) + context.printer.write("\(codec).lower(\(arguments[0]));") return [] } ) @@ -2249,16 +2574,25 @@ struct IntrinsicJSFragment: Sendable { // MARK: - Struct Helpers - static func structHelper(structDefinition: ExportedStruct, allStructs: [ExportedStruct]) -> IntrinsicJSFragment { + /// - Parameter moduleName: The module declaring the struct. The factory + /// identifier is qualified with it so that two modules declaring a struct + /// of the same name don't mint the same `const`. + static func structHelper( + structDefinition: ExportedStruct, + allStructs: [ExportedStruct], + moduleName: String + ) -> IntrinsicJSFragment { + let factoryName = HelperNaming.structHelperFactory( + HelperNaming.qualified(base: structDefinition.abiName, module: moduleName) + ) return IntrinsicJSFragment( - parameters: ["structName"], + parameters: [], printCode: { arguments, context in let printer = context.printer - let structName = arguments[0] let capturedStructDef = structDefinition let capturedAllStructs = allStructs - printer.write("const __bjs_create\(structName)Helpers = () => ({") + printer.write("const \(factoryName) = () => ({") try printer.indent { printer.write("lower: (value) => {") try printer.indent { @@ -2357,7 +2691,7 @@ struct IntrinsicJSFragment: Sendable { ) try printer.indent { printer.write( - "\(JSGlueVariableScope.reservedStructHelpers).\(structDef.abiName).lower(this);" + "\(JSGlueVariableScope.reservedStructHelpers).\(context.scope.structHelperKey(forTypeNamed: structDef.abiName)).lower(this);" ) var paramForwardings: [String] = [] @@ -2428,9 +2762,10 @@ struct IntrinsicJSFragment: Sendable { parameters: ["value"], printCode: { arguments, context in let printer = context.printer + let nestedBase = context.scope.structHelperKey(forTypeNamed: nestedName) let value = arguments[0] printer.write( - "\(JSGlueVariableScope.reservedStructHelpers).\(nestedName.replacingOccurrences(of: ".", with: "_")).lower(\(value));" + "\(JSGlueVariableScope.reservedStructHelpers).\(nestedBase).lower(\(value));" ) return [] } @@ -2467,9 +2802,10 @@ struct IntrinsicJSFragment: Sendable { parameters: [], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) + let nestedBase = scope.structHelperKey(forTypeNamed: nestedName) let structVar = scope.variable("struct") printer.write( - "const \(structVar) = \(JSGlueVariableScope.reservedStructHelpers).\(nestedName.replacingOccurrences(of: ".", with: "_")).lift();" + "const \(structVar) = \(JSGlueVariableScope.reservedStructHelpers).\(nestedBase).lift();" ) return [structVar] } @@ -2608,7 +2944,7 @@ private extension BridgeType { return .inlineFlag case .closure: return .inlineFlag - case .swiftStruct, .array, .dictionary, .void, .namespaceEnum: + case .swiftStruct, .array, .dictionary, .void, .namespaceEnum, .generic: return .stackABI case .nullable(let wrapped, _): return wrapped.optionalConvention @@ -2706,7 +3042,7 @@ private extension BridgeType { return [("caseId", .i32)] case .closure: return [("funcRef", .i32)] - case .void, .namespaceEnum, .swiftStruct, .array, .dictionary: + case .void, .namespaceEnum, .swiftStruct, .array, .dictionary, .generic: return [] case .nullable(let wrapped, _): return wrapped.wasmParams diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSIntrinsicRegistry.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSIntrinsicRegistry.swift index e3654e89f..5c6596bcf 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSIntrinsicRegistry.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSIntrinsicRegistry.swift @@ -7,6 +7,20 @@ final class JSIntrinsicRegistry { private var entries: [String: [String]] = [:] var classNamespaces: [String: [String]] = [:] + /// Maps a type name as carried by `BridgeType` (struct ABI name, class name, + /// enum name, ...) to the module that declares it, so generated identifiers + /// derived from type names can be module-qualified. + /// + /// The whole link output shares one JS scope, so two modules declaring a + /// same-named `@JS` type would otherwise mint the same identifier. + var typeOwnerModules: [String: String] = [:] + + /// Module-scope `{ lower, lift }` codec helpers, one per type shape, in + /// dependency order: a composed codec is appended after the codecs it is + /// built from, so the emitted `const`s can be evaluated top to bottom. + private var codecNameOrder: [String] = [] + private var codecBodies: [String: [String]] = [:] + var isEmpty: Bool { entries.isEmpty } @@ -18,9 +32,34 @@ final class JSIntrinsicRegistry { entries[name] = printer.lines } + /// Registers a named codec helper once per name. + /// + /// `build` may itself register the codecs this one is composed from; those + /// are appended first, which is what keeps the emitted declarations in a + /// valid evaluation order. + func registerNamedCodec(name: String, build: (CodeFragmentPrinter) throws -> Void) rethrows { + guard codecBodies[name] == nil else { return } + let printer = CodeFragmentPrinter() + try build(printer) + guard codecBodies[name] == nil else { return } + codecBodies[name] = printer.lines + codecNameOrder.append(name) + } + + var hasNamedCodecs: Bool { + !codecNameOrder.isEmpty + } + + func emitNamedCodecLines() -> [String] { + codecNameOrder.flatMap { codecBodies[$0] ?? [] } + } + func reset() { entries.removeAll() classNamespaces.removeAll() + typeOwnerModules.removeAll() + codecNameOrder.removeAll() + codecBodies.removeAll() } func emitLines() -> [String] { diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 21704d1c9..641105d12 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -22,6 +22,23 @@ extension NamespacedExportedType { public struct ABINameGenerator { static let prefixComponent = "bjs" + /// ABI parameter name carrying the runtime type ID for the generic parameter at `index`. + public static func genericTypeIdParameterName(index: Int) -> String { "_generic\(index)TypeId" } + + /// Name of the per-module type-handle registration function. The wasm module + /// exports it under this name, and it calls back into a JS import hook of the + /// same name (in the `bjs` import namespace) with a buffer of type IDs. + public static func typeRegistrationFunctionName(moduleName: String) -> String { + "bjs_\(moduleName)_register_type_handles" + } + + /// Name of the core type-handle registration function. Unlike the per-module + /// ones, this is defined once in the JavaScriptKit library (see + /// `_bjs_core_register_type_handles` in `BridgeJSIntrinsics.swift`) so the + /// primitive handles exist exactly once in the final binary and the JS glue + /// registers their codecs once per linked bundle. + public static let coreTypeRegistrationFunctionName = "bjs_core_register_type_handles" + /// Generates ABI name using standardized namespace + context pattern public static func generateABIName( baseName: String, @@ -273,10 +290,120 @@ public enum BridgeType: Codable, Equatable, Hashable, Sendable { case namespaceEnum(String) case swiftProtocol(String) case swiftStruct(String) + case generic(String) indirect case closure(ClosureSignature, useJSTypedClosure: Bool) indirect case alias(name: String, underlying: BridgeType) } +extension BridgeType { + public var referencedGenericName: String? { + switch self { + case .generic(let name): return name + case .array(.generic(let name)): return name + case .nullable(.generic(let name), _): return name + case .dictionary(.generic(let name)): return name + default: return nil + } + } + + public static let genericBridgeablePrimitives: [(token: String, type: BridgeType)] = [ + ("Bool", .bool), + ("Int", .integer(.int)), + ("Int8", .integer(.int8)), + ("UInt8", .integer(.uint8)), + ("Int16", .integer(.int16)), + ("UInt16", .integer(.uint16)), + ("Int32", .integer(.int32)), + ("UInt32", .integer(.uint32)), + ("UInt", .integer(.uint)), + ("Int64", .integer(.int64)), + ("UInt64", .integer(.uint64)), + ("Float", .float), + ("Double", .double), + ("String", .string), + ("JSValue", .jsValue), + ] + +} + +// MARK: - Generic type registration + +/// One `BridgedSwiftGenericBridgeable` type participating in generic bridging. +/// +/// `swiftName` is the Swift expression naming the type (used by Swift codegen to +/// read `.bridgeJSTypeID`); `bridgeType` describes the stack ABI (used +/// by the JS link layer to emit the matching codec). +public struct GenericBridgeableTypeEntry: Sendable { + public let swiftName: String + public let bridgeType: BridgeType + + public init(swiftName: String, bridgeType: BridgeType) { + self.swiftName = swiftName + self.bridgeType = bridgeType + } +} + +extension ExportedEnum { + /// The `BridgeType` an enum bridges as when used as a generic argument, or + /// `nil` when it can't be one (namespace enums). + public var genericBridgeType: BridgeType? { + switch enumType { + case .simple: + return .caseEnum(name) + case .rawValue: + guard let rawType = rawType else { return nil } + return .rawValueEnum(name, rawType) + case .associatedValue: + return .associatedValueEnum(name) + case .namespace: + return nil + } + } +} + +extension ExportedSkeleton { + /// The module's `@JS` types that conform to `BridgedSwiftGenericBridgeable`. + /// The order is the contract between the Swift registration function and the + /// JS codec array; both derive it from this skeleton, so they line up. + public var genericBridgeableTypeEntries: [GenericBridgeableTypeEntry] { + var entries: [GenericBridgeableTypeEntry] = [] + for structDef in structs { + entries.append( + GenericBridgeableTypeEntry( + swiftName: structDef.swiftCallName, + bridgeType: .swiftStruct(structDef.abiName) + ) + ) + } + for klass in classes where klass.isFinal == true { + entries.append( + GenericBridgeableTypeEntry(swiftName: klass.swiftCallName, bridgeType: .swiftHeapObject(klass.name)) + ) + } + for enumDef in enums { + guard let bridgeType = enumDef.genericBridgeType else { continue } + entries.append(GenericBridgeableTypeEntry(swiftName: enumDef.swiftCallName, bridgeType: bridgeType)) + } + return entries + } +} + +extension BridgeJSSkeleton { + /// The ordered list of types this module registers type handles for, or + /// `nil` when it emits no registration function. + /// + /// Only the module's own `@JS` types appear here: the core (primitive) + /// handles are owned by the JavaScriptKit library, which registers them once + /// for the whole binary via ``ABINameGenerator/coreTypeRegistrationFunctionName``. + /// A module that only *uses* generics therefore needs no registration + /// function of its own. + public var typeRegistrationEntries: [GenericBridgeableTypeEntry]? { + let exportedEntries = exported?.genericBridgeableTypeEntries ?? [] + guard !exportedEntries.isEmpty else { return nil } + return exportedEntries + } +} + public enum WasmCoreType: String, Codable, Sendable { case i32, i64, f32, f64, pointer } @@ -905,6 +1032,7 @@ public struct ExportedClass: Codable, NamespacedExportedType { public var namespace: [String]? public var identityMode: Bool? // nil = use config default, true/false = override public var documentation: String? + public var isFinal: Bool? public init( name: String, @@ -915,7 +1043,8 @@ public struct ExportedClass: Codable, NamespacedExportedType { properties: [ExportedProperty] = [], namespace: [String]? = nil, identityMode: Bool? = nil, - documentation: String? = nil + documentation: String? = nil, + isFinal: Bool? = nil ) { self.name = name self.swiftCallName = swiftCallName @@ -926,6 +1055,7 @@ public struct ExportedClass: Codable, NamespacedExportedType { self.namespace = namespace self.identityMode = identityMode self.documentation = documentation + self.isFinal = isFinal } } @@ -1254,6 +1384,9 @@ public struct ImportedFunctionSkeleton: Codable { /// determine the access level of bridge-generated helpers (e.g. typed /// closure inits) that surface through this function's signature. public let accessLevel: BridgeJSAccessLevel + public let genericParameters: [String]? + public var genericParameterNames: [String] { genericParameters ?? [] } + public var isGeneric: Bool { !genericParameterNames.isEmpty } public var resolvedJSName: String { jsName ?? name } @@ -1265,7 +1398,8 @@ public struct ImportedFunctionSkeleton: Codable { returnType: BridgeType, effects: Effects = Effects(isAsync: false, isThrows: true), documentation: String? = nil, - accessLevel: BridgeJSAccessLevel = .internal + accessLevel: BridgeJSAccessLevel = .internal, + genericParameters: [String]? = nil ) { self.name = name self.jsName = jsName @@ -1275,10 +1409,11 @@ public struct ImportedFunctionSkeleton: Codable { self.effects = effects self.documentation = documentation self.accessLevel = accessLevel + self.genericParameters = genericParameters } private enum CodingKeys: String, CodingKey { - case name, jsName, from, parameters, returnType, effects, documentation, accessLevel + case name, jsName, from, parameters, returnType, effects, documentation, accessLevel, genericParameters } public init(from decoder: any Decoder) throws { @@ -1291,6 +1426,7 @@ public struct ImportedFunctionSkeleton: Codable { self.effects = try container.decode(Effects.self, forKey: .effects) self.documentation = try container.decodeIfPresent(String.self, forKey: .documentation) self.accessLevel = try container.decodeIfPresent(BridgeJSAccessLevel.self, forKey: .accessLevel) ?? .internal + self.genericParameters = try container.decodeIfPresent([String].self, forKey: .genericParameters) } public func abiName(context: ImportedTypeSkeleton?) -> String { @@ -1311,20 +1447,29 @@ public struct ImportedConstructorSkeleton: Codable { /// Source access level of the originating Swift `init`. Inherits from the /// enclosing `@JSClass` type when not annotated explicitly. public let accessLevel: BridgeJSAccessLevel + public let genericParameters: [String]? + public var genericParameterNames: [String] { genericParameters ?? [] } + public var isGeneric: Bool { !genericParameterNames.isEmpty } - public init(parameters: [Parameter], accessLevel: BridgeJSAccessLevel = .internal) { + public init( + parameters: [Parameter], + accessLevel: BridgeJSAccessLevel = .internal, + genericParameters: [String]? = nil + ) { self.parameters = parameters self.accessLevel = accessLevel + self.genericParameters = genericParameters } private enum CodingKeys: String, CodingKey { - case parameters, accessLevel + case parameters, accessLevel, genericParameters } public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.parameters = try container.decode([Parameter].self, forKey: .parameters) self.accessLevel = try container.decodeIfPresent(BridgeJSAccessLevel.self, forKey: .accessLevel) ?? .internal + self.genericParameters = try container.decodeIfPresent([String].self, forKey: .genericParameters) } public func abiName(context: ImportedTypeSkeleton) -> String { @@ -1571,6 +1716,17 @@ public struct ImportedFileSkeleton: Codable { } } +extension ImportedFileSkeleton { + public var hasGenericDeclarations: Bool { + functions.contains(where: \.isGeneric) + || types.contains { + $0.methods.contains(where: \.isGeneric) + || $0.staticMethods.contains(where: \.isGeneric) + || ($0.constructor?.isGeneric ?? false) + } + } +} + public struct ImportedModuleSkeleton: Codable { public var children: [ImportedFileSkeleton] @@ -1579,6 +1735,12 @@ public struct ImportedModuleSkeleton: Codable { } } +extension ImportedModuleSkeleton { + public var hasGenericDeclarations: Bool { + children.contains { $0.hasGenericDeclarations } + } +} + // MARK: - Closure signature collection visitor public struct ClosureSignatureCollectorVisitor: BridgeSkeletonVisitor { @@ -1753,7 +1915,7 @@ extension BridgeType { case .bool, .integer, .float, .double, .string, .jsValue, .jsObject, .swiftHeapObject, .unsafePointer, .swiftProtocol, .void, .caseEnum, .rawValueEnum, .associatedValueEnum, .swiftStruct, - .namespaceEnum, .closure: + .namespaceEnum, .closure, .generic: return self } } @@ -1800,6 +1962,8 @@ extension BridgeType { return nil case .alias(_, let underlying): return underlying.abiReturnType + case .generic: + return nil } } @@ -1891,6 +2055,8 @@ extension BridgeType { // `name` is the namespace-qualified swiftCallName (unique), so the underlying // representation isn't mangled in - aliases bridge via their JS type's ABI. return "Al\(name.count)\(name)" + case .generic(let name): + return "\(name.count)\(name)T" } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift index 140ebda63..96d9c4705 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift @@ -240,9 +240,12 @@ import BridgeJSUtilities return try exporter?.finalize() } + // Type-handle registration is shared by exported types and generic imports. + let typeRegistration = GenericTypeRegistrationCodegen().render(for: skeleton) + // Combine and write unified Swift output let outputSwiftURL = outputDirectory.appending(path: "BridgeJS.swift") - let combinedSwift = [closureSupport, exportResult, importResult].compactMap { $0 } + let combinedSwift = [closureSupport, exportResult, importResult, typeRegistration].compactMap { $0 } let outputSwift = combineGeneratedSwift( combinedSwift, importingExternalModules: skeleton.usedExternalModules diff --git a/Plugins/BridgeJS/Sources/BridgeJSToolInternal/BridgeJSToolInternal.swift b/Plugins/BridgeJS/Sources/BridgeJSToolInternal/BridgeJSToolInternal.swift index cb6a5481c..971c9608e 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSToolInternal/BridgeJSToolInternal.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSToolInternal/BridgeJSToolInternal.swift @@ -98,7 +98,8 @@ import ArgumentParser skeleton: $0 ).finalize() } - let combinedSwift = [exported, imported].compactMap { $0 } + let typeRegistration = GenericTypeRegistrationCodegen().render(for: skeleton) + let combinedSwift = [exported, imported, typeRegistration].compactMap { $0 } print(combinedSwift.joined(separator: "\n\n")) } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCodegenTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCodegenTests.swift index 60b2fd485..6d2f3d453 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCodegenTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCodegenTests.swift @@ -141,6 +141,9 @@ import Testing swiftParts.append(s) } } + if let typeRegistration = GenericTypeRegistrationCodegen().render(for: skeleton) { + swiftParts.append(typeRegistration) + } let combinedSwift = swiftParts .map { $0.trimmingCharacters(in: .newlines) } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift index 642debedc..87e3f2b7e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift @@ -139,6 +139,226 @@ import Testing try snapshot(bridgeJSLink: bridgeJSLink, name: "MixedModules") } + private func linkedJS(forFixture input: String) throws -> String { + let url = Self.inputsDirectory.appendingPathComponent(input) + let name = url.deletingPathExtension().lastPathComponent + let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) + let importSwift = SwiftToSkeleton( + progress: .silent, + moduleName: "TestModule", + exposeToGlobal: false, + externalModuleIndex: .empty + ) + importSwift.addSourceFile(sourceFile, inputFilePath: "\(name).swift") + let importResult = try importSwift.finalize() + var bridgeJSLink = BridgeJSLink(sharedMemory: false) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let unifiedData = try encoder.encode(importResult) + try bridgeJSLink.addSkeletonFile(data: unifiedData) + return try bridgeJSLink.link().0 + } + + @Test + func genericRuntimeIsGatedToGenericBuilds() throws { + let genericJS = try linkedJS(forFixture: "GenericImports.swift") + #expect(genericJS.contains("__bjs_codecByTypeId")) + #expect(genericJS.contains("__bjs_primitiveCodecs")) + #expect(genericJS.contains("bjs[\"bjs_TestModule_register_type_handles\"] = function(base, count) {")) + #expect(genericJS.contains("instance.exports[\"bjs_TestModule_register_type_handles\"]();")) + // Eager registration hook: called by the instantiate.js template right + // after WASI initialization (the lazy guard remains as a fallback). + #expect(genericJS.contains("afterInitialize: () => {")) + + // Modules with @JS types but no generic declarations still emit a Swift + // registration export (their types may be used by a dependent module's + // generic function), so the link layer must install a no-op hook for the + // wasm import — but the generic runtime itself must be omitted. The + // shared codec runtime (combinators + primitive codecs) gates + // independently: it is emitted because the fixture bridges an optional + // struct through the container stack ABI. + let nonGenericJS = try linkedJS(forFixture: "SwiftStructImports.swift") + #expect(!nonGenericJS.contains("__bjs_codecByTypeId")) + // The composed codec is hoisted to a module-scope const, so the call site + // only reads it. + #expect( + nonGenericJS.contains( + "const __bjs_codec_Optional_TestModule_Point = __bjs_optionalCodec(__bjs_codec_TestModule_Point);" + ) + ) + #expect(nonGenericJS.contains("__bjs_codec_Optional_TestModule_Point.lower(ret);")) + #expect(nonGenericJS.contains("__bjs_primitiveCodecs")) + #expect(nonGenericJS.contains("bjs[\"bjs_TestModule_register_type_handles\"] = function() {};")) + #expect(!nonGenericJS.contains("instance.exports[\"bjs_TestModule_register_type_handles\"]();")) + // Without the generic runtime the hook is not emitted at all; the + // instantiate template calls it with optional chaining. + #expect(!nonGenericJS.contains("afterInitialize: () => {")) + + // Builds that bridge no containers at all pay nothing for the shared + // codec runtime either. + let containerFreeJS = try linkedJS(forFixture: "PrimitiveParameters.swift") + #expect(!containerFreeJS.contains("__bjs_arrayCodec")) + #expect(!containerFreeJS.contains("__bjs_optionalCodec")) + #expect(!containerFreeJS.contains("__bjs_dictCodec")) + #expect(!containerFreeJS.contains("__bjs_primitiveCodecs")) + #expect(!containerFreeJS.contains("__bjs_stringCodec")) + } + + @Test + func distinctTypeNamesAcrossModulesLinkWithHandleIdentity() throws { + // Type identity is pointer-based (each type owns a BridgeJSTypeHandle), + // so each module registers its own handle IDs against its own codec + // array, and every identifier minted from a type name is qualified with + // the declaring module. + let first = try makeSkeleton( + """ + @JS public struct Point { + public var x: Int + @JS public init(x: Int) { self.x = x } + } + + @JSFunction func identity(_ value: T) throws(JSException) -> T + """, + moduleName: "FirstModule" + ) + let second = try makeSkeleton( + """ + @JS public struct Coordinate { + public var x: Int + @JS public init(x: Int) { self.x = x } + } + """, + moduleName: "SecondModule" + ) + let bridgeJSLink = BridgeJSLink(skeletons: [first, second], sharedMemory: false) + let js = try bridgeJSLink.link().outputJs + #expect(js.contains("bjs[\"bjs_FirstModule_register_type_handles\"] = function(base, count) {")) + #expect(js.contains("bjs[\"bjs_SecondModule_register_type_handles\"] = function(base, count) {")) + + // Each module's helper factory, the `const` holding it, and the + // `structHelpers` key are all module-qualified, so nothing collides. + #expect(js.contains("const __bjs_createStructHelpers_FirstModule_Point = () => ({")) + #expect(js.contains("const __bjs_createStructHelpers_SecondModule_Coordinate = () => ({")) + #expect( + js.contains( + "const __bjs_helpers_FirstModule_Point = __bjs_createStructHelpers_FirstModule_Point();" + ) + ) + #expect(js.contains("structHelpers.FirstModule_Point = __bjs_helpers_FirstModule_Point;")) + #expect(js.contains("structHelpers.SecondModule_Coordinate = __bjs_helpers_SecondModule_Coordinate;")) + // References resolve to the declaring module's entry, not to a bare name. + #expect(!js.contains("structHelpers.Point")) + #expect(!js.contains("structHelpers.Coordinate")) + } + + /// Two modules declaring a `@JS` type of the same name cannot be linked into + /// one glue module: `BridgeType` carries only the type name, the top-level + /// `const Values` / `class ` declarations are minted from it, and + /// so are the `bjs["swift_js_struct_*_"]` wasm import names. The + /// link step rejects them instead of emitting glue that throws at load time + /// or binds one module's values to the other module's helpers. + @Test(arguments: [ + ( + "struct", + """ + @JS public struct Point { + public var x: Int + @JS public init(x: Int) { self.x = x } + } + """, + "Duplicate @JS struct 'Point' declared by modules 'FirstModule' and 'SecondModule'." + ), + ( + "enum", + """ + @JS public enum Tagged { + case number(Int) + case text(String) + } + """, + "Duplicate @JS enum 'Tagged' declared by modules 'FirstModule' and 'SecondModule'." + ), + ( + "class", + """ + @JS public class Box { + @JS public init() {} + } + """, + "Duplicate @JS class 'Box' declared by modules 'FirstModule' and 'SecondModule'." + ), + ]) + func sameTypeNameAcrossModulesIsRejected(kind: String, source: String, expectedMessage: String) throws { + let first = try makeSkeleton(source, moduleName: "FirstModule") + let second = try makeSkeleton(source, moduleName: "SecondModule") + let bridgeJSLink = BridgeJSLink(skeletons: [first, second], sharedMemory: false) + #expect(throws: BridgeJSLinkError.self) { + _ = try bridgeJSLink.link() + } + do { + _ = try bridgeJSLink.link() + } catch let error as BridgeJSLinkError { + #expect(error.message.contains(expectedMessage)) + #expect(error.message.contains("Rename one of them")) + } + } + + /// A namespace enum declares no runtime value of its own, so two modules may + /// keep contributing to the same namespace. + @Test + func sameNamespaceEnumNameAcrossModulesIsAllowed() throws { + let source = """ + @JS public enum Shared { + @JS public static func ping() -> Int { 0 } + } + """ + let first = try makeSkeleton(source, moduleName: "FirstModule") + let second = try makeSkeleton(source, moduleName: "SecondModule") + _ = try BridgeJSLink(skeletons: [first, second], sharedMemory: false).link() + } + + @Test + func moduleWithoutGenericsStillRegistersItsTypeCodecs() throws { + // A module cannot know whether a dependent module will pass its types to + // a generic function, so a module with @JS types but no generic + // declaration of its own still registers a codec for each of them, and + // the linked glue drives every module's registration export. This is + // what lets a type defined in Core be the generic argument of a generic + // import declared in App. + let core = try makeSkeleton( + """ + @JS public struct Vector3D { + public var x: Int + @JS public init(x: Int) { self.x = x } + } + """, + moduleName: "Core" + ) + let app = try makeSkeleton( + """ + @JSFunction func identity(_ value: T) throws(JSException) -> T + """, + moduleName: "App" + ) + + // Core declares nothing imported at all, yet still registers its types. + #expect(core.imported == nil) + let coreEntries = try #require(core.typeRegistrationEntries) + #expect(coreEntries.contains { $0.swiftName == "Vector3D" }) + + // App exports no @JS types, so it owns no handles: only the core + // (library-owned) registration and Core's own registration run. + #expect(app.typeRegistrationEntries == nil) + + let js = try BridgeJSLink(skeletons: [core, app], sharedMemory: false).link().outputJs + #expect(js.contains("instance.exports[\"bjs_core_register_type_handles\"]();")) + #expect(js.contains("instance.exports[\"bjs_Core_register_type_handles\"]();")) + #expect(!js.contains("bjs_App_register_type_handles")) + // `lower(v)` is unique to a codec literal in a registration array; + // `structHelpers.Core_Vector3D` on its own is emitted for every @JS struct. + #expect(js.contains("structHelpers.Core_Vector3D.lower(v);")) + } + @Test func perClassIdentityModeFromAnnotation() throws { let url = Self.inputsDirectory.appendingPathComponent("IdentityModeClass.swift") diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/CodegenTestSupport.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/CodegenTestSupport.swift new file mode 100644 index 000000000..b855982b1 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/CodegenTestSupport.swift @@ -0,0 +1,51 @@ +import Foundation +import SwiftParser +import SwiftSyntax +import Testing + +@testable import BridgeJSLink +@testable import BridgeJSCore +@testable import BridgeJSSkeleton + +func makeSkeleton( + _ source: String, + moduleName: String = "TestModule", + dependencies: [(moduleName: String, skeleton: BridgeJSSkeleton)] = [] +) throws -> BridgeJSSkeleton { + let swiftAPI = SwiftToSkeleton( + progress: .silent, + moduleName: moduleName, + exposeToGlobal: false, + externalModuleIndex: ExternalModuleIndex(dependencies: dependencies) + ) + swiftAPI.addSourceFile(Parser.parse(source: source), inputFilePath: "\(moduleName).swift") + return try swiftAPI.finalize() +} + +func expectDiagnostic( + source: String, + moduleName: String = "App", + contains message: String, + sourceLocation: Testing.SourceLocation = #_sourceLocation +) { + do { + _ = try makeSkeleton(source, moduleName: moduleName) + Issue.record("Expected diagnostic but resolution succeeded", sourceLocation: sourceLocation) + } catch let error as BridgeJSCoreDiagnosticError { + let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n") + #expect(combined.contains(message), sourceLocation: sourceLocation) + } catch { + Issue.record("Unexpected error: \(error)", sourceLocation: sourceLocation) + } +} + +func linkSource(_ source: String, moduleName: String = "TestModule") throws -> (js: String, dts: String) { + let skeleton = try makeSkeleton(source, moduleName: moduleName) + var bridgeJSLink = BridgeJSLink(sharedMemory: false) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let unifiedData = try encoder.encode(skeleton) + try bridgeJSLink.addSkeletonFile(data: unifiedData) + let result = try bridgeJSLink.link() + return (result.outputJs, result.outputDts) +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/CoreTypeRegistrationContractTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/CoreTypeRegistrationContractTests.swift new file mode 100644 index 000000000..c3995126b --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/CoreTypeRegistrationContractTests.swift @@ -0,0 +1,48 @@ +import Foundation +import Testing + +@testable import BridgeJSSkeleton + +/// The core (primitive) generic type handles are registered by the JavaScriptKit +/// library itself, not by generated code, so the ordering contract between the +/// Swift buffer and the JS codec array spans two repositories' worth of source: +/// `_bjs_core_register_type_handles` in `Sources/JavaScriptKit/BridgeJSIntrinsics.swift` +/// and `BridgeType.genericBridgeablePrimitives` here. +/// +/// The generated glue checks the *count* at registration time; this test checks +/// the *order* at build time so a reordering cannot silently mis-pair codecs. +@Suite struct CoreTypeRegistrationContractTests { + private static var repositoryRoot: URL { + URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() // BridgeJSToolTests + .deletingLastPathComponent() // Tests + .deletingLastPathComponent() // BridgeJS + .deletingLastPathComponent() // Plugins + .deletingLastPathComponent() // + } + + @Test + func coreTypeHandleOrderMatchesGenericBridgeablePrimitives() throws { + let intrinsics = Self.repositoryRoot + .appendingPathComponent("Sources/JavaScriptKit/BridgeJSIntrinsics.swift") + let source = try String(contentsOf: intrinsics, encoding: .utf8) + + let beginMarker = "// BEGIN bjs_core_type_handles" + let endMarker = "// END bjs_core_type_handles" + guard let begin = source.range(of: beginMarker), let end = source.range(of: endMarker) else { + Issue.record("Could not find the core type handle list markers in \(intrinsics.path)") + return + } + + let names = + source[begin.upperBound.. String? in + let trimmed = line.trimmingCharacters(in: .whitespaces) + guard trimmed.hasSuffix(".bridgeJSTypeID,") else { return nil } + return String(trimmed.dropLast(".bridgeJSTypeID,".count)) + } + + #expect(names == BridgeType.genericBridgeablePrimitives.map(\.token)) + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericCodecExpressionTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericCodecExpressionTests.swift new file mode 100644 index 000000000..d0666f89f --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericCodecExpressionTests.swift @@ -0,0 +1,41 @@ +import Testing + +@testable import BridgeJSLink +@testable import BridgeJSSkeleton + +/// The codec a generic call site instantiates for each supported wrapped form +/// of a generic parameter (`T`, `[T]`, `T?`, `[String: T]`). +@Suite struct GenericCodecExpressionTests { + @Test func bareGenericUsesTheElementCodecDirectly() { + #expect(GenericJSCodegen.genericCodecExpression(type: .generic("T"), codec: "c") == "c") + } + + @Test func wrappedGenericsInstantiateTheSharedCombinators() { + #expect( + GenericJSCodegen.genericCodecExpression(type: .array(.generic("T")), codec: "c") + == "__bjs_arrayCodec(c)" + ) + #expect( + GenericJSCodegen.genericCodecExpression(type: .dictionary(.generic("T")), codec: "c") + == "__bjs_dictCodec(c)" + ) + } + + /// The optional combinator carries the null-vs-undefined flavour in its + /// second argument, so the generic path must not drop `JSOptionalKind`. + @Test func optionalGenericPreservesTheOptionalKind() { + #expect( + GenericJSCodegen.genericCodecExpression(type: .nullable(.generic("T"), .null), codec: "c") + == "__bjs_optionalCodec(c)" + ) + #expect( + GenericJSCodegen.genericCodecExpression(type: .nullable(.generic("T"), .undefined), codec: "c") + == "__bjs_optionalCodec(c, true)" + ) + } + + @Test func nonGenericTypesHaveNoGenericCodec() { + #expect(GenericJSCodegen.genericCodecExpression(type: .string, codec: "c") == nil) + #expect(GenericJSCodegen.genericCodecExpression(type: .array(.integer(.int)), codec: "c") == nil) + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift new file mode 100644 index 000000000..61e1e8a84 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift @@ -0,0 +1,65 @@ +import Testing + +@testable import BridgeJSCore + +@Suite struct GenericExportDiagnosticsTests { + + @Test + func genericExportedFunctionRejected() { + expectDiagnostic( + source: """ + @JS public func identity(_ value: T) -> T { value } + """, + contains: "Generic parameters on exported @JS functions are not supported yet" + ) + } + + @Test + func genericMethodOnExportedClassRejected() { + expectDiagnostic( + source: """ + @JS final class Box { + @JS init() {} + @JS func wrap(_ value: T) -> T { value } + } + """, + contains: "Generic parameters on exported @JS functions are not supported yet" + ) + } + + @Test + func genericMethodOnExportedStructRejected() { + expectDiagnostic( + source: """ + @JS struct Pair { + @JS init() {} + @JS func first(_ value: T) -> T { value } + } + """, + contains: "Generic parameters on exported @JS functions are not supported yet" + ) + } + + @Test + func genericStaticMethodOnExportedEnumRejected() { + expectDiagnostic( + source: """ + @JS enum Factory { + case primary + @JS static func one(_ value: T) -> T { value } + } + """, + contains: "Generic parameters on exported @JS functions are not supported yet" + ) + } + + @Test + func unconstrainedGenericExportedFunctionRejected() { + expectDiagnostic( + source: """ + @JS public func identity(_ value: T) -> T { value } + """, + contains: "Generic parameters on exported @JS functions are not supported yet" + ) + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericImportDiagnosticsTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericImportDiagnosticsTests.swift new file mode 100644 index 000000000..fc91eea7d --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericImportDiagnosticsTests.swift @@ -0,0 +1,214 @@ +import Foundation +import SwiftParser +import SwiftSyntax +import Testing + +@testable import BridgeJSCore +@testable import BridgeJSSkeleton + +@Suite struct GenericImportDiagnosticsTests { + + @Test + func genericParameterRequiresBridgeableConstraint() { + expectDiagnostic( + source: """ + @JSFunction func identity(_ value: T) throws(JSException) -> T + """, + contains: "Generic parameter 'T' must be constrained to 'BridgedSwiftGenericBridgeable'" + ) + } + + @Test + func genericWhereClauseUnsupported() { + expectDiagnostic( + source: """ + @JSFunction func identity(_ value: T) throws(JSException) -> T where T: Sendable + """, + contains: "'where' clauses are not supported on @JSFunction" + ) + } + + @Test + func asyncGenericImportUnsupported() { + expectDiagnostic( + source: """ + @JSFunction func identityAsync(_ value: T) async throws(JSException) -> T + """, + contains: "Generic @JSFunction declarations cannot be 'async' yet." + ) + } + + @Test + func genericImportedMethodIsParsed() throws { + let skeleton = try makeSkeleton( + """ + @JSClass struct Box { + @JSFunction func member(_ value: T) throws(JSException) -> T + } + """, + moduleName: "App" + ) + let imported = try #require(skeleton.imported) + let types = imported.children.flatMap { $0.types } + let box = try #require(types.first { $0.name == "Box" }) + let method = try #require(box.methods.first { $0.name == "member" }) + #expect(method.genericParameters == ["T"]) + } + + @Test + func genericImportedConstructorIsParsed() throws { + let skeleton = try makeSkeleton( + """ + @JSClass struct Box { + @JSFunction init(_ value: T) throws(JSException) + } + """, + moduleName: "App" + ) + let imported = try #require(skeleton.imported) + let types = imported.children.flatMap { $0.types } + let box = try #require(types.first { $0.name == "Box" }) + let constructor = try #require(box.constructor) + #expect(constructor.genericParameters == ["T"]) + #expect(constructor.parameters.map(\.type) == [.generic("T")]) + } + + @Test + func genericImportedConstructorUnconstrainedParamIsRejected() { + expectDiagnostic( + source: """ + @JSClass struct Box { + @JSFunction init(_ value: T) throws(JSException) + } + """, + contains: + "Generic parameter 'T' must be constrained to 'BridgedSwiftGenericBridgeable' to be used with @JSFunction." + ) + } + + @Test + func genericImportedConstructorUnusedTypeParamIsRejected() { + expectDiagnostic( + source: """ + @JSClass struct Box { + @JSFunction init(_ value: Int) throws(JSException) + } + """, + contains: + "The generic parameter 'T' must be used in a parameter of a generic @JSFunction initializer." + ) + } + + @Test + func genericImportedConstructorAsyncIsRejected() { + expectDiagnostic( + source: """ + @JSClass struct Box { + @JSFunction init(_ value: T) async throws(JSException) + } + """, + contains: "Generic @JSFunction declarations cannot be 'async' yet." + ) + } + + @Test + func genericImportedConstructorUnsupportedWrapperFormIsRejected() { + expectDiagnostic( + source: """ + @JSClass struct Box { + @JSFunction init(_ value: [[T]]) throws(JSException) + } + """, + contains: "may only be used as a bare type" + ) + } + + @Test(arguments: [ + ("[[T]]", "@JSFunction func f(_ v: [[T]]) throws(JSException)"), + ("[T?]", "@JSFunction func f(_ v: [T?]) throws(JSException)"), + ("T??", "@JSFunction func f(_ v: T??) throws(JSException)"), + ("[Int: T]", "@JSFunction func f(_ v: [Int: T]) throws(JSException)"), + ]) + func unsupportedGenericWrapperFormsInParameter(label: String, source: String) { + expectDiagnostic( + source: source, + contains: "may only be used as a bare type" + ) + } + + @Test(arguments: [ + ("[[T]]", "@JSFunction func f(_ v: T) throws(JSException) -> [[T]]"), + ("[T?]", "@JSFunction func f(_ v: T) throws(JSException) -> [T?]"), + ("T??", "@JSFunction func f(_ v: T) throws(JSException) -> T??"), + ("[Int: T]", "@JSFunction func f(_ v: T) throws(JSException) -> [Int: T]"), + ]) + func unsupportedGenericWrapperFormsInReturn(label: String, source: String) { + expectDiagnostic( + source: source, + contains: "may only be used as a bare type" + ) + } + + @Test + func genericImportedMethodAsyncIsRejected() { + expectDiagnostic( + source: """ + @JSClass struct Box { + @JSFunction func member(_ value: T) async throws(JSException) -> T + } + """, + contains: "Generic @JSFunction declarations cannot be 'async' yet." + ) + } + + @Test + func genericImportedMethodUnconstrainedParamIsRejected() { + expectDiagnostic( + source: """ + @JSClass struct Box { + @JSFunction func member(_ value: T) throws(JSException) -> T + } + """, + contains: + "Generic parameter 'T' must be constrained to 'BridgedSwiftGenericBridgeable' to be used with @JSFunction." + ) + } + + @Test + func genericImportedFunctionUnusedTypeParamIsRejected() { + expectDiagnostic( + source: """ + @JSFunction func unused() throws(JSException) -> Int + """, + contains: + "The generic parameter 'T' must be used in a parameter or return type of a generic @JSFunction declaration." + ) + } + + @Test + func genericImportedMethodUnusedTypeParamIsRejected() { + expectDiagnostic( + source: """ + @JSClass struct Box { + @JSFunction func member() throws(JSException) -> Int + } + """, + contains: + "The generic parameter 'T' must be used in a parameter or return type of a generic @JSFunction declaration." + ) + } + + @Test + func genericImportedReturnOnlyTypeParamIsAllowed() throws { + let skeleton = try makeSkeleton( + """ + @JSFunction func make() throws(JSException) -> T + """, + moduleName: "App" + ) + let imported = try #require(skeleton.imported) + let functions = imported.children.flatMap { $0.functions } + let function = try #require(functions.first { $0.name == "make" }) + #expect(function.genericParameters == ["T"]) + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift new file mode 100644 index 000000000..e76f2861b --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift @@ -0,0 +1,63 @@ +import Testing + +@Suite struct GenericMethodOnlyModuleCodegenTests { + @Test + func importMethodOnlyEmitsJSRuntimeInfrastructure() throws { + // A module with generic imports but no exported @JS types needs a + // populated codec table, but it owns none of the entries: the core + // handles come from the JavaScriptKit library's own registration, so the + // module emits no registration function of its own. + let js = try linkSource( + """ + @JSClass struct OnlyConsumer { + @JSFunction func identity(_ value: T) throws(JSException) -> T + } + """ + ).js + #expect(js.contains("const __bjs_codecByTypeId = new Map();")) + #expect(js.contains("function __bjs_codecForTypeId(typeId) {")) + #expect(js.contains("bjs[\"bjs_core_register_type_handles\"] = function(base, count) {")) + #expect(js.contains("instance.exports[\"bjs_core_register_type_handles\"]();")) + #expect(!js.contains("bjs_TestModule_register_type_handles")) + } + + @Test + func importConstructorOnlyEmitsJSRuntimeInfrastructure() throws { + // A generic initializer alone must switch the generic runtime on: its + // thunk calls __bjs_codecForTypeId, so a module whose only generic + // declaration is an initializer would otherwise emit a call to a + // function that was never defined. + let js = try linkSource( + """ + @JSClass struct OnlyBoxed { + @JSFunction init(_ value: T) throws(JSException) + } + """ + ).js + #expect(js.contains("const __bjs_codecByTypeId = new Map();")) + #expect(js.contains("function __bjs_codecForTypeId(typeId) {")) + #expect(js.contains("bjs[\"bjs_core_register_type_handles\"] = function(base, count) {")) + #expect(js.contains("instance.exports[\"bjs_core_register_type_handles\"]();")) + #expect(!js.contains("bjs_TestModule_register_type_handles")) + } + + @Test + func exportedTypesStillRegisterTheirOwnHandlesOnly() throws { + // A module that exports @JS types registers exactly those, without + // repeating the core entries the library already owns. + let js = try linkSource( + """ + @JS struct Point { + var x: Int + @JS init(x: Int) { self.x = x } + } + @JSClass struct Consumer { + @JSFunction func identity(_ value: T) throws(JSException) -> T + } + """ + ).js + #expect(js.contains("bjs[\"bjs_TestModule_register_type_handles\"] = function(base, count) {")) + // The primitive entries appear exactly once, in the core hook. + #expect(js.components(separatedBy: "__bjs_primitiveCodecs.Bool,").count - 1 == 1) + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericImports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericImports.swift new file mode 100644 index 000000000..5fd3d0226 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericImports.swift @@ -0,0 +1,74 @@ +@JS +struct GenericPoint { + var x: Int + var y: Int +} + +@JS enum GenericColor { + case red + case green +} + +@JS enum GenericMode: String { + case light + case dark +} + +@JS enum GenericTagged { + case number(value: Int) + case text(value: String) +} + +@JS final class GenericImportBox { + @JS var value: Int + @JS init(value: Int) { + self.value = value + } + @JS func get() -> Int { + value + } +} + +@JSFunction func genericRoundTrip(_ value: T) throws(JSException) -> T + +@JSFunction func genericParse(_ json: String) throws(JSException) -> T + +@JSFunction func importGenericCombine( + _ a: T, + _ b: U +) throws(JSException) -> U + +@JSFunction func importGenericCaseDistinct( + _ a: T, + _ b: t +) throws(JSException) -> T + +@JSFunction func importGenericArray(_ values: [T]) throws(JSException) -> [T] + +@JSFunction func importGenericOptional(_ value: T?) throws(JSException) -> T? + +@JSFunction func importGenericDictionary( + _ values: [String: T] +) throws(JSException) -> [String: T] + +// A generic parameter alongside another parameter that pushes onto the shared +// stacks: both are lowered in reverse declaration order. +@JSFunction func importGenericAfterOptionalArray( + _ values: [Int]?, + _ value: T +) throws(JSException) -> T + +@JSClass struct GenericPairFactory { + @JSFunction init( + _ tag: String, + _ first: T, + _ second: U + ) throws(JSException) +} + +@JSClass struct GenericConsumer { + @JSFunction init(_ value: T) throws(JSException) + @JSFunction func accept(_ value: T) throws(JSException) + @JSFunction func identity(_ value: T) throws(JSException) -> T + @JSFunction static func box(_ value: T) throws(JSException) -> T +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/NamedCodecHelperTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/NamedCodecHelperTests.swift new file mode 100644 index 000000000..c5464c78f --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/NamedCodecHelperTests.swift @@ -0,0 +1,105 @@ +import Testing + +@testable import BridgeJSLink +@testable import BridgeJSSkeleton + +/// Every type shape gets one module-scope `{ lower, lift }` helper, shared by the +/// container combinators' element positions and by the generic type-handle +/// registration table, and composed codecs are hoisted so that no call site +/// builds one. +@Suite struct NamedCodecHelperTests { + private func codecDeclarations(in js: String) -> [String] { + js.split(separator: "\n") + .map { $0.trimmingCharacters(in: .whitespaces) } + .filter { $0.hasPrefix("const \(ContainerCodecJS.namedCodecPrefix)") } + } + + @Test + func composedCodecsAreHoistedAndReusedByCallSites() throws { + let js = try linkSource( + """ + @JS func mirror(_ values: [String: Int?]) -> [String: Int?] { values } + @JS func mirrorAgain(_ values: [String: Int?]) -> [String: Int?] { values } + """ + ).js + + // Declared once, at module scope, out of the thunks. + #expect( + codecDeclarations(in: js) == [ + "const __bjs_codec_Optional_Int = __bjs_optionalCodec(__bjs_primitiveCodecs.Int);", + "const __bjs_codec_Dict_Optional_Int = __bjs_dictCodec(__bjs_codec_Optional_Int);", + ] + ) + // Call sites only read the helper; they never compose one. + #expect(js.contains("__bjs_codec_Dict_Optional_Int.lower(values);")) + #expect(js.contains("__bjs_codec_Dict_Optional_Int.lift();")) + let composedAtCallSite = js.contains("__bjs_dictCodec(__bjs_optionalCodec(") + #expect(!composedAtCallSite) + } + + @Test + func helperNamesAreQualifiedWithTheDeclaringModule() throws { + let js = try linkSource( + """ + @JS struct Point { + var x: Int + @JS init(x: Int) { self.x = x } + } + @JS func mirror(_ points: [Point]) -> [Point] { points } + """, + moduleName: "Core" + ).js + + #expect(js.contains("const __bjs_codec_Core_Point = {")) + #expect(js.contains("const __bjs_codec_Array_Core_Point = __bjs_arrayCodec(__bjs_codec_Core_Point);")) + } + + /// The type-table entry and the element position of a container must resolve + /// to the same helper, so a type's stack ABI is described exactly once. + @Test + func registrationTableReusesTheSameHelperAsElementPositions() throws { + let js = try linkSource( + """ + @JS struct Point { + var x: Int + @JS init(x: Int) { self.x = x } + } + @JS func mirror(_ points: [Point]) -> [Point] { points } + @JSClass struct Consumer { + @JSFunction func identity(_ value: T) throws(JSException) -> T + } + """, + moduleName: "Core" + ).js + + #expect(js.contains("const __bjs_codec_Core_Point = {")) + #expect(js.contains("const __bjs_codec_Array_Core_Point = __bjs_arrayCodec(__bjs_codec_Core_Point);")) + // One entry in the registration array, referencing the same helper. + let registrationArray = + js + .components(separatedBy: "bjs[\"bjs_Core_register_type_handles\"] = function(base, count) {") + .last + .map { $0.components(separatedBy: "];")[0] } + #expect(registrationArray?.contains("__bjs_codec_Core_Point,") == true) + // The struct's marshalling code is emitted once, in its helper factory. + #expect(js.components(separatedBy: "structHelpers.Core_Point.lower(v);").count - 1 == 1) + } + + /// A string-backed raw value enum bridges exactly as `String`, so it shares + /// the string codec instead of minting a redundant helper. + @Test + func stringBackedRawValueEnumsShareTheStringCodec() throws { + let js = try linkSource( + """ + @JS enum Mode: String { + case light + case dark + } + @JS func mirror(_ modes: [Mode]) -> [Mode] { modes } + """ + ).js + + #expect(js.contains("const __bjs_codec_Array_String = __bjs_arrayCodec(__bjs_stringCodec);")) + #expect(!js.contains("__bjs_codec_TestModule_Mode")) + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.json index bcdc43375..b47afb905 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.json @@ -79,6 +79,7 @@ } ] }, + "isFinal" : true, "methods" : [ { "abiName" : "bjs_PolygonReference_snapshot", @@ -196,6 +197,7 @@ } ] }, + "isFinal" : true, "methods" : [ ], diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.swift index a9252e57f..4483de428 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.swift @@ -257,6 +257,18 @@ fileprivate func _bjs_TagReference_wrap_extern(_ pointer: UnsafeMutableRawPointe return _bjs_TagReference_wrap_extern(pointer) } +extension PolygonReference: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PolygonReference.bridgeJSMakeTypeHandle() +} + +extension TagReference: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TagReference.bridgeJSMakeTypeHandle() +} + +extension InnerTag: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = InnerTag.bridgeJSMakeTypeHandle() +} + extension Polygon: _BridgedSwiftAlias, _BridgedSwiftStackType {} extension Tag: _BridgedSwiftAlias, _BridgedSwiftStackType {} @@ -394,4 +406,21 @@ func _$Surface_label_get(_ self: JSObject) throws(JSException) -> String { throw error } return String.bridgeJSLiftReturn(ret) -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + PolygonReference.bridgeJSTypeID, + TagReference.bridgeJSTypeID, + InnerTag.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AliasInClosure.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AliasInClosure.json index d76761e0b..c9107133d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AliasInClosure.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AliasInClosure.json @@ -34,6 +34,7 @@ } ] }, + "isFinal" : true, "methods" : [ ], diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AliasInClosure.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AliasInClosure.swift index 3c87bcdcc..0a208bf70 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AliasInClosure.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AliasInClosure.swift @@ -187,4 +187,23 @@ fileprivate func _bjs_PolygonReference_wrap_extern(_ pointer: UnsafeMutableRawPo return _bjs_PolygonReference_wrap_extern(pointer) } -extension Polygon: _BridgedSwiftAlias, _BridgedSwiftStackType {} \ No newline at end of file +extension PolygonReference: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PolygonReference.bridgeJSMakeTypeHandle() +} + +extension Polygon: _BridgedSwiftAlias, _BridgedSwiftStackType {} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + PolygonReference.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift index 51c6911bd..94b8eb208 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ArrayTypes.swift @@ -501,6 +501,18 @@ fileprivate func _bjs_MultiArrayContainer_wrap_extern(_ pointer: UnsafeMutableRa return _bjs_MultiArrayContainer_wrap_extern(pointer) } +extension Point: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Point.bridgeJSMakeTypeHandle() +} + +extension Direction: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Direction.bridgeJSMakeTypeHandle() +} + +extension Status: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Status.bridgeJSMakeTypeHandle() +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_checkArray") fileprivate func bjs_checkArray_extern(_ a: Int32) -> Void @@ -643,4 +655,21 @@ func _$importProcessBooleans(_ values: [Bool]) throws(JSException) -> [Bool] { throw error } return [Bool].bridgeJSLiftReturn() -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Point.bridgeJSTypeID, + Direction.bridgeJSTypeID, + Status.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift index f2223ee7c..81c8c1c56 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift @@ -335,6 +335,18 @@ public func _bjs_asyncRoundTripEnumDictionary() -> Int32 { #endif } +extension AsyncPoint: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AsyncPoint.bridgeJSMakeTypeHandle() +} + +extension AsyncDirection: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AsyncDirection.bridgeJSMakeTypeHandle() +} + +extension AsyncTheme: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AsyncTheme.bridgeJSMakeTypeHandle() +} + @JSFunction func Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) #if arch(wasm32) @@ -713,4 +725,21 @@ func _$Promise_resolve_SD14AsyncDirectionO(_ promise: JSObject, _ value: [String let promiseValue = promise.bridgeJSLowerParameter() promise_resolve_TestModule_SD14AsyncDirectionO(promiseValue) if let error = _swift_js_take_exception() { throw error } -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + AsyncPoint.bridgeJSTypeID, + AsyncDirection.bridgeJSTypeID, + AsyncTheme.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AsyncAssociatedValueEnum.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AsyncAssociatedValueEnum.swift index 3208eda33..6776998bc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AsyncAssociatedValueEnum.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AsyncAssociatedValueEnum.swift @@ -52,6 +52,10 @@ public func _bjs_asyncRoundTripOptionalAssociatedValueEnum(_ valueIsSome: Int32, #endif } +extension AsyncPayloadResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AsyncPayloadResult.bridgeJSMakeTypeHandle() +} + @JSFunction func Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) #if arch(wasm32) @@ -113,4 +117,19 @@ func _$Promise_resolve_Sq18AsyncPayloadResultO(_ promise: JSObject, _ value: Opt let promiseValue = promise.bridgeJSLowerParameter() promise_resolve_TestModule_Sq18AsyncPayloadResultO(promiseValue, valueIsSome, valueCaseId) if let error = _swift_js_take_exception() { throw error } -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + AsyncPayloadResult.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ClassWithNestedTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ClassWithNestedTypes.swift index 52c633045..c9c291317 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ClassWithNestedTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ClassWithNestedTypes.swift @@ -174,4 +174,28 @@ fileprivate func _bjs_Account_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> #endif @inline(never) fileprivate func _bjs_Account_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_Account_wrap_extern(pointer) -} \ No newline at end of file +} + +extension Account.Credentials: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Account.Credentials.bridgeJSMakeTypeHandle() +} + +extension Account.Role: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Account.Role.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Account.Credentials.bridgeJSTypeID, + Account.Role.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DefaultParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DefaultParameters.swift index 507827646..e2e74e532 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DefaultParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DefaultParameters.swift @@ -636,4 +636,33 @@ fileprivate func _bjs_ConstructorDefaults_wrap_extern(_ pointer: UnsafeMutableRa #endif @inline(never) fileprivate func _bjs_ConstructorDefaults_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_ConstructorDefaults_wrap_extern(pointer) -} \ No newline at end of file +} + +extension Config: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Config.bridgeJSMakeTypeHandle() +} + +extension MathOperations: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = MathOperations.bridgeJSMakeTypeHandle() +} + +extension Status: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Status.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Config.bridgeJSTypeID, + MathOperations.bridgeJSTypeID, + Status.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.swift index 26a4c087e..2990eeabe 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DictionaryTypes.swift @@ -149,6 +149,10 @@ fileprivate func _bjs_Box_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int return _bjs_Box_wrap_extern(pointer) } +extension Counters: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Counters.bridgeJSMakeTypeHandle() +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_importMirrorDictionary") fileprivate func bjs_importMirrorDictionary_extern() -> Void @@ -168,4 +172,19 @@ func _$importMirrorDictionary(_ values: [String: Double]) throws(JSException) -> throw error } return [String: Double].bridgeJSLiftReturn() -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Counters.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DocComments.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DocComments.swift index f91df6c26..fab694b18 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DocComments.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/DocComments.swift @@ -314,4 +314,28 @@ fileprivate func _bjs_Greeter_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> #endif @inline(never) fileprivate func _bjs_Greeter_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_Greeter_wrap_extern(pointer) -} \ No newline at end of file +} + +extension Point: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Point.bridgeJSMakeTypeHandle() +} + +extension Color: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Color.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Point.bridgeJSTypeID, + Color.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAlias.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAlias.json index 0d63db899..c876db410 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAlias.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAlias.json @@ -31,6 +31,7 @@ } ] }, + "isFinal" : true, "methods" : [ ], diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAlias.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAlias.swift index 1e74a127b..5689b143f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAlias.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAlias.swift @@ -51,4 +51,23 @@ fileprivate func _bjs_ColorBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) - return _bjs_ColorBox_wrap_extern(pointer) } -extension Color: _BridgedSwiftAlias, _BridgedSwiftStackType {} \ No newline at end of file +extension ColorBox: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ColorBox.bridgeJSMakeTypeHandle() +} + +extension Color: _BridgedSwiftAlias, _BridgedSwiftStackType {} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + ColorBox.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAssociatedValue.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAssociatedValue.swift index 6d5549699..4ca3236f8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAssociatedValue.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAssociatedValue.swift @@ -631,4 +631,73 @@ fileprivate func _bjs_User_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> In #endif @inline(never) fileprivate func _bjs_User_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_User_wrap_extern(pointer) -} \ No newline at end of file +} + +extension Point: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Point.bridgeJSMakeTypeHandle() +} + +extension APIResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = APIResult.bridgeJSMakeTypeHandle() +} + +extension ComplexResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ComplexResult.bridgeJSMakeTypeHandle() +} + +extension Utilities.Result: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Utilities.Result.bridgeJSMakeTypeHandle() +} + +extension NetworkingResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = NetworkingResult.bridgeJSMakeTypeHandle() +} + +extension APIOptionalResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = APIOptionalResult.bridgeJSMakeTypeHandle() +} + +extension Precision: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Precision.bridgeJSMakeTypeHandle() +} + +extension CardinalDirection: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = CardinalDirection.bridgeJSMakeTypeHandle() +} + +extension TypedPayloadResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TypedPayloadResult.bridgeJSMakeTypeHandle() +} + +extension AllTypesResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AllTypesResult.bridgeJSMakeTypeHandle() +} + +extension OptionalAllTypesResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = OptionalAllTypesResult.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Point.bridgeJSTypeID, + APIResult.bridgeJSTypeID, + ComplexResult.bridgeJSTypeID, + Utilities.Result.bridgeJSTypeID, + NetworkingResult.bridgeJSTypeID, + APIOptionalResult.bridgeJSTypeID, + Precision.bridgeJSTypeID, + CardinalDirection.bridgeJSTypeID, + TypedPayloadResult.bridgeJSTypeID, + AllTypesResult.bridgeJSTypeID, + OptionalAllTypesResult.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAssociatedValueImport.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAssociatedValueImport.swift index 55d1992a3..2c275a7a3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAssociatedValueImport.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumAssociatedValueImport.swift @@ -26,6 +26,10 @@ extension PayloadSignal: _BridgedSwiftAssociatedValueEnum { } } +extension PayloadSignal: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PayloadSignal.bridgeJSMakeTypeHandle() +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_PayloadSignalControls_roundTrip_static") fileprivate func bjs_PayloadSignalControls_roundTrip_static_extern(_ signal: Int32) -> Int32 @@ -109,4 +113,19 @@ func _$PayloadSignalControls_roundTripOptional(_ self: JSObject, _ signal: Optio throw error } return Optional.bridgeJSLiftReturn(ret) -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + PayloadSignal.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumCase.swift index 66692ee14..dab981312 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumCase.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumCase.swift @@ -227,4 +227,38 @@ public func _bjs_roundTripOptionalTSDirection(_ inputIsSome: Int32, _ inputValue #else fatalError("Only available on WebAssembly") #endif -} \ No newline at end of file +} + +extension Direction: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Direction.bridgeJSMakeTypeHandle() +} + +extension Status: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Status.bridgeJSMakeTypeHandle() +} + +extension TSDirection: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TSDirection.bridgeJSMakeTypeHandle() +} + +extension PublicStatus: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PublicStatus.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Direction.bridgeJSTypeID, + Status.bridgeJSTypeID, + TSDirection.bridgeJSTypeID, + PublicStatus.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumCaseImport.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumCaseImport.swift index f297e1620..6ed293525 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumCaseImport.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumCaseImport.swift @@ -33,6 +33,10 @@ extension Signal: _BridgedSwiftCaseEnum { } } +extension Signal: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Signal.bridgeJSMakeTypeHandle() +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_SignalControls_roundTrip_static") fileprivate func bjs_SignalControls_roundTrip_static_extern(_ signal: Int32) -> Int32 @@ -94,4 +98,19 @@ func _$SignalControls_current(_ self: JSObject) throws(JSException) -> Signal { throw error } return Signal.bridgeJSLiftReturn(ret) -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Signal.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumNamespace.Global.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumNamespace.Global.swift index 4f588f6c7..9d6908bcc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumNamespace.Global.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumNamespace.Global.swift @@ -358,4 +358,38 @@ fileprivate func _bjs_Formatting_Converter_wrap_extern(_ pointer: UnsafeMutableR #endif @inline(never) fileprivate func _bjs_Formatting_Converter_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_Formatting_Converter_wrap_extern(pointer) -} \ No newline at end of file +} + +extension Networking.API.Method: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Networking.API.Method.bridgeJSMakeTypeHandle() +} + +extension Configuration.LogLevel: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Configuration.LogLevel.bridgeJSMakeTypeHandle() +} + +extension Configuration.Port: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Configuration.Port.bridgeJSMakeTypeHandle() +} + +extension Internal.SupportedMethod: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Internal.SupportedMethod.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Networking.API.Method.bridgeJSTypeID, + Configuration.LogLevel.bridgeJSTypeID, + Configuration.Port.bridgeJSTypeID, + Internal.SupportedMethod.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumNamespace.swift index 4f588f6c7..9d6908bcc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumNamespace.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumNamespace.swift @@ -358,4 +358,38 @@ fileprivate func _bjs_Formatting_Converter_wrap_extern(_ pointer: UnsafeMutableR #endif @inline(never) fileprivate func _bjs_Formatting_Converter_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_Formatting_Converter_wrap_extern(pointer) -} \ No newline at end of file +} + +extension Networking.API.Method: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Networking.API.Method.bridgeJSMakeTypeHandle() +} + +extension Configuration.LogLevel: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Configuration.LogLevel.bridgeJSMakeTypeHandle() +} + +extension Configuration.Port: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Configuration.Port.bridgeJSMakeTypeHandle() +} + +extension Internal.SupportedMethod: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Internal.SupportedMethod.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Networking.API.Method.bridgeJSTypeID, + Configuration.LogLevel.bridgeJSTypeID, + Configuration.Port.bridgeJSTypeID, + Internal.SupportedMethod.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumRawType.swift index e70a6b0aa..2dbb21422 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumRawType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumRawType.swift @@ -451,6 +451,54 @@ public func _bjs_validateSession(_ session: Int64) -> Void { #endif } +extension Theme: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Theme.bridgeJSMakeTypeHandle() +} + +extension TSTheme: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TSTheme.bridgeJSMakeTypeHandle() +} + +extension FeatureFlag: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = FeatureFlag.bridgeJSMakeTypeHandle() +} + +extension HttpStatus: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = HttpStatus.bridgeJSMakeTypeHandle() +} + +extension TSHttpStatus: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TSHttpStatus.bridgeJSMakeTypeHandle() +} + +extension Priority: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Priority.bridgeJSMakeTypeHandle() +} + +extension FileSize: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = FileSize.bridgeJSMakeTypeHandle() +} + +extension UserId: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = UserId.bridgeJSMakeTypeHandle() +} + +extension TokenId: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TokenId.bridgeJSMakeTypeHandle() +} + +extension SessionId: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = SessionId.bridgeJSMakeTypeHandle() +} + +extension Precision: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Precision.bridgeJSMakeTypeHandle() +} + +extension Ratio: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Ratio.bridgeJSMakeTypeHandle() +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_takesFeatureFlag") fileprivate func bjs_takesFeatureFlag_extern(_ flagBytes: Int32, _ flagLength: Int32) -> Void @@ -490,4 +538,30 @@ func _$returnsFeatureFlag() throws(JSException) -> FeatureFlag { throw error } return FeatureFlag.bridgeJSLiftReturn(ret) -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Theme.bridgeJSTypeID, + TSTheme.bridgeJSTypeID, + FeatureFlag.bridgeJSTypeID, + HttpStatus.bridgeJSTypeID, + TSHttpStatus.bridgeJSTypeID, + Priority.bridgeJSTypeID, + FileSize.bridgeJSTypeID, + UserId.bridgeJSTypeID, + TokenId.bridgeJSTypeID, + SessionId.bridgeJSTypeID, + Precision.bridgeJSTypeID, + Ratio.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.json new file mode 100644 index 000000000..c7f3e98ae --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.json @@ -0,0 +1,669 @@ +{ + "exported" : { + "aliases" : [ + + ], + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_GenericImportBox_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "isFinal" : true, + "methods" : [ + { + "abiName" : "bjs_GenericImportBox_get", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "get", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "name" : "GenericImportBox", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "GenericImportBox" + } + ], + "enums" : [ + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "red" + }, + { + "associatedValues" : [ + + ], + "name" : "green" + } + ], + "emitStyle" : "const", + "name" : "GenericColor", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericColor", + "tsFullPath" : "GenericColor" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark" + } + ], + "emitStyle" : "const", + "name" : "GenericMode", + "rawType" : "String", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericMode", + "tsFullPath" : "GenericMode" + }, + { + "cases" : [ + { + "associatedValues" : [ + { + "label" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "name" : "number" + }, + { + "associatedValues" : [ + { + "label" : "value", + "type" : { + "string" : { + + } + } + } + ], + "name" : "text" + } + ], + "emitStyle" : "const", + "name" : "GenericTagged", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericTagged", + "tsFullPath" : "GenericTagged" + } + ], + "exposeToGlobal" : false, + "functions" : [ + + ], + "protocols" : [ + + ], + "structs" : [ + { + "methods" : [ + + ], + "name" : "GenericPoint", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "x", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "y", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "GenericPoint" + } + ] + }, + "imported" : { + "children" : [ + { + "functions" : [ + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "genericRoundTrip", + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "genericParse", + "parameters" : [ + { + "name" : "json", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T", + "U" + ], + "name" : "importGenericCombine", + "parameters" : [ + { + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "name" : "b", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "U" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T", + "t" + ], + "name" : "importGenericCaseDistinct", + "parameters" : [ + { + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "name" : "b", + "type" : { + "generic" : { + "_0" : "t" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "importGenericArray", + "parameters" : [ + { + "name" : "values", + "type" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "importGenericOptional", + "parameters" : [ + { + "name" : "value", + "type" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "importGenericDictionary", + "parameters" : [ + { + "name" : "values", + "type" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + } + ], + "returnType" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "importGenericAfterOptionalArray", + "parameters" : [ + { + "name" : "values", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + }, + "_1" : "null" + } + } + }, + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "types" : [ + { + "accessLevel" : "internal", + "constructor" : { + "accessLevel" : "internal", + "genericParameters" : [ + "T", + "U" + ], + "parameters" : [ + { + "name" : "tag", + "type" : { + "string" : { + + } + } + }, + { + "name" : "first", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "name" : "second", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ] + }, + "getters" : [ + + ], + "methods" : [ + + ], + "name" : "GenericPairFactory", + "setters" : [ + + ], + "staticMethods" : [ + + ] + }, + { + "accessLevel" : "internal", + "constructor" : { + "accessLevel" : "internal", + "genericParameters" : [ + "T" + ], + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ] + }, + "getters" : [ + + ], + "methods" : [ + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "accept", + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "identity", + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "name" : "GenericConsumer", + "setters" : [ + + ], + "staticMethods" : [ + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "box", + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ] + } + ] + } + ] + }, + "moduleName" : "TestModule", + "usedExternalModules" : [ + + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.swift new file mode 100644 index 000000000..7714c498e --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.swift @@ -0,0 +1,505 @@ +extension GenericColor: _BridgedSwiftCaseEnum { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> GenericColor { + return bridgeJSLiftParameter(value) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> GenericColor { + return GenericColor(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSLowerParameter() + } + + @_spi(BridgeJS) @usableFromInline init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .red + case 1: + self = .green + default: + return nil + } + } + + @_spi(BridgeJS) @usableFromInline var bridgeJSRawValue: Int32 { + switch self { + case .red: + return 0 + case .green: + return 1 + } + } +} + +extension GenericMode: _BridgedSwiftEnumNoPayload, _BridgedSwiftRawValueEnum { +} + +extension GenericTagged: _BridgedSwiftAssociatedValueEnum { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPopPayload(_ caseId: Int32) -> GenericTagged { + switch caseId { + case 0: + return .number(value: Int.bridgeJSStackPop()) + case 1: + return .text(value: String.bridgeJSStackPop()) + default: + fatalError("Unknown GenericTagged case ID: \(caseId)") + } + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPushPayload() -> Int32 { + switch self { + case .number(let value): + value.bridgeJSStackPush() + return Int32(0) + case .text(let value): + value.bridgeJSStackPush() + return Int32(1) + } + } +} + +extension GenericPoint: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> GenericPoint { + let y = Int.bridgeJSStackPop() + let x = Int.bridgeJSStackPop() + return GenericPoint(x: x, y: y) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.x.bridgeJSStackPush() + self.y.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_GenericPoint(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_GenericPoint())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_GenericPoint") +fileprivate func _bjs_struct_lower_GenericPoint_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_GenericPoint_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_GenericPoint(_ objectId: Int32) -> Void { + return _bjs_struct_lower_GenericPoint_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_GenericPoint") +fileprivate func _bjs_struct_lift_GenericPoint_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_GenericPoint_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_GenericPoint() -> Int32 { + return _bjs_struct_lift_GenericPoint_extern() +} + +@_expose(wasm, "bjs_GenericImportBox_init") +@_cdecl("bjs_GenericImportBox_init") +public func _bjs_GenericImportBox_init(_ value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = GenericImportBox(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_GenericImportBox_get") +@_cdecl("bjs_GenericImportBox_get") +public func _bjs_GenericImportBox_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = GenericImportBox.bridgeJSLiftParameter(_self).get() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_GenericImportBox_value_get") +@_cdecl("bjs_GenericImportBox_value_get") +public func _bjs_GenericImportBox_value_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = GenericImportBox.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_GenericImportBox_value_set") +@_cdecl("bjs_GenericImportBox_value_set") +public func _bjs_GenericImportBox_value_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + GenericImportBox.bridgeJSLiftParameter(_self).value = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_GenericImportBox_deinit") +@_cdecl("bjs_GenericImportBox_deinit") +public func _bjs_GenericImportBox_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension GenericImportBox: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_GenericImportBox_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_GenericImportBox_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_GenericImportBox_wrap") +fileprivate func _bjs_GenericImportBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_GenericImportBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_GenericImportBox_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_GenericImportBox_wrap_extern(pointer) +} + +extension GenericPoint: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericPoint.bridgeJSMakeTypeHandle() +} + +extension GenericImportBox: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericImportBox.bridgeJSMakeTypeHandle() +} + +extension GenericColor: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericColor.bridgeJSMakeTypeHandle() +} + +extension GenericMode: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericMode.bridgeJSMakeTypeHandle() +} + +extension GenericTagged: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericTagged.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_genericRoundTrip") +fileprivate func bjs_genericRoundTrip_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_genericRoundTrip_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_genericRoundTrip(_ _generic0TypeId: Int32) -> Void { + return bjs_genericRoundTrip_extern(_generic0TypeId) +} + +func _$genericRoundTrip(_ value: T) throws(JSException) -> T { + value.bridgeJSStackPush() + bjs_genericRoundTrip(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_genericParse") +fileprivate func bjs_genericParse_extern(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_genericParse_extern(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_genericParse(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_genericParse_extern(jsonBytes, jsonLength, _generic0TypeId) +} + +func _$genericParse(_ json: String) throws(JSException) -> T { + json.bridgeJSWithLoweredParameter { (jsonBytes, jsonLength) in + bjs_genericParse(jsonBytes, jsonLength, T.bridgeJSTypeID) + } + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_importGenericCombine") +fileprivate func bjs_importGenericCombine_extern(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void +#else +fileprivate func bjs_importGenericCombine_extern(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_importGenericCombine(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + return bjs_importGenericCombine_extern(_generic0TypeId, _generic1TypeId) +} + +func _$importGenericCombine(_ a: T, _ b: U) throws(JSException) -> U { + b.bridgeJSStackPush() + a.bridgeJSStackPush() + bjs_importGenericCombine(T.bridgeJSTypeID, U.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return U.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_importGenericCaseDistinct") +fileprivate func bjs_importGenericCaseDistinct_extern(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void +#else +fileprivate func bjs_importGenericCaseDistinct_extern(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_importGenericCaseDistinct(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + return bjs_importGenericCaseDistinct_extern(_generic0TypeId, _generic1TypeId) +} + +func _$importGenericCaseDistinct(_ a: T, _ b: t) throws(JSException) -> T { + b.bridgeJSStackPush() + a.bridgeJSStackPush() + bjs_importGenericCaseDistinct(T.bridgeJSTypeID, t.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_importGenericArray") +fileprivate func bjs_importGenericArray_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_importGenericArray_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_importGenericArray(_ _generic0TypeId: Int32) -> Void { + return bjs_importGenericArray_extern(_generic0TypeId) +} + +func _$importGenericArray(_ values: [T]) throws(JSException) -> [T] { + let _ = values.bridgeJSLowerParameter() + bjs_importGenericArray(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return [T].bridgeJSLiftReturn() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_importGenericOptional") +fileprivate func bjs_importGenericOptional_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_importGenericOptional_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_importGenericOptional(_ _generic0TypeId: Int32) -> Void { + return bjs_importGenericOptional_extern(_generic0TypeId) +} + +func _$importGenericOptional(_ value: Optional) throws(JSException) -> Optional { + value.bridgeJSStackPush() + bjs_importGenericOptional(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return Optional.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_importGenericDictionary") +fileprivate func bjs_importGenericDictionary_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_importGenericDictionary_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_importGenericDictionary(_ _generic0TypeId: Int32) -> Void { + return bjs_importGenericDictionary_extern(_generic0TypeId) +} + +func _$importGenericDictionary(_ values: [String: T]) throws(JSException) -> [String: T] { + let _ = values.bridgeJSLowerParameter() + bjs_importGenericDictionary(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return [String: T].bridgeJSLiftReturn() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_importGenericAfterOptionalArray") +fileprivate func bjs_importGenericAfterOptionalArray_extern(_ values: Int32, _ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_importGenericAfterOptionalArray_extern(_ values: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_importGenericAfterOptionalArray(_ values: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_importGenericAfterOptionalArray_extern(values, _generic0TypeId) +} + +func _$importGenericAfterOptionalArray(_ values: Optional<[Int]>, _ value: T) throws(JSException) -> T { + value.bridgeJSStackPush() + let valuesIsSome = values.bridgeJSLowerParameter() + bjs_importGenericAfterOptionalArray(valuesIsSome, T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_GenericPairFactory_init") +fileprivate func bjs_GenericPairFactory_init_extern(_ tagBytes: Int32, _ tagLength: Int32, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Int32 +#else +fileprivate func bjs_GenericPairFactory_init_extern(_ tagBytes: Int32, _ tagLength: Int32, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_GenericPairFactory_init(_ tagBytes: Int32, _ tagLength: Int32, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Int32 { + return bjs_GenericPairFactory_init_extern(tagBytes, tagLength, _generic0TypeId, _generic1TypeId) +} + +func _$GenericPairFactory_init(_ tag: String, _ first: T, _ second: U) throws(JSException) -> JSObject { + let ret0 = tag.bridgeJSWithLoweredParameter { (tagBytes, tagLength) in + second.bridgeJSStackPush() + first.bridgeJSStackPush() + let ret = bjs_GenericPairFactory_init(tagBytes, tagLength, T.bridgeJSTypeID, U.bridgeJSTypeID) + return ret + } + let ret = ret0 + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_GenericConsumer_init") +fileprivate func bjs_GenericConsumer_init_extern(_ _generic0TypeId: Int32) -> Int32 +#else +fileprivate func bjs_GenericConsumer_init_extern(_ _generic0TypeId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_GenericConsumer_init(_ _generic0TypeId: Int32) -> Int32 { + return bjs_GenericConsumer_init_extern(_generic0TypeId) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_GenericConsumer_box_static") +fileprivate func bjs_GenericConsumer_box_static_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_GenericConsumer_box_static_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_GenericConsumer_box_static(_ _generic0TypeId: Int32) -> Void { + return bjs_GenericConsumer_box_static_extern(_generic0TypeId) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_GenericConsumer_accept") +fileprivate func bjs_GenericConsumer_accept_extern(_ self: Int32, _ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_GenericConsumer_accept_extern(_ self: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_GenericConsumer_accept(_ self: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_GenericConsumer_accept_extern(self, _generic0TypeId) +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_GenericConsumer_identity") +fileprivate func bjs_GenericConsumer_identity_extern(_ self: Int32, _ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_GenericConsumer_identity_extern(_ self: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_GenericConsumer_identity(_ self: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_GenericConsumer_identity_extern(self, _generic0TypeId) +} + +func _$GenericConsumer_init(_ value: T) throws(JSException) -> JSObject { + value.bridgeJSStackPush() + let ret = bjs_GenericConsumer_init(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) +} + +func _$GenericConsumer_box(_ value: T) throws(JSException) -> T { + value.bridgeJSStackPush() + bjs_GenericConsumer_box_static(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +func _$GenericConsumer_accept(_ self: JSObject, _ value: T) throws(JSException) -> Void { + value.bridgeJSStackPush() + let selfValue = self.bridgeJSLowerParameter() + bjs_GenericConsumer_accept(selfValue, T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } +} + +func _$GenericConsumer_identity(_ self: JSObject, _ value: T) throws(JSException) -> T { + value.bridgeJSStackPush() + let selfValue = self.bridgeJSLowerParameter() + bjs_GenericConsumer_identity(selfValue, T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + GenericPoint.bridgeJSTypeID, + GenericImportBox.bridgeJSTypeID, + GenericColor.bridgeJSTypeID, + GenericMode.bridgeJSTypeID, + GenericTagged.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.swift index 62f9a3b68..b9fddf706 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportedTypeInExportedInterface.swift @@ -104,6 +104,10 @@ public func _bjs_roundtripFooContainer() -> Void { #endif } +extension FooContainer: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = FooContainer.bridgeJSMakeTypeHandle() +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_Foo_init") fileprivate func bjs_Foo_init_extern() -> Int32 @@ -122,4 +126,19 @@ func _$Foo_init() throws(JSException) -> JSObject { throw error } return JSObject.bridgeJSLiftReturn(ret) -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + FooContainer.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSNameOverride.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSNameOverride.swift index b525b5152..8e01bca22 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSNameOverride.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSNameOverride.swift @@ -348,4 +348,28 @@ fileprivate func _bjs_RenamedMembers_wrap_extern(_ pointer: UnsafeMutableRawPoin #endif @inline(never) fileprivate func _bjs_RenamedMembers_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_RenamedMembers_wrap_extern(pointer) -} \ No newline at end of file +} + +extension RenamedVector: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = RenamedVector.bridgeJSMakeTypeHandle() +} + +extension RenamedEnumMembers: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = RenamedEnumMembers.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + RenamedVector.bridgeJSTypeID, + RenamedEnumMembers.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.swift index ed1a080e9..9e1b0e0f6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/NestedType.swift @@ -176,4 +176,28 @@ fileprivate func _bjs_Player_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> #endif @inline(never) fileprivate func _bjs_Player_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_Player_wrap_extern(pointer) -} \ No newline at end of file +} + +extension User.Stats: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = User.Stats.bridgeJSMakeTypeHandle() +} + +extension Player.Stats: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Player.Stats.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + User.Stats.bridgeJSTypeID, + Player.Stats.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift index cfda92ac0..7c2db9a98 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift @@ -1043,4 +1043,38 @@ fileprivate func _bjs_DelegateManager_wrap_extern(_ pointer: UnsafeMutableRawPoi #endif @inline(never) fileprivate func _bjs_DelegateManager_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_DelegateManager_wrap_extern(pointer) -} \ No newline at end of file +} + +extension Direction: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Direction.bridgeJSMakeTypeHandle() +} + +extension ExampleEnum: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExampleEnum.bridgeJSMakeTypeHandle() +} + +extension Result: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Result.bridgeJSMakeTypeHandle() +} + +extension Priority: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Priority.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Direction.bridgeJSTypeID, + ExampleEnum.bridgeJSTypeID, + Result.bridgeJSTypeID, + Priority.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticFunctions.Global.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticFunctions.Global.swift index 896258915..2d5a93c54 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticFunctions.Global.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticFunctions.Global.swift @@ -207,4 +207,28 @@ fileprivate func _bjs_MathUtils_wrap_extern(_ pointer: UnsafeMutableRawPointer) #endif @inline(never) fileprivate func _bjs_MathUtils_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_MathUtils_wrap_extern(pointer) -} \ No newline at end of file +} + +extension Calculator: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Calculator.bridgeJSMakeTypeHandle() +} + +extension APIResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = APIResult.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Calculator.bridgeJSTypeID, + APIResult.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticFunctions.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticFunctions.swift index 896258915..2d5a93c54 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticFunctions.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticFunctions.swift @@ -207,4 +207,28 @@ fileprivate func _bjs_MathUtils_wrap_extern(_ pointer: UnsafeMutableRawPointer) #endif @inline(never) fileprivate func _bjs_MathUtils_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_MathUtils_wrap_extern(pointer) -} \ No newline at end of file +} + +extension Calculator: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Calculator.bridgeJSMakeTypeHandle() +} + +extension APIResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = APIResult.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Calculator.bridgeJSTypeID, + APIResult.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticProperties.Global.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticProperties.Global.swift index ded55dbd4..721c5335a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticProperties.Global.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticProperties.Global.swift @@ -338,4 +338,23 @@ fileprivate func _bjs_PropertyClass_wrap_extern(_ pointer: UnsafeMutableRawPoint #endif @inline(never) fileprivate func _bjs_PropertyClass_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_PropertyClass_wrap_extern(pointer) -} \ No newline at end of file +} + +extension PropertyEnum: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PropertyEnum.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + PropertyEnum.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticProperties.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticProperties.swift index ded55dbd4..721c5335a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticProperties.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StaticProperties.swift @@ -338,4 +338,23 @@ fileprivate func _bjs_PropertyClass_wrap_extern(_ pointer: UnsafeMutableRawPoint #endif @inline(never) fileprivate func _bjs_PropertyClass_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_PropertyClass_wrap_extern(pointer) -} \ No newline at end of file +} + +extension PropertyEnum: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PropertyEnum.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + PropertyEnum.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StructWithNestedTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StructWithNestedTypes.swift index ad99f0a03..8fc6db1a7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StructWithNestedTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StructWithNestedTypes.swift @@ -246,4 +246,53 @@ public func _bjs_Widget_Bounds_static_zero() -> Void { #else fatalError("Only available on WebAssembly") #endif -} \ No newline at end of file +} + +extension Shape: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Shape.bridgeJSMakeTypeHandle() +} + +extension Widget: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Widget.bridgeJSMakeTypeHandle() +} + +extension Widget.Layout: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Widget.Layout.bridgeJSMakeTypeHandle() +} + +extension Widget.Bounds: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Widget.Bounds.bridgeJSMakeTypeHandle() +} + +extension Shape.Kind: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Shape.Kind.bridgeJSMakeTypeHandle() +} + +extension Widget.Variant: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Widget.Variant.bridgeJSMakeTypeHandle() +} + +extension Widget.Layout.Alignment: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Widget.Layout.Alignment.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Shape.bridgeJSTypeID, + Widget.bridgeJSTypeID, + Widget.Layout.bridgeJSTypeID, + Widget.Bounds.bridgeJSTypeID, + Shape.Kind.bridgeJSTypeID, + Widget.Variant.bridgeJSTypeID, + Widget.Layout.Alignment.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift index c7ac02fb1..f349f0c40 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift @@ -2637,6 +2637,26 @@ fileprivate func _bjs_TestProcessor_wrap_extern(_ pointer: UnsafeMutableRawPoint return _bjs_TestProcessor_wrap_extern(pointer) } +extension Animal: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Animal.bridgeJSMakeTypeHandle() +} + +extension Direction: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Direction.bridgeJSMakeTypeHandle() +} + +extension Theme: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Theme.bridgeJSMakeTypeHandle() +} + +extension HttpStatus: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = HttpStatus.bridgeJSMakeTypeHandle() +} + +extension APIResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = APIResult.bridgeJSMakeTypeHandle() +} + @JSFunction func Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) #if arch(wasm32) @@ -2720,4 +2740,23 @@ func _$Promise_resolve_9APIResultO(_ promise: JSObject, _ value: APIResult) thro let promiseValue = promise.bridgeJSLowerParameter() promise_resolve_TestModule_9APIResultO(promiseValue, valueCaseId) if let error = _swift_js_take_exception() { throw error } -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Animal.bridgeJSTypeID, + Direction.bridgeJSTypeID, + Theme.bridgeJSTypeID, + HttpStatus.bridgeJSTypeID, + APIResult.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.swift index f98038b45..b4e54961c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStruct.swift @@ -630,4 +630,63 @@ fileprivate func _bjs_Greeter_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> #endif @inline(never) fileprivate func _bjs_Greeter_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_Greeter_wrap_extern(pointer) -} \ No newline at end of file +} + +extension DataPoint: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = DataPoint.bridgeJSMakeTypeHandle() +} + +extension Address: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Address.bridgeJSMakeTypeHandle() +} + +extension Person: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Person.bridgeJSMakeTypeHandle() +} + +extension Session: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Session.bridgeJSMakeTypeHandle() +} + +extension Measurement: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Measurement.bridgeJSMakeTypeHandle() +} + +extension ConfigStruct: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ConfigStruct.bridgeJSMakeTypeHandle() +} + +extension Container: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Container.bridgeJSMakeTypeHandle() +} + +extension Vector2D: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Vector2D.bridgeJSMakeTypeHandle() +} + +extension Precision: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Precision.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + DataPoint.bridgeJSTypeID, + Address.bridgeJSTypeID, + Person.bridgeJSTypeID, + Session.bridgeJSTypeID, + Measurement.bridgeJSTypeID, + ConfigStruct.bridgeJSTypeID, + Container.bridgeJSTypeID, + Vector2D.bridgeJSTypeID, + Precision.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStructImports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStructImports.swift index 38ec94c0d..4e9899470 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStructImports.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftStructImports.swift @@ -46,6 +46,10 @@ fileprivate func _bjs_struct_lift_Point_extern() -> Int32 { return _bjs_struct_lift_Point_extern() } +extension Point: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Point.bridgeJSMakeTypeHandle() +} + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_translate") fileprivate func bjs_translate_extern(_ dx: Int32, _ dy: Int32) -> Void @@ -88,4 +92,19 @@ func _$roundTripOptional(_ point: Optional) throws(JSException) -> Option throw error } return Optional.bridgeJSLiftReturn() -} \ No newline at end of file +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + Point.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/UnsafePointer.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/UnsafePointer.swift index b97729084..69011da18 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/UnsafePointer.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/UnsafePointer.swift @@ -177,4 +177,23 @@ public func _bjs_roundTripPointerFields() -> Void { #else fatalError("Only available on WebAssembly") #endif -} \ No newline at end of file +} + +extension PointerFields: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PointerFields.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_TestModule_register_type_handles") +fileprivate func _bjs_TestModule_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_TestModule_register_type_handles") +public func _bjs_TestModule_register_type_handles() { + let typeIds: [Int32] = [ + PointerFields.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_TestModule_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.d.ts index e3092afb3..9815b6514 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.d.ts @@ -67,5 +67,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.js index 92fb5a109..53755e3a9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.js @@ -37,7 +37,367 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createInnerTagValuesHelpers = () => ({ + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_TestModule_PolygonReference = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports['PolygonReference'].__construct(ptr); + return obj; + }, + }; + const __bjs_codec_Array_TestModule_PolygonReference = __bjs_arrayCodec(__bjs_codec_TestModule_PolygonReference); + const __bjs_codec_TestModule_InnerTag = { + lower: (v) => { + const caseId = enumHelpers.TestModule_InnerTag.lower(v); + i32Stack.push(caseId); + }, + lift: () => { + const enumValue = enumHelpers.TestModule_InnerTag.lift(i32Stack.pop()); + return enumValue; + }, + }; + const __bjs_codec_Optional_TestModule_InnerTag = __bjs_optionalCodec(__bjs_codec_TestModule_InnerTag); + const __bjs_codec_Array_Optional_TestModule_InnerTag = __bjs_arrayCodec(__bjs_codec_Optional_TestModule_InnerTag); + const __bjs_codec_TestModule_Surface = { + lower: (v) => { + const objId = swift.memory.retain(v); + i32Stack.push(objId); + }, + lift: () => { + const objId = i32Stack.pop(); + const obj = swift.memory.getObject(objId); + swift.memory.release(objId); + return obj; + }, + }; + const __bjs_codec_Optional_TestModule_Surface = __bjs_optionalCodec(__bjs_codec_TestModule_Surface); + + const __bjs_createEnumHelpers_TestModule_InnerTag = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -139,6 +499,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -284,12 +646,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_produceOptionalCanvas"] = function bjs_produceOptionalCanvas() { try { let ret = imports.produceOptionalCanvas(); - const isSome = ret != null; - if (isSome) { - const objId = swift.memory.retain(ret); - i32Stack.push(objId); - } - i32Stack.push(isSome ? 1 : 0); + __bjs_codec_Optional_TestModule_Surface.lower(ret); } catch (error) { setException(error); } @@ -417,8 +774,8 @@ export async function createInstantiator(options, swift) { return TagReference.__construct(ret); } } - const InnerTagHelpers = __bjs_createInnerTagValuesHelpers(); - enumHelpers.InnerTag = InnerTagHelpers; + const __bjs_helpers_TestModule_InnerTag = __bjs_createEnumHelpers_TestModule_InnerTag(); + enumHelpers.TestModule_InnerTag = __bjs_helpers_TestModule_InnerTag; const exports = { roundtripPolygon: function bjs_roundtripPolygon(polygon) { @@ -440,24 +797,9 @@ export async function createInstantiator(options, swift) { return optResult; }, polygonArray: function bjs_polygonArray(polygons) { - for (const elem of polygons) { - ptrStack.push(elem.pointer); - } - i32Stack.push(polygons.length); + __bjs_codec_Array_TestModule_PolygonReference.lower(polygons); instance.exports.bjs_polygonArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const ptr = ptrStack.pop(); - const obj = PolygonReference.__construct(ptr); - arrayResult.push(obj); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_PolygonReference.lift(); return arrayResult; }, validatePolygon: function bjs_validatePolygon(polygon) { @@ -477,35 +819,9 @@ export async function createInstantiator(options, swift) { return TagReference.__construct(ret); }, roundtripTags: function bjs_roundtripTags(xs) { - for (const elem of xs) { - const isSome = elem != null ? 1 : 0; - if (isSome) { - const caseId = enumHelpers.InnerTag.lower(elem); - i32Stack.push(caseId); - } - i32Stack.push(isSome); - } - i32Stack.push(xs.length); + __bjs_codec_Array_Optional_TestModule_InnerTag.lower(xs); instance.exports.bjs_roundtripTags(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const isSome1 = i32Stack.pop(); - let optValue; - if (isSome1 === 0) { - optValue = null; - } else { - const enumValue = enumHelpers.InnerTag.lift(i32Stack.pop()); - optValue = enumValue; - } - arrayResult.push(optValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Optional_TestModule_InnerTag.lift(); return arrayResult; }, describeUser: function bjs_describeUser(owner) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.d.ts index 73ea3b570..4d2bab311 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.d.ts @@ -27,5 +27,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.js index a38fa118e..fa9095cd8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.js @@ -131,6 +131,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts index f48189956..529b16095 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.d.ts @@ -91,5 +91,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js index 419cf15d5..e5b8438d7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js @@ -44,7 +44,438 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createPointHelpers = () => ({ + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_Array_Int = __bjs_arrayCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_Array_String = __bjs_arrayCodec(__bjs_stringCodec); + const __bjs_codec_Array_Double = __bjs_arrayCodec(__bjs_primitiveCodecs.Double); + const __bjs_codec_Array_Bool = __bjs_arrayCodec(__bjs_primitiveCodecs.Bool); + const __bjs_codec_TestModule_Point = { + lower: (v) => { + structHelpers.TestModule_Point.lower(v); + }, + lift: () => { + const struct = structHelpers.TestModule_Point.lift(); + return struct; + }, + }; + const __bjs_codec_Array_TestModule_Point = __bjs_arrayCodec(__bjs_codec_TestModule_Point); + const __bjs_codec_TestModule_Direction = { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const caseId = i32Stack.pop(); + return caseId; + }, + }; + const __bjs_codec_Array_TestModule_Direction = __bjs_arrayCodec(__bjs_codec_TestModule_Direction); + const __bjs_codec_TestModule_Status = { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const rawValue = i32Stack.pop(); + return rawValue; + }, + }; + const __bjs_codec_Array_TestModule_Status = __bjs_arrayCodec(__bjs_codec_TestModule_Status); + const __bjs_codec_Surp = { + lower: (v) => { + ptrStack.push((v | 0)); + }, + lift: () => { + const pointer = ptrStack.pop(); + return pointer; + }, + }; + const __bjs_codec_Array_Surp = __bjs_arrayCodec(__bjs_codec_Surp); + const __bjs_codec_Sumrp = { + lower: (v) => { + ptrStack.push((v | 0)); + }, + lift: () => { + const pointer = ptrStack.pop(); + return pointer; + }, + }; + const __bjs_codec_Array_Sumrp = __bjs_arrayCodec(__bjs_codec_Sumrp); + const __bjs_codec_Sop = { + lower: (v) => { + ptrStack.push((v | 0)); + }, + lift: () => { + const pointer = ptrStack.pop(); + return pointer; + }, + }; + const __bjs_codec_Array_Sop = __bjs_arrayCodec(__bjs_codec_Sop); + const __bjs_codec_Optional_Int = __bjs_optionalCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_Array_Optional_Int = __bjs_arrayCodec(__bjs_codec_Optional_Int); + const __bjs_codec_Optional_String = __bjs_optionalCodec(__bjs_stringCodec); + const __bjs_codec_Array_Optional_String = __bjs_arrayCodec(__bjs_codec_Optional_String); + const __bjs_codec_Optional_Array_Int = __bjs_optionalCodec(__bjs_codec_Array_Int); + const __bjs_codec_Optional_TestModule_Point = __bjs_optionalCodec(__bjs_codec_TestModule_Point); + const __bjs_codec_Array_Optional_TestModule_Point = __bjs_arrayCodec(__bjs_codec_Optional_TestModule_Point); + const __bjs_codec_Optional_TestModule_Direction = __bjs_optionalCodec(__bjs_codec_TestModule_Direction); + const __bjs_codec_Array_Optional_TestModule_Direction = __bjs_arrayCodec(__bjs_codec_Optional_TestModule_Direction); + const __bjs_codec_Optional_TestModule_Status = __bjs_optionalCodec(__bjs_codec_TestModule_Status); + const __bjs_codec_Array_Optional_TestModule_Status = __bjs_arrayCodec(__bjs_codec_Optional_TestModule_Status); + const __bjs_codec_Array_Array_Int = __bjs_arrayCodec(__bjs_codec_Array_Int); + const __bjs_codec_Array_Array_String = __bjs_arrayCodec(__bjs_codec_Array_String); + const __bjs_codec_Array_Array_TestModule_Point = __bjs_arrayCodec(__bjs_codec_Array_TestModule_Point); + const __bjs_codec_TestModule_Item = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports['Item'].__construct(ptr); + return obj; + }, + }; + const __bjs_codec_Array_TestModule_Item = __bjs_arrayCodec(__bjs_codec_TestModule_Item); + const __bjs_codec_Array_Array_TestModule_Item = __bjs_arrayCodec(__bjs_codec_Array_TestModule_Item); + const __bjs_codec_JSObject = { + lower: (v) => { + const objId = swift.memory.retain(v); + i32Stack.push(objId); + }, + lift: () => { + const objId = i32Stack.pop(); + const obj = swift.memory.getObject(objId); + swift.memory.release(objId); + return obj; + }, + }; + const __bjs_codec_Array_JSObject = __bjs_arrayCodec(__bjs_codec_JSObject); + const __bjs_codec_Optional_JSObject = __bjs_optionalCodec(__bjs_codec_JSObject); + const __bjs_codec_Array_Optional_JSObject = __bjs_arrayCodec(__bjs_codec_Optional_JSObject); + const __bjs_codec_Array_Array_JSObject = __bjs_arrayCodec(__bjs_codec_Array_JSObject); + const __bjs_codec_Optional_Array_String = __bjs_optionalCodec(__bjs_codec_Array_String); + + const __bjs_createStructHelpers_TestModule_Point = () => ({ lower: (value) => { f64Stack.push(value.x); f64Stack.push(value.y); @@ -132,12 +563,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_Point"] = function(objectId) { - structHelpers.Point.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Point.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Point"] = function() { - const value = structHelpers.Point.lift(); + const value = structHelpers.TestModule_Point.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -264,18 +697,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_importProcessNumbers"] = function bjs_importProcessNumbers() { try { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const f64 = f64Stack.pop(); - arrayResult.push(f64); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Double.lift(); imports.importProcessNumbers(arrayResult); } catch (error) { setException(error); @@ -284,82 +706,34 @@ export async function createInstantiator(options, swift) { TestModule["bjs_importGetNumbers"] = function bjs_importGetNumbers() { try { let ret = imports.importGetNumbers(); - for (const elem of ret) { - f64Stack.push(elem); - } - i32Stack.push(ret.length); + __bjs_codec_Array_Double.lower(ret); } catch (error) { setException(error); } } TestModule["bjs_importTransformNumbers"] = function bjs_importTransformNumbers() { try { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const f64 = f64Stack.pop(); - arrayResult.push(f64); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Double.lift(); let ret = imports.importTransformNumbers(arrayResult); - for (const elem of ret) { - f64Stack.push(elem); - } - i32Stack.push(ret.length); + __bjs_codec_Array_Double.lower(ret); } catch (error) { setException(error); } } TestModule["bjs_importProcessStrings"] = function bjs_importProcessStrings() { try { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const string = strStack.pop(); - arrayResult.push(string); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_String.lift(); let ret = imports.importProcessStrings(arrayResult); - for (const elem of ret) { - const bytes = textEncoder.encode(elem); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(ret.length); + __bjs_codec_Array_String.lower(ret); } catch (error) { setException(error); } } TestModule["bjs_importProcessBooleans"] = function bjs_importProcessBooleans() { try { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const bool = i32Stack.pop() !== 0; - arrayResult.push(bool); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Bool.lift(); let ret = imports.importProcessBooleans(arrayResult); - for (const elem of ret) { - i32Stack.push(elem ? 1 : 0); - } - i32Stack.push(ret.length); + __bjs_codec_Array_Bool.lower(ret); } catch (error) { setException(error); } @@ -442,758 +816,192 @@ export async function createInstantiator(options, swift) { } constructor(nums, strs) { - for (const elem of nums) { - i32Stack.push((elem | 0)); - } - i32Stack.push(nums.length); - for (const elem1 of strs) { - const bytes = textEncoder.encode(elem1); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(strs.length); + __bjs_codec_Array_Int.lower(nums); + __bjs_codec_Array_String.lower(strs); const ret = instance.exports.bjs_MultiArrayContainer_init(); return MultiArrayContainer.__construct(ret); } get numbers() { instance.exports.bjs_MultiArrayContainer_numbers_get(this.pointer); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Int.lift(); return arrayResult; } get strings() { instance.exports.bjs_MultiArrayContainer_strings_get(this.pointer); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const string = strStack.pop(); - arrayResult.push(string); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_String.lift(); return arrayResult; } } - const PointHelpers = __bjs_createPointHelpers(); - structHelpers.Point = PointHelpers; + const __bjs_helpers_TestModule_Point = __bjs_createStructHelpers_TestModule_Point(); + structHelpers.TestModule_Point = __bjs_helpers_TestModule_Point; const exports = { processIntArray: function bjs_processIntArray(values) { - for (const elem of values) { - i32Stack.push((elem | 0)); - } - i32Stack.push(values.length); + __bjs_codec_Array_Int.lower(values); instance.exports.bjs_processIntArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Int.lift(); return arrayResult; }, processStringArray: function bjs_processStringArray(values) { - for (const elem of values) { - const bytes = textEncoder.encode(elem); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(values.length); + __bjs_codec_Array_String.lower(values); instance.exports.bjs_processStringArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const string = strStack.pop(); - arrayResult.push(string); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_String.lift(); return arrayResult; }, processDoubleArray: function bjs_processDoubleArray(values) { - for (const elem of values) { - f64Stack.push(elem); - } - i32Stack.push(values.length); + __bjs_codec_Array_Double.lower(values); instance.exports.bjs_processDoubleArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const f64 = f64Stack.pop(); - arrayResult.push(f64); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Double.lift(); return arrayResult; }, processBoolArray: function bjs_processBoolArray(values) { - for (const elem of values) { - i32Stack.push(elem ? 1 : 0); - } - i32Stack.push(values.length); + __bjs_codec_Array_Bool.lower(values); instance.exports.bjs_processBoolArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const bool = i32Stack.pop() !== 0; - arrayResult.push(bool); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Bool.lift(); return arrayResult; }, processPointArray: function bjs_processPointArray(points) { - for (const elem of points) { - structHelpers.Point.lower(elem); - } - i32Stack.push(points.length); + __bjs_codec_Array_TestModule_Point.lower(points); instance.exports.bjs_processPointArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const struct = structHelpers.Point.lift(); - arrayResult.push(struct); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_Point.lift(); return arrayResult; }, processDirectionArray: function bjs_processDirectionArray(directions) { - for (const elem of directions) { - i32Stack.push((elem | 0)); - } - i32Stack.push(directions.length); + __bjs_codec_Array_TestModule_Direction.lower(directions); instance.exports.bjs_processDirectionArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const caseId = i32Stack.pop(); - arrayResult.push(caseId); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_Direction.lift(); return arrayResult; }, processStatusArray: function bjs_processStatusArray(statuses) { - for (const elem of statuses) { - i32Stack.push((elem | 0)); - } - i32Stack.push(statuses.length); + __bjs_codec_Array_TestModule_Status.lower(statuses); instance.exports.bjs_processStatusArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const rawValue = i32Stack.pop(); - arrayResult.push(rawValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_Status.lift(); return arrayResult; }, sumIntArray: function bjs_sumIntArray(values) { - for (const elem of values) { - i32Stack.push((elem | 0)); - } - i32Stack.push(values.length); + __bjs_codec_Array_Int.lower(values); const ret = instance.exports.bjs_sumIntArray(); return ret; }, findFirstPoint: function bjs_findFirstPoint(points, matching) { - for (const elem of points) { - structHelpers.Point.lower(elem); - } - i32Stack.push(points.length); + __bjs_codec_Array_TestModule_Point.lower(points); const matchingBytes = textEncoder.encode(matching); const matchingId = swift.memory.retain(matchingBytes); instance.exports.bjs_findFirstPoint(matchingId, matchingBytes.length); - const structValue = structHelpers.Point.lift(); + const structValue = structHelpers.TestModule_Point.lift(); return structValue; }, processUnsafeRawPointerArray: function bjs_processUnsafeRawPointerArray(values) { - for (const elem of values) { - ptrStack.push((elem | 0)); - } - i32Stack.push(values.length); + __bjs_codec_Array_Surp.lower(values); instance.exports.bjs_processUnsafeRawPointerArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const pointer = ptrStack.pop(); - arrayResult.push(pointer); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Surp.lift(); return arrayResult; }, processUnsafeMutableRawPointerArray: function bjs_processUnsafeMutableRawPointerArray(values) { - for (const elem of values) { - ptrStack.push((elem | 0)); - } - i32Stack.push(values.length); + __bjs_codec_Array_Sumrp.lower(values); instance.exports.bjs_processUnsafeMutableRawPointerArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const pointer = ptrStack.pop(); - arrayResult.push(pointer); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Sumrp.lift(); return arrayResult; }, processOpaquePointerArray: function bjs_processOpaquePointerArray(values) { - for (const elem of values) { - ptrStack.push((elem | 0)); - } - i32Stack.push(values.length); + __bjs_codec_Array_Sop.lower(values); instance.exports.bjs_processOpaquePointerArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const pointer = ptrStack.pop(); - arrayResult.push(pointer); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Sop.lift(); return arrayResult; }, processOptionalIntArray: function bjs_processOptionalIntArray(values) { - for (const elem of values) { - const isSome = elem != null ? 1 : 0; - if (isSome) { - i32Stack.push((elem | 0)); - } - i32Stack.push(isSome); - } - i32Stack.push(values.length); + __bjs_codec_Array_Optional_Int.lower(values); instance.exports.bjs_processOptionalIntArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const isSome1 = i32Stack.pop(); - let optValue; - if (isSome1 === 0) { - optValue = null; - } else { - const int = i32Stack.pop(); - optValue = int; - } - arrayResult.push(optValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Optional_Int.lift(); return arrayResult; }, processOptionalStringArray: function bjs_processOptionalStringArray(values) { - for (const elem of values) { - const isSome = elem != null ? 1 : 0; - if (isSome) { - const bytes = textEncoder.encode(elem); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(isSome); - } - i32Stack.push(values.length); + __bjs_codec_Array_Optional_String.lower(values); instance.exports.bjs_processOptionalStringArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const isSome1 = i32Stack.pop(); - let optValue; - if (isSome1 === 0) { - optValue = null; - } else { - const string = strStack.pop(); - optValue = string; - } - arrayResult.push(optValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Optional_String.lift(); return arrayResult; }, processOptionalArray: function bjs_processOptionalArray(values) { - const isSome = values != null; - if (isSome) { - for (const elem of values) { - i32Stack.push((elem | 0)); - } - i32Stack.push(values.length); - } - i32Stack.push(+isSome); + __bjs_codec_Optional_Array_Int.lower(values); instance.exports.bjs_processOptionalArray(); - const isSome1 = i32Stack.pop(); - let optResult; - if (isSome1) { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } - optResult = arrayResult; - } else { - optResult = null; - } - return optResult; + const optValue = __bjs_codec_Optional_Array_Int.lift(); + return optValue; }, processOptionalPointArray: function bjs_processOptionalPointArray(points) { - for (const elem of points) { - const isSome = elem != null ? 1 : 0; - if (isSome) { - structHelpers.Point.lower(elem); - } - i32Stack.push(isSome); - } - i32Stack.push(points.length); + __bjs_codec_Array_Optional_TestModule_Point.lower(points); instance.exports.bjs_processOptionalPointArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const isSome1 = i32Stack.pop(); - let optValue; - if (isSome1 === 0) { - optValue = null; - } else { - const struct = structHelpers.Point.lift(); - optValue = struct; - } - arrayResult.push(optValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Optional_TestModule_Point.lift(); return arrayResult; }, processOptionalDirectionArray: function bjs_processOptionalDirectionArray(directions) { - for (const elem of directions) { - const isSome = elem != null ? 1 : 0; - if (isSome) { - i32Stack.push((elem | 0)); - } - i32Stack.push(isSome); - } - i32Stack.push(directions.length); + __bjs_codec_Array_Optional_TestModule_Direction.lower(directions); instance.exports.bjs_processOptionalDirectionArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const isSome1 = i32Stack.pop(); - let optValue; - if (isSome1 === 0) { - optValue = null; - } else { - const caseId = i32Stack.pop(); - optValue = caseId; - } - arrayResult.push(optValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Optional_TestModule_Direction.lift(); return arrayResult; }, processOptionalStatusArray: function bjs_processOptionalStatusArray(statuses) { - for (const elem of statuses) { - const isSome = elem != null ? 1 : 0; - if (isSome) { - i32Stack.push((elem | 0)); - } - i32Stack.push(isSome); - } - i32Stack.push(statuses.length); + __bjs_codec_Array_Optional_TestModule_Status.lower(statuses); instance.exports.bjs_processOptionalStatusArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const isSome1 = i32Stack.pop(); - let optValue; - if (isSome1 === 0) { - optValue = null; - } else { - const rawValue = i32Stack.pop(); - optValue = rawValue; - } - arrayResult.push(optValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Optional_TestModule_Status.lift(); return arrayResult; }, processNestedIntArray: function bjs_processNestedIntArray(values) { - for (const elem of values) { - for (const elem1 of elem) { - i32Stack.push((elem1 | 0)); - } - i32Stack.push(elem.length); - } - i32Stack.push(values.length); + __bjs_codec_Array_Array_Int.lower(values); instance.exports.bjs_processNestedIntArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const arrayLen1 = i32Stack.pop(); - let arrayResult1; - if (arrayLen1 === -1) { - arrayResult1 = taStack.pop(); - } else { - arrayResult1 = []; - for (let i1 = 0; i1 < arrayLen1; i1++) { - const int = i32Stack.pop(); - arrayResult1.push(int); - } - arrayResult1.reverse(); - } - arrayResult.push(arrayResult1); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Array_Int.lift(); return arrayResult; }, processNestedStringArray: function bjs_processNestedStringArray(values) { - for (const elem of values) { - for (const elem1 of elem) { - const bytes = textEncoder.encode(elem1); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(elem.length); - } - i32Stack.push(values.length); + __bjs_codec_Array_Array_String.lower(values); instance.exports.bjs_processNestedStringArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const arrayLen1 = i32Stack.pop(); - let arrayResult1; - if (arrayLen1 === -1) { - arrayResult1 = taStack.pop(); - } else { - arrayResult1 = []; - for (let i1 = 0; i1 < arrayLen1; i1++) { - const string = strStack.pop(); - arrayResult1.push(string); - } - arrayResult1.reverse(); - } - arrayResult.push(arrayResult1); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Array_String.lift(); return arrayResult; }, processNestedPointArray: function bjs_processNestedPointArray(points) { - for (const elem of points) { - for (const elem1 of elem) { - structHelpers.Point.lower(elem1); - } - i32Stack.push(elem.length); - } - i32Stack.push(points.length); + __bjs_codec_Array_Array_TestModule_Point.lower(points); instance.exports.bjs_processNestedPointArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const arrayLen1 = i32Stack.pop(); - let arrayResult1; - if (arrayLen1 === -1) { - arrayResult1 = taStack.pop(); - } else { - arrayResult1 = []; - for (let i1 = 0; i1 < arrayLen1; i1++) { - const struct = structHelpers.Point.lift(); - arrayResult1.push(struct); - } - arrayResult1.reverse(); - } - arrayResult.push(arrayResult1); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Array_TestModule_Point.lift(); return arrayResult; }, processItemArray: function bjs_processItemArray(items) { - for (const elem of items) { - ptrStack.push(elem.pointer); - } - i32Stack.push(items.length); + __bjs_codec_Array_TestModule_Item.lower(items); instance.exports.bjs_processItemArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const ptr = ptrStack.pop(); - const obj = Item.__construct(ptr); - arrayResult.push(obj); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_Item.lift(); return arrayResult; }, processNestedItemArray: function bjs_processNestedItemArray(items) { - for (const elem of items) { - for (const elem1 of elem) { - ptrStack.push(elem1.pointer); - } - i32Stack.push(elem.length); - } - i32Stack.push(items.length); + __bjs_codec_Array_Array_TestModule_Item.lower(items); instance.exports.bjs_processNestedItemArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const arrayLen1 = i32Stack.pop(); - let arrayResult1; - if (arrayLen1 === -1) { - arrayResult1 = taStack.pop(); - } else { - arrayResult1 = []; - for (let i1 = 0; i1 < arrayLen1; i1++) { - const ptr = ptrStack.pop(); - const obj = Item.__construct(ptr); - arrayResult1.push(obj); - } - arrayResult1.reverse(); - } - arrayResult.push(arrayResult1); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Array_TestModule_Item.lift(); return arrayResult; }, processJSObjectArray: function bjs_processJSObjectArray(objects) { - for (const elem of objects) { - const objId = swift.memory.retain(elem); - i32Stack.push(objId); - } - i32Stack.push(objects.length); + __bjs_codec_Array_JSObject.lower(objects); instance.exports.bjs_processJSObjectArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const objId1 = i32Stack.pop(); - const obj = swift.memory.getObject(objId1); - swift.memory.release(objId1); - arrayResult.push(obj); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_JSObject.lift(); return arrayResult; }, processOptionalJSObjectArray: function bjs_processOptionalJSObjectArray(objects) { - for (const elem of objects) { - const isSome = elem != null ? 1 : 0; - if (isSome) { - const objId = swift.memory.retain(elem); - i32Stack.push(objId); - } - i32Stack.push(isSome); - } - i32Stack.push(objects.length); + __bjs_codec_Array_Optional_JSObject.lower(objects); instance.exports.bjs_processOptionalJSObjectArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const isSome1 = i32Stack.pop(); - let optValue; - if (isSome1 === 0) { - optValue = null; - } else { - const objId1 = i32Stack.pop(); - const obj = swift.memory.getObject(objId1); - swift.memory.release(objId1); - optValue = obj; - } - arrayResult.push(optValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Optional_JSObject.lift(); return arrayResult; }, processNestedJSObjectArray: function bjs_processNestedJSObjectArray(objects) { - for (const elem of objects) { - for (const elem1 of elem) { - const objId = swift.memory.retain(elem1); - i32Stack.push(objId); - } - i32Stack.push(elem.length); - } - i32Stack.push(objects.length); + __bjs_codec_Array_Array_JSObject.lower(objects); instance.exports.bjs_processNestedJSObjectArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const arrayLen1 = i32Stack.pop(); - let arrayResult1; - if (arrayLen1 === -1) { - arrayResult1 = taStack.pop(); - } else { - arrayResult1 = []; - for (let i1 = 0; i1 < arrayLen1; i1++) { - const objId1 = i32Stack.pop(); - const obj = swift.memory.getObject(objId1); - swift.memory.release(objId1); - arrayResult1.push(obj); - } - arrayResult1.reverse(); - } - arrayResult.push(arrayResult1); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Array_JSObject.lift(); return arrayResult; }, multiArrayParams: function bjs_multiArrayParams(nums, strs) { - for (const elem of nums) { - i32Stack.push((elem | 0)); - } - i32Stack.push(nums.length); - for (const elem1 of strs) { - const bytes = textEncoder.encode(elem1); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(strs.length); + __bjs_codec_Array_Int.lower(nums); + __bjs_codec_Array_String.lower(strs); const ret = instance.exports.bjs_multiArrayParams(); return ret; }, multiOptionalArrayParams: function bjs_multiOptionalArrayParams(a, b) { - const isSome = a != null; - if (isSome) { - for (const elem of a) { - i32Stack.push((elem | 0)); - } - i32Stack.push(a.length); - } - i32Stack.push(+isSome); - const isSome1 = b != null; - if (isSome1) { - for (const elem1 of b) { - const bytes = textEncoder.encode(elem1); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(b.length); - } - i32Stack.push(+isSome1); + __bjs_codec_Optional_Array_Int.lower(a); + __bjs_codec_Optional_Array_String.lower(b); const ret = instance.exports.bjs_multiOptionalArrayParams(); return ret; }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.d.ts index 507a96d4a..fefbf0039 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.d.ts @@ -55,5 +55,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js index 9f2faf589..ea86e699e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js @@ -41,6 +41,240 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + function __bjs_jsValueLower(value) { let kind; let payload1; @@ -130,7 +364,31 @@ export async function createInstantiator(options, swift) { return jsValue; } - const __bjs_createAsyncPointHelpers = () => ({ + const __bjs_codec_TestModule_AsyncPoint = { + lower: (v) => { + structHelpers.TestModule_AsyncPoint.lower(v); + }, + lift: () => { + const struct = structHelpers.TestModule_AsyncPoint.lift(); + return struct; + }, + }; + const __bjs_codec_Optional_TestModule_AsyncPoint = __bjs_optionalCodec(__bjs_codec_TestModule_AsyncPoint); + const __bjs_codec_Array_TestModule_AsyncPoint = __bjs_arrayCodec(__bjs_codec_TestModule_AsyncPoint); + const __bjs_codec_TestModule_AsyncDirection = { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const caseId = i32Stack.pop(); + return caseId; + }, + }; + const __bjs_codec_Array_TestModule_AsyncDirection = __bjs_arrayCodec(__bjs_codec_TestModule_AsyncDirection); + const __bjs_codec_Dict_TestModule_AsyncPoint = __bjs_dictCodec(__bjs_codec_TestModule_AsyncPoint); + const __bjs_codec_Dict_TestModule_AsyncDirection = __bjs_dictCodec(__bjs_codec_TestModule_AsyncDirection); + + const __bjs_createStructHelpers_TestModule_AsyncPoint = () => ({ lower: (value) => { i32Stack.push((value.x | 0)); i32Stack.push((value.y | 0)); @@ -217,12 +475,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_AsyncPoint"] = function(objectId) { - structHelpers.AsyncPoint.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_AsyncPoint.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_AsyncPoint"] = function() { - const value = structHelpers.AsyncPoint.lift(); + const value = structHelpers.TestModule_AsyncPoint.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -282,7 +542,7 @@ export async function createInstantiator(options, swift) { } bjs["promise_resolve_TestModule_10AsyncPointV"] = function(promise) { try { - const structValue = structHelpers.AsyncPoint.lift(); + const structValue = structHelpers.TestModule_AsyncPoint.lift(); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(structValue); } catch (error) { setException(error); @@ -328,7 +588,7 @@ export async function createInstantiator(options, swift) { try { let optResult; if (value) { - const struct = structHelpers.AsyncPoint.lift(); + const struct = structHelpers.TestModule_AsyncPoint.lift(); optResult = struct; } else { optResult = null; @@ -340,18 +600,7 @@ export async function createInstantiator(options, swift) { } bjs["promise_resolve_TestModule_Sa10AsyncPointV"] = function(promise) { try { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const struct = structHelpers.AsyncPoint.lift(); - arrayResult.push(struct); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_AsyncPoint.lift(); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(arrayResult); } catch (error) { setException(error); @@ -359,18 +608,7 @@ export async function createInstantiator(options, swift) { } bjs["promise_resolve_TestModule_Sa14AsyncDirectionO"] = function(promise) { try { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const caseId = i32Stack.pop(); - arrayResult.push(caseId); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_AsyncDirection.lift(); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(arrayResult); } catch (error) { setException(error); @@ -378,13 +616,7 @@ export async function createInstantiator(options, swift) { } bjs["promise_resolve_TestModule_SD10AsyncPointV"] = function(promise) { try { - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const struct = structHelpers.AsyncPoint.lift(); - const string = strStack.pop(); - dictResult[string] = struct; - } + const dictResult = __bjs_codec_Dict_TestModule_AsyncPoint.lift(); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(dictResult); } catch (error) { setException(error); @@ -392,13 +624,7 @@ export async function createInstantiator(options, swift) { } bjs["promise_resolve_TestModule_SD14AsyncDirectionO"] = function(promise) { try { - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const caseId = i32Stack.pop(); - const string = strStack.pop(); - dictResult[string] = caseId; - } + const dictResult = __bjs_codec_Dict_TestModule_AsyncDirection.lift(); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(dictResult); } catch (error) { setException(error); @@ -516,8 +742,8 @@ export async function createInstantiator(options, swift) { /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { const js = swift.memory.heap; - const AsyncPointHelpers = __bjs_createAsyncPointHelpers(); - structHelpers.AsyncPoint = AsyncPointHelpers; + const __bjs_helpers_TestModule_AsyncPoint = __bjs_createStructHelpers_TestModule_AsyncPoint(); + structHelpers.TestModule_AsyncPoint = __bjs_helpers_TestModule_AsyncPoint; const exports = { asyncReturnVoid: function bjs_asyncReturnVoid() { @@ -565,14 +791,14 @@ export async function createInstantiator(options, swift) { return ret1; }, asyncRoundTripStruct: function bjs_asyncRoundTripStruct(v) { - structHelpers.AsyncPoint.lower(v); + structHelpers.TestModule_AsyncPoint.lower(v); const ret = instance.exports.bjs_asyncRoundTripStruct(); const ret1 = swift.memory.getObject(ret); swift.memory.release(ret); return ret1; }, asyncRoundTripStructThrows: function bjs_asyncRoundTripStructThrows(v) { - structHelpers.AsyncPoint.lower(v); + structHelpers.TestModule_AsyncPoint.lower(v); const ret = instance.exports.bjs_asyncRoundTripStructThrows(); const ret1 = swift.memory.getObject(ret); swift.memory.release(ret); @@ -597,8 +823,8 @@ export async function createInstantiator(options, swift) { return ret1; }, asyncCombineStructs: function bjs_asyncCombineStructs(a, b) { - structHelpers.AsyncPoint.lower(a); - structHelpers.AsyncPoint.lower(b); + structHelpers.TestModule_AsyncPoint.lower(a); + structHelpers.TestModule_AsyncPoint.lower(b); const ret = instance.exports.bjs_asyncCombineStructs(); const ret1 = swift.memory.getObject(ret); swift.memory.release(ret); @@ -643,63 +869,35 @@ export async function createInstantiator(options, swift) { return ret1; }, asyncRoundTripOptionalStruct: function bjs_asyncRoundTripOptionalStruct(v) { - const isSome = v != null; - if (isSome) { - structHelpers.AsyncPoint.lower(v); - } - i32Stack.push(+isSome); + __bjs_codec_Optional_TestModule_AsyncPoint.lower(v); const ret = instance.exports.bjs_asyncRoundTripOptionalStruct(); const ret1 = swift.memory.getObject(ret); swift.memory.release(ret); return ret1; }, asyncRoundTripStructArray: function bjs_asyncRoundTripStructArray(v) { - for (const elem of v) { - structHelpers.AsyncPoint.lower(elem); - } - i32Stack.push(v.length); + __bjs_codec_Array_TestModule_AsyncPoint.lower(v); const ret = instance.exports.bjs_asyncRoundTripStructArray(); const ret1 = swift.memory.getObject(ret); swift.memory.release(ret); return ret1; }, asyncRoundTripEnumArray: function bjs_asyncRoundTripEnumArray(v) { - for (const elem of v) { - i32Stack.push((elem | 0)); - } - i32Stack.push(v.length); + __bjs_codec_Array_TestModule_AsyncDirection.lower(v); const ret = instance.exports.bjs_asyncRoundTripEnumArray(); const ret1 = swift.memory.getObject(ret); swift.memory.release(ret); return ret1; }, asyncRoundTripStructDictionary: function bjs_asyncRoundTripStructDictionary(v) { - const entries = Object.entries(v); - for (const entry of entries) { - const [key, value] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - structHelpers.AsyncPoint.lower(value); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_TestModule_AsyncPoint.lower(v); const ret = instance.exports.bjs_asyncRoundTripStructDictionary(); const ret1 = swift.memory.getObject(ret); swift.memory.release(ret); return ret1; }, asyncRoundTripEnumDictionary: function bjs_asyncRoundTripEnumDictionary(v) { - const entries = Object.entries(v); - for (const entry of entries) { - const [key, value] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - i32Stack.push((value | 0)); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_TestModule_AsyncDirection.lower(v); const ret = instance.exports.bjs_asyncRoundTripEnumDictionary(); const ret1 = swift.memory.getObject(ret); swift.memory.release(ret); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.d.ts index d25336ef7..c0c8900d7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.d.ts @@ -29,5 +29,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.js index 98c0aff46..04d9ca992 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.js @@ -127,7 +127,7 @@ export async function createInstantiator(options, swift) { return jsValue; } - const __bjs_createAsyncPayloadResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_AsyncPayloadResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -239,6 +239,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -248,7 +250,7 @@ export async function createInstantiator(options, swift) { } bjs["promise_resolve_TestModule_18AsyncPayloadResultO"] = function(promise, value) { try { - const enumValue = enumHelpers.AsyncPayloadResult.lift(value); + const enumValue = enumHelpers.TestModule_AsyncPayloadResult.lift(value); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(enumValue); } catch (error) { setException(error); @@ -258,7 +260,7 @@ export async function createInstantiator(options, swift) { try { let optResult; if (valueIsSome) { - const enumValue = enumHelpers.AsyncPayloadResult.lift(valueCaseId); + const enumValue = enumHelpers.TestModule_AsyncPayloadResult.lift(valueCaseId); optResult = enumValue; } else { optResult = null; @@ -380,12 +382,12 @@ export async function createInstantiator(options, swift) { /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { const js = swift.memory.heap; - const AsyncPayloadResultHelpers = __bjs_createAsyncPayloadResultValuesHelpers(); - enumHelpers.AsyncPayloadResult = AsyncPayloadResultHelpers; + const __bjs_helpers_TestModule_AsyncPayloadResult = __bjs_createEnumHelpers_TestModule_AsyncPayloadResult(); + enumHelpers.TestModule_AsyncPayloadResult = __bjs_helpers_TestModule_AsyncPayloadResult; const exports = { asyncRoundTripAssociatedValueEnum: function bjs_asyncRoundTripAssociatedValueEnum(value) { - const valueCaseId = enumHelpers.AsyncPayloadResult.lower(value); + const valueCaseId = enumHelpers.TestModule_AsyncPayloadResult.lower(value); const ret = instance.exports.bjs_asyncRoundTripAssociatedValueEnum(valueCaseId); const ret1 = swift.memory.getObject(ret); swift.memory.release(ret); @@ -395,7 +397,7 @@ export async function createInstantiator(options, swift) { const isSome = value != null; let result; if (isSome) { - const valueCaseId = enumHelpers.AsyncPayloadResult.lower(value); + const valueCaseId = enumHelpers.TestModule_AsyncPayloadResult.lower(value); result = valueCaseId; } else { result = 0; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.d.ts index e612ae1e1..f1bf7e0c6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.d.ts @@ -19,5 +19,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js index fa50b23f2..96c0d11e8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js @@ -221,6 +221,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.d.ts index 491a66795..97a9c23ad 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.d.ts @@ -19,5 +19,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js index c359886b3..47dd161a2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js @@ -220,6 +220,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.d.ts index 5537696c4..a2bd7b41b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.d.ts @@ -47,5 +47,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.js index 272bb8c49..fa74b5097 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.js @@ -36,7 +36,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createAccount_CredentialsHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Account_Credentials = () => ({ lower: (value) => { const bytes = textEncoder.encode(value.token); const id = swift.memory.retain(bytes); @@ -124,12 +124,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_Account_Credentials"] = function(objectId) { - structHelpers.Account_Credentials.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Account_Credentials.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Account_Credentials"] = function() { - const value = structHelpers.Account_Credentials.lift(); + const value = structHelpers.TestModule_Account_Credentials.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -342,8 +344,8 @@ export async function createInstantiator(options, swift) { return ret; } } - const Account_CredentialsHelpers = __bjs_createAccount_CredentialsHelpers(); - structHelpers.Account_Credentials = Account_CredentialsHelpers; + const __bjs_helpers_TestModule_Account_Credentials = __bjs_createStructHelpers_TestModule_Account_Credentials(); + structHelpers.TestModule_Account_Credentials = __bjs_helpers_TestModule_Account_Credentials; const exports = { Account: Object.assign(Account, { @@ -353,7 +355,7 @@ export async function createInstantiator(options, swift) { const tokenBytes = textEncoder.encode(token); const tokenId = swift.memory.retain(tokenBytes); instance.exports.bjs_Account_Credentials_init(tokenId, tokenBytes.length); - const structValue = structHelpers.Account_Credentials.lift(); + const structValue = structHelpers.TestModule_Account_Credentials.lift(); return structValue; }, get maxLength() { @@ -362,7 +364,7 @@ export async function createInstantiator(options, swift) { }, empty: function() { instance.exports.bjs_Account_Credentials_static_empty(); - const structValue = structHelpers.Account_Credentials.lift(); + const structValue = structHelpers.TestModule_Account_Credentials.lift(); return structValue; }, }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.d.ts index 961b9fa5b..7cec6e66b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.d.ts @@ -161,5 +161,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js index ba2b7cc77..e41408943 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js @@ -37,7 +37,345 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createConfigHelpers = () => ({ + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_TestModule_Config = { + lower: (v) => { + structHelpers.TestModule_Config.lower(v); + }, + lift: () => { + const struct = structHelpers.TestModule_Config.lift(); + return struct; + }, + }; + const __bjs_codec_Optional_TestModule_Config = __bjs_optionalCodec(__bjs_codec_TestModule_Config); + const __bjs_codec_Array_Int = __bjs_arrayCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_Array_String = __bjs_arrayCodec(__bjs_stringCodec); + const __bjs_codec_Array_Double = __bjs_arrayCodec(__bjs_primitiveCodecs.Double); + const __bjs_codec_Array_Bool = __bjs_arrayCodec(__bjs_primitiveCodecs.Bool); + + const __bjs_createStructHelpers_TestModule_Config = () => ({ lower: (value) => { const bytes = textEncoder.encode(value.name); const id = swift.memory.retain(bytes); @@ -53,7 +391,7 @@ export async function createInstantiator(options, swift) { return { name: string, value: int, enabled: bool }; } }); - const __bjs_createMathOperationsHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_MathOperations = () => ({ lower: (value) => { f64Stack.push(value.baseValue); }, @@ -61,12 +399,12 @@ export async function createInstantiator(options, swift) { const f64 = f64Stack.pop(); const instance1 = { baseValue: f64 }; instance1.add = function(a, b = 10.0) { - structHelpers.MathOperations.lower(this); + structHelpers.TestModule_MathOperations.lower(this); const ret = instance.exports.bjs_MathOperations_add(a, b); return ret; }.bind(instance1); instance1.multiply = function(a, b) { - structHelpers.MathOperations.lower(this); + structHelpers.TestModule_MathOperations.lower(this); const ret1 = instance.exports.bjs_MathOperations_multiply(a, b); return ret1; }.bind(instance1); @@ -149,19 +487,21 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_Config"] = function(objectId) { - structHelpers.Config.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Config.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Config"] = function() { - const value = structHelpers.Config.lift(); + const value = structHelpers.TestModule_Config.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_MathOperations"] = function(objectId) { - structHelpers.MathOperations.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_MathOperations.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_MathOperations"] = function() { - const value = structHelpers.MathOperations.lift(); + const value = structHelpers.TestModule_MathOperations.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -449,11 +789,11 @@ export async function createInstantiator(options, swift) { instance.exports.bjs_ConstructorDefaults_tag_set(this.pointer, +isSome, result, result1); } } - const ConfigHelpers = __bjs_createConfigHelpers(); - structHelpers.Config = ConfigHelpers; + const __bjs_helpers_TestModule_Config = __bjs_createStructHelpers_TestModule_Config(); + structHelpers.TestModule_Config = __bjs_helpers_TestModule_Config; - const MathOperationsHelpers = __bjs_createMathOperationsHelpers(); - structHelpers.MathOperations = MathOperationsHelpers; + const __bjs_helpers_TestModule_MathOperations = __bjs_createStructHelpers_TestModule_MathOperations(); + structHelpers.TestModule_MathOperations = __bjs_helpers_TestModule_MathOperations; const exports = { testStringDefault: function bjs_testStringDefault(message = "Hello World") { @@ -535,137 +875,51 @@ export async function createInstantiator(options, swift) { return EmptyGreeter.__construct(ret); }, testOptionalStructDefault: function bjs_testOptionalStructDefault(point = null) { - const isSome = point != null; - if (isSome) { - structHelpers.Config.lower(point); - } - i32Stack.push(+isSome); + __bjs_codec_Optional_TestModule_Config.lower(point); instance.exports.bjs_testOptionalStructDefault(); - const isSome1 = i32Stack.pop(); - const optResult = isSome1 ? structHelpers.Config.lift() : null; - return optResult; + const optValue = __bjs_codec_Optional_TestModule_Config.lift(); + return optValue; }, testOptionalStructWithValueDefault: function bjs_testOptionalStructWithValueDefault(point = { name: "default", value: 42, enabled: true }) { - const isSome = point != null; - if (isSome) { - structHelpers.Config.lower(point); - } - i32Stack.push(+isSome); + __bjs_codec_Optional_TestModule_Config.lower(point); instance.exports.bjs_testOptionalStructWithValueDefault(); - const isSome1 = i32Stack.pop(); - const optResult = isSome1 ? structHelpers.Config.lift() : null; - return optResult; + const optValue = __bjs_codec_Optional_TestModule_Config.lift(); + return optValue; }, testIntArrayDefault: function bjs_testIntArrayDefault(values = [1, 2, 3]) { - for (const elem of values) { - i32Stack.push((elem | 0)); - } - i32Stack.push(values.length); + __bjs_codec_Array_Int.lower(values); instance.exports.bjs_testIntArrayDefault(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Int.lift(); return arrayResult; }, testStringArrayDefault: function bjs_testStringArrayDefault(names = ["a", "b", "c"]) { - for (const elem of names) { - const bytes = textEncoder.encode(elem); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(names.length); + __bjs_codec_Array_String.lower(names); instance.exports.bjs_testStringArrayDefault(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const string = strStack.pop(); - arrayResult.push(string); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_String.lift(); return arrayResult; }, testDoubleArrayDefault: function bjs_testDoubleArrayDefault(values = [1.5, 2.5, 3.5]) { - for (const elem of values) { - f64Stack.push(elem); - } - i32Stack.push(values.length); + __bjs_codec_Array_Double.lower(values); instance.exports.bjs_testDoubleArrayDefault(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const f64 = f64Stack.pop(); - arrayResult.push(f64); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Double.lift(); return arrayResult; }, testBoolArrayDefault: function bjs_testBoolArrayDefault(flags = [true, false, true]) { - for (const elem of flags) { - i32Stack.push(elem ? 1 : 0); - } - i32Stack.push(flags.length); + __bjs_codec_Array_Bool.lower(flags); instance.exports.bjs_testBoolArrayDefault(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const bool = i32Stack.pop() !== 0; - arrayResult.push(bool); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Bool.lift(); return arrayResult; }, testEmptyArrayDefault: function bjs_testEmptyArrayDefault(items = []) { - for (const elem of items) { - i32Stack.push((elem | 0)); - } - i32Stack.push(items.length); + __bjs_codec_Array_Int.lower(items); instance.exports.bjs_testEmptyArrayDefault(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Int.lift(); return arrayResult; }, testMixedWithArrayDefault: function bjs_testMixedWithArrayDefault(name = "test", values = [10, 20, 30], enabled = true) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); - for (const elem of values) { - i32Stack.push((elem | 0)); - } - i32Stack.push(values.length); + __bjs_codec_Array_Int.lower(values); instance.exports.bjs_testMixedWithArrayDefault(nameId, nameBytes.length, enabled); const ret = tmpRetString; tmpRetString = undefined; @@ -678,7 +932,7 @@ export async function createInstantiator(options, swift) { MathOperations: { init: function(baseValue = 0.0) { instance.exports.bjs_MathOperations_init(baseValue); - const structValue = structHelpers.MathOperations.lift(); + const structValue = structHelpers.TestModule_MathOperations.lift(); return structValue; }, subtract: function(a, b = 5.0) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.d.ts index 652177cd8..2479e3f25 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.d.ts @@ -35,5 +35,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js index d0ac5307f..0de3fde05 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js @@ -31,44 +31,363 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createCountersHelpers = () => ({ + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_Dict_Int = __bjs_dictCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_Dict_String = __bjs_dictCodec(__bjs_stringCodec); + const __bjs_codec_Optional_Dict_String = __bjs_optionalCodec(__bjs_codec_Dict_String); + const __bjs_codec_Array_Int = __bjs_arrayCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_Dict_Array_Int = __bjs_dictCodec(__bjs_codec_Array_Int); + const __bjs_codec_TestModule_Box = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports['Box'].__construct(ptr); + return obj; + }, + }; + const __bjs_codec_Dict_TestModule_Box = __bjs_dictCodec(__bjs_codec_TestModule_Box); + const __bjs_codec_Optional_TestModule_Box = __bjs_optionalCodec(__bjs_codec_TestModule_Box); + const __bjs_codec_Dict_Optional_TestModule_Box = __bjs_dictCodec(__bjs_codec_Optional_TestModule_Box); + const __bjs_codec_Dict_Double = __bjs_dictCodec(__bjs_primitiveCodecs.Double); + const __bjs_codec_Optional_Int = __bjs_optionalCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_Dict_Optional_Int = __bjs_dictCodec(__bjs_codec_Optional_Int); + + const __bjs_createStructHelpers_TestModule_Counters = () => ({ lower: (value) => { const bytes = textEncoder.encode(value.name); const id = swift.memory.retain(bytes); i32Stack.push(bytes.length); i32Stack.push(id); - const entries = Object.entries(value.counts); - for (const entry of entries) { - const [key, value] = entry; - const bytes1 = textEncoder.encode(key); - const id1 = swift.memory.retain(bytes1); - i32Stack.push(bytes1.length); - i32Stack.push(id1); - const isSome = value != null ? 1 : 0; - if (isSome) { - i32Stack.push((value | 0)); - } - i32Stack.push(isSome); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_Optional_Int.lower(value.counts); }, lift: () => { - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const int = i32Stack.pop(); - optValue = int; - } - const string = strStack.pop(); - dictResult[string] = optValue; - } - const string1 = strStack.pop(); - return { name: string1, counts: dictResult }; + const dictResult = __bjs_codec_Dict_Optional_Int.lift(); + const string = strStack.pop(); + return { name: string, counts: dictResult }; } }); @@ -148,12 +467,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_Counters"] = function(objectId) { - structHelpers.Counters.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Counters.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Counters"] = function() { - const value = structHelpers.Counters.lift(); + const value = structHelpers.TestModule_Counters.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -262,24 +583,9 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_importMirrorDictionary"] = function bjs_importMirrorDictionary() { try { - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const f64 = f64Stack.pop(); - const string = strStack.pop(); - dictResult[string] = f64; - } + const dictResult = __bjs_codec_Dict_Double.lift(); let ret = imports.importMirrorDictionary(dictResult); - const entries = Object.entries(ret); - for (const entry of entries) { - const [key, value] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - f64Stack.push(value); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_Double.lower(ret); } catch (error) { setException(error); } @@ -356,160 +662,44 @@ export async function createInstantiator(options, swift) { } } - const CountersHelpers = __bjs_createCountersHelpers(); - structHelpers.Counters = CountersHelpers; + const __bjs_helpers_TestModule_Counters = __bjs_createStructHelpers_TestModule_Counters(); + structHelpers.TestModule_Counters = __bjs_helpers_TestModule_Counters; const exports = { mirrorDictionary: function bjs_mirrorDictionary(values) { - const entries = Object.entries(values); - for (const entry of entries) { - const [key, value] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - i32Stack.push((value | 0)); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_Int.lower(values); instance.exports.bjs_mirrorDictionary(); - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const int = i32Stack.pop(); - const string = strStack.pop(); - dictResult[string] = int; - } + const dictResult = __bjs_codec_Dict_Int.lift(); return dictResult; }, optionalDictionary: function bjs_optionalDictionary(values) { - const isSome = values != null; - if (isSome) { - const entries = Object.entries(values); - for (const entry of entries) { - const [key, value] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - const bytes1 = textEncoder.encode(value); - const id1 = swift.memory.retain(bytes1); - i32Stack.push(bytes1.length); - i32Stack.push(id1); - } - i32Stack.push(entries.length); - } - i32Stack.push(+isSome); + __bjs_codec_Optional_Dict_String.lower(values); instance.exports.bjs_optionalDictionary(); - const isSome1 = i32Stack.pop(); - let optResult; - if (isSome1) { - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const string = strStack.pop(); - const string1 = strStack.pop(); - dictResult[string1] = string; - } - optResult = dictResult; - } else { - optResult = null; - } - return optResult; + const optValue = __bjs_codec_Optional_Dict_String.lift(); + return optValue; }, nestedDictionary: function bjs_nestedDictionary(values) { - const entries = Object.entries(values); - for (const entry of entries) { - const [key, value] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - for (const elem of value) { - i32Stack.push((elem | 0)); - } - i32Stack.push(value.length); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_Array_Int.lower(values); instance.exports.bjs_nestedDictionary(); - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i1 = 0; i1 < arrayLen; i1++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } - const string = strStack.pop(); - dictResult[string] = arrayResult; - } + const dictResult = __bjs_codec_Dict_Array_Int.lift(); return dictResult; }, boxDictionary: function bjs_boxDictionary(boxes) { - const entries = Object.entries(boxes); - for (const entry of entries) { - const [key, value] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - ptrStack.push(value.pointer); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_TestModule_Box.lower(boxes); instance.exports.bjs_boxDictionary(); - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const ptr = ptrStack.pop(); - const obj = Box.__construct(ptr); - const string = strStack.pop(); - dictResult[string] = obj; - } + const dictResult = __bjs_codec_Dict_TestModule_Box.lift(); return dictResult; }, optionalBoxDictionary: function bjs_optionalBoxDictionary(boxes) { - const entries = Object.entries(boxes); - for (const entry of entries) { - const [key, value] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - const isSome = value != null ? 1 : 0; - if (isSome) { - ptrStack.push(value.pointer); - } - i32Stack.push(isSome); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_Optional_TestModule_Box.lower(boxes); instance.exports.bjs_optionalBoxDictionary(); - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const isSome1 = i32Stack.pop(); - let optValue; - if (isSome1 === 0) { - optValue = null; - } else { - const ptr = ptrStack.pop(); - const obj = Box.__construct(ptr); - optValue = obj; - } - const string = strStack.pop(); - dictResult[string] = optValue; - } + const dictResult = __bjs_codec_Dict_Optional_TestModule_Box.lift(); return dictResult; }, roundtripCounters: function bjs_roundtripCounters(counters) { - structHelpers.Counters.lower(counters); + structHelpers.TestModule_Counters.lower(counters); instance.exports.bjs_roundtripCounters(); - const structValue = structHelpers.Counters.lift(); + const structValue = structHelpers.TestModule_Counters.lift(); return structValue; }, Box, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.d.ts index 196ef73fe..f37d8945d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.d.ts @@ -136,5 +136,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.js index f29814675..fffb28b1e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.js @@ -37,7 +37,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createPointHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Point = () => ({ lower: (value) => { f64Stack.push(value.x); f64Stack.push(value.y); @@ -124,12 +124,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_Point"] = function(objectId) { - structHelpers.Point.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Point.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Point"] = function() { - const value = structHelpers.Point.lift(); + const value = structHelpers.TestModule_Point.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -347,8 +349,8 @@ export async function createInstantiator(options, swift) { instance.exports.bjs_Greeter_name_set(this.pointer, valueId, valueBytes.length); } } - const PointHelpers = __bjs_createPointHelpers(); - structHelpers.Point = PointHelpers; + const __bjs_helpers_TestModule_Point = __bjs_createStructHelpers_TestModule_Point(); + structHelpers.TestModule_Point = __bjs_helpers_TestModule_Point; const exports = { greet: function bjs_greet(name, greeting = "Hello") { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.d.ts index d2772fa8b..4921cd937 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.d.ts @@ -26,5 +26,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.js index 42f3fd958..d8bc06fcf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.js @@ -106,6 +106,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.d.ts index 36fc92474..9e6d967ff 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.d.ts @@ -195,5 +195,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js index 36683fd58..a22b38137 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js @@ -112,7 +112,401 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createPointHelpers = () => ({ + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_Optional_String = __bjs_optionalCodec(__bjs_stringCodec); + const __bjs_codec_Optional_Bool = __bjs_optionalCodec(__bjs_primitiveCodecs.Bool); + const __bjs_codec_Optional_Int = __bjs_optionalCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_TestModule_Precision = { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const rawValue = f32Stack.pop(); + return rawValue; + }, + }; + const __bjs_codec_Optional_TestModule_Precision = __bjs_optionalCodec(__bjs_codec_TestModule_Precision); + const __bjs_codec_TestModule_CardinalDirection = { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const caseId = i32Stack.pop(); + return caseId; + }, + }; + const __bjs_codec_Optional_TestModule_CardinalDirection = __bjs_optionalCodec(__bjs_codec_TestModule_CardinalDirection); + const __bjs_codec_Array_Int = __bjs_arrayCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_TestModule_Point = { + lower: (v) => { + structHelpers.TestModule_Point.lower(v); + }, + lift: () => { + const struct = structHelpers.TestModule_Point.lift(); + return struct; + }, + }; + const __bjs_codec_Optional_TestModule_Point = __bjs_optionalCodec(__bjs_codec_TestModule_Point); + const __bjs_codec_TestModule_User = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports['User'].__construct(ptr); + return obj; + }, + }; + const __bjs_codec_Optional_TestModule_User = __bjs_optionalCodec(__bjs_codec_TestModule_User); + const __bjs_codec_JSObject = { + lower: (v) => { + const objId = swift.memory.retain(v); + i32Stack.push(objId); + }, + lift: () => { + const objId = i32Stack.pop(); + const obj = swift.memory.getObject(objId); + swift.memory.release(objId); + return obj; + }, + }; + const __bjs_codec_Optional_JSObject = __bjs_optionalCodec(__bjs_codec_JSObject); + const __bjs_codec_TestModule_APIResult = { + lower: (v) => { + const caseId = enumHelpers.TestModule_APIResult.lower(v); + i32Stack.push(caseId); + }, + lift: () => { + const enumValue = enumHelpers.TestModule_APIResult.lift(i32Stack.pop()); + return enumValue; + }, + }; + const __bjs_codec_Optional_TestModule_APIResult = __bjs_optionalCodec(__bjs_codec_TestModule_APIResult); + const __bjs_codec_Optional_Array_Int = __bjs_optionalCodec(__bjs_codec_Array_Int); + + const __bjs_createStructHelpers_TestModule_Point = () => ({ lower: (value) => { f64Stack.push(value.x); f64Stack.push(value.y); @@ -123,7 +517,7 @@ export async function createInstantiator(options, swift) { return { x: f641, y: f64 }; } }); - const __bjs_createAPIResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_APIResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -184,7 +578,7 @@ export async function createInstantiator(options, swift) { } } }); - const __bjs_createComplexResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_ComplexResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -286,7 +680,7 @@ export async function createInstantiator(options, swift) { } } }); - const __bjs_createResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_Result = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -339,7 +733,7 @@ export async function createInstantiator(options, swift) { } } }); - const __bjs_createNetworkingResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_NetworkingResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -377,53 +771,23 @@ export async function createInstantiator(options, swift) { } } }); - const __bjs_createAPIOptionalResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_APIOptionalResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { case APIOptionalResultValues.Tag.Success: { - const isSome = value.param0 != null ? 1 : 0; - if (isSome) { - const bytes = textEncoder.encode(value.param0); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(isSome); + __bjs_codec_Optional_String.lower(value.param0); return APIOptionalResultValues.Tag.Success; } case APIOptionalResultValues.Tag.Failure: { - const isSome = value.param1 != null ? 1 : 0; - if (isSome) { - i32Stack.push(value.param1 ? 1 : 0); - } - i32Stack.push(isSome); - const isSome1 = value.param0 != null ? 1 : 0; - if (isSome1) { - i32Stack.push((value.param0 | 0)); - } - i32Stack.push(isSome1); + __bjs_codec_Optional_Bool.lower(value.param1); + __bjs_codec_Optional_Int.lower(value.param0); return APIOptionalResultValues.Tag.Failure; } case APIOptionalResultValues.Tag.Status: { - const isSome = value.param2 != null ? 1 : 0; - if (isSome) { - const bytes = textEncoder.encode(value.param2); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - } - i32Stack.push(isSome); - const isSome1 = value.param1 != null ? 1 : 0; - if (isSome1) { - i32Stack.push((value.param1 | 0)); - } - i32Stack.push(isSome1); - const isSome2 = value.param0 != null ? 1 : 0; - if (isSome2) { - i32Stack.push(value.param0 ? 1 : 0); - } - i32Stack.push(isSome2); + __bjs_codec_Optional_String.lower(value.param2); + __bjs_codec_Optional_Int.lower(value.param1); + __bjs_codec_Optional_Bool.lower(value.param0); return APIOptionalResultValues.Tag.Status; } default: throw new Error("Unknown APIOptionalResultValues tag: " + String(enumTag)); @@ -433,67 +797,25 @@ export async function createInstantiator(options, swift) { tag = tag | 0; switch (tag) { case APIOptionalResultValues.Tag.Success: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const string = strStack.pop(); - optValue = string; - } + const optValue = __bjs_codec_Optional_String.lift(); return { tag: APIOptionalResultValues.Tag.Success, param0: optValue }; } case APIOptionalResultValues.Tag.Failure: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const bool = i32Stack.pop() !== 0; - optValue = bool; - } - const isSome1 = i32Stack.pop(); - let optValue1; - if (isSome1 === 0) { - optValue1 = null; - } else { - const int = i32Stack.pop(); - optValue1 = int; - } + const optValue = __bjs_codec_Optional_Bool.lift(); + const optValue1 = __bjs_codec_Optional_Int.lift(); return { tag: APIOptionalResultValues.Tag.Failure, param0: optValue1, param1: optValue }; } case APIOptionalResultValues.Tag.Status: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const string = strStack.pop(); - optValue = string; - } - const isSome1 = i32Stack.pop(); - let optValue1; - if (isSome1 === 0) { - optValue1 = null; - } else { - const int = i32Stack.pop(); - optValue1 = int; - } - const isSome2 = i32Stack.pop(); - let optValue2; - if (isSome2 === 0) { - optValue2 = null; - } else { - const bool = i32Stack.pop() !== 0; - optValue2 = bool; - } + const optValue = __bjs_codec_Optional_String.lift(); + const optValue1 = __bjs_codec_Optional_Int.lift(); + const optValue2 = __bjs_codec_Optional_Bool.lift(); return { tag: APIOptionalResultValues.Tag.Status, param0: optValue2, param1: optValue1, param2: optValue }; } default: throw new Error("Unknown APIOptionalResultValues tag returned from Swift: " + String(tag)); } } }); - const __bjs_createTypedPayloadResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_TypedPayloadResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -506,19 +828,11 @@ export async function createInstantiator(options, swift) { return TypedPayloadResultValues.Tag.Direction; } case TypedPayloadResultValues.Tag.OptPrecision: { - const isSome = value.param0 != null ? 1 : 0; - if (isSome) { - f32Stack.push(Math.fround(value.param0)); - } - i32Stack.push(isSome); + __bjs_codec_Optional_TestModule_Precision.lower(value.param0); return TypedPayloadResultValues.Tag.OptPrecision; } case TypedPayloadResultValues.Tag.OptDirection: { - const isSome = value.param0 != null ? 1 : 0; - if (isSome) { - i32Stack.push((value.param0 | 0)); - } - i32Stack.push(isSome); + __bjs_codec_Optional_TestModule_CardinalDirection.lower(value.param0); return TypedPayloadResultValues.Tag.OptDirection; } case TypedPayloadResultValues.Tag.Empty: { @@ -539,25 +853,11 @@ export async function createInstantiator(options, swift) { return { tag: TypedPayloadResultValues.Tag.Direction, param0: caseId }; } case TypedPayloadResultValues.Tag.OptPrecision: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const rawValue = f32Stack.pop(); - optValue = rawValue; - } + const optValue = __bjs_codec_Optional_TestModule_Precision.lift(); return { tag: TypedPayloadResultValues.Tag.OptPrecision, param0: optValue }; } case TypedPayloadResultValues.Tag.OptDirection: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const caseId = i32Stack.pop(); - optValue = caseId; - } + const optValue = __bjs_codec_Optional_TestModule_CardinalDirection.lift(); return { tag: TypedPayloadResultValues.Tag.OptDirection, param0: optValue }; } case TypedPayloadResultValues.Tag.Empty: return { tag: TypedPayloadResultValues.Tag.Empty }; @@ -565,12 +865,12 @@ export async function createInstantiator(options, swift) { } } }); - const __bjs_createAllTypesResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_AllTypesResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { case AllTypesResultValues.Tag.StructPayload: { - structHelpers.Point.lower(value.param0); + structHelpers.TestModule_Point.lower(value.param0); return AllTypesResultValues.Tag.StructPayload; } case AllTypesResultValues.Tag.ClassPayload: { @@ -583,15 +883,12 @@ export async function createInstantiator(options, swift) { return AllTypesResultValues.Tag.JsObjectPayload; } case AllTypesResultValues.Tag.NestedEnum: { - const caseId = enumHelpers.APIResult.lower(value.param0); + const caseId = enumHelpers.TestModule_APIResult.lower(value.param0); i32Stack.push(caseId); return AllTypesResultValues.Tag.NestedEnum; } case AllTypesResultValues.Tag.ArrayPayload: { - for (const elem of value.param0) { - i32Stack.push((elem | 0)); - } - i32Stack.push(value.param0.length); + __bjs_codec_Array_Int.lower(value.param0); return AllTypesResultValues.Tag.ArrayPayload; } case AllTypesResultValues.Tag.Empty: { @@ -604,7 +901,7 @@ export async function createInstantiator(options, swift) { tag = tag | 0; switch (tag) { case AllTypesResultValues.Tag.StructPayload: { - const struct = structHelpers.Point.lift(); + const struct = structHelpers.TestModule_Point.lift(); return { tag: AllTypesResultValues.Tag.StructPayload, param0: struct }; } case AllTypesResultValues.Tag.ClassPayload: { @@ -619,22 +916,11 @@ export async function createInstantiator(options, swift) { return { tag: AllTypesResultValues.Tag.JsObjectPayload, param0: obj }; } case AllTypesResultValues.Tag.NestedEnum: { - const enumValue = enumHelpers.APIResult.lift(i32Stack.pop()); + const enumValue = enumHelpers.TestModule_APIResult.lift(i32Stack.pop()); return { tag: AllTypesResultValues.Tag.NestedEnum, param0: enumValue }; } case AllTypesResultValues.Tag.ArrayPayload: { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Int.lift(); return { tag: AllTypesResultValues.Tag.ArrayPayload, param0: arrayResult }; } case AllTypesResultValues.Tag.Empty: return { tag: AllTypesResultValues.Tag.Empty }; @@ -642,53 +928,28 @@ export async function createInstantiator(options, swift) { } } }); - const __bjs_createOptionalAllTypesResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_OptionalAllTypesResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { case OptionalAllTypesResultValues.Tag.OptStruct: { - const isSome = value.param0 != null ? 1 : 0; - if (isSome) { - structHelpers.Point.lower(value.param0); - } - i32Stack.push(isSome); + __bjs_codec_Optional_TestModule_Point.lower(value.param0); return OptionalAllTypesResultValues.Tag.OptStruct; } case OptionalAllTypesResultValues.Tag.OptClass: { - const isSome = value.param0 != null ? 1 : 0; - if (isSome) { - ptrStack.push(value.param0.pointer); - } - i32Stack.push(isSome); + __bjs_codec_Optional_TestModule_User.lower(value.param0); return OptionalAllTypesResultValues.Tag.OptClass; } case OptionalAllTypesResultValues.Tag.OptJSObject: { - const isSome = value.param0 != null ? 1 : 0; - if (isSome) { - const objId = swift.memory.retain(value.param0); - i32Stack.push(objId); - } - i32Stack.push(isSome); + __bjs_codec_Optional_JSObject.lower(value.param0); return OptionalAllTypesResultValues.Tag.OptJSObject; } case OptionalAllTypesResultValues.Tag.OptNestedEnum: { - const isSome = value.param0 != null ? 1 : 0; - if (isSome) { - const caseId = enumHelpers.APIResult.lower(value.param0); - i32Stack.push(caseId); - } - i32Stack.push(isSome); + __bjs_codec_Optional_TestModule_APIResult.lower(value.param0); return OptionalAllTypesResultValues.Tag.OptNestedEnum; } case OptionalAllTypesResultValues.Tag.OptArray: { - const isSome = value.param0 != null ? 1 : 0; - if (isSome) { - for (const elem of value.param0) { - i32Stack.push((elem | 0)); - } - i32Stack.push(value.param0.length); - } - i32Stack.push(isSome); + __bjs_codec_Optional_Array_Int.lower(value.param0); return OptionalAllTypesResultValues.Tag.OptArray; } case OptionalAllTypesResultValues.Tag.Empty: { @@ -701,72 +962,23 @@ export async function createInstantiator(options, swift) { tag = tag | 0; switch (tag) { case OptionalAllTypesResultValues.Tag.OptStruct: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const struct = structHelpers.Point.lift(); - optValue = struct; - } + const optValue = __bjs_codec_Optional_TestModule_Point.lift(); return { tag: OptionalAllTypesResultValues.Tag.OptStruct, param0: optValue }; } case OptionalAllTypesResultValues.Tag.OptClass: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const ptr = ptrStack.pop(); - const obj = _exports['User'].__construct(ptr); - optValue = obj; - } + const optValue = __bjs_codec_Optional_TestModule_User.lift(); return { tag: OptionalAllTypesResultValues.Tag.OptClass, param0: optValue }; } case OptionalAllTypesResultValues.Tag.OptJSObject: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const objId = i32Stack.pop(); - const obj = swift.memory.getObject(objId); - swift.memory.release(objId); - optValue = obj; - } + const optValue = __bjs_codec_Optional_JSObject.lift(); return { tag: OptionalAllTypesResultValues.Tag.OptJSObject, param0: optValue }; } case OptionalAllTypesResultValues.Tag.OptNestedEnum: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const enumValue = enumHelpers.APIResult.lift(i32Stack.pop()); - optValue = enumValue; - } + const optValue = __bjs_codec_Optional_TestModule_APIResult.lift(); return { tag: OptionalAllTypesResultValues.Tag.OptNestedEnum, param0: optValue }; } case OptionalAllTypesResultValues.Tag.OptArray: { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } - optValue = arrayResult; - } + const optValue = __bjs_codec_Optional_Array_Int.lift(); return { tag: OptionalAllTypesResultValues.Tag.OptArray, param0: optValue }; } case OptionalAllTypesResultValues.Tag.Empty: return { tag: OptionalAllTypesResultValues.Tag.Empty }; @@ -850,12 +1062,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_Point"] = function(objectId) { - structHelpers.Point.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Point.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Point"] = function() { - const value = structHelpers.Point.lift(); + const value = structHelpers.TestModule_Point.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -1033,139 +1247,139 @@ export async function createInstantiator(options, swift) { } } - const PointHelpers = __bjs_createPointHelpers(); - structHelpers.Point = PointHelpers; + const __bjs_helpers_TestModule_Point = __bjs_createStructHelpers_TestModule_Point(); + structHelpers.TestModule_Point = __bjs_helpers_TestModule_Point; - const APIResultHelpers = __bjs_createAPIResultValuesHelpers(); - enumHelpers.APIResult = APIResultHelpers; + const __bjs_helpers_TestModule_APIResult = __bjs_createEnumHelpers_TestModule_APIResult(); + enumHelpers.TestModule_APIResult = __bjs_helpers_TestModule_APIResult; - const ComplexResultHelpers = __bjs_createComplexResultValuesHelpers(); - enumHelpers.ComplexResult = ComplexResultHelpers; + const __bjs_helpers_TestModule_ComplexResult = __bjs_createEnumHelpers_TestModule_ComplexResult(); + enumHelpers.TestModule_ComplexResult = __bjs_helpers_TestModule_ComplexResult; - const ResultHelpers = __bjs_createResultValuesHelpers(); - enumHelpers.Result = ResultHelpers; + const __bjs_helpers_TestModule_Result = __bjs_createEnumHelpers_TestModule_Result(); + enumHelpers.TestModule_Result = __bjs_helpers_TestModule_Result; - const NetworkingResultHelpers = __bjs_createNetworkingResultValuesHelpers(); - enumHelpers.NetworkingResult = NetworkingResultHelpers; + const __bjs_helpers_TestModule_NetworkingResult = __bjs_createEnumHelpers_TestModule_NetworkingResult(); + enumHelpers.TestModule_NetworkingResult = __bjs_helpers_TestModule_NetworkingResult; - const APIOptionalResultHelpers = __bjs_createAPIOptionalResultValuesHelpers(); - enumHelpers.APIOptionalResult = APIOptionalResultHelpers; + const __bjs_helpers_TestModule_APIOptionalResult = __bjs_createEnumHelpers_TestModule_APIOptionalResult(); + enumHelpers.TestModule_APIOptionalResult = __bjs_helpers_TestModule_APIOptionalResult; - const TypedPayloadResultHelpers = __bjs_createTypedPayloadResultValuesHelpers(); - enumHelpers.TypedPayloadResult = TypedPayloadResultHelpers; + const __bjs_helpers_TestModule_TypedPayloadResult = __bjs_createEnumHelpers_TestModule_TypedPayloadResult(); + enumHelpers.TestModule_TypedPayloadResult = __bjs_helpers_TestModule_TypedPayloadResult; - const AllTypesResultHelpers = __bjs_createAllTypesResultValuesHelpers(); - enumHelpers.AllTypesResult = AllTypesResultHelpers; + const __bjs_helpers_TestModule_AllTypesResult = __bjs_createEnumHelpers_TestModule_AllTypesResult(); + enumHelpers.TestModule_AllTypesResult = __bjs_helpers_TestModule_AllTypesResult; - const OptionalAllTypesResultHelpers = __bjs_createOptionalAllTypesResultValuesHelpers(); - enumHelpers.OptionalAllTypesResult = OptionalAllTypesResultHelpers; + const __bjs_helpers_TestModule_OptionalAllTypesResult = __bjs_createEnumHelpers_TestModule_OptionalAllTypesResult(); + enumHelpers.TestModule_OptionalAllTypesResult = __bjs_helpers_TestModule_OptionalAllTypesResult; const exports = { handle: function bjs_handle(result) { - const resultCaseId = enumHelpers.APIResult.lower(result); + const resultCaseId = enumHelpers.TestModule_APIResult.lower(result); instance.exports.bjs_handle(resultCaseId); }, getResult: function bjs_getResult() { instance.exports.bjs_getResult(); - const ret = enumHelpers.APIResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_APIResult.lift(i32Stack.pop()); return ret; }, roundtripAPIResult: function bjs_roundtripAPIResult(result) { - const resultCaseId = enumHelpers.APIResult.lower(result); + const resultCaseId = enumHelpers.TestModule_APIResult.lower(result); instance.exports.bjs_roundtripAPIResult(resultCaseId); - const ret = enumHelpers.APIResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_APIResult.lift(i32Stack.pop()); return ret; }, roundTripOptionalAPIResult: function bjs_roundTripOptionalAPIResult(result) { const isSome = result != null; let result1; if (isSome) { - const resultCaseId = enumHelpers.APIResult.lower(result); + const resultCaseId = enumHelpers.TestModule_APIResult.lower(result); result1 = resultCaseId; } else { result1 = 0; } instance.exports.bjs_roundTripOptionalAPIResult(+isSome, result1); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.APIResult.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_APIResult.lift(tag); return optResult; }, handleComplex: function bjs_handleComplex(result) { - const resultCaseId = enumHelpers.ComplexResult.lower(result); + const resultCaseId = enumHelpers.TestModule_ComplexResult.lower(result); instance.exports.bjs_handleComplex(resultCaseId); }, getComplexResult: function bjs_getComplexResult() { instance.exports.bjs_getComplexResult(); - const ret = enumHelpers.ComplexResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_ComplexResult.lift(i32Stack.pop()); return ret; }, roundtripComplexResult: function bjs_roundtripComplexResult(result) { - const resultCaseId = enumHelpers.ComplexResult.lower(result); + const resultCaseId = enumHelpers.TestModule_ComplexResult.lower(result); instance.exports.bjs_roundtripComplexResult(resultCaseId); - const ret = enumHelpers.ComplexResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_ComplexResult.lift(i32Stack.pop()); return ret; }, roundTripOptionalComplexResult: function bjs_roundTripOptionalComplexResult(result) { const isSome = result != null; let result1; if (isSome) { - const resultCaseId = enumHelpers.ComplexResult.lower(result); + const resultCaseId = enumHelpers.TestModule_ComplexResult.lower(result); result1 = resultCaseId; } else { result1 = 0; } instance.exports.bjs_roundTripOptionalComplexResult(+isSome, result1); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.ComplexResult.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_ComplexResult.lift(tag); return optResult; }, roundTripOptionalUtilitiesResult: function bjs_roundTripOptionalUtilitiesResult(result) { const isSome = result != null; let result1; if (isSome) { - const resultCaseId = enumHelpers.Result.lower(result); + const resultCaseId = enumHelpers.TestModule_Result.lower(result); result1 = resultCaseId; } else { result1 = 0; } instance.exports.bjs_roundTripOptionalUtilitiesResult(+isSome, result1); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.Result.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_Result.lift(tag); return optResult; }, roundTripOptionalNetworkingResult: function bjs_roundTripOptionalNetworkingResult(result) { const isSome = result != null; let result1; if (isSome) { - const resultCaseId = enumHelpers.NetworkingResult.lower(result); + const resultCaseId = enumHelpers.TestModule_NetworkingResult.lower(result); result1 = resultCaseId; } else { result1 = 0; } instance.exports.bjs_roundTripOptionalNetworkingResult(+isSome, result1); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.NetworkingResult.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_NetworkingResult.lift(tag); return optResult; }, roundTripOptionalAPIOptionalResult: function bjs_roundTripOptionalAPIOptionalResult(result) { const isSome = result != null; let result1; if (isSome) { - const resultCaseId = enumHelpers.APIOptionalResult.lower(result); + const resultCaseId = enumHelpers.TestModule_APIOptionalResult.lower(result); result1 = resultCaseId; } else { result1 = 0; } instance.exports.bjs_roundTripOptionalAPIOptionalResult(+isSome, result1); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.APIOptionalResult.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_APIOptionalResult.lift(tag); return optResult; }, compareAPIResults: function bjs_compareAPIResults(result1, result2) { const isSome = result1 != null; let result; if (isSome) { - const result1CaseId = enumHelpers.APIOptionalResult.lower(result1); + const result1CaseId = enumHelpers.TestModule_APIOptionalResult.lower(result1); result = result1CaseId; } else { result = 0; @@ -1173,74 +1387,74 @@ export async function createInstantiator(options, swift) { const isSome1 = result2 != null; let result3; if (isSome1) { - const result2CaseId = enumHelpers.APIOptionalResult.lower(result2); + const result2CaseId = enumHelpers.TestModule_APIOptionalResult.lower(result2); result3 = result2CaseId; } else { result3 = 0; } instance.exports.bjs_compareAPIResults(+isSome, result, +isSome1, result3); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.APIOptionalResult.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_APIOptionalResult.lift(tag); return optResult; }, roundTripTypedPayloadResult: function bjs_roundTripTypedPayloadResult(result) { - const resultCaseId = enumHelpers.TypedPayloadResult.lower(result); + const resultCaseId = enumHelpers.TestModule_TypedPayloadResult.lower(result); instance.exports.bjs_roundTripTypedPayloadResult(resultCaseId); - const ret = enumHelpers.TypedPayloadResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_TypedPayloadResult.lift(i32Stack.pop()); return ret; }, roundTripOptionalTypedPayloadResult: function bjs_roundTripOptionalTypedPayloadResult(result) { const isSome = result != null; let result1; if (isSome) { - const resultCaseId = enumHelpers.TypedPayloadResult.lower(result); + const resultCaseId = enumHelpers.TestModule_TypedPayloadResult.lower(result); result1 = resultCaseId; } else { result1 = 0; } instance.exports.bjs_roundTripOptionalTypedPayloadResult(+isSome, result1); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.TypedPayloadResult.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_TypedPayloadResult.lift(tag); return optResult; }, roundTripAllTypesResult: function bjs_roundTripAllTypesResult(result) { - const resultCaseId = enumHelpers.AllTypesResult.lower(result); + const resultCaseId = enumHelpers.TestModule_AllTypesResult.lower(result); instance.exports.bjs_roundTripAllTypesResult(resultCaseId); - const ret = enumHelpers.AllTypesResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_AllTypesResult.lift(i32Stack.pop()); return ret; }, roundTripOptionalAllTypesResult: function bjs_roundTripOptionalAllTypesResult(result) { const isSome = result != null; let result1; if (isSome) { - const resultCaseId = enumHelpers.AllTypesResult.lower(result); + const resultCaseId = enumHelpers.TestModule_AllTypesResult.lower(result); result1 = resultCaseId; } else { result1 = 0; } instance.exports.bjs_roundTripOptionalAllTypesResult(+isSome, result1); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.AllTypesResult.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_AllTypesResult.lift(tag); return optResult; }, roundTripOptionalPayloadResult: function bjs_roundTripOptionalPayloadResult(result) { - const resultCaseId = enumHelpers.OptionalAllTypesResult.lower(result); + const resultCaseId = enumHelpers.TestModule_OptionalAllTypesResult.lower(result); instance.exports.bjs_roundTripOptionalPayloadResult(resultCaseId); - const ret = enumHelpers.OptionalAllTypesResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_OptionalAllTypesResult.lift(i32Stack.pop()); return ret; }, roundTripOptionalPayloadResultOpt: function bjs_roundTripOptionalPayloadResultOpt(result) { const isSome = result != null; let result1; if (isSome) { - const resultCaseId = enumHelpers.OptionalAllTypesResult.lower(result); + const resultCaseId = enumHelpers.TestModule_OptionalAllTypesResult.lower(result); result1 = resultCaseId; } else { result1 = 0; } instance.exports.bjs_roundTripOptionalPayloadResultOpt(+isSome, result1); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.OptionalAllTypesResult.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_OptionalAllTypesResult.lift(tag); return optResult; }, APIResult: APIResultValues, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.d.ts index d29256af4..c980b7dbf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.d.ts @@ -35,5 +35,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.js index de374bd70..046bd8bab 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.js @@ -38,7 +38,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createPayloadSignalValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_PayloadSignal = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -150,6 +150,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -250,9 +252,9 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_PayloadSignalControls_roundTrip_static"] = function bjs_PayloadSignalControls_roundTrip_static(signal) { try { - const enumValue = enumHelpers.PayloadSignal.lift(signal); + const enumValue = enumHelpers.TestModule_PayloadSignal.lift(signal); let ret = imports.PayloadSignalControls.roundTrip(enumValue); - const caseId = enumHelpers.PayloadSignal.lower(ret); + const caseId = enumHelpers.TestModule_PayloadSignal.lower(ret); return caseId; } catch (error) { setException(error); @@ -260,7 +262,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_PayloadSignalControls_send"] = function bjs_PayloadSignalControls_send(self, signal) { try { - const enumValue = enumHelpers.PayloadSignal.lift(signal); + const enumValue = enumHelpers.TestModule_PayloadSignal.lift(signal); swift.memory.getObject(self).send(enumValue); } catch (error) { setException(error); @@ -269,7 +271,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_PayloadSignalControls_current"] = function bjs_PayloadSignalControls_current(self) { try { let ret = swift.memory.getObject(self).current(); - const caseId = enumHelpers.PayloadSignal.lower(ret); + const caseId = enumHelpers.TestModule_PayloadSignal.lower(ret); return caseId; } catch (error) { setException(error); @@ -279,7 +281,7 @@ export async function createInstantiator(options, swift) { try { let optResult; if (signalIsSome) { - const enumValue = enumHelpers.PayloadSignal.lift(signalCaseId); + const enumValue = enumHelpers.TestModule_PayloadSignal.lift(signalCaseId); optResult = enumValue; } else { optResult = null; @@ -287,7 +289,7 @@ export async function createInstantiator(options, swift) { let ret = swift.memory.getObject(self).roundTripOptional(optResult); const isSome = ret != null; if (isSome) { - const caseId = enumHelpers.PayloadSignal.lower(ret); + const caseId = enumHelpers.TestModule_PayloadSignal.lower(ret); return caseId; } else { return -1; @@ -310,8 +312,8 @@ export async function createInstantiator(options, swift) { /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { const js = swift.memory.heap; - const PayloadSignalHelpers = __bjs_createPayloadSignalValuesHelpers(); - enumHelpers.PayloadSignal = PayloadSignalHelpers; + const __bjs_helpers_TestModule_PayloadSignal = __bjs_createEnumHelpers_TestModule_PayloadSignal(); + enumHelpers.TestModule_PayloadSignal = __bjs_helpers_TestModule_PayloadSignal; const exports = { PayloadSignal: PayloadSignalValues, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.d.ts index 5581df31e..8ea0aa79b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.d.ts @@ -56,5 +56,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.js index c2ae031bb..838e3062b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.js @@ -130,6 +130,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.d.ts index fe48c9174..03e210f3c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.d.ts @@ -29,5 +29,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.js index f2d6b8750..b4e67b6b8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.js @@ -111,6 +111,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.d.ts index 0ca8b16b9..403ef2149 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.d.ts @@ -154,5 +154,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js index 6c45f0333..0a691374b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js @@ -150,6 +150,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.d.ts index b5a85a082..f5d357a64 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.d.ts @@ -115,5 +115,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js index 2a9e7948a..03f0a8d9a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js @@ -131,6 +131,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.d.ts index fbd5ad637..e43673e7a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.d.ts @@ -169,5 +169,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js index 9e18a8d80..082aa6c38 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js @@ -106,6 +106,350 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_TestModule_FileSize = { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const rawValue = i64Stack.pop(); + return rawValue; + }, + }; + const __bjs_codec_Optional_TestModule_FileSize = __bjs_optionalCodec(__bjs_codec_TestModule_FileSize); + const __bjs_codec_TestModule_SessionId = { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const rawValue = i64Stack.pop(); + return rawValue; + }, + }; + const __bjs_codec_Optional_TestModule_SessionId = __bjs_optionalCodec(__bjs_codec_TestModule_SessionId); + return { /** @@ -182,6 +526,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -448,15 +794,8 @@ export async function createInstantiator(options, swift) { roundTripOptionalFileSize: function bjs_roundTripOptionalFileSize(input) { const isSome = input != null; instance.exports.bjs_roundTripOptionalFileSize(+isSome, isSome ? input : 0n); - const isSome1 = i32Stack.pop(); - let optResult; - if (isSome1) { - const rawValue = i64Stack.pop(); - optResult = rawValue; - } else { - optResult = null; - } - return optResult; + const optValue = __bjs_codec_Optional_TestModule_FileSize.lift(); + return optValue; }, setUserId: function bjs_setUserId(id) { instance.exports.bjs_setUserId(id); @@ -496,15 +835,8 @@ export async function createInstantiator(options, swift) { roundTripOptionalSessionId: function bjs_roundTripOptionalSessionId(input) { const isSome = input != null; instance.exports.bjs_roundTripOptionalSessionId(+isSome, isSome ? input : 0n); - const isSome1 = i32Stack.pop(); - let optResult; - if (isSome1) { - const rawValue = i64Stack.pop(); - optResult = rawValue; - } else { - optResult = null; - } - return optResult; + const optValue = __bjs_codec_Optional_TestModule_SessionId.lift(); + return optValue; }, setPrecision: function bjs_setPrecision(precision) { instance.exports.bjs_setPrecision(precision); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.d.ts index d6ab5aa8f..3eea52594 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.d.ts @@ -29,5 +29,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.js index a009f8d71..56a31b784 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.d.ts new file mode 100644 index 000000000..fc8b4b868 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.d.ts @@ -0,0 +1,88 @@ +// 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 const GenericColorValues: { + readonly Red: 0; + readonly Green: 1; +}; +export type GenericColorTag = typeof GenericColorValues[keyof typeof GenericColorValues]; + +export const GenericModeValues: { + readonly Light: "light"; + readonly Dark: "dark"; +}; +export type GenericModeTag = typeof GenericModeValues[keyof typeof GenericModeValues]; + +export const GenericTaggedValues: { + readonly Tag: { + readonly Number: 0; + readonly Text: 1; + }; +}; + +export type GenericTaggedTag = + { tag: typeof GenericTaggedValues.Tag.Number; value: number } | { tag: typeof GenericTaggedValues.Tag.Text; value: string } + +export interface GenericPoint { + x: number; + y: number; +} +export type GenericColorObject = typeof GenericColorValues; + +export type GenericModeObject = typeof GenericModeValues; + +export type GenericTaggedObject = typeof GenericTaggedValues; + +/// 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 GenericImportBox extends SwiftHeapObject { + get(): number; + value: number; +} +export interface GenericPairFactory { +} +export interface GenericConsumer { + accept(value: T): void; + identity(value: T): T; +} +export type Exports = { + GenericColor: GenericColorObject + GenericMode: GenericModeObject + GenericTagged: GenericTaggedObject + GenericImportBox: { + new(value: number): GenericImportBox; + }, +} +export type Imports = { + genericRoundTrip(value: T): T; + genericParse(json: string): T; + importGenericCombine(a: T, b: U): U; + importGenericCaseDistinct(a: T, b: t): T; + importGenericArray(values: T[]): T[]; + importGenericOptional(value: T | null): T | null; + importGenericDictionary(values: Record): Record; + importGenericAfterOptionalArray(values: number[] | null, value: T): T; + GenericPairFactory: { + new(tag: string, first: T, second: U): GenericPairFactory; + } + GenericConsumer: { + new(value: T): GenericConsumer; + box(value: T): T; + } +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.js new file mode 100644 index 000000000..ee65f59b7 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.js @@ -0,0 +1,955 @@ +// 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 const GenericColorValues = { + Red: 0, + Green: 1, +}; + +export const GenericModeValues = { + Light: "light", + Dark: "dark", +}; + +export const GenericTaggedValues = { + Tag: { + Number: 0, + Text: 1, + }, +}; +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + let decodeString; + 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 strStack = []; + let i32Stack = []; + let i64Stack = []; + let f32Stack = []; + let f64Stack = []; + let ptrStack = []; + let taStack = []; + const enumHelpers = {}; + const structHelpers = {}; + const __bjs_codecByTypeId = new Map(); + let __bjs_typeHandlesRegistered = false; + function __bjs_registerTypeHandles() { + if (__bjs_typeHandlesRegistered) { + return; + } + __bjs_typeHandlesRegistered = true; + instance.exports["bjs_core_register_type_handles"](); + instance.exports["bjs_TestModule_register_type_handles"](); + } + function __bjs_codecForTypeId(typeId) { + __bjs_registerTypeHandles(); + const codec = __bjs_codecByTypeId.get(typeId); + if (!codec) { + throw new Error("BridgeJS: no codec registered for type ID " + typeId); + } + return codec; + } + + let _exports = null; + let bjs = null; + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_Array_Int = __bjs_arrayCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_TestModule_GenericPoint = { + lower: (v) => { + structHelpers.TestModule_GenericPoint.lower(v); + }, + lift: () => { + const struct = structHelpers.TestModule_GenericPoint.lift(); + return struct; + }, + }; + const __bjs_codec_TestModule_GenericImportBox = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports['GenericImportBox'].__construct(ptr); + return obj; + }, + }; + const __bjs_codec_TestModule_GenericColor = { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const caseId = i32Stack.pop(); + return caseId; + }, + }; + const __bjs_codec_TestModule_GenericTagged = { + lower: (v) => { + const caseId = enumHelpers.TestModule_GenericTagged.lower(v); + i32Stack.push(caseId); + }, + lift: () => { + const enumValue = enumHelpers.TestModule_GenericTagged.lift(i32Stack.pop()); + return enumValue; + }, + }; + + const __bjs_createStructHelpers_TestModule_GenericPoint = () => ({ + lower: (value) => { + i32Stack.push((value.x | 0)); + i32Stack.push((value.y | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + const int1 = i32Stack.pop(); + return { x: int1, y: int }; + } + }); + const __bjs_createEnumHelpers_TestModule_GenericTagged = () => ({ + lower: (value) => { + const enumTag = value.tag; + switch (enumTag) { + case GenericTaggedValues.Tag.Number: { + i32Stack.push((value.value | 0)); + return GenericTaggedValues.Tag.Number; + } + case GenericTaggedValues.Tag.Text: { + const bytes = textEncoder.encode(value.value); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + return GenericTaggedValues.Tag.Text; + } + default: throw new Error("Unknown GenericTaggedValues tag: " + String(enumTag)); + } + }, + lift: (tag) => { + tag = tag | 0; + switch (tag) { + case GenericTaggedValues.Tag.Number: { + const int = i32Stack.pop(); + return { tag: GenericTaggedValues.Tag.Number, value: int }; + } + case GenericTaggedValues.Tag.Text: { + const string = strStack.pop(); + return { tag: GenericTaggedValues.Tag.Text, value: string }; + } + default: throw new Error("Unknown GenericTaggedValues tag returned from Swift: " + String(tag)); + } + } + }); + + 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) { + tmpRetString = decodeString(ptr, len); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + swift.memory.release(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + return swift.memory.retain(decodeString(ptr, len)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); + 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_i32"] = function(v) { + i32Stack.push(v | 0); + } + bjs["swift_js_push_f32"] = function(v) { + f32Stack.push(Math.fround(v)); + } + bjs["swift_js_push_f64"] = function(v) { + f64Stack.push(v); + } + bjs["swift_js_push_string"] = function(ptr, len) { + const value = decodeString(ptr, len); + strStack.push(value); + } + bjs["swift_js_pop_i32"] = function() { + return i32Stack.pop(); + } + bjs["swift_js_pop_f32"] = function() { + return f32Stack.pop(); + } + bjs["swift_js_pop_f64"] = function() { + return f64Stack.pop(); + } + bjs["swift_js_push_pointer"] = function(pointer) { + ptrStack.push(pointer); + } + bjs["swift_js_pop_pointer"] = function() { + return ptrStack.pop(); + } + bjs["swift_js_push_i64"] = function(v) { + i64Stack.push(v); + } + bjs["swift_js_pop_i64"] = function() { + return i64Stack.pop(); + } + const taCtors = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array]; + bjs["swift_js_push_typed_array"] = function(kind, ptr, count) { + const Ctor = taCtors[kind]; + const byteLen = count * Ctor.BYTES_PER_ELEMENT; + const copy = memory.buffer.slice(ptr, ptr + byteLen); + taStack.push(Array.from(new Ctor(copy))); + } + bjs["swift_js_struct_lower_GenericPoint"] = function(objectId) { + structHelpers.TestModule_GenericPoint.lower(swift.memory.getObject(objectId)); + } + bjs["swift_js_struct_lift_GenericPoint"] = function() { + const value = structHelpers.TestModule_GenericPoint.lift(); + return swift.memory.retain(value); + } + bjs["bjs_core_register_type_handles"] = function(base, count) { + const codecs = [ + __bjs_primitiveCodecs.Bool, + __bjs_primitiveCodecs.Int, + __bjs_primitiveCodecs.Int8, + __bjs_primitiveCodecs.UInt8, + __bjs_primitiveCodecs.Int16, + __bjs_primitiveCodecs.UInt16, + __bjs_primitiveCodecs.Int32, + __bjs_primitiveCodecs.UInt32, + __bjs_primitiveCodecs.UInt, + __bjs_primitiveCodecs.Int64, + __bjs_primitiveCodecs.UInt64, + __bjs_primitiveCodecs.Float, + __bjs_primitiveCodecs.Double, + __bjs_primitiveCodecs.String, + __bjs_primitiveCodecs.JSValue, + ]; + if (count !== codecs.length) { + throw new Error("BridgeJS: type handle registration mismatch for core types"); + } + const typeIds = new Int32Array(memory.buffer, base >>> 0, count >>> 0); + for (let i = 0; i < count; i++) { + __bjs_codecByTypeId.set(typeIds[i], codecs[i]); + } + } + bjs["bjs_TestModule_register_type_handles"] = function(base, count) { + const codecs = [ + __bjs_codec_TestModule_GenericPoint, + __bjs_codec_TestModule_GenericImportBox, + __bjs_codec_TestModule_GenericColor, + __bjs_stringCodec, + __bjs_codec_TestModule_GenericTagged, + ]; + if (count !== codecs.length) { + throw new Error("BridgeJS: type handle registration mismatch for module 'TestModule'"); + } + const typeIds = new Int32Array(memory.buffer, base >>> 0, count >>> 0); + for (let i = 0; i < count; i++) { + __bjs_codecByTypeId.set(typeIds[i], codecs[i]); + } + } + const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); + bjs["swift_js_make_promise"] = function() { + let resolve, reject; + const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); + promise[__bjs_promiseSettlers] = { resolve, reject }; + return swift.memory.retain(promise); + } + 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 { + tmpRetString = decodeString(ptr, len); + } + } + 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; + } + bjs["swift_js_closure_unregister"] = function(funcRef) {} + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_GenericImportBox_wrap"] = function(pointer) { + const obj = _exports['GenericImportBox'].__construct(pointer); + return swift.memory.retain(obj); + }; + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; + TestModule["bjs_genericRoundTrip"] = function bjs_genericRoundTrip(tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const value = codecT.lift(); + let ret = imports.genericRoundTrip(value); + codecT.lower(ret); + } catch (error) { + setException(error); + } + } + TestModule["bjs_genericParse"] = function bjs_genericParse(jsonBytes, jsonCount, tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const string = decodeString(jsonBytes, jsonCount); + let ret = imports.genericParse(string); + codecT.lower(ret); + } catch (error) { + setException(error); + } + } + TestModule["bjs_importGenericCombine"] = function bjs_importGenericCombine(tTypeId, uTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const codecU = __bjs_codecForTypeId(uTypeId); + const a = codecT.lift(); + const b = codecU.lift(); + let ret = imports.importGenericCombine(a, b); + codecU.lower(ret); + } catch (error) { + setException(error); + } + } + TestModule["bjs_importGenericCaseDistinct"] = function bjs_importGenericCaseDistinct(tTypeId, tTypeId1) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const codect = __bjs_codecForTypeId(tTypeId1); + const a = codecT.lift(); + const b = codect.lift(); + let ret = imports.importGenericCaseDistinct(a, b); + codecT.lower(ret); + } catch (error) { + setException(error); + } + } + TestModule["bjs_importGenericArray"] = function bjs_importGenericArray(tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const values = __bjs_arrayCodec(codecT).lift(); + let ret = imports.importGenericArray(values); + __bjs_arrayCodec(codecT).lower(ret); + } catch (error) { + setException(error); + } + } + TestModule["bjs_importGenericOptional"] = function bjs_importGenericOptional(tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const value = __bjs_optionalCodec(codecT).lift(); + let ret = imports.importGenericOptional(value); + __bjs_optionalCodec(codecT).lower(ret); + } catch (error) { + setException(error); + } + } + TestModule["bjs_importGenericDictionary"] = function bjs_importGenericDictionary(tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const values = __bjs_dictCodec(codecT).lift(); + let ret = imports.importGenericDictionary(values); + __bjs_dictCodec(codecT).lower(ret); + } catch (error) { + setException(error); + } + } + TestModule["bjs_importGenericAfterOptionalArray"] = function bjs_importGenericAfterOptionalArray(values, tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + let optResult; + if (values) { + const arrayResult = __bjs_codec_Array_Int.lift(); + optResult = arrayResult; + } else { + optResult = null; + } + const value = codecT.lift(); + let ret = imports.importGenericAfterOptionalArray(optResult, value); + codecT.lower(ret); + } catch (error) { + setException(error); + } + } + TestModule["bjs_GenericPairFactory_init"] = function bjs_GenericPairFactory_init(tagBytes, tagCount, tTypeId, uTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const codecU = __bjs_codecForTypeId(uTypeId); + const string = decodeString(tagBytes, tagCount); + const first = codecT.lift(); + const second = codecU.lift(); + return swift.memory.retain(new imports.GenericPairFactory(string, first, second)); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_GenericConsumer_init"] = function bjs_GenericConsumer_init(tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const value = codecT.lift(); + return swift.memory.retain(new imports.GenericConsumer(value)); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_GenericConsumer_box_static"] = function bjs_GenericConsumer_box_static(tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const value = codecT.lift(); + let ret = imports.GenericConsumer.box(value); + codecT.lower(ret); + } catch (error) { + setException(error); + } + } + TestModule["bjs_GenericConsumer_accept"] = function bjs_GenericConsumer_accept(self, tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const value = codecT.lift(); + swift.memory.getObject(self).accept(value); + } catch (error) { + setException(error); + } + } + TestModule["bjs_GenericConsumer_identity"] = function bjs_GenericConsumer_identity(self, tTypeId) { + try { + const codecT = __bjs_codecForTypeId(tTypeId); + const value = codecT.lift(); + let ret = swift.memory.getObject(self).identity(value); + codecT.lower(ret); + } catch (error) { + setException(error); + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + + decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + afterInitialize: () => { + __bjs_registerTypeHandles(); + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + const swiftHeapObjectFinalizationRegistry = (typeof FinalizationRegistry === "undefined") ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry((state) => { + if (state.hasReleased) { + return; + } + state.hasReleased = true; + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + }); + + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype, identityCache) { + pointer = pointer >>> 0; + const makeFresh = (identityMap) => { + const obj = Object.create(prototype); + const state = { pointer, deinit, hasReleased: false, identityMap }; + obj.pointer = pointer; + obj.__swiftHeapObjectState = state; + swiftHeapObjectFinalizationRegistry.register(obj, state, state); + if (identityMap) { + identityMap.set(pointer, new WeakRef(obj)); + } + return obj; + }; + + if (!identityCache) { + return makeFresh(null); + } + + const cached = identityCache.get(pointer)?.deref(); + if (cached && !cached.__swiftHeapObjectState.hasReleased) { + deinit(pointer); + return cached; + } + if (identityCache.has(pointer)) { + identityCache.delete(pointer); + } + + return makeFresh(identityCache); + } + + release() { + const state = this.__swiftHeapObjectState; + if (state.hasReleased) { + return; + } + state.hasReleased = true; + swiftHeapObjectFinalizationRegistry.unregister(state); + state.identityMap?.delete(state.pointer); + state.deinit(state.pointer); + } + } + class GenericImportBox extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_GenericImportBox_deinit, GenericImportBox.prototype, null); + } + + constructor(value) { + const ret = instance.exports.bjs_GenericImportBox_init(value); + return GenericImportBox.__construct(ret); + } + get() { + const ret = instance.exports.bjs_GenericImportBox_get(this.pointer); + return ret; + } + get value() { + const ret = instance.exports.bjs_GenericImportBox_value_get(this.pointer); + return ret; + } + set value(value) { + instance.exports.bjs_GenericImportBox_value_set(this.pointer, value); + } + } + const __bjs_helpers_TestModule_GenericPoint = __bjs_createStructHelpers_TestModule_GenericPoint(); + structHelpers.TestModule_GenericPoint = __bjs_helpers_TestModule_GenericPoint; + + const __bjs_helpers_TestModule_GenericTagged = __bjs_createEnumHelpers_TestModule_GenericTagged(); + enumHelpers.TestModule_GenericTagged = __bjs_helpers_TestModule_GenericTagged; + + const exports = { + GenericColor: GenericColorValues, + GenericMode: GenericModeValues, + GenericTagged: GenericTaggedValues, + GenericImportBox, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.d.ts index 312f56786..e4754d8e0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.d.ts @@ -17,5 +17,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.js index b1d830768..adb70913c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.d.ts index ae1152016..0dbdafe7b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.d.ts @@ -19,5 +19,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.js index 6f43c2d9c..760a6b50c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.d.ts index 02d17c011..64acfac35 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.d.ts @@ -38,5 +38,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js index 83f53d8a6..1e1d14696 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.d.ts index 02d17c011..64acfac35 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.d.ts @@ -38,5 +38,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js index bb6f36902..c85aee3af 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.d.ts index 02d17c011..64acfac35 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.d.ts @@ -38,5 +38,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js index bb6f36902..c85aee3af 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.d.ts index cd4f822e2..e0da68c50 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.d.ts @@ -17,5 +17,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.js index 07341894e..42c02479f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.js @@ -31,6 +31,332 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_Array_Int = __bjs_arrayCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_Array_String = __bjs_arrayCodec(__bjs_stringCodec); + return { /** @@ -107,6 +433,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -207,41 +534,16 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_roundtrip"] = function bjs_roundtrip() { try { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Int.lift(); let ret = imports.roundtrip(arrayResult); - for (const elem of ret) { - i32Stack.push((elem | 0)); - } - i32Stack.push(ret.length); + __bjs_codec_Array_Int.lower(ret); } catch (error) { setException(error); } } TestModule["bjs_logStrings"] = function bjs_logStrings() { try { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const string = strStack.pop(); - arrayResult.push(string); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_String.lift(); imports.logStrings(arrayResult); } catch (error) { setException(error); @@ -251,34 +553,12 @@ export async function createInstantiator(options, swift) { try { let optResult; if (a) { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Int.lift(); optResult = arrayResult; } else { optResult = null; } - const arrayLen1 = i32Stack.pop(); - let arrayResult1; - if (arrayLen1 === -1) { - arrayResult1 = taStack.pop(); - } else { - arrayResult1 = []; - for (let i1 = 0; i1 < arrayLen1; i1++) { - const int1 = i32Stack.pop(); - arrayResult1.push(int1); - } - arrayResult1.reverse(); - } + const arrayResult1 = __bjs_codec_Array_Int.lift(); let ret = imports.optionalArrayThenArray(optResult, arrayResult1); return ret; } catch (error) { @@ -291,34 +571,12 @@ export async function createInstantiator(options, swift) { const string = decodeString(sBytes, sCount); let optResult; if (a) { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const int = i32Stack.pop(); - arrayResult.push(int); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Int.lift(); optResult = arrayResult; } else { optResult = null; } - const arrayLen1 = i32Stack.pop(); - let arrayResult1; - if (arrayLen1 === -1) { - arrayResult1 = taStack.pop(); - } else { - arrayResult1 = []; - for (let i1 = 0; i1 < arrayLen1; i1++) { - const int1 = i32Stack.pop(); - arrayResult1.push(int1); - } - arrayResult1.reverse(); - } + const arrayResult1 = __bjs_codec_Array_Int.lift(); let ret = imports.borrowedStringAroundStackParams(string, optResult, arrayResult1); return ret; } catch (error) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.d.ts index 22b4e6a1c..1d5f31efd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.d.ts @@ -26,5 +26,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js index 4328e4d4e..d532bbff6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js @@ -31,7 +31,346 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createFooContainerHelpers = () => ({ + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_TestModule_Foo = { + lower: (v) => { + const objId = swift.memory.retain(v); + i32Stack.push(objId); + }, + lift: () => { + const objId = i32Stack.pop(); + const obj = swift.memory.getObject(objId); + swift.memory.release(objId); + return obj; + }, + }; + const __bjs_codec_Array_TestModule_Foo = __bjs_arrayCodec(__bjs_codec_TestModule_Foo); + const __bjs_codec_Optional_TestModule_Foo = __bjs_optionalCodec(__bjs_codec_TestModule_Foo); + const __bjs_codec_Array_Optional_TestModule_Foo = __bjs_arrayCodec(__bjs_codec_Optional_TestModule_Foo); + + const __bjs_createStructHelpers_TestModule_FooContainer = () => ({ lower: (value) => { let id; if (value.foo != null) { @@ -40,24 +379,10 @@ export async function createInstantiator(options, swift) { id = undefined; } i32Stack.push(id !== undefined ? id : 0); - const isSome = value.optionalFoo != null ? 1 : 0; - if (isSome) { - const objId = swift.memory.retain(value.optionalFoo); - i32Stack.push(objId); - } - i32Stack.push(isSome); + __bjs_codec_Optional_TestModule_Foo.lower(value.optionalFoo); }, lift: () => { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const objId = i32Stack.pop(); - const obj = swift.memory.getObject(objId); - swift.memory.release(objId); - optValue = obj; - } + const optValue = __bjs_codec_Optional_TestModule_Foo.lift(); const objectId = i32Stack.pop(); let value; if (objectId !== 0) { @@ -146,12 +471,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_FooContainer"] = function(objectId) { - structHelpers.FooContainer.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_FooContainer.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_FooContainer"] = function() { - const value = structHelpers.FooContainer.lift(); + const value = structHelpers.TestModule_FooContainer.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -272,8 +599,8 @@ export async function createInstantiator(options, swift) { /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { const js = swift.memory.heap; - const FooContainerHelpers = __bjs_createFooContainerHelpers(); - structHelpers.FooContainer = FooContainerHelpers; + const __bjs_helpers_TestModule_FooContainer = __bjs_createStructHelpers_TestModule_FooContainer(); + structHelpers.TestModule_FooContainer = __bjs_helpers_TestModule_FooContainer; const exports = { makeFoo: function bjs_makeFoo() { @@ -289,66 +616,21 @@ export async function createInstantiator(options, swift) { return ret1; }, processFooArray: function bjs_processFooArray(foos) { - for (const elem of foos) { - const objId = swift.memory.retain(elem); - i32Stack.push(objId); - } - i32Stack.push(foos.length); + __bjs_codec_Array_TestModule_Foo.lower(foos); instance.exports.bjs_processFooArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const objId1 = i32Stack.pop(); - const obj = swift.memory.getObject(objId1); - swift.memory.release(objId1); - arrayResult.push(obj); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_Foo.lift(); return arrayResult; }, processOptionalFooArray: function bjs_processOptionalFooArray(foos) { - for (const elem of foos) { - const isSome = elem != null ? 1 : 0; - if (isSome) { - const objId = swift.memory.retain(elem); - i32Stack.push(objId); - } - i32Stack.push(isSome); - } - i32Stack.push(foos.length); + __bjs_codec_Array_Optional_TestModule_Foo.lower(foos); instance.exports.bjs_processOptionalFooArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const isSome1 = i32Stack.pop(); - let optValue; - if (isSome1 === 0) { - optValue = null; - } else { - const objId1 = i32Stack.pop(); - const obj = swift.memory.getObject(objId1); - swift.memory.release(objId1); - optValue = obj; - } - arrayResult.push(optValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_Optional_TestModule_Foo.lift(); return arrayResult; }, roundtripFooContainer: function bjs_roundtripFooContainer(container) { - structHelpers.FooContainer.lower(container); + structHelpers.TestModule_FooContainer.lower(container); instance.exports.bjs_roundtripFooContainer(); - const structValue = structHelpers.FooContainer.lift(); + const structValue = structHelpers.TestModule_FooContainer.lift(); return structValue; }, }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.d.ts index ac0e05a91..edc243baa 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.d.ts @@ -33,5 +33,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.js index 8d1ac2698..e8fe5ec10 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.d.ts index aaf227cf7..e6dfad7fa 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.d.ts @@ -27,5 +27,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.js index 08215f159..15b48d4b9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts index 3b2b5de99..3cb232260 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.d.ts @@ -28,5 +28,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js index a38b0a391..5eaba3c8f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.d.ts index a6267bd31..b0c2eff74 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.d.ts @@ -17,5 +17,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.js index 1c995923a..6d1b1b6fb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.js @@ -112,6 +112,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.d.ts index 818d57a9d..e9f73cfae 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.d.ts @@ -13,5 +13,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.js index 038374240..9428698be 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.js @@ -109,6 +109,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.d.ts index 624691d83..9afd16f74 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.d.ts @@ -17,5 +17,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.js index e03dcbab4..aacf5e61a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.js @@ -109,6 +109,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.d.ts index d31aeebe3..d6cbf725c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.d.ts @@ -64,5 +64,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.js index 543ae05f0..2ec71c904 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.js @@ -36,7 +36,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createRenamedVectorHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_RenamedVector = () => ({ lower: (value) => { f64Stack.push(value.dx); f64Stack.push(value.dy); @@ -46,7 +46,7 @@ export async function createInstantiator(options, swift) { const f641 = f64Stack.pop(); const instance1 = { dx: f641, dy: f64 }; instance1.magnitude = function() { - structHelpers.RenamedVector.lower(this); + structHelpers.TestModule_RenamedVector.lower(this); const ret = instance.exports.bjs_RenamedVector_magnitude(); return ret; }.bind(instance1); @@ -129,12 +129,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_RenamedVector"] = function(objectId) { - structHelpers.RenamedVector.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_RenamedVector.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_RenamedVector"] = function() { - const value = structHelpers.RenamedVector.lift(); + const value = structHelpers.TestModule_RenamedVector.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -354,8 +356,8 @@ export async function createInstantiator(options, swift) { return ret; } } - const RenamedVectorHelpers = __bjs_createRenamedVectorHelpers(); - structHelpers.RenamedVector = RenamedVectorHelpers; + const __bjs_helpers_TestModule_RenamedVector = __bjs_createStructHelpers_TestModule_RenamedVector(); + structHelpers.TestModule_RenamedVector = __bjs_helpers_TestModule_RenamedVector; const exports = { makeGreeting: function bjs_makeGreeting(name) { @@ -417,12 +419,12 @@ export async function createInstantiator(options, swift) { RenamedVector: { get originVector() { instance.exports.bjs_RenamedVector_static_origin_get(); - const structValue = structHelpers.RenamedVector.lift(); + const structValue = structHelpers.TestModule_RenamedVector.lift(); return structValue; }, fromPolar: function(radius, angle) { instance.exports.bjs_RenamedVector_static_fromPolar(radius, angle); - const structValue = structHelpers.RenamedVector.lift(); + const structValue = structHelpers.TestModule_RenamedVector.lift(); return structValue; }, }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.d.ts index b842e7d7d..c77ca0828 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.d.ts @@ -17,5 +17,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.js index 5c713cc78..f02f6bcf2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.d.ts index 85109479e..951f1e7aa 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.d.ts @@ -36,5 +36,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js index ae59008ba..9fe12ff47 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js @@ -31,6 +31,240 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + function __bjs_jsValueLower(value) { let kind; let payload1; @@ -120,6 +354,9 @@ export async function createInstantiator(options, swift) { return jsValue; } + const __bjs_codec_Array_JSValue = __bjs_arrayCodec(__bjs_primitiveCodecs.JSValue); + const __bjs_codec_Optional_Array_JSValue = __bjs_optionalCodec(__bjs_codec_Array_JSValue); + return { /** @@ -196,6 +433,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -316,29 +554,9 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_jsEchoJSValueArray"] = function bjs_jsEchoJSValueArray() { try { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const jsValuePayload2 = f64Stack.pop(); - const jsValuePayload1 = i32Stack.pop(); - const jsValueKind = i32Stack.pop(); - const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); - arrayResult.push(jsValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_JSValue.lift(); let ret = imports.jsEchoJSValueArray(arrayResult); - for (const elem of ret) { - const [elemKind, elemPayload1, elemPayload2] = __bjs_jsValueLower(elem); - i32Stack.push(elemKind); - i32Stack.push(elemPayload1); - f64Stack.push(elemPayload2); - } - i32Stack.push(ret.length); + __bjs_codec_Array_JSValue.lower(ret); } catch (error) { setException(error); } @@ -564,67 +782,16 @@ export async function createInstantiator(options, swift) { return optResult; }, roundTripJSValueArray: function bjs_roundTripJSValueArray(values) { - for (const elem of values) { - const [elemKind, elemPayload1, elemPayload2] = __bjs_jsValueLower(elem); - i32Stack.push(elemKind); - i32Stack.push(elemPayload1); - f64Stack.push(elemPayload2); - } - i32Stack.push(values.length); + __bjs_codec_Array_JSValue.lower(values); instance.exports.bjs_roundTripJSValueArray(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const jsValuePayload2 = f64Stack.pop(); - const jsValuePayload1 = i32Stack.pop(); - const jsValueKind = i32Stack.pop(); - const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); - arrayResult.push(jsValue); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_JSValue.lift(); return arrayResult; }, roundTripOptionalJSValueArray: function bjs_roundTripOptionalJSValueArray(values) { - const isSome = values != null; - if (isSome) { - for (const elem of values) { - const [elemKind, elemPayload1, elemPayload2] = __bjs_jsValueLower(elem); - i32Stack.push(elemKind); - i32Stack.push(elemPayload1); - f64Stack.push(elemPayload2); - } - i32Stack.push(values.length); - } - i32Stack.push(+isSome); + __bjs_codec_Optional_Array_JSValue.lower(values); instance.exports.bjs_roundTripOptionalJSValueArray(); - const isSome1 = i32Stack.pop(); - let optResult; - if (isSome1) { - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const jsValuePayload2 = f64Stack.pop(); - const jsValuePayload1 = i32Stack.pop(); - const jsValueKind = i32Stack.pop(); - const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); - arrayResult.push(jsValue); - } - arrayResult.reverse(); - } - optResult = arrayResult; - } else { - optResult = null; - } - return optResult; + const optValue = __bjs_codec_Optional_Array_JSValue.lift(); + return optValue; }, JSValueHolder, }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.d.ts index c7ff9a39c..737e94bce 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.d.ts @@ -29,5 +29,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js index 39ecf8d99..5db38dde5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.d.ts index 01a392e91..88d337296 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.d.ts @@ -51,5 +51,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js index 62d7651e8..66818952b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.d.ts index 89aad5c32..634065017 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.d.ts @@ -29,5 +29,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js index 69bfe5ff1..a732808a6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.d.ts index ac9ea13c4..76daa290c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.d.ts @@ -126,5 +126,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js index aa5e3dbb4..024ef49c1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js @@ -31,6 +31,341 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_TestModule_Greeter = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports.__Swift.Foundation.Greeter.__construct(ptr); + return obj; + }, + }; + const __bjs_codec_Array_TestModule_Greeter = __bjs_arrayCodec(__bjs_codec_TestModule_Greeter); + return { /** @@ -106,6 +441,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -356,19 +692,7 @@ export async function createInstantiator(options, swift) { } getItems() { instance.exports.bjs_Collections_Container_getItems(this.pointer); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const ptr = ptrStack.pop(); - const obj = Greeter.__construct(ptr); - arrayResult.push(obj); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_Greeter.lift(); return arrayResult; } addItem(item) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.d.ts index debd3ffcf..59961720b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.d.ts @@ -73,5 +73,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js index 9a5c6473e..7da962422 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js @@ -31,6 +31,341 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_TestModule_Greeter = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports.__Swift.Foundation.Greeter.__construct(ptr); + return obj; + }, + }; + const __bjs_codec_Array_TestModule_Greeter = __bjs_arrayCodec(__bjs_codec_TestModule_Greeter); + return { /** @@ -106,6 +441,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -356,19 +692,7 @@ export async function createInstantiator(options, swift) { } getItems() { instance.exports.bjs_Collections_Container_getItems(this.pointer); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const ptr = ptrStack.pop(); - const obj = Greeter.__construct(ptr); - arrayResult.push(obj); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_Greeter.lift(); return arrayResult; } addItem(item) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.d.ts index c418ed8a5..5dfb48fcf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.d.ts @@ -42,5 +42,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js index 972f9ae74..a21711b2b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js @@ -31,7 +31,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createUser_StatsHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_User_Stats = () => ({ lower: (value) => { i32Stack.push((value.health | 0)); f64Stack.push(value.score); @@ -42,7 +42,7 @@ export async function createInstantiator(options, swift) { return { health: int, score: f64 }; } }); - const __bjs_createPlayer_StatsHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Player_Stats = () => ({ lower: (value) => { i32Stack.push((value.level | 0)); const bytes = textEncoder.encode(value.rating); @@ -132,19 +132,21 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_User_Stats"] = function(objectId) { - structHelpers.User_Stats.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_User_Stats.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_User_Stats"] = function() { - const value = structHelpers.User_Stats.lift(); + const value = structHelpers.TestModule_User_Stats.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Player_Stats"] = function(objectId) { - structHelpers.Player_Stats.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Player_Stats.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Player_Stats"] = function() { - const value = structHelpers.Player_Stats.lift(); + const value = structHelpers.TestModule_Player_Stats.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -344,11 +346,11 @@ export async function createInstantiator(options, swift) { return ret; } } - const User_StatsHelpers = __bjs_createUser_StatsHelpers(); - structHelpers.User_Stats = User_StatsHelpers; + const __bjs_helpers_TestModule_User_Stats = __bjs_createStructHelpers_TestModule_User_Stats(); + structHelpers.TestModule_User_Stats = __bjs_helpers_TestModule_User_Stats; - const Player_StatsHelpers = __bjs_createPlayer_StatsHelpers(); - structHelpers.Player_Stats = Player_StatsHelpers; + const __bjs_helpers_TestModule_Player_Stats = __bjs_createStructHelpers_TestModule_Player_Stats(); + structHelpers.TestModule_Player_Stats = __bjs_helpers_TestModule_Player_Stats; const exports = { Player, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.d.ts index 0f64324cd..324947e18 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.d.ts @@ -82,5 +82,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js index 5a253cdc0..f9761419b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js @@ -31,6 +31,356 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_JSObject = { + lower: (v) => { + const objId = swift.memory.retain(v); + i32Stack.push(objId); + }, + lift: () => { + const objId = i32Stack.pop(); + const obj = swift.memory.getObject(objId); + swift.memory.release(objId); + return obj; + }, + }; + const __bjs_codec_Optional_JSObject = __bjs_optionalCodec(__bjs_codec_JSObject); + const __bjs_codec_TestModule_WithOptionalJSClass = { + lower: (v) => { + const objId = swift.memory.retain(v); + i32Stack.push(objId); + }, + lift: () => { + const objId = i32Stack.pop(); + const obj = swift.memory.getObject(objId); + swift.memory.release(objId); + return obj; + }, + }; + const __bjs_codec_Optional_TestModule_WithOptionalJSClass = __bjs_optionalCodec(__bjs_codec_TestModule_WithOptionalJSClass); + return { /** @@ -107,6 +457,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -314,12 +665,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_WithOptionalJSClass_childOrNull_get"] = function bjs_WithOptionalJSClass_childOrNull_get(self) { try { let ret = swift.memory.getObject(self).childOrNull; - const isSome = ret != null; - if (isSome) { - const objId = swift.memory.retain(ret); - i32Stack.push(objId); - } - i32Stack.push(isSome ? 1 : 0); + __bjs_codec_Optional_TestModule_WithOptionalJSClass.lower(ret); } catch (error) { setException(error); } @@ -490,12 +836,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_WithOptionalJSClass_roundTripChildOrNull"] = function bjs_WithOptionalJSClass_roundTripChildOrNull(self, valueIsSome, valueObjectId) { try { let ret = swift.memory.getObject(self).roundTripChildOrNull(valueIsSome ? swift.memory.getObject(valueObjectId) : null); - const isSome = ret != null; - if (isSome) { - const objId = swift.memory.retain(ret); - i32Stack.push(objId); - } - i32Stack.push(isSome ? 1 : 0); + __bjs_codec_Optional_TestModule_WithOptionalJSClass.lower(ret); } catch (error) { setException(error); } @@ -722,17 +1063,8 @@ export async function createInstantiator(options, swift) { result = 0; } instance.exports.bjs_roundTripExportedOptionalJSObject(+isSome, result); - const isSome1 = i32Stack.pop(); - let optResult; - if (isSome1) { - const objId = i32Stack.pop(); - const obj = swift.memory.getObject(objId); - swift.memory.release(objId); - optResult = obj; - } else { - optResult = null; - } - return optResult; + const optValue = __bjs_codec_Optional_JSObject.lift(); + return optValue; }, roundTripExportedOptionalJSClass: function bjs_roundTripExportedOptionalJSClass(value) { const isSome = value != null; @@ -743,17 +1075,8 @@ export async function createInstantiator(options, swift) { result = 0; } instance.exports.bjs_roundTripExportedOptionalJSClass(+isSome, result); - const isSome1 = i32Stack.pop(); - let optResult; - if (isSome1) { - const objId = i32Stack.pop(); - const obj = swift.memory.getObject(objId); - swift.memory.release(objId); - optResult = obj; - } else { - optResult = null; - } - return optResult; + const optValue = __bjs_codec_Optional_TestModule_WithOptionalJSClass.lift(); + return optValue; }, roundTripString: function bjs_roundTripString(name) { const isSome = name != null; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.d.ts index 961f97635..19680ec06 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.d.ts @@ -15,5 +15,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.js index 46d57d793..d4480a65c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.d.ts index 77e269d16..a28a7b4bb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.d.ts @@ -20,5 +20,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.js index bb4e8552d..e9b298fb2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.d.ts index 5872a3020..d7cd0e2e6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.d.ts @@ -44,5 +44,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js index 61560134a..beb9417df 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts index a413fa500..f55109d2b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.d.ts @@ -119,5 +119,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js index b2a894ffa..8c97e1cb7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js @@ -55,7 +55,345 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createResultValuesHelpers = () => ({ + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_TestModule_MyViewControllerDelegate = { + lower: (v) => { + const objId = swift.memory.retain(v); + i32Stack.push(objId); + }, + lift: () => { + const objId = i32Stack.pop(); + const obj = swift.memory.getObject(objId); + swift.memory.release(objId); + return obj; + }, + }; + const __bjs_codec_Array_TestModule_MyViewControllerDelegate = __bjs_arrayCodec(__bjs_codec_TestModule_MyViewControllerDelegate); + const __bjs_codec_Dict_TestModule_MyViewControllerDelegate = __bjs_dictCodec(__bjs_codec_TestModule_MyViewControllerDelegate); + + const __bjs_createEnumHelpers_TestModule_Result = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -163,6 +501,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -366,7 +706,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_MyViewControllerDelegate_result_get"] = function bjs_MyViewControllerDelegate_result_get(self) { try { let ret = swift.memory.getObject(self).result; - const caseId = enumHelpers.Result.lower(ret); + const caseId = enumHelpers.TestModule_Result.lower(ret); return caseId; } catch (error) { setException(error); @@ -374,7 +714,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_result_set"] = function bjs_MyViewControllerDelegate_result_set(self, value) { try { - const enumValue = enumHelpers.Result.lift(value); + const enumValue = enumHelpers.TestModule_Result.lift(value); swift.memory.getObject(self).result = enumValue; } catch (error) { setException(error); @@ -385,7 +725,7 @@ export async function createInstantiator(options, swift) { let ret = swift.memory.getObject(self).optionalResult; const isSome = ret != null; if (isSome) { - const caseId = enumHelpers.Result.lower(ret); + const caseId = enumHelpers.TestModule_Result.lower(ret); return caseId; } else { return -1; @@ -398,7 +738,7 @@ export async function createInstantiator(options, swift) { try { let optResult; if (valueIsSome) { - const enumValue = enumHelpers.Result.lift(valueCaseId); + const enumValue = enumHelpers.TestModule_Result.lift(valueCaseId); optResult = enumValue; } else { optResult = null; @@ -556,7 +896,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_MyViewControllerDelegate_handleResult"] = function bjs_MyViewControllerDelegate_handleResult(self, result) { try { - const enumValue = enumHelpers.Result.lift(result); + const enumValue = enumHelpers.TestModule_Result.lift(result); swift.memory.getObject(self).handleResult(enumValue); } catch (error) { setException(error); @@ -565,7 +905,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_MyViewControllerDelegate_getResult"] = function bjs_MyViewControllerDelegate_getResult(self) { try { let ret = swift.memory.getObject(self).getResult(); - const caseId = enumHelpers.Result.lower(ret); + const caseId = enumHelpers.TestModule_Result.lower(ret); return caseId; } catch (error) { setException(error); @@ -724,11 +1064,7 @@ export async function createInstantiator(options, swift) { } constructor(delegates) { - for (const elem of delegates) { - const objId = swift.memory.retain(elem); - i32Stack.push(objId); - } - i32Stack.push(delegates.length); + __bjs_codec_Array_TestModule_MyViewControllerDelegate.lower(delegates); const ret = instance.exports.bjs_DelegateManager_init(); return DelegateManager.__construct(ret); } @@ -737,107 +1073,37 @@ export async function createInstantiator(options, swift) { } get delegates() { instance.exports.bjs_DelegateManager_delegates_get(this.pointer); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const objId = i32Stack.pop(); - const obj = swift.memory.getObject(objId); - swift.memory.release(objId); - arrayResult.push(obj); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_MyViewControllerDelegate.lift(); return arrayResult; } set delegates(value) { - for (const elem of value) { - const objId = swift.memory.retain(elem); - i32Stack.push(objId); - } - i32Stack.push(value.length); + __bjs_codec_Array_TestModule_MyViewControllerDelegate.lower(value); instance.exports.bjs_DelegateManager_delegates_set(this.pointer); } get delegatesByName() { instance.exports.bjs_DelegateManager_delegatesByName_get(this.pointer); - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const objId = i32Stack.pop(); - const obj = swift.memory.getObject(objId); - swift.memory.release(objId); - const string = strStack.pop(); - dictResult[string] = obj; - } + const dictResult = __bjs_codec_Dict_TestModule_MyViewControllerDelegate.lift(); return dictResult; } set delegatesByName(value) { - const entries = Object.entries(value); - for (const entry of entries) { - const [key, value1] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - const objId = swift.memory.retain(value1); - i32Stack.push(objId); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_TestModule_MyViewControllerDelegate.lower(value); instance.exports.bjs_DelegateManager_delegatesByName_set(this.pointer); } } - const ResultHelpers = __bjs_createResultValuesHelpers(); - enumHelpers.Result = ResultHelpers; + const __bjs_helpers_TestModule_Result = __bjs_createEnumHelpers_TestModule_Result(); + enumHelpers.TestModule_Result = __bjs_helpers_TestModule_Result; const exports = { processDelegates: function bjs_processDelegates(delegates) { - for (const elem of delegates) { - const objId = swift.memory.retain(elem); - i32Stack.push(objId); - } - i32Stack.push(delegates.length); + __bjs_codec_Array_TestModule_MyViewControllerDelegate.lower(delegates); instance.exports.bjs_processDelegates(); - const arrayLen = i32Stack.pop(); - let arrayResult; - if (arrayLen === -1) { - arrayResult = taStack.pop(); - } else { - arrayResult = []; - for (let i = 0; i < arrayLen; i++) { - const objId1 = i32Stack.pop(); - const obj = swift.memory.getObject(objId1); - swift.memory.release(objId1); - arrayResult.push(obj); - } - arrayResult.reverse(); - } + const arrayResult = __bjs_codec_Array_TestModule_MyViewControllerDelegate.lift(); return arrayResult; }, processDelegatesByName: function bjs_processDelegatesByName(delegates) { - const entries = Object.entries(delegates); - for (const entry of entries) { - const [key, value] = entry; - const bytes = textEncoder.encode(key); - const id = swift.memory.retain(bytes); - i32Stack.push(bytes.length); - i32Stack.push(id); - const objId = swift.memory.retain(value); - i32Stack.push(objId); - } - i32Stack.push(entries.length); + __bjs_codec_Dict_TestModule_MyViewControllerDelegate.lower(delegates); instance.exports.bjs_processDelegatesByName(); - const dictLen = i32Stack.pop(); - const dictResult = {}; - for (let i = 0; i < dictLen; i++) { - const objId1 = i32Stack.pop(); - const obj = swift.memory.getObject(objId1); - swift.memory.release(objId1); - const string = strStack.pop(); - dictResult[string] = obj; - } + const dictResult = __bjs_codec_Dict_TestModule_MyViewControllerDelegate.lift(); return dictResult; }, Direction: DirectionValues, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.d.ts index 7d5a3c9aa..ce87ccd29 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.d.ts @@ -34,5 +34,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js index 01f9fe0e1..eed26c581 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js @@ -131,6 +131,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.d.ts index e5602e42d..b97a1bd8e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.d.ts @@ -73,5 +73,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js index 25f989a00..193d66703 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js @@ -42,7 +42,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createAPIResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_APIResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -150,6 +150,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -351,8 +353,8 @@ export async function createInstantiator(options, swift) { return ret; } } - const APIResultHelpers = __bjs_createAPIResultValuesHelpers(); - enumHelpers.APIResult = APIResultHelpers; + const __bjs_helpers_TestModule_APIResult = __bjs_createEnumHelpers_TestModule_APIResult(); + enumHelpers.TestModule_APIResult = __bjs_helpers_TestModule_APIResult; if (typeof globalThis.Utils === 'undefined') { globalThis.Utils = {}; @@ -381,9 +383,9 @@ export async function createInstantiator(options, swift) { APIResult: { ...APIResultValues, roundtrip: function(value) { - const valueCaseId = enumHelpers.APIResult.lower(value); + const valueCaseId = enumHelpers.TestModule_APIResult.lower(value); instance.exports.bjs_APIResult_static_roundtrip(valueCaseId); - const ret = enumHelpers.APIResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_APIResult.lift(i32Stack.pop()); return ret; } }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.d.ts index a168f3ad1..6176abb6f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.d.ts @@ -63,5 +63,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js index ca4093992..711d48d0a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js @@ -42,7 +42,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createAPIResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_APIResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -150,6 +150,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -351,8 +353,8 @@ export async function createInstantiator(options, swift) { return ret; } } - const APIResultHelpers = __bjs_createAPIResultValuesHelpers(); - enumHelpers.APIResult = APIResultHelpers; + const __bjs_helpers_TestModule_APIResult = __bjs_createEnumHelpers_TestModule_APIResult(); + enumHelpers.TestModule_APIResult = __bjs_helpers_TestModule_APIResult; const exports = { Calculator: { @@ -375,9 +377,9 @@ export async function createInstantiator(options, swift) { APIResult: { ...APIResultValues, roundtrip: function(value) { - const valueCaseId = enumHelpers.APIResult.lower(value); + const valueCaseId = enumHelpers.TestModule_APIResult.lower(value); instance.exports.bjs_APIResult_static_roundtrip(valueCaseId); - const ret = enumHelpers.APIResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_APIResult.lift(i32Stack.pop()); return ret; } }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.d.ts index b54e14def..42cfe5870 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.d.ts @@ -68,5 +68,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js index 63dd9cba5..e2a9093b6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js @@ -111,6 +111,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.d.ts index aea927c79..42ff8507c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.d.ts @@ -54,5 +54,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js index b5680b9b0..f442745e5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js @@ -111,6 +111,8 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.d.ts index 5e45162a1..8d562d13a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.d.ts @@ -17,5 +17,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.js index 2c3da5f26..ebddbefd2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.d.ts index b43ff062c..667db342e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.d.ts @@ -15,5 +15,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.js index 057bf9658..d2dfa204f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.d.ts index fe4708fd8..cf231e076 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.d.ts @@ -69,5 +69,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.js index ee5cc0a3e..b1be21624 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.js @@ -46,7 +46,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createShapeHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Shape = () => ({ lower: (value) => { const bytes = textEncoder.encode(value.label); const id = swift.memory.retain(bytes); @@ -58,7 +58,7 @@ export async function createInstantiator(options, swift) { return { label: string }; } }); - const __bjs_createWidgetHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Widget = () => ({ lower: (value) => { const bytes = textEncoder.encode(value.name); const id = swift.memory.retain(bytes); @@ -70,7 +70,7 @@ export async function createInstantiator(options, swift) { return { name: string }; } }); - const __bjs_createWidget_LayoutHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Widget_Layout = () => ({ lower: (value) => { i32Stack.push((value.padding | 0)); }, @@ -79,7 +79,7 @@ export async function createInstantiator(options, swift) { return { padding: int }; } }); - const __bjs_createWidget_BoundsHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Widget_Bounds = () => ({ lower: (value) => { i32Stack.push((value.width | 0)); i32Stack.push((value.height | 0)); @@ -166,33 +166,35 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_Shape"] = function(objectId) { - structHelpers.Shape.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Shape.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Shape"] = function() { - const value = structHelpers.Shape.lift(); + const value = structHelpers.TestModule_Shape.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Widget"] = function(objectId) { - structHelpers.Widget.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Widget.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Widget"] = function() { - const value = structHelpers.Widget.lift(); + const value = structHelpers.TestModule_Widget.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Widget_Layout"] = function(objectId) { - structHelpers.Widget_Layout.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Widget_Layout.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Widget_Layout"] = function() { - const value = structHelpers.Widget_Layout.lift(); + const value = structHelpers.TestModule_Widget_Layout.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Widget_Bounds"] = function(objectId) { - structHelpers.Widget_Bounds.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Widget_Bounds.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Widget_Bounds"] = function() { - const value = structHelpers.Widget_Bounds.lift(); + const value = structHelpers.TestModule_Widget_Bounds.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -304,17 +306,17 @@ export async function createInstantiator(options, swift) { /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { const js = swift.memory.heap; - const ShapeHelpers = __bjs_createShapeHelpers(); - structHelpers.Shape = ShapeHelpers; + const __bjs_helpers_TestModule_Shape = __bjs_createStructHelpers_TestModule_Shape(); + structHelpers.TestModule_Shape = __bjs_helpers_TestModule_Shape; - const WidgetHelpers = __bjs_createWidgetHelpers(); - structHelpers.Widget = WidgetHelpers; + const __bjs_helpers_TestModule_Widget = __bjs_createStructHelpers_TestModule_Widget(); + structHelpers.TestModule_Widget = __bjs_helpers_TestModule_Widget; - const Widget_LayoutHelpers = __bjs_createWidget_LayoutHelpers(); - structHelpers.Widget_Layout = Widget_LayoutHelpers; + const __bjs_helpers_TestModule_Widget_Layout = __bjs_createStructHelpers_TestModule_Widget_Layout(); + structHelpers.TestModule_Widget_Layout = __bjs_helpers_TestModule_Widget_Layout; - const Widget_BoundsHelpers = __bjs_createWidget_BoundsHelpers(); - structHelpers.Widget_Bounds = Widget_BoundsHelpers; + const __bjs_helpers_TestModule_Widget_Bounds = __bjs_createStructHelpers_TestModule_Widget_Bounds(); + structHelpers.TestModule_Widget_Bounds = __bjs_helpers_TestModule_Widget_Bounds; const exports = { Shape: { @@ -322,7 +324,7 @@ export async function createInstantiator(options, swift) { const labelBytes = textEncoder.encode(label); const labelId = swift.memory.retain(labelBytes); instance.exports.bjs_Shape_init(labelId, labelBytes.length); - const structValue = structHelpers.Shape.lift(); + const structValue = structHelpers.TestModule_Shape.lift(); return structValue; }, Kind: KindValues, @@ -332,14 +334,14 @@ export async function createInstantiator(options, swift) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); instance.exports.bjs_Widget_init(nameId, nameBytes.length); - const structValue = structHelpers.Widget.lift(); + const structValue = structHelpers.TestModule_Widget.lift(); return structValue; }, Variant: VariantValues, Bounds: { init: function(width, height) { instance.exports.bjs_Widget_Bounds_init(width, height); - const structValue = structHelpers.Widget_Bounds.lift(); + const structValue = structHelpers.TestModule_Widget_Bounds.lift(); return structValue; }, get dimensions() { @@ -348,7 +350,7 @@ export async function createInstantiator(options, swift) { }, zero: function() { instance.exports.bjs_Widget_Bounds_static_zero(); - const structValue = structHelpers.Widget_Bounds.lift(); + const structValue = structHelpers.TestModule_Widget_Bounds.lift(); return structValue; }, }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.d.ts index 2f56a1cb8..d0c84e109 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.d.ts @@ -43,5 +43,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js index be63f59be..92ef435fb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.d.ts index 70f23c11a..81fd7f109 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.d.ts @@ -114,5 +114,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js index 62c2de8c6..ded3f77e5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js @@ -61,6 +61,240 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + function __bjs_jsValueLower(value) { let kind; let payload1; @@ -175,7 +409,18 @@ export async function createInstantiator(options, swift) { return swift.memory.retain(real); }; - const __bjs_createAnimalHelpers = () => ({ + const __bjs_codec_TestModule_Animal = { + lower: (v) => { + structHelpers.TestModule_Animal.lower(v); + }, + lift: () => { + const struct = structHelpers.TestModule_Animal.lift(); + return struct; + }, + }; + const __bjs_codec_Optional_TestModule_Animal = __bjs_optionalCodec(__bjs_codec_TestModule_Animal); + + const __bjs_createStructHelpers_TestModule_Animal = () => ({ lower: (value) => { const bytes = textEncoder.encode(value.type); const id = swift.memory.retain(bytes); @@ -187,7 +432,7 @@ export async function createInstantiator(options, swift) { return { type: string }; } }); - const __bjs_createAPIResultValuesHelpers = () => ({ + const __bjs_createEnumHelpers_TestModule_APIResult = () => ({ lower: (value) => { const enumTag = value.tag; switch (enumTag) { @@ -324,12 +569,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_Animal"] = function(objectId) { - structHelpers.Animal.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Animal.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Animal"] = function() { - const value = structHelpers.Animal.lift(); + const value = structHelpers.TestModule_Animal.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -347,7 +594,7 @@ export async function createInstantiator(options, swift) { } bjs["promise_resolve_TestModule_6AnimalV"] = function(promise) { try { - const structValue = structHelpers.Animal.lift(); + const structValue = structHelpers.TestModule_Animal.lift(); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(structValue); } catch (error) { setException(error); @@ -355,7 +602,7 @@ export async function createInstantiator(options, swift) { } bjs["promise_resolve_TestModule_9APIResultO"] = function(promise, value) { try { - const enumValue = enumHelpers.APIResult.lift(value); + const enumValue = enumHelpers.TestModule_APIResult.lift(value); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(enumValue); } catch (error) { setException(error); @@ -517,18 +764,18 @@ export async function createInstantiator(options, swift) { bjs["invoke_js_callback_TestModule_10TestModule6AnimalV_6AnimalV"] = function(callbackId) { try { const callback = swift.memory.getObject(callbackId); - const structValue = structHelpers.Animal.lift(); + const structValue = structHelpers.TestModule_Animal.lift(); let ret = callback(structValue); - structHelpers.Animal.lower(ret); + structHelpers.TestModule_Animal.lower(ret); } catch (error) { setException(error); } } bjs["make_swift_closure_TestModule_10TestModule6AnimalV_6AnimalV"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModule6AnimalV_6AnimalV = function(param0) { - structHelpers.Animal.lower(param0); + structHelpers.TestModule_Animal.lower(param0); instance.exports.invoke_swift_closure_TestModule_10TestModule6AnimalV_6AnimalV(boxPtr); - const structValue = structHelpers.Animal.lift(); + const structValue = structHelpers.TestModule_Animal.lift(); if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); swift.memory.release(tmpRetException); @@ -565,9 +812,9 @@ export async function createInstantiator(options, swift) { bjs["invoke_js_callback_TestModule_10TestModule9APIResultO_9APIResultO"] = function(callbackId, param0) { try { const callback = swift.memory.getObject(callbackId); - const enumValue = enumHelpers.APIResult.lift(param0); + const enumValue = enumHelpers.TestModule_APIResult.lift(param0); let ret = callback(enumValue); - const caseId = enumHelpers.APIResult.lower(ret); + const caseId = enumHelpers.TestModule_APIResult.lower(ret); return caseId; } catch (error) { setException(error); @@ -575,9 +822,9 @@ export async function createInstantiator(options, swift) { } bjs["make_swift_closure_TestModule_10TestModule9APIResultO_9APIResultO"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModule9APIResultO_9APIResultO = function(param0) { - const param0CaseId = enumHelpers.APIResult.lower(param0); + const param0CaseId = enumHelpers.TestModule_APIResult.lower(param0); instance.exports.invoke_swift_closure_TestModule_10TestModule9APIResultO_9APIResultO(boxPtr, param0CaseId); - const ret = enumHelpers.APIResult.lift(i32Stack.pop()); + const ret = enumHelpers.TestModule_APIResult.lift(i32Stack.pop()); if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); swift.memory.release(tmpRetException); @@ -857,38 +1104,29 @@ export async function createInstantiator(options, swift) { const callback = swift.memory.getObject(callbackId); let optResult; if (param0) { - const struct = structHelpers.Animal.lift(); + const struct = structHelpers.TestModule_Animal.lift(); optResult = struct; } else { optResult = null; } let ret = callback(optResult); - const isSome = ret != null; - if (isSome) { - structHelpers.Animal.lower(ret); - } - i32Stack.push(isSome ? 1 : 0); + __bjs_codec_Optional_TestModule_Animal.lower(ret); } catch (error) { setException(error); } } bjs["make_swift_closure_TestModule_10TestModuleSq6AnimalV_Sq6AnimalV"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModuleSq6AnimalV_Sq6AnimalV = function(param0) { - const isSome = param0 != null; - if (isSome) { - structHelpers.Animal.lower(param0); - } - i32Stack.push(+isSome); + __bjs_codec_Optional_TestModule_Animal.lower(param0); instance.exports.invoke_swift_closure_TestModule_10TestModuleSq6AnimalV_Sq6AnimalV(boxPtr); - const isSome1 = i32Stack.pop(); - const optResult = isSome1 ? structHelpers.Animal.lift() : null; + const optValue = __bjs_codec_Optional_TestModule_Animal.lift(); if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); swift.memory.release(tmpRetException); tmpRetException = undefined; throw error; } - return optResult; + return optValue; }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleSq6AnimalV_Sq6AnimalV); } @@ -930,7 +1168,7 @@ export async function createInstantiator(options, swift) { const callback = swift.memory.getObject(callbackId); let optResult; if (param0IsSome) { - const enumValue = enumHelpers.APIResult.lift(param0CaseId); + const enumValue = enumHelpers.TestModule_APIResult.lift(param0CaseId); optResult = enumValue; } else { optResult = null; @@ -938,7 +1176,7 @@ export async function createInstantiator(options, swift) { let ret = callback(optResult); const isSome = ret != null; if (isSome) { - const caseId = enumHelpers.APIResult.lower(ret); + const caseId = enumHelpers.TestModule_APIResult.lower(ret); return caseId; } else { return -1; @@ -952,14 +1190,14 @@ export async function createInstantiator(options, swift) { const isSome = param0 != null; let result; if (isSome) { - const param0CaseId = enumHelpers.APIResult.lower(param0); + const param0CaseId = enumHelpers.TestModule_APIResult.lower(param0); result = param0CaseId; } else { result = 0; } instance.exports.invoke_swift_closure_TestModule_10TestModuleSq9APIResultO_Sq9APIResultO(boxPtr, +isSome, result); const tag = i32Stack.pop(); - const optResult = tag === -1 ? null : enumHelpers.APIResult.lift(tag); + const optResult = tag === -1 ? null : enumHelpers.TestModule_APIResult.lift(tag); if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); swift.memory.release(tmpRetException); @@ -1227,7 +1465,7 @@ export async function createInstantiator(options, swift) { bjs["invoke_js_callback_TestModule_10TestModules6AnimalV_y"] = function(callbackId) { try { const callback = swift.memory.getObject(callbackId); - const structValue = structHelpers.Animal.lift(); + const structValue = structHelpers.TestModule_Animal.lift(); callback(structValue); } catch (error) { setException(error); @@ -1235,7 +1473,7 @@ export async function createInstantiator(options, swift) { } bjs["make_swift_closure_TestModule_10TestModules6AnimalV_y"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModules6AnimalV_y = function(param0) { - structHelpers.Animal.lower(param0); + structHelpers.TestModule_Animal.lower(param0); instance.exports.invoke_swift_closure_TestModule_10TestModules6AnimalV_y(boxPtr); if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); @@ -1271,7 +1509,7 @@ export async function createInstantiator(options, swift) { bjs["invoke_js_callback_TestModule_10TestModules9APIResultO_y"] = function(callbackId, param0) { try { const callback = swift.memory.getObject(callbackId); - const enumValue = enumHelpers.APIResult.lift(param0); + const enumValue = enumHelpers.TestModule_APIResult.lift(param0); callback(enumValue); } catch (error) { setException(error); @@ -1279,7 +1517,7 @@ export async function createInstantiator(options, swift) { } bjs["make_swift_closure_TestModule_10TestModules9APIResultO_y"] = function(boxPtr, file, line) { const lower_closure_TestModule_10TestModules9APIResultO_y = function(param0) { - const param0CaseId = enumHelpers.APIResult.lower(param0); + const param0CaseId = enumHelpers.TestModule_APIResult.lower(param0); instance.exports.invoke_swift_closure_TestModule_10TestModules9APIResultO_y(boxPtr, param0CaseId); if (tmpRetException) { const error = swift.memory.getObject(tmpRetException); @@ -1414,11 +1652,11 @@ export async function createInstantiator(options, swift) { return TestProcessor.__construct(ret); } } - const AnimalHelpers = __bjs_createAnimalHelpers(); - structHelpers.Animal = AnimalHelpers; + const __bjs_helpers_TestModule_Animal = __bjs_createStructHelpers_TestModule_Animal(); + structHelpers.TestModule_Animal = __bjs_helpers_TestModule_Animal; - const APIResultHelpers = __bjs_createAPIResultValuesHelpers(); - enumHelpers.APIResult = APIResultHelpers; + const __bjs_helpers_TestModule_APIResult = __bjs_createEnumHelpers_TestModule_APIResult(); + enumHelpers.TestModule_APIResult = __bjs_helpers_TestModule_APIResult; const exports = { roundtripAnimal: function bjs_roundtripAnimal(animalClosure) { @@ -1569,7 +1807,7 @@ export async function createInstantiator(options, swift) { const typeBytes = textEncoder.encode(type); const typeId = swift.memory.retain(typeBytes); instance.exports.bjs_Animal_init(typeId, typeBytes.length); - const structValue = structHelpers.Animal.lift(); + const structValue = structHelpers.TestModule_Animal.lift(); return structValue; }, }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.d.ts index b66f960f8..47f1b89f9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.d.ts @@ -17,5 +17,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js index d03915f87..96c78dd22 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js @@ -221,6 +221,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.d.ts index 3b394fb06..3503e138e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.d.ts @@ -90,5 +90,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js index 92a99becb..db4fdbe93 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js @@ -36,7 +36,357 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createDataPointHelpers = () => ({ + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_Optional_Int = __bjs_optionalCodec(__bjs_primitiveCodecs.Int); + const __bjs_codec_Optional_Bool = __bjs_optionalCodec(__bjs_primitiveCodecs.Bool); + const __bjs_codec_Optional_String = __bjs_optionalCodec(__bjs_stringCodec); + const __bjs_codec_TestModule_Precision = { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const rawValue = f32Stack.pop(); + return rawValue; + }, + }; + const __bjs_codec_Optional_TestModule_Precision = __bjs_optionalCodec(__bjs_codec_TestModule_Precision); + const __bjs_codec_JSObject = { + lower: (v) => { + const objId = swift.memory.retain(v); + i32Stack.push(objId); + }, + lift: () => { + const objId = i32Stack.pop(); + const obj = swift.memory.getObject(objId); + swift.memory.release(objId); + return obj; + }, + }; + const __bjs_codec_Optional_JSObject = __bjs_optionalCodec(__bjs_codec_JSObject); + + const __bjs_createStructHelpers_TestModule_DataPoint = () => ({ lower: (value) => { f64Stack.push(value.x); f64Stack.push(value.y); @@ -44,41 +394,19 @@ export async function createInstantiator(options, swift) { const id = swift.memory.retain(bytes); i32Stack.push(bytes.length); i32Stack.push(id); - const isSome = value.optCount != null ? 1 : 0; - if (isSome) { - i32Stack.push((value.optCount | 0)); - } - i32Stack.push(isSome); - const isSome1 = value.optFlag != null ? 1 : 0; - if (isSome1) { - i32Stack.push(value.optFlag ? 1 : 0); - } - i32Stack.push(isSome1); + __bjs_codec_Optional_Int.lower(value.optCount); + __bjs_codec_Optional_Bool.lower(value.optFlag); }, lift: () => { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const bool = i32Stack.pop() !== 0; - optValue = bool; - } - const isSome1 = i32Stack.pop(); - let optValue1; - if (isSome1 === 0) { - optValue1 = null; - } else { - const int = i32Stack.pop(); - optValue1 = int; - } + const optValue = __bjs_codec_Optional_Bool.lift(); + const optValue1 = __bjs_codec_Optional_Int.lift(); const string = strStack.pop(); const f64 = f64Stack.pop(); const f641 = f64Stack.pop(); return { x: f641, y: f64, label: string, optCount: optValue1, optFlag: optValue }; } }); - const __bjs_createAddressHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Address = () => ({ lower: (value) => { const bytes = textEncoder.encode(value.street); const id = swift.memory.retain(bytes); @@ -88,59 +416,34 @@ export async function createInstantiator(options, swift) { const id1 = swift.memory.retain(bytes1); i32Stack.push(bytes1.length); i32Stack.push(id1); - const isSome = value.zipCode != null ? 1 : 0; - if (isSome) { - i32Stack.push((value.zipCode | 0)); - } - i32Stack.push(isSome); + __bjs_codec_Optional_Int.lower(value.zipCode); }, lift: () => { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const int = i32Stack.pop(); - optValue = int; - } + const optValue = __bjs_codec_Optional_Int.lift(); const string = strStack.pop(); const string1 = strStack.pop(); return { street: string1, city: string, zipCode: optValue }; } }); - const __bjs_createPersonHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Person = () => ({ lower: (value) => { const bytes = textEncoder.encode(value.name); const id = swift.memory.retain(bytes); i32Stack.push(bytes.length); i32Stack.push(id); i32Stack.push((value.age | 0)); - structHelpers.Address.lower(value.address); - const isSome = value.email != null ? 1 : 0; - if (isSome) { - const bytes1 = textEncoder.encode(value.email); - const id1 = swift.memory.retain(bytes1); - i32Stack.push(bytes1.length); - i32Stack.push(id1); - } - i32Stack.push(isSome); + structHelpers.TestModule_Address.lower(value.address); + __bjs_codec_Optional_String.lower(value.email); }, lift: () => { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const string = strStack.pop(); - optValue = string; - } - const struct = structHelpers.Address.lift(); + const optValue = __bjs_codec_Optional_String.lift(); + const struct = structHelpers.TestModule_Address.lift(); const int = i32Stack.pop(); - const string1 = strStack.pop(); - return { name: string1, age: int, address: struct, email: optValue }; + const string = strStack.pop(); + return { name: string, age: int, address: struct, email: optValue }; } }); - const __bjs_createSessionHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Session = () => ({ lower: (value) => { i32Stack.push((value.id | 0)); ptrStack.push(value.owner.pointer); @@ -152,38 +455,27 @@ export async function createInstantiator(options, swift) { return { id: int, owner: obj }; } }); - const __bjs_createMeasurementHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Measurement = () => ({ lower: (value) => { f64Stack.push(value.value); f32Stack.push(Math.fround(value.precision)); - const isSome = value.optionalPrecision != null ? 1 : 0; - if (isSome) { - f32Stack.push(Math.fround(value.optionalPrecision)); - } - i32Stack.push(isSome); + __bjs_codec_Optional_TestModule_Precision.lower(value.optionalPrecision); }, lift: () => { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const rawValue = f32Stack.pop(); - optValue = rawValue; - } - const rawValue1 = f32Stack.pop(); + const optValue = __bjs_codec_Optional_TestModule_Precision.lift(); + const rawValue = f32Stack.pop(); const f64 = f64Stack.pop(); - return { value: f64, precision: rawValue1, optionalPrecision: optValue }; + return { value: f64, precision: rawValue, optionalPrecision: optValue }; } }); - const __bjs_createConfigStructHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_ConfigStruct = () => ({ lower: (value) => { }, lift: () => { return { }; } }); - const __bjs_createContainerHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Container = () => ({ lower: (value) => { let id; if (value.object != null) { @@ -192,24 +484,10 @@ export async function createInstantiator(options, swift) { id = undefined; } i32Stack.push(id !== undefined ? id : 0); - const isSome = value.optionalObject != null ? 1 : 0; - if (isSome) { - const objId = swift.memory.retain(value.optionalObject); - i32Stack.push(objId); - } - i32Stack.push(isSome); + __bjs_codec_Optional_JSObject.lower(value.optionalObject); }, lift: () => { - const isSome = i32Stack.pop(); - let optValue; - if (isSome === 0) { - optValue = null; - } else { - const objId = i32Stack.pop(); - const obj = swift.memory.getObject(objId); - swift.memory.release(objId); - optValue = obj; - } + const optValue = __bjs_codec_Optional_JSObject.lift(); const objectId = i32Stack.pop(); let value; if (objectId !== 0) { @@ -221,7 +499,7 @@ export async function createInstantiator(options, swift) { return { object: value, optionalObject: optValue }; } }); - const __bjs_createVector2DHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_Vector2D = () => ({ lower: (value) => { f64Stack.push(value.dx); f64Stack.push(value.dy); @@ -231,18 +509,18 @@ export async function createInstantiator(options, swift) { const f641 = f64Stack.pop(); const instance1 = { dx: f641, dy: f64 }; instance1.magnitude = function() { - structHelpers.Vector2D.lower(this); + structHelpers.TestModule_Vector2D.lower(this); const ret = instance.exports.bjs_Vector2D_magnitude(); return ret; }.bind(instance1); instance1.scaled = function(factor) { - structHelpers.Vector2D.lower(this); + structHelpers.TestModule_Vector2D.lower(this); const ret1 = instance.exports.bjs_Vector2D_scaled(factor); - const structValue = structHelpers.Vector2D.lift(); + const structValue = structHelpers.TestModule_Vector2D.lift(); return structValue; }.bind(instance1); instance1.describe = function() { - structHelpers.Vector2D.lower(this); + structHelpers.TestModule_Vector2D.lower(this); const ret2 = instance.exports.bjs_Vector2D_describe(); const ret3 = tmpRetString; tmpRetString = undefined; @@ -327,61 +605,63 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_DataPoint"] = function(objectId) { - structHelpers.DataPoint.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_DataPoint.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_DataPoint"] = function() { - const value = structHelpers.DataPoint.lift(); + const value = structHelpers.TestModule_DataPoint.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Address"] = function(objectId) { - structHelpers.Address.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Address.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Address"] = function() { - const value = structHelpers.Address.lift(); + const value = structHelpers.TestModule_Address.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Person"] = function(objectId) { - structHelpers.Person.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Person.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Person"] = function() { - const value = structHelpers.Person.lift(); + const value = structHelpers.TestModule_Person.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Session"] = function(objectId) { - structHelpers.Session.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Session.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Session"] = function() { - const value = structHelpers.Session.lift(); + const value = structHelpers.TestModule_Session.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Measurement"] = function(objectId) { - structHelpers.Measurement.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Measurement.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Measurement"] = function() { - const value = structHelpers.Measurement.lift(); + const value = structHelpers.TestModule_Measurement.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_ConfigStruct"] = function(objectId) { - structHelpers.ConfigStruct.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_ConfigStruct.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_ConfigStruct"] = function() { - const value = structHelpers.ConfigStruct.lift(); + const value = structHelpers.TestModule_ConfigStruct.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Container"] = function(objectId) { - structHelpers.Container.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Container.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Container"] = function() { - const value = structHelpers.Container.lift(); + const value = structHelpers.TestModule_Container.lift(); return swift.memory.retain(value); } bjs["swift_js_struct_lower_Vector2D"] = function(objectId) { - structHelpers.Vector2D.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Vector2D.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Vector2D"] = function() { - const value = structHelpers.Vector2D.lift(); + const value = structHelpers.TestModule_Vector2D.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -582,41 +862,41 @@ export async function createInstantiator(options, swift) { instance.exports.bjs_Greeter_name_set(this.pointer, valueId, valueBytes.length); } } - const DataPointHelpers = __bjs_createDataPointHelpers(); - structHelpers.DataPoint = DataPointHelpers; + const __bjs_helpers_TestModule_DataPoint = __bjs_createStructHelpers_TestModule_DataPoint(); + structHelpers.TestModule_DataPoint = __bjs_helpers_TestModule_DataPoint; - const AddressHelpers = __bjs_createAddressHelpers(); - structHelpers.Address = AddressHelpers; + const __bjs_helpers_TestModule_Address = __bjs_createStructHelpers_TestModule_Address(); + structHelpers.TestModule_Address = __bjs_helpers_TestModule_Address; - const PersonHelpers = __bjs_createPersonHelpers(); - structHelpers.Person = PersonHelpers; + const __bjs_helpers_TestModule_Person = __bjs_createStructHelpers_TestModule_Person(); + structHelpers.TestModule_Person = __bjs_helpers_TestModule_Person; - const SessionHelpers = __bjs_createSessionHelpers(); - structHelpers.Session = SessionHelpers; + const __bjs_helpers_TestModule_Session = __bjs_createStructHelpers_TestModule_Session(); + structHelpers.TestModule_Session = __bjs_helpers_TestModule_Session; - const MeasurementHelpers = __bjs_createMeasurementHelpers(); - structHelpers.Measurement = MeasurementHelpers; + const __bjs_helpers_TestModule_Measurement = __bjs_createStructHelpers_TestModule_Measurement(); + structHelpers.TestModule_Measurement = __bjs_helpers_TestModule_Measurement; - const ConfigStructHelpers = __bjs_createConfigStructHelpers(); - structHelpers.ConfigStruct = ConfigStructHelpers; + const __bjs_helpers_TestModule_ConfigStruct = __bjs_createStructHelpers_TestModule_ConfigStruct(); + structHelpers.TestModule_ConfigStruct = __bjs_helpers_TestModule_ConfigStruct; - const ContainerHelpers = __bjs_createContainerHelpers(); - structHelpers.Container = ContainerHelpers; + const __bjs_helpers_TestModule_Container = __bjs_createStructHelpers_TestModule_Container(); + structHelpers.TestModule_Container = __bjs_helpers_TestModule_Container; - const Vector2DHelpers = __bjs_createVector2DHelpers(); - structHelpers.Vector2D = Vector2DHelpers; + const __bjs_helpers_TestModule_Vector2D = __bjs_createStructHelpers_TestModule_Vector2D(); + structHelpers.TestModule_Vector2D = __bjs_helpers_TestModule_Vector2D; const exports = { roundtrip: function bjs_roundtrip(session) { - structHelpers.Person.lower(session); + structHelpers.TestModule_Person.lower(session); instance.exports.bjs_roundtrip(); - const structValue = structHelpers.Person.lift(); + const structValue = structHelpers.TestModule_Person.lift(); return structValue; }, roundtripContainer: function bjs_roundtripContainer(container) { - structHelpers.Container.lower(container); + structHelpers.TestModule_Container.lower(container); instance.exports.bjs_roundtripContainer(); - const structValue = structHelpers.Container.lift(); + const structValue = structHelpers.TestModule_Container.lift(); return structValue; }, Precision: PrecisionValues, @@ -661,7 +941,7 @@ export async function createInstantiator(options, swift) { 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 ? 1 : 0 : 0); - const structValue = structHelpers.DataPoint.lift(); + const structValue = structHelpers.TestModule_DataPoint.lift(); return structValue; }, get dimensions() { @@ -670,7 +950,7 @@ export async function createInstantiator(options, swift) { }, origin: function() { instance.exports.bjs_DataPoint_static_origin(); - const structValue = structHelpers.DataPoint.lift(); + const structValue = structHelpers.TestModule_DataPoint.lift(); return structValue; }, }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.d.ts index e97b50fda..e95b78349 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.d.ts @@ -19,5 +19,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.js index 4a2e18d6b..b3119f0d3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.js @@ -31,7 +31,341 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createPointHelpers = () => ({ + const __bjs_arrayCodecCache = new WeakMap(); + function __bjs_arrayCodec(elementCodec) { + let codec = __bjs_arrayCodecCache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + for (let i = 0; i < value.length; i++) { + elementCodec.lower(value[i]); + } + i32Stack.push(value.length); + }, + lift() { + const count = i32Stack.pop(); + if (count === -1) { + return taStack.pop(); + } + const result = new Array(count); + for (let i = count - 1; i >= 0; i--) { + result[i] = elementCodec.lift(); + } + return result; + }, + }; + __bjs_arrayCodecCache.set(elementCodec, codec); + return codec; + } + const __bjs_optionalCodecCache = new WeakMap(); + const __bjs_optionalCodecUndefinedOrCache = new WeakMap(); + function __bjs_optionalCodec(elementCodec, isUndefinedOr = false) { + const cache = isUndefinedOr ? __bjs_optionalCodecUndefinedOrCache : __bjs_optionalCodecCache; + let codec = cache.get(elementCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const isSome = isUndefinedOr ? value !== undefined : value != null; + if (isSome) { + elementCodec.lower(value); + i32Stack.push(1); + } else { + i32Stack.push(0); + } + }, + lift() { + if (i32Stack.pop() === 0) { + return isUndefinedOr ? undefined : null; + } + return elementCodec.lift(); + }, + }; + cache.set(elementCodec, codec); + return codec; + } + const __bjs_dictCodecCache = new WeakMap(); + function __bjs_dictCodec(valueCodec) { + let codec = __bjs_dictCodecCache.get(valueCodec); + if (codec !== undefined) { + return codec; + } + codec = { + lower(value) { + const keys = Object.keys(value); + for (let i = 0; i < keys.length; i++) { + __bjs_stringCodec.lower(keys[i]); + valueCodec.lower(value[keys[i]]); + } + i32Stack.push(keys.length); + }, + lift() { + const count = i32Stack.pop(); + const result = {}; + for (let i = 0; i < count; i++) { + const value = valueCodec.lift(); + const key = __bjs_stringCodec.lift(); + result[key] = value; + } + return result; + }, + }; + __bjs_dictCodecCache.set(valueCodec, codec); + return codec; + } + + const __bjs_stringCodec = { + lower: (v) => { + const bytes = textEncoder.encode(v); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + }, + lift: () => { + const string = strStack.pop(); + return string; + }, + }; + const __bjs_primitiveCodecs = { + Bool: { + lower: (v) => { + i32Stack.push(v ? 1 : 0); + }, + lift: () => { + const bool = i32Stack.pop() !== 0; + return bool; + }, + }, + Int: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + Int8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt8: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt16: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + return int; + }, + }, + UInt32: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + UInt: { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const int = i32Stack.pop() >>> 0; + return int; + }, + }, + Int64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + UInt64: { + lower: (v) => { + i64Stack.push(v); + }, + lift: () => { + const int = i64Stack.pop(); + return int; + }, + }, + Float: { + lower: (v) => { + f32Stack.push(Math.fround(v)); + }, + lift: () => { + const f32 = f32Stack.pop(); + return f32; + }, + }, + Double: { + lower: (v) => { + f64Stack.push(v); + }, + lift: () => { + const f64 = f64Stack.pop(); + return f64; + }, + }, + String: __bjs_stringCodec, + JSValue: { + lower: (v) => { + const [vKind, vPayload1, vPayload2] = __bjs_jsValueLower(v); + i32Stack.push(vKind); + i32Stack.push(vPayload1); + f64Stack.push(vPayload2); + }, + lift: () => { + const jsValuePayload2 = f64Stack.pop(); + const jsValuePayload1 = i32Stack.pop(); + const jsValueKind = i32Stack.pop(); + const jsValue = __bjs_jsValueLift(jsValueKind, jsValuePayload1, jsValuePayload2); + return jsValue; + }, + }, + }; + + function __bjs_jsValueLower(value) { + let kind; + let payload1; + let payload2; + if (value === null) { + kind = 4; + payload1 = 0; + payload2 = 0; + } else { + switch (typeof value) { + case "boolean": + kind = 0; + payload1 = value ? 1 : 0; + payload2 = 0; + break; + case "number": + kind = 2; + payload1 = 0; + payload2 = value; + break; + case "string": + kind = 1; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "undefined": + kind = 5; + payload1 = 0; + payload2 = 0; + break; + case "object": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "function": + kind = 3; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "symbol": + kind = 7; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + case "bigint": + kind = 8; + payload1 = swift.memory.retain(value); + payload2 = 0; + break; + default: + throw new TypeError("Unsupported JSValue type"); + } + } + return [kind, payload1, payload2]; + } + function __bjs_jsValueLift(kind, payload1, payload2) { + let jsValue; + switch (kind) { + case 0: + jsValue = payload1 !== 0; + break; + case 1: + jsValue = swift.memory.getObject(payload1); + break; + case 2: + jsValue = payload2; + break; + case 3: + jsValue = swift.memory.getObject(payload1); + break; + case 4: + jsValue = null; + break; + case 5: + jsValue = undefined; + break; + case 7: + jsValue = swift.memory.getObject(payload1); + break; + case 8: + jsValue = swift.memory.getObject(payload1); + break; + default: + throw new TypeError("Unsupported JSValue kind " + kind); + } + return jsValue; + } + + const __bjs_codec_TestModule_Point = { + lower: (v) => { + structHelpers.TestModule_Point.lower(v); + }, + lift: () => { + const struct = structHelpers.TestModule_Point.lift(); + return struct; + }, + }; + const __bjs_codec_Optional_TestModule_Point = __bjs_optionalCodec(__bjs_codec_TestModule_Point); + + const __bjs_createStructHelpers_TestModule_Point = () => ({ lower: (value) => { i32Stack.push((value.x | 0)); i32Stack.push((value.y | 0)); @@ -119,12 +453,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_Point"] = function(objectId) { - structHelpers.Point.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_Point.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_Point"] = function() { - const value = structHelpers.Point.lift(); + const value = structHelpers.TestModule_Point.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -225,9 +561,9 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_translate"] = function bjs_translate(dx, dy) { try { - const structValue = structHelpers.Point.lift(); + const structValue = structHelpers.TestModule_Point.lift(); let ret = imports.translate(structValue, dx, dy); - structHelpers.Point.lower(ret); + structHelpers.TestModule_Point.lower(ret); } catch (error) { setException(error); } @@ -236,17 +572,13 @@ export async function createInstantiator(options, swift) { try { let optResult; if (point) { - const struct = structHelpers.Point.lift(); + const struct = structHelpers.TestModule_Point.lift(); optResult = struct; } else { optResult = null; } let ret = imports.roundTripOptional(optResult); - const isSome = ret != null; - if (isSome) { - structHelpers.Point.lower(ret); - } - i32Stack.push(isSome ? 1 : 0); + __bjs_codec_Optional_TestModule_Point.lower(ret); } catch (error) { setException(error); } @@ -265,8 +597,8 @@ export async function createInstantiator(options, swift) { /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { const js = swift.memory.heap; - const PointHelpers = __bjs_createPointHelpers(); - structHelpers.Point = PointHelpers; + const __bjs_helpers_TestModule_Point = __bjs_createStructHelpers_TestModule_Point(); + structHelpers.TestModule_Point = __bjs_helpers_TestModule_Point; const exports = { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.d.ts index 99adf95b6..606de53ad 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.d.ts @@ -29,5 +29,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.js index 66d6494fd..500a005a3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.js @@ -131,6 +131,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.d.ts index 9199ad1ae..13dccd568 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.d.ts @@ -14,5 +14,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.js index 6ff126525..58ff6a85a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.js @@ -106,6 +106,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.d.ts index 5a4ee78ce..b1ecc2000 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.d.ts @@ -34,5 +34,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.js index 457bfa973..b15310e76 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.js @@ -31,7 +31,7 @@ export async function createInstantiator(options, swift) { let _exports = null; let bjs = null; - const __bjs_createPointerFieldsHelpers = () => ({ + const __bjs_createStructHelpers_TestModule_PointerFields = () => ({ lower: (value) => { ptrStack.push((value.raw | 0)); ptrStack.push((value.mutRaw | 0)); @@ -124,12 +124,14 @@ export async function createInstantiator(options, swift) { taStack.push(Array.from(new Ctor(copy))); } bjs["swift_js_struct_lower_PointerFields"] = function(objectId) { - structHelpers.PointerFields.lower(swift.memory.getObject(objectId)); + structHelpers.TestModule_PointerFields.lower(swift.memory.getObject(objectId)); } bjs["swift_js_struct_lift_PointerFields"] = function() { - const value = structHelpers.PointerFields.lift(); + const value = structHelpers.TestModule_PointerFields.lift(); return swift.memory.retain(value); } + bjs["bjs_core_register_type_handles"] = function() {}; + bjs["bjs_TestModule_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; @@ -241,8 +243,8 @@ export async function createInstantiator(options, swift) { /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { const js = swift.memory.heap; - const PointerFieldsHelpers = __bjs_createPointerFieldsHelpers(); - structHelpers.PointerFields = PointerFieldsHelpers; + const __bjs_helpers_TestModule_PointerFields = __bjs_createStructHelpers_TestModule_PointerFields(); + structHelpers.TestModule_PointerFields = __bjs_helpers_TestModule_PointerFields; const exports = { takeUnsafeRawPointer: function bjs_takeUnsafeRawPointer(p) { @@ -281,15 +283,15 @@ export async function createInstantiator(options, swift) { return ret; }, roundTripPointerFields: function bjs_roundTripPointerFields(value) { - structHelpers.PointerFields.lower(value); + structHelpers.TestModule_PointerFields.lower(value); instance.exports.bjs_roundTripPointerFields(); - const structValue = structHelpers.PointerFields.lift(); + const structValue = structHelpers.TestModule_PointerFields.lift(); return structValue; }, PointerFields: { init: function(raw, mutRaw, opaque, ptr, mutPtr) { instance.exports.bjs_PointerFields_init(raw, mutRaw, opaque, ptr, mutPtr); - const structValue = structHelpers.PointerFields.lift(); + const structValue = structHelpers.TestModule_PointerFields.lift(); return structValue; }, }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.d.ts index 7acba67a0..d15ce0a8a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.d.ts @@ -15,5 +15,6 @@ export function createInstantiator(options: { }, swift: any): Promise<{ addImports: (importObject: WebAssembly.Imports) => void; setInstance: (instance: WebAssembly.Instance) => void; + afterInitialize?: () => void; createExports: (instance: WebAssembly.Instance) => Exports; }>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.js index 3c75771c5..81b09eaf4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.js @@ -107,6 +107,7 @@ export async function createInstantiator(options, swift) { const copy = memory.buffer.slice(ptr, ptr + byteLen); taStack.push(Array.from(new Ctor(copy))); } + bjs["bjs_core_register_type_handles"] = function() {}; const __bjs_promiseSettlers = Symbol("JavaScriptKit.promiseSettlers"); bjs["swift_js_make_promise"] = function() { let resolve, reject; diff --git a/Plugins/PackageToJS/Templates/instantiate.js b/Plugins/PackageToJS/Templates/instantiate.js index 36d840099..62aa724bf 100644 --- a/Plugins/PackageToJS/Templates/instantiate.js +++ b/Plugins/PackageToJS/Templates/instantiate.js @@ -70,10 +70,15 @@ async function createInstantiator(options, swift) { swift_js_closure_unregister: unexpectedBjsCall, swift_js_push_typed_array: unexpectedBjsCall, swift_js_make_promise: unexpectedBjsCall, + // Imported unconditionally by JavaScriptKit's core type-handle + // registration export, which is only invoked by BridgeJS glue. + bjs_core_register_type_handles: unexpectedBjsCall, }; }, /** @param {WebAssembly.Instance} instance */ setInstance: (instance) => {}, + /** Called once the instance is fully initialized and Swift code may run. */ + afterInitialize: () => {}, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { return {}; @@ -84,17 +89,18 @@ async function createInstantiator(options, swift) { /** @type {import('./instantiate.d').instantiate} */ export async function instantiate(options) { - const result = await _instantiate(options); + const { instantiator, ...result } = await _instantiate(options); /* #if IS_WASI */ options.wasi.initialize(result.instance); /* #endif */ + instantiator.afterInitialize?.(); result.swift.main(); return result; } /** @type {import('./instantiate.d').instantiateForThread} */ export async function instantiateForThread(tid, startArg, options) { - const result = await _instantiate(options); + const { instantiator, ...result } = await _instantiate(options); /* #if IS_WASI */ options.wasi.setInstance(result.instance); /* #endif */ @@ -102,7 +108,7 @@ export async function instantiateForThread(tid, startArg, options) { return result; } -/** @type {import('./instantiate.d').instantiate} */ +/** @param {import('./instantiate.d').InstantiateOptions} options */ async function _instantiate(options) { const _WebAssembly = options.WebAssembly || WebAssembly; const moduleSource = options.module; @@ -184,5 +190,6 @@ async function _instantiate(options) { instance, swift, exports, + instantiator, }; } diff --git a/Sources/JavaScriptKit/BridgeJSIntrinsics.swift b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift index 4eeae4dac..71f7fffce 100644 --- a/Sources/JavaScriptKit/BridgeJSIntrinsics.swift +++ b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift @@ -204,6 +204,58 @@ extension _BridgedSwiftStackType { } } +/// Types usable as the generic argument of a generic imported `@JSFunction`. +/// Each conforming type owns a ``BridgeJSTypeHandle`` whose pointer is the +/// runtime type ID that selects the matching JS codec. Do not conform types by +/// hand; marking them `@JS` emits the conformance together with the JS codec. +public protocol BridgedSwiftGenericBridgeable: _BridgedSwiftStackType +where StackLiftResult == Self { + @_spi(BridgeJS) static var bridgeJSTypeHandle: BridgeJSTypeHandle { get } +} + +extension BridgedSwiftGenericBridgeable { + /// The runtime type ID passed across the bridge for this type. + @_spi(BridgeJS) public static var bridgeJSTypeID: Int32 { bridgeJSTypeHandle.typeID } + + /// Creates the type's unique handle. A generic static function so + /// conformances compile under Embedded Swift. + @_spi(BridgeJS) public static func bridgeJSMakeTypeHandle() -> BridgeJSTypeHandle { + #if hasFeature(Embedded) + return BridgeJSTypeHandle() + #else + return BridgeJSTypeHandle(Self.self) + #endif + } +} + +/// A per-type identity token for generic bridging: each conforming type stores +/// exactly one handle in a `static let`, so the handle's pointer identifies the +/// type at runtime without relying on type names, which could collide across +/// modules. +public final class BridgeJSTypeHandle: Sendable { + #if hasFeature(Embedded) + public init() {} + #else + /// The conforming type, for exported generics (planned follow-up) to map a + /// type ID back to. `nonisolated(unsafe)`: an immutable metatype is safe to + /// share, but the compiler cannot infer that. + public nonisolated(unsafe) let type: any BridgedSwiftGenericBridgeable.Type + + public init(_ type: any BridgedSwiftGenericBridgeable.Type) { + self.type = type + } + #endif + + /// The handle object's own address; pointers are 32-bit on wasm32. + @_spi(BridgeJS) public var typeID: Int32 { + #if arch(wasm32) + return Int32(bitPattern: UInt32(UInt(bitPattern: Unmanaged.passUnretained(self).toOpaque()))) + #else + _onlyAvailableOnWasm() + #endif + } +} + /// Types that bridge with the same (isSome, value) ABI as Optional. /// Used by JSUndefinedOr so all bridge methods delegate to Optional. public protocol _BridgedAsOptional { @@ -808,6 +860,49 @@ extension String: _BridgedSwiftStackType { } } +extension Bool: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Bool.bridgeJSMakeTypeHandle() +} +extension Int: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Int.bridgeJSMakeTypeHandle() +} +extension Float: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Float.bridgeJSMakeTypeHandle() +} +extension Double: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Double.bridgeJSMakeTypeHandle() +} +extension String: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = String.bridgeJSMakeTypeHandle() +} +extension UInt: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = UInt.bridgeJSMakeTypeHandle() +} +extension Int8: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Int8.bridgeJSMakeTypeHandle() +} +extension UInt8: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = UInt8.bridgeJSMakeTypeHandle() +} +extension Int16: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Int16.bridgeJSMakeTypeHandle() +} +extension UInt16: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = UInt16.bridgeJSMakeTypeHandle() +} +extension Int32: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Int32.bridgeJSMakeTypeHandle() +} +extension UInt32: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = UInt32.bridgeJSMakeTypeHandle() +} +extension Int64: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Int64.bridgeJSMakeTypeHandle() +} +extension UInt64: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = UInt64.bridgeJSMakeTypeHandle() +} + extension JSObject: _BridgedSwiftStackType { // JSObject is a non-final class, so we must explicitly specify the associated type // rather than relying on the default `Self` (which Swift requires for covariant returns). @@ -914,6 +1009,60 @@ extension JSValue: _BridgedSwiftStackType { } } +extension JSValue: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = JSValue.bridgeJSMakeTypeHandle() +} + +// MARK: Core generic type-handle registration +// +// Every `BridgedSwiftGenericBridgeable` type publishes its runtime type ID to the +// JS glue, which pairs the IDs with the codec array it emitted in the same order. +// The core types below are owned by this library, so their registration lives +// here once for the whole binary instead of being copied into every module's +// generated registration function; generated per-module registration only carries +// that module's own `@JS` types. +// +// The order is the ABI contract with the JS side: it must match +// `BridgeType.genericBridgeablePrimitives` in +// `Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift`, from which +// the link step builds the core codec array. `CoreTypeRegistrationContractTests` +// checks the two lists stay in sync at build time, and the generated JS verifies +// the count at registration time. +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_core_register_type_handles") +private func _bjs_core_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +/// Publishes the core (primitive) BridgeJS type handles to the JS glue. +/// +/// Called by the generated glue once per instance, before any module's own +/// registration function. Not intended to be called from user code. +@_expose(wasm, "bjs_core_register_type_handles") +public func _bjs_core_register_type_handles() { + // BEGIN bjs_core_type_handles + let typeIds: [Int32] = [ + Bool.bridgeJSTypeID, + Int.bridgeJSTypeID, + Int8.bridgeJSTypeID, + UInt8.bridgeJSTypeID, + Int16.bridgeJSTypeID, + UInt16.bridgeJSTypeID, + Int32.bridgeJSTypeID, + UInt32.bridgeJSTypeID, + UInt.bridgeJSTypeID, + Int64.bridgeJSTypeID, + UInt64.bridgeJSTypeID, + Float.bridgeJSTypeID, + Double.bridgeJSTypeID, + String.bridgeJSTypeID, + JSValue.bridgeJSTypeID, + ] + // END bjs_core_type_handles + typeIds.withUnsafeBufferPointer { buffer in + _bjs_core_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif + /// A protocol that Swift heap objects exposed to JavaScript via `@JS class` must conform to. /// /// The conformance is automatically synthesized by the BridgeJS code generator. diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Generating-from-TypeScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Generating-from-TypeScript.md index 9c0a80dc1..5a47746d6 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Generating-from-TypeScript.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Generating-from-TypeScript.md @@ -416,4 +416,4 @@ When a TypeScript name is not a valid Swift identifier (e.g. contains dashes, sp ## Limitations - No first-class support for async/Promise-returning functions;. -- No generic type parameter can appear on a bridged function signature. \ No newline at end of file +- No generic type parameter can appear on a bridged function signature generated from TypeScript; a type parameter is lowered to `JSObject`. Generic imports are available only through `@JSFunction` declarations written in Swift — see . \ No newline at end of file diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-JavaScript/Importing-JS-Function.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-JavaScript/Importing-JS-Function.md index 1b2be6cb0..ebc9ac857 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-JavaScript/Importing-JS-Function.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-JavaScript/Importing-JS-Function.md @@ -77,11 +77,23 @@ If you used `from: .global` or `.module`, do not pass the function in `getImport Bound functions are `throws(JSException)`. Call them with `try` or `try?`; they throw when the JavaScript implementation throws. +## Generic functions + +A `@JSFunction` can be generic over a type parameter constrained to `BridgedSwiftGenericBridgeable`, so one declaration serves every bridged type: + +```swift +@JSFunction func parse(_ json: String) throws(JSException) -> T + +let user: User = try parse(jsonString) // T inferred from the call site +``` + +`T` can be any supported primitive, `String`, `JSValue`, or a `@JS` struct, `@JS` enum, or `final @JS class` (see ), used bare or wrapped as `[T]`, `T?`, or `[String: T]`. A function may declare multiple type parameters, and a return-only generic (`func make() -> T`) works too. Generic initializers, methods, and static methods on `@JSClass` types are supported the same way. `async` generic functions and `where` clauses are not supported. + ## Supported features | Feature | Status | |:--|:--| | Primitive parameter/result types (e.g. `Double`, `Bool`) | ✅ | | `String` parameter/result type | ✅ | +| Generic parameter/result types (constrained to `BridgedSwiftGenericBridgeable`) | ✅ | | Async function | ❌ | -| Generics | ❌ | diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md index 5c609ab72..539f7ce15 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md @@ -31,6 +31,10 @@ When using `JSTypedArray` (or convenience typealiases) in `@JS` signatures, t See for usage details. +## Generic type parameters + +An imported `@JSFunction` can be generic over a type parameter constrained to `BridgedSwiftGenericBridgeable` (see ); exported `@JS` functions cannot yet. The constraint is satisfied by all supported primitives, `String`, `JSValue`, and any `@JS` struct, `@JS` enum, or `final @JS class`, including ones from another linked module. Do not write the conformance by hand; marking the type `@JS` is what provides it, together with the JavaScript side of the bridge. + ## See Also - diff --git a/Tests/BridgeJSGlobalTests/Generated/BridgeJS.swift b/Tests/BridgeJSGlobalTests/Generated/BridgeJS.swift index 4e35a1c9f..91638a428 100644 --- a/Tests/BridgeJSGlobalTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSGlobalTests/Generated/BridgeJS.swift @@ -353,4 +353,38 @@ fileprivate func _bjs_GlobalUtils_PublicConverter_wrap_extern(_ pointer: UnsafeM #endif @inline(never) fileprivate func _bjs_GlobalUtils_PublicConverter_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { return _bjs_GlobalUtils_PublicConverter_wrap_extern(pointer) -} \ No newline at end of file +} + +extension GlobalNetworking.API.CallMethod: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GlobalNetworking.API.CallMethod.bridgeJSMakeTypeHandle() +} + +extension GlobalConfiguration.PublicLogLevel: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GlobalConfiguration.PublicLogLevel.bridgeJSMakeTypeHandle() +} + +extension GlobalConfiguration.AvailablePort: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GlobalConfiguration.AvailablePort.bridgeJSMakeTypeHandle() +} + +extension Internal.SupportedServerMethod: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Internal.SupportedServerMethod.bridgeJSMakeTypeHandle() +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "bjs_BridgeJSGlobalTests_register_type_handles") +fileprivate func _bjs_BridgeJSGlobalTests_register_type_handles_extern(_ base: UnsafePointer?, _ count: Int32) + +@_expose(wasm, "bjs_BridgeJSGlobalTests_register_type_handles") +public func _bjs_BridgeJSGlobalTests_register_type_handles() { + let typeIds: [Int32] = [ + GlobalNetworking.API.CallMethod.bridgeJSTypeID, + GlobalConfiguration.PublicLogLevel.bridgeJSTypeID, + GlobalConfiguration.AvailablePort.bridgeJSTypeID, + Internal.SupportedServerMethod.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_BridgeJSGlobalTests_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 6c5fe3b05..c70de88dc 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -5991,6 +5991,75 @@ extension ImportedPayloadSignal: _BridgedSwiftAssociatedValueEnum { } } +extension GenericRTColor: _BridgedSwiftCaseEnum { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> GenericRTColor { + return bridgeJSLiftParameter(value) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> GenericRTColor { + return GenericRTColor(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSLowerParameter() + } + + @_spi(BridgeJS) @usableFromInline init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .red + case 1: + self = .green + case 2: + self = .blue + default: + return nil + } + } + + @_spi(BridgeJS) @usableFromInline var bridgeJSRawValue: Int32 { + switch self { + case .red: + return 0 + case .green: + return 1 + case .blue: + return 2 + } + } +} + +extension GenericRTMode: _BridgedSwiftEnumNoPayload, _BridgedSwiftRawValueEnum { +} + +extension GenericRTLevel: _BridgedSwiftEnumNoPayload, _BridgedSwiftRawValueEnum { +} + +extension GenericRTOutcome: _BridgedSwiftAssociatedValueEnum { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPopPayload(_ caseId: Int32) -> GenericRTOutcome { + switch caseId { + case 0: + return .ok(code: Int.bridgeJSStackPop()) + case 1: + return .fail(message: String.bridgeJSStackPop()) + default: + fatalError("Unknown GenericRTOutcome case ID: \(caseId)") + } + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPushPayload() -> Int32 { + switch self { + case .ok(let code): + code.bridgeJSStackPush() + return Int32(0) + case .fail(let message): + message.bridgeJSStackPush() + return Int32(1) + } + } +} + @_expose(wasm, "bjs_IntegerTypesSupportExports_static_roundTripInt") @_cdecl("bjs_IntegerTypesSupportExports_static_roundTripInt") public func _bjs_IntegerTypesSupportExports_static_roundTripInt(_ v: Int32) -> Int32 { @@ -6779,6 +6848,102 @@ public func _bjs_NestedTypeHost_Label_static_untitled() -> Void { #endif } +extension GenericRTPoint: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> GenericRTPoint { + let y = Int.bridgeJSStackPop() + let x = Int.bridgeJSStackPop() + return GenericRTPoint(x: x, y: y) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.x.bridgeJSStackPush() + self.y.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_GenericRTPoint(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_GenericRTPoint())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_GenericRTPoint") +fileprivate func _bjs_struct_lower_GenericRTPoint_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_GenericRTPoint_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_GenericRTPoint(_ objectId: Int32) -> Void { + return _bjs_struct_lower_GenericRTPoint_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_GenericRTPoint") +fileprivate func _bjs_struct_lift_GenericRTPoint_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_GenericRTPoint_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_GenericRTPoint() -> Int32 { + return _bjs_struct_lift_GenericRTPoint_extern() +} + +extension GenericRTNamespace.Metadata: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> GenericRTNamespace.Metadata { + let count = Int.bridgeJSStackPop() + let label = String.bridgeJSStackPop() + return GenericRTNamespace.Metadata(label: label, count: count) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.label.bridgeJSStackPush() + self.count.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_GenericRTNamespace_Metadata(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_GenericRTNamespace_Metadata())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_GenericRTNamespace_Metadata") +fileprivate func _bjs_struct_lower_GenericRTNamespace_Metadata_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_GenericRTNamespace_Metadata_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_GenericRTNamespace_Metadata(_ objectId: Int32) -> Void { + return _bjs_struct_lower_GenericRTNamespace_Metadata_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_GenericRTNamespace_Metadata") +fileprivate func _bjs_struct_lift_GenericRTNamespace_Metadata_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_GenericRTNamespace_Metadata_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_GenericRTNamespace_Metadata() -> Int32 { + return _bjs_struct_lift_GenericRTNamespace_Metadata_extern() +} + extension Point: _BridgedSwiftStruct { @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> Point { let y = Int.bridgeJSStackPop() @@ -13206,6 +13371,80 @@ fileprivate func _bjs_NestedTypeHost_wrap_extern(_ pointer: UnsafeMutableRawPoin return _bjs_NestedTypeHost_wrap_extern(pointer) } +@_expose(wasm, "bjs_ImportGenericBox_init") +@_cdecl("bjs_ImportGenericBox_init") +public func _bjs_ImportGenericBox_init(_ value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ImportGenericBox(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ImportGenericBox_get") +@_cdecl("bjs_ImportGenericBox_get") +public func _bjs_ImportGenericBox_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = ImportGenericBox.bridgeJSLiftParameter(_self).get() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ImportGenericBox_value_get") +@_cdecl("bjs_ImportGenericBox_value_get") +public func _bjs_ImportGenericBox_value_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = ImportGenericBox.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ImportGenericBox_value_set") +@_cdecl("bjs_ImportGenericBox_value_set") +public func _bjs_ImportGenericBox_value_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + ImportGenericBox.bridgeJSLiftParameter(_self).value = Int.bridgeJSLiftParameter(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ImportGenericBox_deinit") +@_cdecl("bjs_ImportGenericBox_deinit") +public func _bjs_ImportGenericBox_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ImportGenericBox: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ImportGenericBox_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ImportGenericBox_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ImportGenericBox_wrap") +fileprivate func _bjs_ImportGenericBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ImportGenericBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ImportGenericBox_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ImportGenericBox_wrap_extern(pointer) +} + @_expose(wasm, "bjs_JSNameRenamedClass_init") @_cdecl("bjs_JSNameRenamedClass_init") public func _bjs_JSNameRenamedClass_init(_ value: Int32) -> UnsafeMutableRawPointer { @@ -13608,6 +13847,274 @@ fileprivate func _bjs_LeakCheck_wrap_extern(_ pointer: UnsafeMutableRawPointer) return _bjs_LeakCheck_wrap_extern(pointer) } +extension JSCoordinate: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = JSCoordinate.bridgeJSMakeTypeHandle() +} + +extension NestedStructGroupA.Metadata: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = NestedStructGroupA.Metadata.bridgeJSMakeTypeHandle() +} + +extension NestedStructGroupB.Metadata: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = NestedStructGroupB.Metadata.bridgeJSMakeTypeHandle() +} + +extension NestedTypeHost.Label: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = NestedTypeHost.Label.bridgeJSMakeTypeHandle() +} + +extension GenericRTPoint: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericRTPoint.bridgeJSMakeTypeHandle() +} + +extension GenericRTNamespace.Metadata: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericRTNamespace.Metadata.bridgeJSMakeTypeHandle() +} + +extension Point: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Point.bridgeJSMakeTypeHandle() +} + +extension PointerFields: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PointerFields.bridgeJSMakeTypeHandle() +} + +extension DataPoint: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = DataPoint.bridgeJSMakeTypeHandle() +} + +extension PublicPoint: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PublicPoint.bridgeJSMakeTypeHandle() +} + +extension Address: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Address.bridgeJSMakeTypeHandle() +} + +extension Contact: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Contact.bridgeJSMakeTypeHandle() +} + +extension Config: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Config.bridgeJSMakeTypeHandle() +} + +extension SessionData: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = SessionData.bridgeJSMakeTypeHandle() +} + +extension ValidationReport: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ValidationReport.bridgeJSMakeTypeHandle() +} + +extension AdvancedConfig: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AdvancedConfig.bridgeJSMakeTypeHandle() +} + +extension MeasurementConfig: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = MeasurementConfig.bridgeJSMakeTypeHandle() +} + +extension MathOperations: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = MathOperations.bridgeJSMakeTypeHandle() +} + +extension CopyableCart: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = CopyableCart.bridgeJSMakeTypeHandle() +} + +extension CopyableCartItem: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = CopyableCartItem.bridgeJSMakeTypeHandle() +} + +extension CopyableNestedCart: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = CopyableNestedCart.bridgeJSMakeTypeHandle() +} + +extension ConfigStruct: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ConfigStruct.bridgeJSMakeTypeHandle() +} + +extension Vector2D: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Vector2D.bridgeJSMakeTypeHandle() +} + +extension JSObjectContainer: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = JSObjectContainer.bridgeJSMakeTypeHandle() +} + +extension FooContainer: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = FooContainer.bridgeJSMakeTypeHandle() +} + +extension ArrayMembers: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ArrayMembers.bridgeJSMakeTypeHandle() +} + +extension PolygonReference: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PolygonReference.bridgeJSMakeTypeHandle() +} + +extension TagReference: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TagReference.bridgeJSMakeTypeHandle() +} + +extension TagHolderReference: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TagHolderReference.bridgeJSMakeTypeHandle() +} + +extension PriorityReference: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = PriorityReference.bridgeJSMakeTypeHandle() +} + +extension ImportGenericBox: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ImportGenericBox.bridgeJSMakeTypeHandle() +} + +extension Severity: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Severity.bridgeJSMakeTypeHandle() +} + +extension Shape: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Shape.bridgeJSMakeTypeHandle() +} + +extension InnerTag: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = InnerTag.bridgeJSMakeTypeHandle() +} + +extension AsyncImportedPayloadResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AsyncImportedPayloadResult.bridgeJSMakeTypeHandle() +} + +extension Direction: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Direction.bridgeJSMakeTypeHandle() +} + +extension Status: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Status.bridgeJSMakeTypeHandle() +} + +extension Theme: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Theme.bridgeJSMakeTypeHandle() +} + +extension HttpStatus: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = HttpStatus.bridgeJSMakeTypeHandle() +} + +extension FileSize: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = FileSize.bridgeJSMakeTypeHandle() +} + +extension SessionId: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = SessionId.bridgeJSMakeTypeHandle() +} + +extension Precision: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Precision.bridgeJSMakeTypeHandle() +} + +extension Ratio: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Ratio.bridgeJSMakeTypeHandle() +} + +extension TSDirection: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TSDirection.bridgeJSMakeTypeHandle() +} + +extension TSTheme: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TSTheme.bridgeJSMakeTypeHandle() +} + +extension AsyncPayloadResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AsyncPayloadResult.bridgeJSMakeTypeHandle() +} + +extension Networking.API.Method: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Networking.API.Method.bridgeJSMakeTypeHandle() +} + +extension Configuration.LogLevel: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Configuration.LogLevel.bridgeJSMakeTypeHandle() +} + +extension Configuration.Port: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Configuration.Port.bridgeJSMakeTypeHandle() +} + +extension Internal.SupportedMethod: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Internal.SupportedMethod.bridgeJSMakeTypeHandle() +} + +extension APIResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = APIResult.bridgeJSMakeTypeHandle() +} + +extension ComplexResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ComplexResult.bridgeJSMakeTypeHandle() +} + +extension Utilities.Result: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = Utilities.Result.bridgeJSMakeTypeHandle() +} + +extension API.NetworkingResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = API.NetworkingResult.bridgeJSMakeTypeHandle() +} + +extension AllTypesResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AllTypesResult.bridgeJSMakeTypeHandle() +} + +extension TypedPayloadResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = TypedPayloadResult.bridgeJSMakeTypeHandle() +} + +extension StaticCalculator: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = StaticCalculator.bridgeJSMakeTypeHandle() +} + +extension StaticPropertyEnum: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = StaticPropertyEnum.bridgeJSMakeTypeHandle() +} + +extension NestedTypeHost.Variant: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = NestedTypeHost.Variant.bridgeJSMakeTypeHandle() +} + +extension LightColor: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = LightColor.bridgeJSMakeTypeHandle() +} + +extension ImportedPayloadSignal: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ImportedPayloadSignal.bridgeJSMakeTypeHandle() +} + +extension GenericRTColor: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericRTColor.bridgeJSMakeTypeHandle() +} + +extension GenericRTMode: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericRTMode.bridgeJSMakeTypeHandle() +} + +extension GenericRTLevel: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericRTLevel.bridgeJSMakeTypeHandle() +} + +extension GenericRTOutcome: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericRTOutcome.bridgeJSMakeTypeHandle() +} + +extension OptionalAllTypesResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = OptionalAllTypesResult.bridgeJSMakeTypeHandle() +} + +extension APIOptionalResult: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = APIOptionalResult.bridgeJSMakeTypeHandle() +} + @JSFunction func Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) #if arch(wasm32) @@ -16817,6 +17324,346 @@ func _$jsJoinStringThenStackParams(_ s: String, _ a: Optional<[Int]>, _ b: [Int] return String.bridgeJSLiftReturn(ret) } +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsGenericRoundTrip") +fileprivate func bjs_jsGenericRoundTrip_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_jsGenericRoundTrip_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGenericRoundTrip(_ _generic0TypeId: Int32) -> Void { + return bjs_jsGenericRoundTrip_extern(_generic0TypeId) +} + +func _$jsGenericRoundTrip(_ value: T) throws(JSException) -> T { + value.bridgeJSStackPush() + bjs_jsGenericRoundTrip(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsGenericRoundTripClass") +fileprivate func bjs_jsGenericRoundTripClass_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_jsGenericRoundTripClass_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGenericRoundTripClass(_ _generic0TypeId: Int32) -> Void { + return bjs_jsGenericRoundTripClass_extern(_generic0TypeId) +} + +func _$jsGenericRoundTripClass(_ value: T) throws(JSException) -> T { + value.bridgeJSStackPush() + bjs_jsGenericRoundTripClass(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsGenericParsePoint") +fileprivate func bjs_jsGenericParsePoint_extern(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_jsGenericParsePoint_extern(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGenericParsePoint(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_jsGenericParsePoint_extern(jsonBytes, jsonLength, _generic0TypeId) +} + +func _$jsGenericParsePoint(_ json: String) throws(JSException) -> T { + json.bridgeJSWithLoweredParameter { (jsonBytes, jsonLength) in + bjs_jsGenericParsePoint(jsonBytes, jsonLength, T.bridgeJSTypeID) + } + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsImportPickFirst") +fileprivate func bjs_jsImportPickFirst_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_jsImportPickFirst_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsImportPickFirst(_ _generic0TypeId: Int32) -> Void { + return bjs_jsImportPickFirst_extern(_generic0TypeId) +} + +func _$jsImportPickFirst(_ a: T, _ b: T) throws(JSException) -> T { + b.bridgeJSStackPush() + a.bridgeJSStackPush() + bjs_jsImportPickFirst(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsImportMakeInt") +fileprivate func bjs_jsImportMakeInt_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_jsImportMakeInt_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsImportMakeInt(_ _generic0TypeId: Int32) -> Void { + return bjs_jsImportMakeInt_extern(_generic0TypeId) +} + +func _$jsImportMakeInt() throws(JSException) -> T { + bjs_jsImportMakeInt(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsImportCombineSecond") +fileprivate func bjs_jsImportCombineSecond_extern(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void +#else +fileprivate func bjs_jsImportCombineSecond_extern(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsImportCombineSecond(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + return bjs_jsImportCombineSecond_extern(_generic0TypeId, _generic1TypeId) +} + +func _$jsImportCombineSecond(_ a: T, _ b: U) throws(JSException) -> U { + b.bridgeJSStackPush() + a.bridgeJSStackPush() + bjs_jsImportCombineSecond(T.bridgeJSTypeID, U.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return U.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsGenericArrayRoundTrip") +fileprivate func bjs_jsGenericArrayRoundTrip_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_jsGenericArrayRoundTrip_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGenericArrayRoundTrip(_ _generic0TypeId: Int32) -> Void { + return bjs_jsGenericArrayRoundTrip_extern(_generic0TypeId) +} + +func _$jsGenericArrayRoundTrip(_ values: [T]) throws(JSException) -> [T] { + let _ = values.bridgeJSLowerParameter() + bjs_jsGenericArrayRoundTrip(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return [T].bridgeJSLiftReturn() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsGenericOptionalRoundTrip") +fileprivate func bjs_jsGenericOptionalRoundTrip_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_jsGenericOptionalRoundTrip_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGenericOptionalRoundTrip(_ _generic0TypeId: Int32) -> Void { + return bjs_jsGenericOptionalRoundTrip_extern(_generic0TypeId) +} + +func _$jsGenericOptionalRoundTrip(_ value: Optional) throws(JSException) -> Optional { + value.bridgeJSStackPush() + bjs_jsGenericOptionalRoundTrip(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return Optional.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsGenericDictRoundTrip") +fileprivate func bjs_jsGenericDictRoundTrip_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_jsGenericDictRoundTrip_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGenericDictRoundTrip(_ _generic0TypeId: Int32) -> Void { + return bjs_jsGenericDictRoundTrip_extern(_generic0TypeId) +} + +func _$jsGenericDictRoundTrip(_ values: [String: T]) throws(JSException) -> [String: T] { + let _ = values.bridgeJSLowerParameter() + bjs_jsGenericDictRoundTrip(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return [String: T].bridgeJSLiftReturn() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsGenericAfterOptionalArray") +fileprivate func bjs_jsGenericAfterOptionalArray_extern(_ values: Int32, _ _generic0TypeId: Int32) -> Int32 +#else +fileprivate func bjs_jsGenericAfterOptionalArray_extern(_ values: Int32, _ _generic0TypeId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGenericAfterOptionalArray(_ values: Int32, _ _generic0TypeId: Int32) -> Int32 { + return bjs_jsGenericAfterOptionalArray_extern(values, _generic0TypeId) +} + +func _$jsGenericAfterOptionalArray(_ values: Optional<[Int]>, _ value: T) throws(JSException) -> String { + value.bridgeJSStackPush() + let valuesIsSome = values.bridgeJSLowerParameter() + let ret = bjs_jsGenericAfterOptionalArray(valuesIsSome, T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsGenericThrowOrRoundTrip") +fileprivate func bjs_jsGenericThrowOrRoundTrip_extern(_ shouldThrow: Int32, _ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_jsGenericThrowOrRoundTrip_extern(_ shouldThrow: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_jsGenericThrowOrRoundTrip(_ shouldThrow: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_jsGenericThrowOrRoundTrip_extern(shouldThrow, _generic0TypeId) +} + +func _$jsGenericThrowOrRoundTrip(_ shouldThrow: Bool, _ value: T) throws(JSException) -> T { + value.bridgeJSStackPush() + let shouldThrowValue = shouldThrow.bridgeJSLowerParameter() + bjs_jsGenericThrowOrRoundTrip(shouldThrowValue, T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ImportGenericConsumer_init") +fileprivate func bjs_ImportGenericConsumer_init_extern() -> Int32 +#else +fileprivate func bjs_ImportGenericConsumer_init_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ImportGenericConsumer_init() -> Int32 { + return bjs_ImportGenericConsumer_init_extern() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ImportGenericConsumer_box_static") +fileprivate func bjs_ImportGenericConsumer_box_static_extern(_ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_ImportGenericConsumer_box_static_extern(_ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ImportGenericConsumer_box_static(_ _generic0TypeId: Int32) -> Void { + return bjs_ImportGenericConsumer_box_static_extern(_generic0TypeId) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ImportGenericConsumer_identity") +fileprivate func bjs_ImportGenericConsumer_identity_extern(_ self: Int32, _ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_ImportGenericConsumer_identity_extern(_ self: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ImportGenericConsumer_identity(_ self: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_ImportGenericConsumer_identity_extern(self, _generic0TypeId) +} + +func _$ImportGenericConsumer_init() throws(JSException) -> JSObject { + let ret = bjs_ImportGenericConsumer_init() + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) +} + +func _$ImportGenericConsumer_box(_ value: T) throws(JSException) -> T { + value.bridgeJSStackPush() + bjs_ImportGenericConsumer_box_static(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +func _$ImportGenericConsumer_identity(_ self: JSObject, _ value: T) throws(JSException) -> T { + value.bridgeJSStackPush() + let selfValue = self.bridgeJSLowerParameter() + bjs_ImportGenericConsumer_identity(selfValue, T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ImportGenericBoxed_init") +fileprivate func bjs_ImportGenericBoxed_init_extern(_ _generic0TypeId: Int32) -> Int32 +#else +fileprivate func bjs_ImportGenericBoxed_init_extern(_ _generic0TypeId: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ImportGenericBoxed_init(_ _generic0TypeId: Int32) -> Int32 { + return bjs_ImportGenericBoxed_init_extern(_generic0TypeId) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ImportGenericBoxed_unwrap") +fileprivate func bjs_ImportGenericBoxed_unwrap_extern(_ self: Int32, _ _generic0TypeId: Int32) -> Void +#else +fileprivate func bjs_ImportGenericBoxed_unwrap_extern(_ self: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ImportGenericBoxed_unwrap(_ self: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_ImportGenericBoxed_unwrap_extern(self, _generic0TypeId) +} + +func _$ImportGenericBoxed_init(_ value: T) throws(JSException) -> JSObject { + value.bridgeJSStackPush() + let ret = bjs_ImportGenericBoxed_init(T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return JSObject.bridgeJSLiftReturn(ret) +} + +func _$ImportGenericBoxed_unwrap(_ self: JSObject) throws(JSException) -> T { + let selfValue = self.bridgeJSLowerParameter() + bjs_ImportGenericBoxed_unwrap(selfValue, T.bridgeJSTypeID) + if let error = _swift_js_take_exception() { + throw error + } + return T.bridgeJSStackPop() +} + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsTranslatePoint") fileprivate func bjs_jsTranslatePoint_extern(_ dx: Int32, _ dy: Int32) -> Void @@ -18191,4 +19038,85 @@ func _$SwiftClassSupportImports_jsConsumeOptionalLeakCheck(_ value: Optional?, _ count: Int32) + +@_expose(wasm, "bjs_BridgeJSRuntimeTests_register_type_handles") +public func _bjs_BridgeJSRuntimeTests_register_type_handles() { + let typeIds: [Int32] = [ + JSCoordinate.bridgeJSTypeID, + NestedStructGroupA.Metadata.bridgeJSTypeID, + NestedStructGroupB.Metadata.bridgeJSTypeID, + NestedTypeHost.Label.bridgeJSTypeID, + GenericRTPoint.bridgeJSTypeID, + GenericRTNamespace.Metadata.bridgeJSTypeID, + Point.bridgeJSTypeID, + PointerFields.bridgeJSTypeID, + DataPoint.bridgeJSTypeID, + PublicPoint.bridgeJSTypeID, + Address.bridgeJSTypeID, + Contact.bridgeJSTypeID, + Config.bridgeJSTypeID, + SessionData.bridgeJSTypeID, + ValidationReport.bridgeJSTypeID, + AdvancedConfig.bridgeJSTypeID, + MeasurementConfig.bridgeJSTypeID, + MathOperations.bridgeJSTypeID, + CopyableCart.bridgeJSTypeID, + CopyableCartItem.bridgeJSTypeID, + CopyableNestedCart.bridgeJSTypeID, + ConfigStruct.bridgeJSTypeID, + Vector2D.bridgeJSTypeID, + JSObjectContainer.bridgeJSTypeID, + FooContainer.bridgeJSTypeID, + ArrayMembers.bridgeJSTypeID, + PolygonReference.bridgeJSTypeID, + TagReference.bridgeJSTypeID, + TagHolderReference.bridgeJSTypeID, + PriorityReference.bridgeJSTypeID, + ImportGenericBox.bridgeJSTypeID, + Severity.bridgeJSTypeID, + Shape.bridgeJSTypeID, + InnerTag.bridgeJSTypeID, + AsyncImportedPayloadResult.bridgeJSTypeID, + Direction.bridgeJSTypeID, + Status.bridgeJSTypeID, + Theme.bridgeJSTypeID, + HttpStatus.bridgeJSTypeID, + FileSize.bridgeJSTypeID, + SessionId.bridgeJSTypeID, + Precision.bridgeJSTypeID, + Ratio.bridgeJSTypeID, + TSDirection.bridgeJSTypeID, + TSTheme.bridgeJSTypeID, + AsyncPayloadResult.bridgeJSTypeID, + Networking.API.Method.bridgeJSTypeID, + Configuration.LogLevel.bridgeJSTypeID, + Configuration.Port.bridgeJSTypeID, + Internal.SupportedMethod.bridgeJSTypeID, + APIResult.bridgeJSTypeID, + ComplexResult.bridgeJSTypeID, + Utilities.Result.bridgeJSTypeID, + API.NetworkingResult.bridgeJSTypeID, + AllTypesResult.bridgeJSTypeID, + TypedPayloadResult.bridgeJSTypeID, + StaticCalculator.bridgeJSTypeID, + StaticPropertyEnum.bridgeJSTypeID, + NestedTypeHost.Variant.bridgeJSTypeID, + LightColor.bridgeJSTypeID, + ImportedPayloadSignal.bridgeJSTypeID, + GenericRTColor.bridgeJSTypeID, + GenericRTMode.bridgeJSTypeID, + GenericRTLevel.bridgeJSTypeID, + GenericRTOutcome.bridgeJSTypeID, + OptionalAllTypesResult.bridgeJSTypeID, + APIOptionalResult.bridgeJSTypeID, + ] + typeIds.withUnsafeBufferPointer { buffer in + _bjs_BridgeJSRuntimeTests_register_type_handles_extern(buffer.baseAddress, Int32(buffer.count)) + } +} +#endif \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index d4e1878e1..8622b1cc9 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -127,6 +127,7 @@ } ] }, + "isFinal" : true, "methods" : [ { "abiName" : "bjs_PolygonReference_vertexCount", @@ -265,6 +266,7 @@ "swiftCallName" : "PolygonReference" }, { + "isFinal" : true, "methods" : [ { "abiName" : "bjs_TagReference_describe", @@ -327,6 +329,7 @@ } ] }, + "isFinal" : true, "methods" : [ { "abiName" : "bjs_TagHolderReference_describe", @@ -380,6 +383,7 @@ "swiftCallName" : "TagHolderReference" }, { + "isFinal" : true, "methods" : [ { "abiName" : "bjs_PriorityReference_describe", @@ -4840,6 +4844,70 @@ ], "swiftCallName" : "NestedTypeHost" }, + { + "constructor" : { + "abiName" : "bjs_ImportGenericBox_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "isFinal" : true, + "methods" : [ + { + "abiName" : "bjs_ImportGenericBox_get", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "get", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "name" : "ImportGenericBox", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ImportGenericBox" + }, { "constructor" : { "abiName" : "bjs_JSNameRenamedClass_init", @@ -10343,6 +10411,152 @@ { "cases" : [ + ], + "emitStyle" : "const", + "name" : "GenericRTNamespace", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericRTNamespace", + "tsFullPath" : "GenericRTNamespace" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "red" + }, + { + "associatedValues" : [ + + ], + "name" : "green" + }, + { + "associatedValues" : [ + + ], + "name" : "blue" + } + ], + "emitStyle" : "const", + "name" : "GenericRTColor", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericRTColor", + "tsFullPath" : "GenericRTColor" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark" + } + ], + "emitStyle" : "const", + "name" : "GenericRTMode", + "rawType" : "String", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericRTMode", + "tsFullPath" : "GenericRTMode" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "low", + "rawValue" : "1" + }, + { + "associatedValues" : [ + + ], + "name" : "high", + "rawValue" : "9" + } + ], + "emitStyle" : "const", + "name" : "GenericRTLevel", + "rawType" : "Int", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericRTLevel", + "tsFullPath" : "GenericRTLevel" + }, + { + "cases" : [ + { + "associatedValues" : [ + { + "label" : "code", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "name" : "ok" + }, + { + "associatedValues" : [ + { + "label" : "message", + "type" : { + "string" : { + + } + } + } + ], + "name" : "fail" + } + ], + "emitStyle" : "const", + "name" : "GenericRTOutcome", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericRTOutcome", + "tsFullPath" : "GenericRTOutcome" + }, + { + "cases" : [ + ], "emitStyle" : "const", "name" : "IntegerTypesSupportExports", @@ -18468,6 +18682,82 @@ { "methods" : [ + ], + "name" : "GenericRTPoint", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "x", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "y", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "GenericRTPoint" + }, + { + "methods" : [ + + ], + "name" : "Metadata", + "namespace" : [ + "GenericRTNamespace" + ], + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "label", + "namespace" : [ + "GenericRTNamespace" + ], + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "count", + "namespace" : [ + "GenericRTNamespace" + ], + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "GenericRTNamespace.Metadata" + }, + { + "methods" : [ + ], "name" : "Point", "properties" : [ @@ -23775,6 +24065,498 @@ ] }, + { + "functions" : [ + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsGenericRoundTrip", + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsGenericRoundTripClass", + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsGenericParsePoint", + "parameters" : [ + { + "name" : "json", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsImportPickFirst", + "parameters" : [ + { + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "name" : "b", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsImportMakeInt", + "parameters" : [ + + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T", + "U" + ], + "name" : "jsImportCombineSecond", + "parameters" : [ + { + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "name" : "b", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "U" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsGenericArrayRoundTrip", + "parameters" : [ + { + "name" : "values", + "type" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsGenericOptionalRoundTrip", + "parameters" : [ + { + "name" : "value", + "type" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsGenericDictRoundTrip", + "parameters" : [ + { + "name" : "values", + "type" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + } + ], + "returnType" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsGenericAfterOptionalArray", + "parameters" : [ + { + "name" : "values", + "type" : { + "nullable" : { + "_0" : { + "array" : { + "_0" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + }, + "_1" : "null" + } + } + }, + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "jsGenericThrowOrRoundTrip", + "parameters" : [ + { + "name" : "shouldThrow", + "type" : { + "bool" : { + + } + } + }, + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "types" : [ + { + "accessLevel" : "internal", + "constructor" : { + "accessLevel" : "internal", + "parameters" : [ + + ] + }, + "getters" : [ + + ], + "methods" : [ + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "identity", + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "name" : "ImportGenericConsumer", + "setters" : [ + + ], + "staticMethods" : [ + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "box", + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ] + }, + { + "accessLevel" : "internal", + "constructor" : { + "accessLevel" : "internal", + "genericParameters" : [ + "T" + ], + "parameters" : [ + { + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ] + }, + "getters" : [ + + ], + "methods" : [ + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + "T" + ], + "name" : "unwrap", + "parameters" : [ + + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "name" : "ImportGenericBoxed", + "setters" : [ + + ], + "staticMethods" : [ + + ] + } + ] + }, { "functions" : [ { diff --git a/Tests/BridgeJSRuntimeTests/ImportGenericAPIs.swift b/Tests/BridgeJSRuntimeTests/ImportGenericAPIs.swift new file mode 100644 index 000000000..bfd63839f --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/ImportGenericAPIs.swift @@ -0,0 +1,280 @@ +import Testing +import JavaScriptKit + +@JS struct GenericRTPoint { + var x: Int + var y: Int +} + +@JS enum GenericRTNamespace { + @JS struct Metadata { + var label: String + var count: Int + } +} + +@JS enum GenericRTColor { + case red + case green + case blue +} + +@JS enum GenericRTMode: String { + case light + case dark +} + +@JS enum GenericRTLevel: Int { + case low = 1 + case high = 9 +} + +@JS enum GenericRTOutcome { + case ok(code: Int) + case fail(message: String) +} + +@JS final class ImportGenericBox { + @JS var value: Int + @JS init(value: Int) { + self.value = value + } + @JS func get() -> Int { + value + } +} + +@JSFunction func jsGenericRoundTrip(_ value: T) throws(JSException) -> T +@JSFunction func jsGenericRoundTripClass(_ value: T) throws(JSException) -> T +@JSFunction func jsGenericParsePoint(_ json: String) throws(JSException) -> T +@JSFunction func jsImportPickFirst(_ a: T, _ b: T) throws(JSException) -> T +@JSFunction func jsImportMakeInt() throws(JSException) -> T +@JSFunction func jsImportCombineSecond( + _ a: T, + _ b: U +) throws(JSException) -> U +@JSFunction func jsGenericArrayRoundTrip(_ values: [T]) throws(JSException) -> [T] +@JSFunction func jsGenericOptionalRoundTrip(_ value: T?) throws(JSException) -> T? +@JSFunction func jsGenericDictRoundTrip( + _ values: [String: T] +) throws(JSException) -> [String: T] +@JSFunction func jsGenericAfterOptionalArray( + _ values: [Int]?, + _ value: T +) throws(JSException) -> String + +@JSClass struct ImportGenericConsumer { + @JSFunction init() throws(JSException) + @JSFunction func identity(_ value: T) throws(JSException) -> T + @JSFunction static func box(_ value: T) throws(JSException) -> T +} + +@JSFunction func jsGenericThrowOrRoundTrip( + _ shouldThrow: Bool, + _ value: T +) throws(JSException) -> T + +@JSClass struct ImportGenericBoxed { + @JSFunction init(_ value: T) throws(JSException) + @JSFunction func unwrap() throws(JSException) -> T +} + +@Suite struct ImportGenericAPITests { + @Test func genericRoundTripScalars() throws { + #expect(try jsGenericRoundTrip(42) == 42) + #expect(try jsGenericRoundTrip(-7) == -7) + #expect(try jsGenericRoundTrip(3.5) == 3.5) + #expect(try jsGenericRoundTrip(Float(1.25)) == Float(1.25)) + #expect(try jsGenericRoundTrip(true) == true) + #expect(try jsGenericRoundTrip(false) == false) + #expect(try jsGenericRoundTrip("hello") == "hello") + #expect(try jsGenericRoundTrip("") == "") + } + + @Test func genericRoundTripNumerics() throws { + #expect(try jsGenericRoundTrip(Int8(-5)) == Int8(-5)) + #expect(try jsGenericRoundTrip(Int8.min) == Int8.min) + #expect(try jsGenericRoundTrip(Int8.max) == Int8.max) + #expect(try jsGenericRoundTrip(UInt8(200)) == UInt8(200)) + #expect(try jsGenericRoundTrip(UInt8.max) == UInt8.max) + #expect(try jsGenericRoundTrip(Int16(-1000)) == Int16(-1000)) + #expect(try jsGenericRoundTrip(UInt16(60000)) == UInt16(60000)) + #expect(try jsGenericRoundTrip(Int32(-123456)) == Int32(-123456)) + #expect(try jsGenericRoundTrip(UInt32(3_000_000_000)) == UInt32(3_000_000_000)) + #expect(try jsGenericRoundTrip(UInt32.max) == UInt32.max) + #expect(try jsGenericRoundTrip(UInt(42)) == UInt(42)) + #expect(try jsGenericRoundTrip(UInt(4_000_000_000)) == UInt(4_000_000_000)) + #expect(try jsGenericRoundTrip(Int64(-9_000_000_000)) == Int64(-9_000_000_000)) + #expect(try jsGenericRoundTrip(Int64.min) == Int64.min) + #expect(try jsGenericRoundTrip(Int64.max) == Int64.max) + #expect(try jsGenericRoundTrip(UInt64(18_000_000_000_000_000_000)) == UInt64(18_000_000_000_000_000_000)) + #expect(try jsGenericRoundTrip(UInt64.max) == UInt64.max) + } + + @Test func genericRoundTripJSValue() throws { + let number = try jsGenericRoundTrip(JSValue.number(3.5)) + #expect(number.number == 3.5) + let string = try jsGenericRoundTrip(JSValue.string("hi")) + #expect(string.string == "hi") + let boolean = try jsGenericRoundTrip(JSValue.boolean(true)) + #expect(boolean.boolean == true) + #expect(try jsGenericRoundTrip(JSValue.null).isNull) + #expect(try jsGenericRoundTrip(JSValue.undefined).isUndefined) + let object = JSObject.global.Object.function!.new() + object.tag = 7 + let roundTripped = try jsGenericRoundTrip(JSValue.object(object)) + #expect(roundTripped.object?.tag.number == 7) + } + + @Test func genericWrappedRoundTrip() throws { + #expect(try jsGenericArrayRoundTrip([1, 2, 3]) == [1, 2, 3]) + #expect(try jsGenericArrayRoundTrip(["a", "b"]) == ["a", "b"]) + #expect(try jsGenericArrayRoundTrip([Int]()) == []) + #expect(try jsGenericOptionalRoundTrip(Optional.some(7)) == 7) + #expect(try jsGenericOptionalRoundTrip(Optional.none) == nil) + #expect(try jsGenericOptionalRoundTrip(Optional.some("hi")) == "hi") + let outcome = try jsGenericOptionalRoundTrip(Optional.some(.ok(code: 5))) + guard case .some(.ok(let code)) = outcome else { + Issue.record("expected .ok") + return + } + #expect(code == 5) + #expect(try jsGenericOptionalRoundTrip(Optional.none) == nil) + #expect(try jsGenericDictRoundTrip(["x": 1, "y": 2]) == ["x": 1, "y": 2]) + #expect(try jsGenericDictRoundTrip([String: String]()) == [:]) + } + + @Test func genericRoundTripEnums() throws { + #expect(try jsGenericRoundTrip(GenericRTColor.red) == .red) + #expect(try jsGenericRoundTrip(GenericRTColor.blue) == .blue) + #expect(try jsGenericRoundTrip(GenericRTMode.dark) == .dark) + #expect(try jsGenericRoundTrip(GenericRTMode.light).rawValue == "light") + #expect(try jsGenericRoundTrip(GenericRTLevel.high) == .high) + let outcome = try jsGenericRoundTrip(GenericRTOutcome.ok(code: 42)) + guard case .ok(let code) = outcome else { + Issue.record("expected .ok") + return + } + #expect(code == 42) + let failure = try jsGenericRoundTrip(GenericRTOutcome.fail(message: "boom")) + guard case .fail(let message) = failure else { + Issue.record("expected .fail") + return + } + #expect(message == "boom") + } + + @Test func genericRoundTripStruct() throws { + let point = try jsGenericRoundTrip(GenericRTPoint(x: 1, y: 2)) + #expect(point.x == 1) + #expect(point.y == 2) + } + + @Test func genericRoundTripNestedStruct() throws { + let metadata = try jsGenericRoundTrip(GenericRTNamespace.Metadata(label: "alpha", count: 7)) + #expect(metadata.label == "alpha") + #expect(metadata.count == 7) + } + + @Test func genericParse() throws { + let point: GenericRTPoint = try jsGenericParsePoint("{\"x\": 10, \"y\": 20}") + #expect(point.x == 10) + #expect(point.y == 20) + let n: Int = try jsGenericParsePoint("42") + #expect(n == 42) + let string: String = try jsGenericParsePoint("\"hi\"") + #expect(string == "hi") + } + + @Test func genericPickFirstMultiUse() throws { + #expect(try jsImportPickFirst(10, 20) as Int == 10) + #expect(try jsImportPickFirst("a", "b") as String == "a") + let firstPoint = try jsImportPickFirst(GenericRTPoint(x: 1, y: 2), GenericRTPoint(x: 3, y: 4)) + #expect(firstPoint.x == 1) + #expect(firstPoint.y == 2) + } + + @Test func genericMakeReturnOnly() throws { + let made: Int = try jsImportMakeInt() + #expect(made == 123) + } + + @Test func genericCombineSecondMultiParameter() throws { + #expect(try jsImportCombineSecond(7, "hello") as String == "hello") + #expect(try jsImportCombineSecond("x", 9) as Int == 9) + let point = try jsImportCombineSecond(42, GenericRTPoint(x: 5, y: 6)) + #expect(point.x == 5) + #expect(point.y == 6) + } + + @Test func genericRoundTripHeapObjectClass() throws { + let box = ImportGenericBox(value: 314) + #expect(box.get() == 314) + let sameBox = try jsGenericRoundTripClass(box) + #expect(sameBox.get() == 314) + sameBox.value = 271 + #expect(box.get() == 271) + } + + @Test func genericMixedConsecutiveCalls() throws { + #expect(try jsGenericRoundTrip(1) == 1) + #expect(try jsGenericRoundTrip("two") == "two") + #expect(try jsGenericRoundTrip(3.0) == 3.0) + let p = try jsGenericRoundTrip(GenericRTPoint(x: 4, y: 5)) + #expect(p.x == 4) + #expect(p.y == 5) + } + + @Test func importGenericInstanceMethod() throws { + let consumer = try ImportGenericConsumer() + #expect(try consumer.identity(42) == 42) + #expect(try consumer.identity(-7) == -7) + #expect(try consumer.identity("hi") == "hi") + #expect(try consumer.identity(true) == true) + #expect(try consumer.identity(false) == false) + let point = try consumer.identity(GenericRTPoint(x: 3, y: 4)) + #expect(point.x == 3) + #expect(point.y == 4) + } + + /// A generic parameter shares the stacks with any other stack-lowered + /// parameter, so both have to arrive in declaration order. + @Test func genericAlongsideOptionalArray() throws { + #expect(try jsGenericAfterOptionalArray([1, 2], 42) == "[1,2]|42") + #expect(try jsGenericAfterOptionalArray([1, 2], GenericRTPoint(x: 3, y: 4)) == #"[1,2]|{"x":3,"y":4}"#) + #expect(try jsGenericAfterOptionalArray(nil, "x") == #"null|"x""#) + } + + @Test func genericImportPropagatesJSException() throws { + #expect(try jsGenericThrowOrRoundTrip(false, 42) == 42) + #expect(try jsGenericThrowOrRoundTrip(false, GenericRTPoint(x: 1, y: 2)).x == 1) + do { + let _: Int = try jsGenericThrowOrRoundTrip(true, 0) + Issue.record("Expected exception") + } catch { + #expect(error.description.contains("TestError")) + } + // A throwing generic call must not strand its argument on the shared + // stack: the next call has to read its own value back. + #expect(try jsGenericThrowOrRoundTrip(false, GenericRTPoint(x: 7, y: 8)).y == 8) + } + + @Test func importGenericConstructor() throws { + let boxedInt = try ImportGenericBoxed(42) + #expect(try boxedInt.unwrap() == 42) + let boxedText = try ImportGenericBoxed("boxed") + #expect(try boxedText.unwrap() == "boxed") + let boxedPoint = try ImportGenericBoxed(GenericRTPoint(x: 1, y: 2)) + let point: GenericRTPoint = try boxedPoint.unwrap() + #expect(point.x == 1) + #expect(point.y == 2) + } + + @Test func importGenericStaticMethod() throws { + #expect(try ImportGenericConsumer.box(7) == 7) + #expect(try ImportGenericConsumer.box("s") == "s") + #expect(try ImportGenericConsumer.box(true) == true) + let color = try ImportGenericConsumer.box(GenericRTColor.green) + #expect(color == .green) + } +} diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 9ca873301..b7e21e821 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -161,6 +161,38 @@ export async function setupOptions(options, context) { jsJoinOptionalStructThenArray: joinStackParams, jsJoinEnumThenArray: joinStackParams, jsJoinStringThenStackParams: joinStackParams, + jsGenericRoundTrip: (v) => v, + jsGenericRoundTripClass: (v) => v, + jsGenericParsePoint: (json) => JSON.parse(json), + jsImportPickFirst: (a, b) => a, + jsImportMakeInt: () => 123, + jsImportCombineSecond: (a, b) => b, + jsGenericThrowOrRoundTrip: (shouldThrow, v) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return v; + }, + jsGenericArrayRoundTrip: (v) => v, + jsGenericOptionalRoundTrip: (v) => v, + jsGenericDictRoundTrip: (v) => v, + jsGenericAfterOptionalArray: (a, b) => `${JSON.stringify(a)}|${JSON.stringify(b)}`, + ImportGenericConsumer: class { + identity(value) { + return value; + } + static box(value) { + return value; + } + }, + ImportGenericBoxed: class { + constructor(value) { + this.value = value; + } + unwrap() { + return this.value; + } + }, roundTripArrayMembers: (value) => { return value; }, diff --git a/docs/superpowers/javascriptkit-508-counterproposal.html b/docs/superpowers/javascriptkit-508-counterproposal.html new file mode 100644 index 000000000..de70990cd --- /dev/null +++ b/docs/superpowers/javascriptkit-508-counterproposal.html @@ -0,0 +1,413 @@ + + + + + + JavaScriptKit #508 — Design Review & Alternative Approach + + + +
+
+

JavaScriptKit #508 — design review & alternative approach

+

+ A peer review of the AoT-composition design, exploring whether there's a better-fitting approach. The conclusion + agrees with ~90% of it; the one alternative worth weighing is keeping the durable contract at the + skeleton-IR / JSON schema layer rather than freezing a public inter-module runtime ABI + — with evidence that the three issues can ship with no developer workflow change. +

+
+ Direction: skeleton-IR is the contract + Divergence: don't freeze the runtime ABI + Date: 2026-06-17 +
+
+ Short version. Yuta's per-target direction is right. Our one change: treat the inter-module boundary + as an internal, codegen-version-stamped detail (regenerate on mismatch) instead of a semver'd public ABI. The three + original problems (snippets, multi-package composition, fast TS feedback) each ship on the existing single-link + architecture with no new commands; the per-target restructure becomes an optional, separable phase. +
+
+ + + + + + + +
+
+ + +
+

Summary & relationship to Yuta's design

+

+ Your design and ours share almost everything: per-target bridge-js.js / bridge-js.d.ts / + BridgeJS.json, a shared runtime helper with private bjs state, primary-walks-emission with + dependencies as lookup-only, early .d.ts, and bundler-friendly ESM imports. The designs differ on + exactly one axis. +

+ + + + + + + + + + + + + + + +
DecisionYuta's designThis proposalSame?
Per-target artifacts (.js/.d.ts/.json)YesYes (regenerable)
Shared runtime helper, private bjs stateYesYes
Primary emission, dependencies lookup-onlyYesYes
Early bridge-js.d.ts (fast TS feedback)YesYes
Bundler/runtime-friendly ESM importsYesYes
The stable cross-version contractcreateBridgeModule() + bridgeJSRuntimeRange as a frozen public runtime ABIThe skeleton JSON schema; per-target modules are internal, codegen-version-stamped, regenerated on mismatch❌ the one difference
+
+ The nesting. + Architecture 2 ⊂ Architecture 3 ⊂ Yuta. + Arch 2 = skeleton contract + single merged glue. Arch 3 = Arch 2 + per-target regenerable modules (internal boundary). + Yuta = Arch 3 + freezing that boundary as a public ABI. Recommendation: Architecture 3 — Yuta's design minus the + one piece the demand analysis shows isn't needed. +
+

+ Notably, Yuta's own Decision 3 (“keep the compatibility surface minimal; generated modules must not read raw runtime storage”) + already pushes toward this. We take that instinct to its conclusion: shrink the public surface to zero by making the + boundary regenerable, so there's no runtime×module version matrix to maintain. +

+
+ + +
+

Demand analysis: is a frozen runtime ABI actually needed?

+

+ A frozen inter-module ABI is only required to compose a package's prebuilt bridge JS without running BridgeJS codegen. + One structural fact removes that need: +

+
+ All Swift links into ONE wasm module/instance. Every package's Swift — ElementaryUI, an OpenAPI client, the app — + is a SwiftPM dependency statically compiled into the single app .wasm, with one memory and one shared bjs ABI. + A package cannot ship a standalone runnable wasm that composes at runtime; they must be co-compiled. So the bridge glue is + intrinsically an app-build artifact, produced where the Swift toolchain is present by necessity. +
+ + + + + + + + + +
ScenarioNeeds prebuilt-compose w/o tooling?Why
Typical Swift-wasm app (SwiftPM deps)NoToolchain + codegen already run; all Swift co-compiles.
Vite / manual-wiring dev loopNoThe .wasm is rebuilt by Swift on any Swift change; bridge regen rides along. JS-only changes reuse committed AoT outputs as plain files.
Pure-JS npm consumer of a Swift libNo (structurally)The lib's Swift must compile into the consumer's wasm; you can't skip co-compiling.
Binary / closed-source wasm packageMarginalNot an established SwiftWasm pattern; would ship its skeleton JSON anyway.
Future: WASM Component ModelMaybeSpeculative; it brings its own canonical ABI that would supersede a hand-rolled one.
+

+ Verdict: the capability the frozen ABI buys isn't reachable today. Its real wins — per-target files, incremental builds, + early .d.ts, Vite-friendly imports — are all captured by Architecture 3 without the ABI. +

+
+ + +
+

Architecture decision: what do we freeze?

+

+ All three problems reduce to one question: when packages built at different times compose, what is the stable thing they agree on? + The codebase already answers this — the skeleton IR. +

+
+

Today = skeleton-IR contract

Each target emits BridgeJS.json; ExternalModuleIndex composes skeletons for cross-module type refs; PackageToJS runs one BridgeJSLink pass over all skeletons → one merged bridge-js.js.

+

Arch 1 — Yuta

Compiled module is the contract. Per-target runtime modules compose via a frozen, semver'd ABI over shared bjs state. Most expensive layer to keep stable.

+

Arch 2 — minimal

Keep skeleton contract + single merged glue. Add snippets, cross-package plumbing, early .d.ts. Fewest moving parts.

+

Arch 3 — recommended

Skeleton contract plus per-target regenerable modules stamped to one codegen version. Yuta's ergonomics; no frozen ABI.

+
+
+ The volatile, performance-critical layer (the inter-module handshake over the bjs stack/scratch state) is the thing you + least want to freeze. A JSON schema is far cheaper to version and migrate than a live runtime ABI. +
+
+ + +
+

Technical approach — concrete change points

+

+ How each problem is solved on the existing single-link architecture. All anchors verified against the current code. + Full task-by-task detail is in the linked plans; this is the mechanism. +

+ +

Problem 1 — package-owned JS snippets (#508)

+

+ Add a third value to the existing import-source axis. Today routing is binary at four sites in BridgeJSLink.swift + (:3335/3359/3458/3513): +

+
// today
+let importRootExpr = X.from == .global ? "globalThis" : "imports"
+
+// proposed: reuse the existing `from:` parameter, add a .module case
+switch X.from {
+case .global:        importRootExpr = "globalThis"
+case .module(name):  importRootExpr = snippetNamespace(swiftModule, name) // package-scoped
+case nil:            importRootExpr = "imports"
+}
+

Authoring stays on the existing macros — no new macro argument:

+
// Swift: bind to package-owned JS
+@JSFunction(from: .module("components"))
+func defineComponent(name: String, render: (JSObject) -> Void)
+
+// bridge-js.config.json
+{ "jsModules": { "components": "JavaScript/components.js" } }
+

+ The generated bridge-js.js emits a real ESM import of the bundled snippet — which also runs its top-level + side effects — and PackageToJS copies the snippet's whole directory (siblings preserved), keyed by the owning + Swift module so two packages can both call a module "components" without collision: +

+
import * as __bjs_snippet_ElementaryUI$components from "./snippets/ElementaryUI/components/components.js";
+

+ Both JSImportFrom enums get .module(String) — the public one in Sources/JavaScriptKit/Macros.swift:12 + (typed in the macro signatures) and the skeleton one in BridgeJSSkeleton.swift:1085 (serialized, back-compatible codec). + SwiftToSkeleton.extractJSImportFrom (:2344) learns to parse .module("…"). A generate-time + diagnostic rejects a .module reference that isn't declared in jsModules. +

+ +

Problem 2 — multi-package composition

+

+ The type resolver already composes across modules (ExternalModuleIndex; proven by CrossModuleResolutionTests), + and runtime composition already crosses packages (SkeletonCollector). The only blocker is a one-line same-package + filter in the build plugin: +

+
// BridgeJSBuildPlugin.swift:110 — drops cross-package dependency targets
+context.package.targets.contains(where: { $0.id == swiftTarget.id })
+

+ Lift it, build a target.id → package map (the pattern SkeletonCollector already uses), and resolve a + cross-package dependency's committed Generated/JavaScript/BridgeJS.json. A source-hash stamp on the skeleton + plus a CI regenerate-check guards against a dependency shipping a stale skeleton. +

+ +

Problem 3 — fast TS feedback

+

+ The export .d.ts is already a pure function of the skeleton — link() computes JS and TS independently: +

+
// BridgeJSLink.link() :1209  →  generateJavaScript(data) + generateTypeScript(data)
+// proposed: a wasm-free, link-free entry point
+public func linkTypeScriptOnly() throws -> String {
+    intrinsicRegistry.reset(); /* set classNamespaces */
+    return generateTypeScript(data: try collectLinkData())
+}
+

+ Emitted per target during generate (primary skeleton only, so it's the target's own surface — not a merged + superset), to Generated/JavaScript/bridge-js.d.ts. Point tsc --watch at it; no wasm build, no final link. +

+

+ Collocation (Yuta feedback): a `.d.ts` must sit next to a real `.js`, since the surface includes value exports + (e.g. createInstantiator). So generate also writes a collocated stub bridge-js.js whose value + exports throw "bridge not built" — the module path always resolves, the typecheck loop is unaffected, and Phase 4's real + per-target runtime module later supersedes the stub at the same path. +

+
+ Important scope rule until Phase 4: Generated/JavaScript/ is a types surface + (+ stub safety), not a runtime import target. Manual-wiring/Vite apps must take runtime value + imports from the packaged output (.build/plugins/PackageToJS/outputs/Package/bridge-js.js) — importing + Generated/JavaScript/bridge-js.js before Phase 4 gets the throwing stub. Phase 4 turns that path into the real + per-target runtime module. Also note: the stable tsc --watch location is an AoT-workflow benefit; build-plugin + users get the early .d.ts in the plugin work dir. +
+ +
+ None of these touch a runtime ABI. The contract that evolves is the skeleton JSON schema (a versioned codec), which is why + Problems 1–3 ship on today's single-link model without the per-target restructure. +
+
+ + +
+

Phased plan — issues map 1:1 to phases

+

+ Full task-by-task plans (Markdown, executable with checkboxes) are linked below. Phases 1–3 each close one original + problem on the existing single-link architecture. The restructure is an optional Phase 4. +

+ +

Workflow impact — existing commands are unchanged

+ + + + + + + + +
Phase / IssueNew command?What changes for the developer
P1 — fast TS feedback (Problem 3)NoSame swift package plugin bridge-js now also drops Generated/JavaScript/bridge-js.d.ts (no wasm); point tsc --watch at it.
P2 — JS snippets / #508 (Problem 1)NoAuthoring only: a JavaScript/ dir, a bridge-js.config.json entry, from: .module("name") on an existing import macro. The snippet rides along with swift package js.
P3 — cross-package types (Problem 2)NoConsumer runs swift package js as before. New expectation: cross-package deps ship committed BridgeJS.json.
P4 — restructure (optional)No mandatoryswift package js still works; optionally enables a manual-wiring (Vite) loop.
+ +

The plans

+
+
+

Phase 1 — fast TS feedback (Problem 3)

+

Add BridgeJSLink.linkTypeScriptOnly() (the .d.ts is already a pure function of skeletons via generateTypeScript(data:)) and emit a per-target bridge-js.d.ts during generate. Per Yuta's feedback, the .d.ts ships with a collocated stub bridge-js.js (value exports throw "not built") so a value import never dangles; the typecheck loop is unaffected. No wasm, no ABI change. Full TDD.

+ → plans/2026-06-17-bridgejs-phase1-early-dts.md +
+
+

Phase 2 — package JS snippets (Problem 1 / #508)

+

Extend both JSImportFrom enums (public Macros.swift + skeleton) with .module(name) and reuse the existing from: parameter — no new macro argument. Route the four importRootExpr sites to a package-scoped snippet namespace, declare snippets in BridgeJSConfig.jsModules, validate that every referenced module is declared, and bundle whole snippet directories (preserving sibling imports) via a SnippetCollector mirroring SkeletonCollector (cross-package, collision-safe). Side effects run via the ESM import; .js/.mjs only. Full TDD, 7 tasks.

+ → plans/2026-06-17-bridgejs-phase2-js-snippets.md +
+
+

Phase 3 — multi-package composition (Problem 2)

+

The resolver already supports cross-module types (CrossModuleResolutionTests); runtime composition already crosses packages (SkeletonCollector). The only gap is the same-package filter at BridgeJSBuildPlugin.swift:110. Lift it and resolve dependency skeletons via committed BridgeJS.json. Full TDD, 5 tasks.

+ → plans/2026-06-17-bridgejs-phase3-cross-package.md +
+
+

Phase 4 — per-target modules (OPTIONAL)

+

Yuta's restructure: extract the shared runtime, split BridgeJSLink into primary + dependencies, emit per-target modules + thin composition. Divergence lives only here: codegen-version stamp + regenerate, not a frozen ABI. Feasibility spikes A/B/C complete — no blocker. The dominant cost is threading shared ABI state through an explicit context in the lowering codegen (`JSGlueGen.swift`) — exactly the internal, regenerable contract the divergence keeps non-public. Not one of the three issues. Architectural.

+ → plans/2026-06-17-bridgejs-phase4-per-target-modules.md +
+
+

+ Markdown is the source of truth (git/PR-friendly, checkbox task tracking). This HTML summarizes; open the .md for full, + executable detail. On GitHub/GitLab the .md renders natively. +

+
+ + +
+

Spike findings — feasibility, grounded in the code

+ + + + + + + + + + +
SpikeFindingRisk
1 · .module import sourceRouting is centralized at 4 importRootExpr sites (BridgeJSLink.swift:3335/3359/3458/3513); JSImportFrom is a one-case enum (:1085); ImportObjectBuilder already keys by module name.Low
2 · Snippet collectionSkeletonCollector (PackageToJSPlugin.swift:703) already walks the transitive graph cross-package; mirror it + reuse the copy mechanism.Low
3 · Per-target partitioningEmission iterates all skeletons; needs a primary + dependencies split. Same refactor Yuta requires.High effort
4 · Early .d.tslink() already calls generateJavaScript and generateTypeScript separately from skeleton-derived data; .d.ts never touches wasm.Low
5 · Cross-packageRuntime composition already crosses packages; build-time type refs blocked solely by the same-package filter at BridgeJSBuildPlugin.swift:110 (a documented discovery limit).Med
6 · Shared bjs stateAll shared state is isolated in generateVariableDeclarations() (:329-358) behind JSGlueVariableScope.reserved* symbols — extractable.Med
+
+ The spikes confirm Architecture 3 is feasible with concrete change points, and that it is minimally divergent from the original design: + the two hard refactors (Spikes 3, 6) are shared; only the contract-freezing decision differs. +
+ +

Phase 4 composition spikes (A · B · C) — completed

+

+ The optional restructure was de-risked by reading the code, since it underpins the counter-proposal's thesis (per-target + modules without a frozen ABI). Result: feasible, no fundamental blocker. +

+
    +
  • A · Shared-state inventory: classified every JSGlueVariableScope.reserved* symbol as runtime-owned vs shared-context. sharedMemory confirmed to touch glue at a single site (:1158), not in per-target thunks.
  • +
  • B · Cross-target references: emission is primary-scoped; dependencies are needed only as a lookup index (namespaces/type resolution). Two coordination wrinkles noted (closure-unregister ownership, helper-factory ownership).
  • +
  • C · Composition shape: the proposed createBridgeRuntime() / createBridgeModule() interface can express everything createInstantiator does. Critical finding: the real cost is that per-target thunk codegen references shared state by bare lexical name (e.g. strStack.pop()) and must be threaded through an explicit context — a pervasive but mechanical edit in JSGlueGen.swift. Runnable two-module PoC deferred until the restructure is greenlit.
  • +
+

Full findings + updated tasks are in plans/2026-06-17-bridgejs-phase4-per-target-modules.md.

+
+ + +
+

Open questions for discussion

+

+ These are the decisions worth settling together before committing the design direction — not blockers, just the forks + where the two approaches diverge. +

+
    +
  1. The contract axis. Should the durable cross-version contract be the skeleton JSON schema (per-target modules regenerated per codegen version), or a frozen bridgeJSRuntimeRange runtime ABI? This is the one substantive difference between the two designs.
  2. +
  3. The demand premise. Is there a real requirement for composing a prebuilt bridge with zero Swift tooling that the demand analysis missed? If so, it changes the calculus and argues for designing the ABI properly.
  4. +
  5. Phasing. Ship Problems 1–3 on the existing single-link architecture first (no workflow change), and treat the per-target restructure as a separate, optional initiative? Or do the restructure up front?
  6. +
  7. Snippet authoring. Config-declared jsModules (explicit) vs convention-only directory discovery (less friction, more “magic”)?
  8. +
  9. TS in snippets. Keep snippets .js/.mjs-only (current proposal, matches the non-goals), or is there appetite for a TS transpile step later?
  10. +
  11. Snippets from the d.ts route. Phase 2 wires from: .module through the Swift-macro path only; bridge-js.d.ts-declared imports can't target snippets yet. Is a JSDoc tag (e.g. /** @bridgeModule components */) that ts2swift lowers to .module worth speccing, and when?
  12. +
  13. Module-name uniqueness. BridgeJS keys bridge state/snippets by Swift module name, but SwiftPM allows same-named targets in different packages. The plans now add a graph-wide duplicate-name diagnostic — is erroring acceptable, or does anyone need a disambiguation scheme?
  14. +
+
+ Bottom line: the proposal agrees with the bulk of the original design. The alternative on the table removes one long-term + liability — a frozen runtime ABI — while keeping every ergonomic win it outlined. Whoever ships it (individually or as a + team), these are the points to align on first. +
+

Artifacts in this bundle

+
    +
  • javascriptkit-508-counterproposal.html — this report
  • +
  • plans/2026-06-17-bridgejs-phase1-early-dts.md — Problem 3 (full TDD)
  • +
  • plans/2026-06-17-bridgejs-phase2-js-snippets.md — Problem 1 / #508 (full TDD)
  • +
  • plans/2026-06-17-bridgejs-phase3-cross-package.md — Problem 2 (full TDD)
  • +
  • plans/2026-06-17-bridgejs-phase4-per-target-modules.md — optional restructure (staged)
  • +
+
+
+ + + + diff --git a/docs/superpowers/plans/2026-06-17-bridgejs-phase1-early-dts.md b/docs/superpowers/plans/2026-06-17-bridgejs-phase1-early-dts.md new file mode 100644 index 000000000..3749cbe59 --- /dev/null +++ b/docs/superpowers/plans/2026-06-17-bridgejs-phase1-early-dts.md @@ -0,0 +1,374 @@ +# BridgeJS Early `.d.ts` (Phase 1) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make a target's exported TypeScript surface (`bridge-js.d.ts`) regenerate from its BridgeJS skeleton at code-generation time, without a WebAssembly build or the final `PackageToJS` link, so TypeScript consumers can typecheck **the target's own exported surface** immediately after editing `@JS` annotations. (Scope note — see M3 below: when a target's exported API *references a type defined in a dependency target*, that name is not yet defined/imported in the per-target `.d.ts`; full standalone typechecking of cross-target references requires emitting `import type` statements, which is a follow-up tracked here, not part of this minimal phase.) + +**Architecture:** `BridgeJSLink` already produces the export `.d.ts` purely from skeleton-derived data (`generateTypeScript(data:)`), entirely independent of the JS/wasm path. This phase exposes a TypeScript-only entry point on `BridgeJSLink`, then calls it from the `generate` subcommand so each target writes a per-target `Generated/JavaScript/bridge-js.{d.ts,js}` **pair** right after its skeleton is produced. No runtime/ABI changes. + +**Tech Stack:** Swift (SwiftPM command/build plugins + plugin tools), Swift Testing, the repo's snapshot-test harness. + +> ### Design note — collocation requirement (Yuta feedback, 2026-06-17) +> TypeScript assumes every `.d.ts` is collocated with a real implementation `.js`. Our generated surface is mostly type-only (`interface`s, `type` aliases), **but it includes value exports** — notably `export function createInstantiator(...)` (verified in the `SwiftClass.d.ts` snapshot) plus enum/namespace `const` objects. So emitting a **lone `.d.ts`** is unsafe: if a consumer does a *non-type* import of a value, `tsc` keeps the import in the emitted JS and it points at a non-existent `bridge-js.js` ("module not found" at runtime). +> +> **What this does and doesn't affect:** +> - **Unaffected (the main win):** editor diagnostics and `tsc --noEmit` typechecking emit no JS, so the fast typecheck loop is safe with the `.d.ts` alone. +> - **Affected:** a consumer that *emits/runs* JS with a value import before the real bridge is built. +> +> **Decision for Phase 1:** always write the `.d.ts` **with a collocated `.js`**. The minimal form is a generated **stub `.js`** (Task 4) whose value exports throw a clear "bridge not built — run `swift package js`" error, so the module path always resolves. When Phase 4's per-target runtime `.js` lands, it supersedes the stub at the same path. (Yuta's "just generate `.ts`" idea doesn't fix it alone: a types-only `.ts` compiles to an empty `.js`, so value imports still fail — just with "undefined is not a function" instead of "module not found".) +> +> **Scope boundaries (F1/F3 from the follow-up review):** +> - **`Generated/JavaScript/` is a TYPES (+ stub-safety) surface until Phase 4 — never a runtime import target.** Manual-wiring/Vite users must take *runtime* imports from the packaged output (`.build/plugins/PackageToJS/outputs/Package/bridge-js.js`), not from `Generated/JavaScript/bridge-js.js` (which is the throwing stub until Phase 4 replaces it). Document this in the Task 2 example check and in any consumer-facing docs. +> - **The fast `tsc --watch` loop is an AoT-workflow benefit.** The build plugin passes `--output-dir ` (`BridgeJSBuildPlugin.swift:53-54`), so for build-plugin users the early `.d.ts`/stub land in `.build/plugins/...` — functional, but not a stable watch path. AoT users (`swift package plugin bridge-js`) get the stable `Sources//Generated/JavaScript/` location. A future `--emit-dts-into-target` opt-in for build-plugin users is possible but out of scope here. + +--- + +## Context: why this phase first + +This is the cheapest and most isolated of the four phases identified in the design review (see `docs/superpowers/specs/2026-06-17-bridgejs-composition-design.md` once written). It ships value on its own (fast TS feedback, JavaScriptKit issue context #508 / Yuta's Decision 6), touches no runtime ABI, and de-risks the larger work. + +### Roadmap — issue-aligned phases (each its own plan) + +Phases 1–3 each close exactly one of the three originally-identified problems, on top of the **existing single-link architecture**, with **no change to the commands a developer runs** (`swift package js`, `swift package plugin bridge-js` stay identical). The per-target/shared-runtime restructure is demoted to an **optional** Phase 4 because none of the three issues require it. + +- **Phase 1 — Problem 3: fast TS feedback.** *(this plan)* `linkTypeScriptOnly()` + per-target `Generated/JavaScript/bridge-js.d.ts` emitted during `generate`. No wasm, no ABI change. +- **Phase 2 — Problem 1: package-owned JS snippets (#508).** Extend **both** `JSImportFrom` enums (public `Macros.swift` + skeleton) with `.module(name)` and reuse the existing `from:` parameter; route the four `importRootExpr` sites in `BridgeJSLink.swift` (~`:3335, 3359, 3458, 3513`) to a package-scoped namespace; add a `SnippetCollector` mirroring `SkeletonCollector` in `PackageToJSPlugin.swift` (it traverses cross-package, so snippets compose across packages); bundle snippet directories and emit real ESM imports. Authoring-only additions for the developer (a `JavaScript/` dir + `bridge-js.config.json` entry + `from: .module("name")`); no new command. +- **Phase 3 — Problem 2: multi-package composition.** Runtime composition already works cross-package via `SkeletonCollector`; the missing user-facing piece is cross-package `@JS` **type references**. Lift the same-package filter at `BridgeJSBuildPlugin.swift:110` and resolve dependency skeletons across package boundaries via committed `Generated/JavaScript/BridgeJS.json`. New *expectation* (not command): cross-package `@JS` dependencies ship committed skeletons. +- **Phase 4 — OPTIONAL architectural investment (not one of the three issues): shared-runtime extraction + per-target bridge modules.** Move `generateVariableDeclarations()` state into a shared runtime helper; split `BridgeJSLink` into `primary + dependencies` so emission is per-target while lookups stay global. This is the same refactor Yuta's design requires — **but our divergence keeps the inter-module boundary an internal, codegen-version-stamped detail rather than a frozen public ABI.** Pursue only if incremental-build and manual-wiring (Vite) ergonomics are wanted; the three issues ship without it. + +--- + +## File Structure + +- **Modify** `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` — add public `linkTypeScriptOnly() -> String` (the `.d.ts`) and `linkStubJavaScript() -> String` (the collocated stub `.js`). Both are pure functions of the loaded skeletons; neither touches wasm. +- **Modify** `Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift` — after writing the skeleton in the `generate` subcommand, emit the per-target `Generated/JavaScript/bridge-js.d.ts` **and** the collocated `Generated/JavaScript/bridge-js.js` stub. +- **Test** `Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift` — assert `linkTypeScriptOnly()` equals `link()`'s `outputDts`, and that the stub `.js` exports exactly the value-bearing names the `.d.ts` declares (so a value import resolves rather than dangles). + +> Naming note: the target-root `bridge-js.d.ts` is the **input** for TypeScript-import (`@JSFunction` generation). The new files live under `Generated/JavaScript/bridge-js.{d.ts,js}` (next to `BridgeJS.json`) and are the **output** export surface. Different directories, no collision. + +--- + +## Task 1: `BridgeJSLink.linkTypeScriptOnly()` + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` (add method next to `link()` at `:1210`) +- Test: `Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift` + +- [ ] **Step 1: Write the failing test** + +Add to `BridgeJSLinkTests` in `Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift`: + +```swift +@Test +func typeScriptOnlyMatchesLink() throws { + let url = Self.inputsDirectory.appendingPathComponent("SwiftClass.swift") + let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) + let importSwift = SwiftToSkeleton( + progress: .silent, + moduleName: "TestModule", + exposeToGlobal: false, + externalModuleIndex: .empty + ) + importSwift.addSourceFile(sourceFile, inputFilePath: "SwiftClass.swift") + let importResult = try importSwift.finalize() + + var fullLink = BridgeJSLink(sharedMemory: false) + var dtsOnlyLink = BridgeJSLink(sharedMemory: false) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let unifiedData = try encoder.encode(importResult) + try fullLink.addSkeletonFile(data: unifiedData) + try dtsOnlyLink.addSkeletonFile(data: unifiedData) + + let (_, expectedDts) = try fullLink.link() + let actualDts = try dtsOnlyLink.linkTypeScriptOnly() + + #expect(actualDts == expectedDts) +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `swift test --package-path Plugins/BridgeJS --filter BridgeJSLinkTests.typeScriptOnlyMatchesLink` +Expected: FAIL to compile — `value of type 'BridgeJSLink' has no member 'linkTypeScriptOnly'`. + +- [ ] **Step 3: Write minimal implementation** + +In `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift`, immediately after the `link()` method (ends at `:1224`), add: + +```swift +/// Generates only the exported TypeScript surface (`.d.ts`) from the loaded skeletons. +/// +/// This is a pure function of the skeletons and never touches the WebAssembly +/// build or JS glue generation, so it can run at code-generation time to give +/// TypeScript consumers fast feedback after editing `@JS` annotations. +public func linkTypeScriptOnly() throws -> String { + intrinsicRegistry.reset() + intrinsicRegistry.classNamespaces = skeletons.reduce(into: [:]) { result, unified in + guard let skeleton = unified.exported else { return } + for klass in skeleton.classes { + if let namespace = klass.namespace { + result[klass.name] = namespace + } + } + } + let data = try collectLinkData() + return generateTypeScript(data: data) +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `swift test --package-path Plugins/BridgeJS --filter BridgeJSLinkTests.typeScriptOnlyMatchesLink` +Expected: PASS. + +- [ ] **Step 5: Run the full link test suite to confirm no regressions** + +Run: `swift test --package-path Plugins/BridgeJS --filter BridgeJSLinkTests` +Expected: PASS (all existing snapshot tests unchanged — no production output paths were modified). + +- [ ] **Step 6: Commit** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift +git commit -m "feat(bridge-js): add linkTypeScriptOnly() for wasm-free .d.ts generation" +``` + +--- + +## Task 2: Emit per-target export `.d.ts` (+ collocated stub `.js`) from the `generate` subcommand + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift` (the `generate` case; skeleton is written near `:264-274`) + +The `generate` subcommand already builds the primary skeleton and loads dependency skeletons (`ExternalModuleIndex(dependencies:)`, `:173`). We reuse those exact objects to drive `BridgeJSLink`. + +- [ ] **Step 1: Locate the skeleton-write site** + +Read `Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift` around `:255-290` to find where `outputSkeletonURL` (`Generated/JavaScript/BridgeJS.json`) is written via `writeIfChanged(skeletonData, to: outputSkeletonURL)`. The new `.d.ts` is written to the **same directory**. + +- [ ] **Step 2: Add the export `.d.ts` emission immediately after the skeleton-write `withSpan` block** + +The `generate` case writes the skeleton inside a `withSpan("Writing output skeleton") { ... }` block ending at `:275`, where the primary skeleton model value is the local `skeleton` (encoded at `:273`). After that closing brace (`:275`), insert: + +```swift +// Emit the exported TypeScript surface alongside the skeleton so TS consumers +// can typecheck without a wasm build or the final PackageToJS link. +// IMPORTANT (Yuta feedback): a .d.ts must be collocated with a real .js, or a +// consumer's *value* import dangles in emitted JS. Write the stub .js too (Task 4). +try withSpan("Writing export .d.ts + stub .js") { + let dir = outputSkeletonURL.deletingLastPathComponent() + var dtsLink = BridgeJSLink(sharedMemory: false) + dtsLink.addSkeleton(skeleton) // primary only — see note below + let exportDts = try dtsLink.linkTypeScriptOnly() + let stubJs = try dtsLink.linkStubJavaScript() // Task 4 + try writeIfChanged(Data(exportDts.utf8), to: dir.appending(path: "bridge-js.d.ts")) + try writeIfChanged(Data(stubJs.utf8), to: dir.appending(path: "bridge-js.js")) +} +``` + +> **`skeleton` is the PRIMARY skeleton only — do not add `dependencySkeletons` here.** `linkTypeScriptOnly()` re-emits a `.d.ts` declaration for *every* added skeleton that has an `exported` section. Because the `primary + dependencies` emission split is Phase 4, adding dependency skeletons would make each target's `Generated/JavaScript/bridge-js.d.ts` a **merged superset** that duplicates its dependencies' declarations — causing duplicate-identifier risk if a consumer references two such files, and a surface broader than the target itself. +> +> **Known limitation — M3 (cross-target referenced types):** with primary-only input, a type referenced from a *dependency* target (e.g. a return type defined in another module) renders **by name** in the `.d.ts` but is neither defined nor imported. Consequences: +> - For a target whose own exported API references a dependency `@JS` type, the per-target `.d.ts` **does not typecheck standalone** (the referenced name is undefined). +> - We deliberately do **not** re-inline dependency declarations here (that caused the duplicate-identifier / merged-superset problem this fix removed). +> +> **Resolution path (follow-up, not this phase):** emit `import type { Foo } from "/bridge-js.d.ts"` for each referenced external type, so the per-target `.d.ts` is a proper TS module that resolves cross-target names without duplication. This needs (a) the set of referenced external types (already available via the `ExternalModuleIndex` model) and (b) a resolvable path to each dependency's `bridge-js.d.ts`. It is the natural companion to Phase 3 (cross-package) / Phase 4 (per-target modules); track it there. The fully-resolved merged surface is meanwhile still produced by the final `PackageToJS` link (the package-level `bridge-js.d.ts`). +> +> `skeleton` is the primary `BridgeJSSkeleton` produced by `swiftToSkeleton.finalize()` (encoded at `:273`). + +- [ ] **Step 3: Add the `addSkeleton(_:)` convenience used above** + +The tool holds decoded `BridgeJSSkeleton` values, not `Data`. Add a sibling to `addSkeletonFile(data:)` in `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` (after `:60`): + +```swift +/// Adds an already-decoded skeleton (used by callers that hold model values +/// rather than serialized skeleton files). +public mutating func addSkeleton(_ skeleton: BridgeJSSkeleton) { + skeletons.append(skeleton) +} +``` + +- [ ] **Step 4: Build the plugin tool to verify it compiles** + +Run: `swift build --package-path Plugins/BridgeJS --product BridgeJSTool` +Expected: Build succeeds. + +- [ ] **Step 5: Verify behavior on an example target** + +Run: +```bash +swift package --package-path Examples/MultiModule plugin bridge-js --target MarkdownParser --allow-writing-to-package-directory +``` +Expected: `Examples/MultiModule/Sources/MarkdownParser/Generated/JavaScript/bridge-js.d.ts` **and** `bridge-js.js` now exist; the `.d.ts` contains the exported declarations and the `.js` is the stub from Task 4. (Adjust target/example name to one that has `@JS` exports.) + +- [ ] **Step 6: Commit** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +git commit -m "feat(bridge-js): emit per-target export bridge-js.{d.ts,js} during generate" +``` + +--- + +## Task 4: Generate the collocated stub `bridge-js.js` (Yuta collocation fix) + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` — add `linkStubJavaScript() -> String`. +- Test: `Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift` + +The stub exists so the `.d.ts` is never an orphan: the module path resolves, the typecheck loop is unaffected, and a value import used *before* the real bridge is built fails with a clear message instead of "module not found". It must export **every value-bearing name** the `.d.ts` declares (so the named import resolves), each throwing. + +- [ ] **Step 1: Write the failing test** + +```swift +@Test func stubJsExportsValueNamesAndThrows() throws { + let url = Self.inputsDirectory.appendingPathComponent("SwiftClass.swift") + let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) + let importSwift = SwiftToSkeleton( + progress: .silent, moduleName: "TestModule", + exposeToGlobal: false, externalModuleIndex: .empty + ) + importSwift.addSourceFile(sourceFile, inputFilePath: "SwiftClass.swift") + var link = BridgeJSLink(sharedMemory: false) + let encoder = JSONEncoder(); encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + try link.addSkeletonFile(data: encoder.encode(try importSwift.finalize())) + + let stub = try link.linkStubJavaScript() + // createInstantiator is a value export in the .d.ts (verified in SwiftClass.d.ts snapshot) + #expect(stub.contains("export function createInstantiator")) + #expect(stub.contains("throw new Error")) +} +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `swift test --package-path Plugins/BridgeJS --filter stubJsExportsValueNamesAndThrows` +Expected: FAIL — no `linkStubJavaScript`. + +- [ ] **Step 3: Implement `linkStubJavaScript()`** + +Reuse `collectLinkData()` to find the value-bearing exports (the same data the `.d.ts` uses). Emit an ESM module that exports each value name with a throwing body. Value exports are: `createInstantiator` (always, when a bridge exists), plus enum/namespace `const` objects and any top-level exported functions. Pure types (`interface`/`type`) are **not** emitted (they don't exist at runtime). + +```swift +/// Generates a placeholder `bridge-js.js` collocated with the early `.d.ts`. +/// Every value-bearing export the `.d.ts` declares is present but throws, so a +/// consumer's value import resolves (no dangling module) yet fails loudly if used +/// before the real bridge is built by `swift package js`. Superseded by the real +/// per-target runtime module in Phase 4. +public func linkStubJavaScript() throws -> String { + let data = try collectLinkData() + var lines: [String] = [ + "// NOTICE: Auto-generated placeholder by BridgeJS. The real runtime is produced", + "// by `swift package js`. This file exists so bridge-js.d.ts is collocated with a module.", + "const __bridgeNotBuilt = (name) => { throw new Error(", + " `BridgeJS runtime is not built yet (accessed '${name}'). Run \\`swift package js\\`.`); };", + ] + for name in data.valueExportNames.sorted() { + lines.append("export function \(name)() { return __bridgeNotBuilt(\"\(name)\"); }") + } + return lines.joined(separator: "\n") + "\n" +} +``` + +> `data.valueExportNames` is the set of value-bearing top-level export identifiers. If `LinkData` doesn't already track these, add a `Set` populated where the `.d.ts` value exports are emitted (top-level functions, enum/namespace consts, and the always-present `createInstantiator`). For enum/namespace consts that must be *objects* (not callables), note that ESM named exports don't support lazy `defineProperty` getters on the namespace — instead export a throwing `Proxy`: +> ```js +> const __stubProxy = (name) => new Proxy({}, { get() { return __bridgeNotBuilt(name); } }); +> export const Direction = __stubProxy("Direction"); +> ``` +> Keep `export function` only for callable exports. Match each value name to how the `.d.ts` declares it. + +- [ ] **Step 4: Run to verify it passes** + +Run: `swift test --package-path Plugins/BridgeJS --filter stubJsExportsValueNamesAndThrows` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift +git commit -m "feat(bridge-js): generate collocated stub bridge-js.js for the early .d.ts" +``` + +--- + +## Task 5: Snapshot-cover the per-target `.d.ts` output + +**Files:** +- Test: `Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift` + +- [ ] **Step 1: Add a snapshot test for the TypeScript-only path across all inputs** + +Add to `BridgeJSLinkTests`: + +```swift +@Test(arguments: collectInputs(extension: ".swift")) +func typeScriptOnlySnapshot(input: String) throws { + let url = Self.inputsDirectory.appendingPathComponent(input) + let name = url.deletingPathExtension().lastPathComponent + let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) + let importSwift = SwiftToSkeleton( + progress: .silent, + moduleName: "TestModule", + exposeToGlobal: false, + externalModuleIndex: .empty + ) + importSwift.addSourceFile(sourceFile, inputFilePath: "\(name).swift") + let importResult = try importSwift.finalize() + var link = BridgeJSLink(sharedMemory: false) + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + try link.addSkeletonFile(data: encoder.encode(importResult)) + + let dts = try link.linkTypeScriptOnly() + try assertSnapshot( + name: name, + input: dts.data(using: .utf8)!, + fileExtension: "ts-only.d.ts" + ) +} +``` + +> If `assertSnapshot` requires the `filePath`/`function`/`sourceLocation` arguments (as the private `snapshot(...)` helper passes them), include them with the same `#filePath`, `#function`, `#_sourceLocation` defaults used at `:13-15`. + +- [ ] **Step 2: Run to generate snapshots (first run records them)** + +Run: `swift test --package-path Plugins/BridgeJS --filter BridgeJSLinkTests.typeScriptOnlySnapshot` +Expected: On first run the harness records `*.ts-only.d.ts` snapshots; confirm the recorded files match the `.d.ts` portion already recorded by the existing `snapshot(input:)` test. + +- [ ] **Step 3: Re-run to verify stability** + +Run: `swift test --package-path Plugins/BridgeJS --filter BridgeJSLinkTests.typeScriptOnlySnapshot` +Expected: PASS with no snapshot diffs. + +- [ ] **Step 4: Commit** + +```bash +git add Plugins/BridgeJS/Tests/BridgeJSToolTests +git commit -m "test(bridge-js): snapshot the TypeScript-only link output" +``` + +--- + +## Self-Review + +**Spec coverage:** +- Fast `.d.ts` without wasm/link → Task 1 (`linkTypeScriptOnly`) + Task 2 (emission during `generate`). ✓ +- **Collocation fix (Yuta):** the `.d.ts` ships with a collocated stub `.js` so a value import never dangles → Task 4 (`linkStubJavaScript`) + Task 2 writes both. The fast **typecheck** loop (editor / `tsc --noEmit`) is unaffected; the stub only matters when a consumer emits/runs before the real bridge is built. ✓ +- Stable per-target output location for TS watch → Task 2 writes `Generated/JavaScript/bridge-js.{d.ts,js}`. ✓ +- No runtime/ABI change → confirmed; only additive methods and two extra output files (the stub `.js` is superseded by Phase 4's real per-target runtime module at the same path). ✓ +- Per-target surface is primary-only (not a merged superset) → Task 2 Step 2 adds only the primary skeleton. ✓ +- **M3 (cross-target referenced types):** the goal is scoped to the target's *own* exported surface; a target that re-exports a dependency `@JS` type yields a `.d.ts` that does not typecheck standalone until `import type` emission is added (a Phase 3/4 follow-up, documented in the Task 2 Known-limitation note). This is an explicit, accepted limitation of the minimal phase — not an oversight. ✓ +- **F-series (follow-up review):** F1 — `Generated/JavaScript/` declared a types-only surface until Phase 4 (runtime imports come from the packaged output); F3 — fast-watch loop honestly scoped to the AoT workflow (build-plugin outputs land in the work dir); F7 — object-shaped stub exports use a throwing `Proxy` const (ESM namespaces don't support defineProperty getters). Design-note + Task 4. ✓ + +**Placeholder scan:** Adaptation notes in Task 2 (the exact `dependencySkeletons` shape / `assertSnapshot` signature) and Task 4 Step 3 (how `valueExportNames` is populated and how object-shaped consts are stubbed) are "match the existing local code" instructions referencing concrete anchors (`:125`, `:13-34`, the `.d.ts` value-export emission), not deferred design. + +**Type consistency:** `linkTypeScriptOnly()`, `linkStubJavaScript()`, and `addSkeleton(_:)` are defined in Tasks 1/4/2 and used consistently in Task 2. `LinkData.valueExportNames` is introduced in Task 4 Step 3. `collectLinkData()`, `generateTypeScript(data:)`, `intrinsicRegistry` all exist in `BridgeJSLink.swift` (`:179`, `:924`, used at `:1211`). `BridgeJSSkeleton` is the decoded type appended in `addSkeletonFile` (`:45`). + +--- + +## Execution Handoff + +Once reviewed, choose execution: +1. **Subagent-Driven (recommended)** — fresh subagent per task with review between tasks. +2. **Inline Execution** — execute tasks in-session with checkpoints. diff --git a/docs/superpowers/plans/2026-06-17-bridgejs-phase2-js-snippets.md b/docs/superpowers/plans/2026-06-17-bridgejs-phase2-js-snippets.md new file mode 100644 index 000000000..c5b43bd13 --- /dev/null +++ b/docs/superpowers/plans/2026-06-17-bridgejs-phase2-js-snippets.md @@ -0,0 +1,593 @@ +# BridgeJS Package-Owned JS Snippets (Phase 2) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Let a Swift package ship its own JavaScript (a "snippet" module) and bind to it from Swift, so that `@JS` imported declarations resolve to the package's own JS — bundled automatically into the final app — instead of `globalThis` or app-provided imports. This closes JavaScriptKit issue #508 (e.g. ElementaryUI custom elements). + +**Architecture:** Add a third import source to the existing single-link model. Today every imported `@JS` binding routes to either `globalThis` (`from: .global`) or the app-provided `imports` object (`from == nil`) — decided at four `importRootExpr` sites in `BridgeJSLink.swift`. We extend the existing `from:` axis with `.module(name)` (no new macro argument), route those sites to a **package-scoped** snippet namespace bound by a real ESM `import` of the package's JS, and collect/copy snippet source trees transitively via a new `SnippetCollector` mirroring `SkeletonCollector`. The import statement also runs the snippet's top-level code, covering load-time side effects. + +**Tech Stack:** Swift (BridgeJS macros API, skeleton, core, link, plugins), Swift Testing + snapshot harness, the PackageToJS plugin. + +--- + +## Context + +Depends on nothing from Phase 1; can ship independently. This is the headline #508 feature. No developer-facing command changes — authoring-only additions (a `JavaScript/` dir, a config entry, and `from: .module("name")` on existing `@JSFunction`/`@JSGetter`/`@JSClass`). The existing `swift package js` / build-plugin run bundles the snippet automatically. + +### Design decision: reuse `from:`, do NOT add a `module:` argument +There are **two** `JSImportFrom` types and they are both load-bearing: +- **Public** `enum JSImportFrom: String { case global }` — `Sources/JavaScriptKit/Macros.swift:12`. This is the type users write; the macros already declare `from: JSImportFrom?` (`Macros.swift:141/159/180/205`). There is **no** `module:` parameter on any macro. +- **Skeleton** `enum JSImportFrom` — `BridgeJSSkeleton.swift:1085`. The serialized form stored in `BridgeJS.json`. + +The macro *implementations* (`BridgeJSMacros/*`) do **not** parse `from:` — `SwiftToSkeleton.AttributeChecker.extractJSImportFrom` (`SwiftToSkeleton.swift:2344`) reads it from attribute syntax and constructs the skeleton enum. Therefore: +- Adding a `module:` argument would require editing the public macro **declarations** in `Macros.swift` (not `BridgeJSMacros/*`), and `@JSFunction(module: …)` would otherwise fail to compile ("extra argument 'module'"). +- Reusing the existing `from:` parameter avoids all macro-signature changes: once `.module(String)` exists on the enum, users write `@JSFunction(from: .module("components"))`, unifying routing on one axis (`.global` / `.module(name)` / `nil`). + +So this plan **extends both enums** and updates `extractJSImportFrom`; it does **not** touch macro expansion code and adds **no** `module:` argument. + +### Scope: JS snippets only (no TypeScript transpilation) +Per the design (and Yuta's non-goals), snippet files are **plain `.js`/`.mjs`** shipped as-is to the bundler/dev-server. A `.ts` snippet is rejected with a diagnostic. Transpiling TS is explicitly out of scope. + +### Scope: macro-authored imports only (F5 — the d.ts route can't target snippets yet) +`.module` is wired through the **Swift macro** path (`extractJSImportFrom`). Projects that declare imports via the TypeScript route (`bridge-js.d.ts` → ts2swift → `BridgeJS.Macros.swift`) have **no way to say "this import comes from my snippet"** in this phase — d.ts-declared imports remain app-provided (`from == nil`). This matters for d.ts-heavy codebases (e.g. khasm-style projects). Follow-up option (out of scope here): a JSDoc tag in the d.ts (e.g. `/** @bridgeModule components */`) that ts2swift lowers to `from: .module("components")`. Listed in the report's Open Questions. + +**Verified anchors:** +- Public enum + macro signatures: `Macros.swift:12, 141, 159, 180, 205` (only used in macro signatures; no `rawValue` dependence in `Sources`/`Tests`/`Examples`, so dropping `: String` is safe). +- `from:` parsing: `SwiftToSkeleton.swift:2344-2357` (`extractJSImportFrom`, splits the expression text on `.` and calls `JSImportFrom(rawValue:)`). +- Skeleton enum + `from:` fields: `BridgeJSSkeleton.swift:1085-1087, 1094, 1187, 1306`. +- Routing sites: `importRootExpr = X.from == .global ? "globalThis" : "imports"` at `BridgeJSLink.swift:3335, 3359, 3458, 3513`; "app must provide" `.d.ts` gated on `from == nil` (`:3339, 3367, 3418`). +- Each unified skeleton carries its owning Swift module name as `unified.moduleName` (used throughout `collectLinkData`, e.g. `:270, 296`) — used to **package-scope** snippet namespaces. +- Transitive collector to mirror: `SkeletonCollector` — `PackageToJSPlugin.swift:703-784` (crosses package boundaries at `:773-776`). +- Template-copy mechanism: `planCopyTemplateFile` / `make.addTask` — `PackageToJS.swift:587, 597-617, 620-634`. +- Config struct: `BridgeJSConfig` in `BridgeJSCore/Misc.swift` (fields `exposeToGlobal`, `identityMode`, `tools`). + +--- + +## File Structure + +- **Modify** `Sources/JavaScriptKit/Macros.swift:12-14` — change the **public** `JSImportFrom` from a `String` raw enum to an enum with `.global` and `.module(String)`. (No macro-signature change; `from:` already exists.) +- **Modify** `Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift:1085-1087` — change the **skeleton** `JSImportFrom` to a custom `Codable` enum with `.global` and `.module(String)` (back-compatible codec). +- **Modify** `Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift:2344` — extend `extractJSImportFrom` to parse `.module("name")` call syntax. +- **Modify** `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` — route `.module(name)` at the four `importRootExpr` sites to a **package-scoped** namespace; emit an ESM `import` per referenced snippet at the top of `bridge-js.js`; record references for validation. +- **Modify** `Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift` — add `jsModules: [String: String]` to `BridgeJSConfig` (logical module name → snippet **entry file** path, relative to the target dir). +- **Create** `Plugins/PackageToJS/Sources/SnippetCollector.swift` — transitive collection of declared snippet entry files + their owning Swift module name. +- **Modify** `Plugins/PackageToJS/Sources/PackageToJS.swift` + `PackageToJSPlugin.swift` — copy each snippet's **source directory** (preserving structure/extension) into `snippets//`, validate every referenced `.module` is declared, and ensure import paths resolve. +- **Tests** under `Plugins/BridgeJS/Tests/BridgeJSToolTests/` + an `Examples/` fixture. + +--- + +## Task 1: Extend the skeleton `JSImportFrom` with `.module(name)` + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift:1085-1087` +- Test: `Plugins/BridgeJS/Tests/BridgeJSToolTests/JSImportFromTests.swift` (new) + +- [ ] **Step 1: Write the failing test** + +```swift +import Foundation +import Testing +@testable import BridgeJSSkeleton + +@Suite struct JSImportFromTests { + @Test func encodesAndDecodesModuleCase() throws { + let value = JSImportFrom.module("elementary-components") + let data = try JSONEncoder().encode(value) + let decoded = try JSONDecoder().decode(JSImportFrom.self, from: data) + #expect(decoded == .module("elementary-components")) + } + + @Test func decodesLegacyGlobalString() throws { + // Back-compat: existing skeletons encode `.global` as the bare string "global". + let data = Data("\"global\"".utf8) + let decoded = try JSONDecoder().decode(JSImportFrom.self, from: data) + #expect(decoded == .global) + } +} +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `swift test --package-path Plugins/BridgeJS --filter JSImportFromTests` +Expected: FAIL to compile — `type 'JSImportFrom' has no member 'module'`. + +- [ ] **Step 3: Replace the skeleton enum with a back-compatible custom codec** + +Replace `BridgeJSSkeleton.swift:1085-1087`: + +```swift +public enum JSImportFrom: Codable, Equatable { + case global + case module(String) + + public init(from decoder: any Decoder) throws { + // Legacy form: the bare string "global". + if let single = try? decoder.singleValueContainer(), + let raw = try? single.decode(String.self), raw == "global" { + self = .global + return + } + // New form: { "module": "" }. + let keyed = try decoder.container(keyedBy: CodingKeys.self) + if let name = try keyed.decodeIfPresent(String.self, forKey: .module) { + self = .module(name) + return + } + throw DecodingError.dataCorrupted( + .init(codingPath: decoder.codingPath, debugDescription: "Unrecognized JSImportFrom") + ) + } + + public func encode(to encoder: any Encoder) throws { + switch self { + case .global: + var c = encoder.singleValueContainer() + try c.encode("global") // preserve legacy bare-string form + case .module(let name): + var c = encoder.container(keyedBy: CodingKeys.self) + try c.encode(name, forKey: .module) + } + } + + private enum CodingKeys: String, CodingKey { case module } +} +``` + +- [ ] **Step 4: Run to verify it passes** + +Run: `swift test --package-path Plugins/BridgeJS --filter JSImportFromTests` +Expected: PASS. + +- [ ] **Step 5: Confirm no skeleton/link snapshot drift** + +Run: `swift test --package-path Plugins/BridgeJS --filter BridgeJSLinkTests` +Expected: PASS — `.global` still serializes to `"global"`, so existing snapshots are unchanged. + +- [ ] **Step 6: Commit** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift Plugins/BridgeJS/Tests/BridgeJSToolTests/JSImportFromTests.swift +git commit -m "feat(bridge-js): add skeleton JSImportFrom.module case (back-compatible codec)" +``` + +--- + +## Task 2: Extend the public enum and parse `from: .module("…")` + +**Files:** +- Modify: `Sources/JavaScriptKit/Macros.swift:12-14` +- Modify: `Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift:2344-2357` +- Test: `Plugins/BridgeJS/Tests/BridgeJSToolTests/ModuleImportSkeletonTests.swift` (new) + +- [ ] **Step 1: Write the failing skeleton test** + +```swift +import Foundation +import SwiftParser +import Testing +@testable import BridgeJSCore +@testable import BridgeJSSkeleton + +@Suite struct ModuleImportSkeletonTests { + @Test func parsesModuleFromAttributeIntoFrom() throws { + let source = """ + import JavaScriptKit + @JSFunction(from: .module("elementary-components")) + func defineComponent(name: String) + """ + let file = Parser.parse(source: source) + let importer = SwiftToSkeleton( + progress: .silent, moduleName: "TestModule", + exposeToGlobal: false, externalModuleIndex: .empty + ) + importer.addSourceFile(file, inputFilePath: "ModuleImport.swift") + let result = try importer.finalize() + let fn = try #require(result.imported?.children.flatMap(\.functions).first) + #expect(fn.from == .module("elementary-components")) + } +} +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `swift test --package-path Plugins/BridgeJS --filter ModuleImportSkeletonTests` +Expected: FAIL — `extractJSImportFrom` returns `nil` for `.module("…")` (current `JSImportFrom(rawValue:)` can't build an associated-value case). + +- [ ] **Step 3: Extend the public enum (drop the raw-value conformance)** + +Replace `Macros.swift:12-14`: + +```swift +/// Controls where BridgeJS reads imported JS values from. +/// +/// - `global`: Read from `globalThis`. +/// - `module(name)`: Read from a package-owned JS snippet module declared in +/// `bridge-js.config.json` under `jsModules`. +public enum JSImportFrom { + case global + case module(String) +} +``` + +> **Source-compatibility note (M1) — this is a public API change, call it out in the changelog.** `JSImportFrom` is `public`; dropping `: String` removes its `RawRepresentable` conformance, so any *external* code using `JSImportFrom(rawValue:)` or `.global.rawValue` stops compiling. A Swift enum with associated values cannot keep a raw type, so the conformance cannot be preserved as-is. +> +> - **In-repo:** verified safe — the enum is referenced only in the four macro signatures; nothing reads `rawValue`. +> - **External impact:** likely negligible (this is a niche macro-argument enum, and BridgeJS is pre-1.0 / post-MVP), but it is still a source break. Document it in `CHANGELOG.md`. +> - **Optional softening (only if external source-compat is a hard requirement):** hand-conform to `RawRepresentable` with `String` raw values — `var rawValue` returns `"global"` / `"module()"`, and `init?(rawValue:)` parses them back. This preserves `.global` round-trips and most existing call sites; weigh the API ugliness against the break. Default recommendation: take the clean break + changelog entry. + +- [ ] **Step 4: Teach `extractJSImportFrom` to parse `.module("…")`** + +Replace the body of `extractJSImportFrom` (`SwiftToSkeleton.swift:2344-2357`) so it handles both the member-access form (`.global`) and the function-call form (`.module("name")`): + +```swift +static func extractJSImportFrom(from attribute: AttributeSyntax) -> JSImportFrom? { + guard let arguments = attribute.arguments?.as(LabeledExprListSyntax.self) else { return nil } + for argument in arguments { + guard argument.label?.text == "from" else { continue } + + // Function-call form: `.module("name")` + if let call = argument.expression.as(FunctionCallExprSyntax.self), + let member = call.calledExpression.as(MemberAccessExprSyntax.self), + member.declName.baseName.text == "module", + let firstArg = call.arguments.first, + let str = firstArg.expression.as(StringLiteralExprSyntax.self), + let seg = str.segments.first?.as(StringSegmentSyntax.self) { + return .module(seg.content.text) + } + + // Member-access form: `.global` / `JSImportFrom.global` + let description = argument.expression.trimmedDescription + let caseName = description.split(separator: ".").last.map(String.init) ?? description + if caseName == "global" { return .global } + } + return nil +} +``` + +> `JSImportFrom` here is the **skeleton** enum (BridgeJSCore imports BridgeJSSkeleton). `.module(_)` was added in Task 1. + +- [ ] **Step 5: Run to verify it passes** + +Run: `swift test --package-path Plugins/BridgeJS --filter ModuleImportSkeletonTests` +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add Sources/JavaScriptKit/Macros.swift Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift Plugins/BridgeJS/Tests/BridgeJSToolTests/ModuleImportSkeletonTests.swift +git commit -m "feat(bridge-js): support from: .module(name) on import macros" +``` + +--- + +## Task 3: Route `.module(name)` to a package-scoped snippet namespace in BridgeJSLink + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` (routing sites `:3335, 3359, 3458, 3513`; import-collection in `collectLinkData` `:267-292`; `LinkData` `:166-177`; JS header in `generateJavaScript` `:1056+`) +- Test: `Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift` + new snapshot input + +**Package-scoping (fixes cross-package name collisions):** the bundled path and JS namespace are keyed by the **owning Swift module name** (`unified.moduleName`, always unique in a package graph) plus the user's logical module name. Two packages each declaring `"components"` therefore never collide. + +- [ ] **Step 1: Add scoping + namespace helpers** + +Add near the `renderImported*` functions (above `:3322`): + +```swift +/// Deterministic, collision-resistant JS identifier for a snippet namespace, +/// scoped by the owning Swift module so two packages can reuse the same logical name. +static func snippetNamespaceConst(swiftModule: String, moduleName: String) -> String { + func enc(_ s: String) -> String { + // Reversible-enough encoding: non-alphanumerics become __ so + // "a-b" and "a.b" and "a_b" never collide. + s.unicodeScalars.map { sc in + (("a"..."z").contains(Character(sc)) || ("A"..."Z").contains(Character(sc)) || ("0"..."9").contains(Character(sc))) + ? String(sc) : "_\(String(sc.value, radix: 16))_" + }.joined() + } + return "__bjs_snippet_\(enc(swiftModule))$\(enc(moduleName))" +} + +/// Bundled path for a snippet module's entry file (matches PackageToJS copy in Task 6). +/// Keyed by BOTH the owning Swift module AND the logical module name so two +/// logical modules in the SAME target (e.g. entries `JavaScript/a/index.js` and +/// `JavaScript/b/index.js`) never collide on a shared `index.js`. +static func snippetImportPath(swiftModule: String, moduleName: String, entryFileName: String) -> String { + "./snippets/\(swiftModule)/\(moduleName)/\(entryFileName)" +} +``` + +- [ ] **Step 2: Route the four sites through the owning skeleton's module name** + +Each `renderImported*` is called from `collectLinkData` with the `unified` skeleton in scope (`:268-292`). Thread the owning `moduleName` into the `renderImported*` functions (add a `swiftModule: String` parameter), and at the four sites replace +`let importRootExpr = X.from == .global ? "globalThis" : "imports"` +with: + +```swift +let importRootExpr: String +switch X.from { +case .global: importRootExpr = "globalThis" +case .module(let name): + importRootExpr = Self.snippetNamespaceConst(swiftModule: swiftModule, moduleName: name) +case nil: importRootExpr = "imports" +} +``` + +Leave the `if X.from == nil { appendDts(...) }` blocks unchanged — only app-provided (`nil`) imports belong in the consumer-facing `.d.ts`; `.module` imports are satisfied by the package. + +- [ ] **Step 3: Record (swiftModule, moduleName) references for import emission + validation** + +In `struct LinkData` (`:166-177`) add: +```swift +// (owning Swift module, logical snippet module name) pairs referenced via .module(_) +var referencedSnippetModules: Set = [] +``` +Define `struct SnippetRef: Hashable { let swiftModule: String; let moduleName: String }` near `LinkData`. + +In the imported-skeleton loop (`:268-292`), when a getter/function/type has `from == .module(name)`, insert `data.referencedSnippetModules.insert(.init(swiftModule: unified.moduleName, moduleName: name))`. + +- [ ] **Step 4: Emit ESM imports for referenced snippets** + +In `generateJavaScript(data:)` (`:1056+`), at the very top of the emitted file, emit one import + namespace const per referenced module, sorted for determinism. The **entry file name** is provided by PackageToJS (Task 6) as the snippet's *real* entry filename (e.g. `components.js`); BridgeJSLink receives it via a new `snippetEntryNames: [SnippetRef: String]` map passed into `BridgeJSLink` (defaulting to `"index.js"` when absent, only so unit snapshots without PackageToJS are stable): + +```swift +for ref in data.referencedSnippetModules.sorted(by: { ($0.swiftModule, $0.moduleName) < ($1.swiftModule, $1.moduleName) }) { + let ns = Self.snippetNamespaceConst(swiftModule: ref.swiftModule, moduleName: ref.moduleName) + let entry = snippetEntryNames[ref] ?? "index.js" + printer.write("import * as \(ns) from \"\(Self.snippetImportPath(swiftModule: ref.swiftModule, moduleName: ref.moduleName, entryFileName: entry))\";") +} +``` + +- [ ] **Step 5: Add a link snapshot input that uses a module import** + +Create `Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ModuleImport.swift`: + +```swift +import JavaScriptKit + +@JSFunction(from: .module("elementary-components")) +func defineComponent(name: String, render: (JSObject) -> Void) +``` + +- [ ] **Step 6: Run link snapshots (records new snapshot)** + +Run: `swift test --package-path Plugins/BridgeJS --filter BridgeJSLinkTests` +Expected: records `ModuleImport.js`/`.d.ts`; verify `bridge-js.js` contains +`import * as __bjs_snippet_TestModule$elementary_2d_components from "./snippets/TestModule/elementary-components/index.js";` +(owning module "TestModule" comes from the test harness; the `elementary-components` subdirectory is the logical module name), the `defineComponent` thunk reads from that namespace, and `ModuleImport.d.ts` does **not** list `defineComponent` under the app `imports` interface. + +- [ ] **Step 7: Commit** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift Plugins/BridgeJS/Tests/BridgeJSToolTests +git commit -m "feat(bridge-js): route .module imports to package-scoped snippet namespaces" +``` + +--- + +## Task 4: Declare snippet entry files in `BridgeJSConfig` + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift` (`BridgeJSConfig`) +- Test: `Plugins/BridgeJS/Tests/BridgeJSToolTests/ConfigJSModulesTests.swift` (new) + +- [ ] **Step 1: Write a failing config-decode test** + +```swift +import Foundation +import Testing +@testable import BridgeJSCore + +@Suite struct ConfigJSModulesTests { + @Test func decodesJsModulesMap() throws { + let json = """ + { "jsModules": { "elementary-components": "JavaScript/elementary-components.js" } } + """ + let config = try JSONDecoder().decode(BridgeJSConfig.self, from: Data(json.utf8)) + #expect(config.jsModules["elementary-components"] == "JavaScript/elementary-components.js") + } + + @Test func defaultsToEmptyWhenAbsent() throws { + let config = try JSONDecoder().decode(BridgeJSConfig.self, from: Data("{}".utf8)) + #expect(config.jsModules.isEmpty) + } +} +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `swift test --package-path Plugins/BridgeJS --filter ConfigJSModulesTests` +Expected: FAIL — `BridgeJSConfig` has no `jsModules`. + +- [ ] **Step 3: Add the field** + +In `Misc.swift`, add to `BridgeJSConfig`, mirroring the existing optional-with-default pattern of `exposeToGlobal`/`identityMode` (field + `CodingKeys` entry + `decodeIfPresent(...) ?? [:]` in `init(from:)`): + +```swift +/// Maps a logical JS module name (used in `from: .module("name")`) to the snippet +/// ENTRY file path relative to the target source directory. The entry file may +/// import sibling files; the whole containing directory is bundled (see PackageToJS). +/// Module names are scoped per target, so two packages may reuse the same name. +public var jsModules: [String: String] = [:] +``` + +- [ ] **Step 4: Run to verify it passes** + +Run: `swift test --package-path Plugins/BridgeJS --filter ConfigJSModulesTests` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift Plugins/BridgeJS/Tests/BridgeJSToolTests/ConfigJSModulesTests.swift +git commit -m "feat(bridge-js): add jsModules snippet declaration to BridgeJSConfig" +``` + +--- + +## Task 5: Validate referenced `.module` names are declared (no dangling imports) + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift` (the `generate` subcommand, where the imported skeleton and `BridgeJSConfig` are both in scope, ~`:132, :255`) +- Test: `Plugins/BridgeJS/Tests/BridgeJSToolTests/` diagnostic test (or a `BridgeJSCore` validation unit test) + +A typo'd or undeclared `from: .module("nope")` must fail with a clear diagnostic at generate time, not produce a dangling ESM import that breaks the bundler/runtime later. + +- [ ] **Step 1: Write a failing validation test** + +Add a unit test that runs `SwiftToSkeleton` on a source with `from: .module("nope")` and asserts a validator (introduced below) reports an error when `nope` is absent from `config.jsModules`. Mirror the existing diagnostic-test style in `DiagnosticsTests.swift`. + +```swift +@Test func undeclaredModuleProducesDiagnostic() throws { + let referenced: Set = ["nope"] + let declared: Set = ["components"] + let missing = BridgeJSConfig.undeclaredSnippetModules(referenced: referenced, declared: declared) + #expect(missing == ["nope"]) +} +``` + +- [ ] **Step 2: Run to verify it fails** + +Run: `swift test --package-path Plugins/BridgeJS --filter undeclaredModuleProducesDiagnostic` +Expected: FAIL — no such helper. + +- [ ] **Step 3: Add the validation helper and call it in `generate`** + +In `Misc.swift` (near `BridgeJSConfig`): + +```swift +extension BridgeJSConfig { + /// Logical snippet module names referenced by `from: .module(_)` but not declared in `jsModules`. + static func undeclaredSnippetModules(referenced: Set, declared: Set) -> Set { + referenced.subtracting(declared) + } +} +``` + +In the `generate` subcommand (`BridgeJSTool.swift`), after building `skeleton`, gather the referenced logical module names from the imported skeleton's `.module(name)` entries, compare against `Set(config.jsModules.keys)`, and on any difference throw a `BridgeJSToolError`: + +```text +"@JS import references undeclared JS module(s): nope. Declare them in +bridge-js.config.json under \"jsModules\", e.g. { \"jsModules\": { \"nope\": \"JavaScript/nope.js\" } }." +``` + +- [ ] **Step 4: Run to verify it passes** + +Run: `swift test --package-path Plugins/BridgeJS --filter undeclaredModuleProducesDiagnostic` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSCore/Misc.swift Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift Plugins/BridgeJS/Tests/BridgeJSToolTests +git commit -m "feat(bridge-js): diagnose undeclared .module references at generate time" +``` + +--- + +## Task 6: Collect and copy snippet directories transitively in PackageToJS + +**Files:** +- Create: `Plugins/PackageToJS/Sources/SnippetCollector.swift` +- Modify: `Plugins/PackageToJS/Sources/PackageToJSPlugin.swift` (invoke alongside `SkeletonCollector`) +- Modify: `Plugins/PackageToJS/Sources/PackageToJS.swift` (copy directories; feed entry names to BridgeJSLink) + +**Directory copy (fixes relative/npm/TS resolution + same-module collisions):** copy each snippet's **containing directory** preserving structure and file extensions, to a path keyed by **both** the owning Swift module and the logical module name: `snippets///`. The entry file keeps its real name; BridgeJSLink imports `./snippets///`. This preserves sibling relative imports (`./helpers.js`) **and** guarantees two logical modules in one target never clobber each other (e.g. both shipping an `index.js`). Bare/npm imports inside snippets remain the app bundler's responsibility (documented in Task 7). + +- [ ] **Step 1: Add `SnippetCollector` mirroring `SkeletonCollector`** + +Create `Plugins/PackageToJS/Sources/SnippetCollector.swift` modeled on `SkeletonCollector` (`PackageToJSPlugin.swift:703-784`), including the cross-package traversal (`:773-776`). For each visited `SwiftSourceModuleTarget`, read its `bridge-js.config.json` `jsModules`; for each `(logicalName, entryRelPath)` resolve the entry file under `target.directoryURL`, reject `.ts`/`.tsx` with a diagnostic, and record: + +```swift +struct CollectedSnippet { + let swiftModule: String // target.name — the scoping key + let moduleName: String // logical name from jsModules + let entryFileURL: URL // resolved entry file + let sourceDirURL: URL // entryFileURL.deletingLastPathComponent() + var entryFileName: String { entryFileURL.lastPathComponent } +} +``` + +Return `[CollectedSnippet]`. Decode `jsModules` via a minimal local `Decodable` to avoid a plugin→tool module dependency. + +> **Module-name uniqueness guard (F2):** the scoping key `snippets//…` assumes Swift module names are unique across the dependency graph — SwiftPM does *not* enforce that (two packages may each have a target `Models`). While collecting, track `moduleName -> Package.ID`; if the same target name appears from **two different packages**, emit a `Diagnostics.error` naming both packages (same wording as the Phase 3 discovery check). This closes the collision for snippets even if Phase 2 ships before Phase 3. + +- [ ] **Step 2: Invoke the collector and feed entry names to the linker** + +In `PackageToJSPlugin.swift` near `:201-202`, run `SnippetCollector.collectFromProduct(name: productName)` and pass the result into `PackagingPlanner` alongside `skeletons`. Build the `snippetEntryNames: [SnippetRef: String]` map (keyed by `(swiftModule, moduleName)`) and pass it into the `BridgeJSLink` constructed at `PackageToJS.swift:602` so the emitted imports use the real entry filename. + +- [ ] **Step 3: Copy snippet directories** + +In `PackageToJS.swift` (near the template-copy/`bridge-js.js` area `:597-634`), add a copy task per `CollectedSnippet` that recursively copies `sourceDirURL` → `snippets///` (preserving structure/extension), reusing the existing copy-task pattern. The destination + entry filename must match BridgeJSLink's emitted import (`./snippets///`). + +> The per-`moduleName` subdirectory is **mandatory, not a fallback** — it is what guarantees collision-freedom when one target declares multiple logical modules (each may legitimately ship its own `index.js`). `snippetImportPath` (Task 3) and this copy destination must use the identical `snippets///` shape. + +- [ ] **Step 4: Lightweight export-name validation (warning, F4)** + +Task 5 validates the module *name* is declared, but nothing checks the snippet actually **exports the symbols Swift binds to** — a typo'd JS export otherwise surfaces only at runtime as `undefined is not a function` deep inside a thunk. Add a cheap static check at collection time: scan the snippet **entry file** for top-level `export` identifiers (regex over `export function ` / `export const|let|var ` / `export { }` — no full JS parse), compare against the imported skeleton's symbol names bound to that module, and emit a **warning** (not an error — the export may come from a re-export or dynamic pattern the scan can't see) listing referenced-but-not-found names. Full type-level validation is explicitly out of scope. + +- [ ] **Step 5: Verify end-to-end on an example** + +Create/extend an example: a target with `Sources//JavaScript/components.js` (which itself `import`s a sibling `./helpers.js`) exporting `defineComponent`, `bridge-js.config.json` `{ "jsModules": { "components": "JavaScript/components.js" } }`, and Swift `@JSFunction(from: .module("components")) func defineComponent(...)`. + +Run: +```bash +swift package --package-path Examples/ --swift-sdk $SWIFT_SDK_ID js +``` +Expected: output contains `snippets//components/components.js` **and** `snippets//components/helpers.js`; `bridge-js.js` imports `./snippets//components/components.js`; the sibling import resolves; the snippet's top-level code runs at load. + +- [ ] **Step 6: Commit** + +```bash +git add Plugins/PackageToJS/Sources/SnippetCollector.swift Plugins/PackageToJS/Sources/PackageToJSPlugin.swift Plugins/PackageToJS/Sources/PackageToJS.swift Examples +git commit -m "feat(packagetojs): bundle package-owned JS snippet directories (package-scoped)" +``` + +--- + +## Task 7: Documentation + +**Files:** +- Modify: `Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/` (new "Shipping JS with a package" article) + +- [ ] **Step 1: Document the authoring flow** + +Cover: (1) put JS in `Sources//JavaScript/.js` (siblings allowed; bare/npm imports are resolved by the app bundler, not BridgeJS); (2) declare it in `bridge-js.config.json` `jsModules` (entry file path; names are per-target scoped); (3) bind from Swift with `from: .module("")`. Note: `.js`/`.mjs` only (no TS transpilation); load-time side effects run because the snippet is imported as an ESM module; bidirectional calls happen by passing Swift closures as arguments. + +- [ ] **Step 2: Commit** + +```bash +git add Sources/JavaScriptKit/Documentation.docc +git commit -m "docs(bridge-js): document shipping JS snippets with a package" +``` + +--- + +## Self-Review + +**Spec coverage:** third import source via reused `from:` axis (Tasks 1–3), authoring via existing macros + config (Tasks 2, 4), undeclared-reference diagnostic (Task 5), directory-preserving transitive bundling + package-scoped naming (Task 6), side effects + docs (Task 7). ✓ + +**First-review fixes folded in:** +- **C1** — extends BOTH `JSImportFrom` enums (`Macros.swift:12` public + skeleton) and updates `extractJSImportFrom`; reuses `from:` (no `module:` arg, no macro-impl edits). Tasks 1–2. +- **C2** — copies the snippet's whole source directory preserving structure/extension; `.js`/`.mjs` only, `.ts` rejected. Task 6. +- **C3** — generate-time validation that every referenced `.module` is declared. Task 5. +- **H1 (cross-package)** — snippet dir + JS namespace are scoped by the owning Swift module (`unified.moduleName`); cross-package `"components"` collisions are impossible. Task 3 + Task 6. +- **M1 (namespace encoding)** — `snippetNamespaceConst` encodes non-alphanumerics as `__`, so `a-b`/`a.b`/`a_b` never collide. Task 3. + +**Follow-up (F-series) fixes folded in:** +- **F2 (module-name uniqueness)** — `SnippetCollector` errors on the same target name appearing from two different packages (the `snippets//` scoping key assumes graph-wide uniqueness SwiftPM doesn't enforce). Task 6 Step 1. +- **F4 (export-name validation)** — lightweight static scan of the snippet entry's `export` identifiers vs the Swift-bound symbol names; warning on missing. Task 6 Step 4. +- **F5 (d.ts route can't target snippets)** — explicitly scoped out with a follow-up option (JSDoc `@bridgeModule` tag lowered by ts2swift); surfaced in the report's Open Questions. Context section. + +**Second-review fixes folded in:** +- **H2 (same-module path identity)** — the bundled path and import are keyed by the **full `SnippetRef`** (`snippets///`), so two logical modules in one target can't collide on a shared `index.js`. The per-`moduleName` subdirectory is mandatory, not a fallback. `snippetImportPath` now takes `moduleName:`. Task 3 Steps 1/4/6 + Task 6. +- **M1 (public RawRepresentable break)** — dropping `: String` from the public `JSImportFrom` is now flagged as an explicit source-compatibility break (changelog entry required), with an optional hand-rolled `RawRepresentable` shim if external source-compat is a hard requirement. Task 2 Step 3. + +**Placeholder scan:** Task 5 Step 3 and Task 6 Steps 1–3 reference existing patterns (`SkeletonCollector`, the copy-task mechanism, the diagnostic style) with concrete anchors rather than re-deriving them; all novel logic (both enum codecs, `extractJSImportFrom`, routing switch, namespace/scoping helpers, validation helper, config field) has complete code. + +**Type consistency:** `JSImportFrom.module(_:)` (both enums), `snippetNamespaceConst(swiftModule:moduleName:)`, `snippetImportPath(swiftModule:moduleName:entryFileName:)`, `SnippetRef`, `LinkData.referencedSnippetModules`, `snippetEntryNames`, `BridgeJSConfig.jsModules`, `undeclaredSnippetModules`, `CollectedSnippet` are defined where introduced and used consistently. The bundled path `./snippets///` is identical in Task 3 Step 4 and Task 6 Step 3. + +--- + +## Execution Handoff + +Subagent-Driven (recommended) or Inline. Execute Tasks 1→2→3 together (they interlock through the snapshot input); then 4→5 (config + validation); then 6 end-to-end. diff --git a/docs/superpowers/plans/2026-06-17-bridgejs-phase3-cross-package.md b/docs/superpowers/plans/2026-06-17-bridgejs-phase3-cross-package.md new file mode 100644 index 000000000..2c16137a4 --- /dev/null +++ b/docs/superpowers/plans/2026-06-17-bridgejs-phase3-cross-package.md @@ -0,0 +1,353 @@ +# BridgeJS Cross-Package `@JS` Composition (Phase 3) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Let a `@JS` declaration in one Swift package reference `@JS` types exported by a *different* Swift package (e.g. an app uses a type from ElementaryUI), so multi-package apps compose without manual re-declaration. This closes the "multi-package composition" problem (#477/#508 discussion). + +**Architecture:** The type-resolution engine already supports cross-module references — `SwiftToSkeleton` consumes an `ExternalModuleIndex` built from dependency skeletons and resolves external types (`CrossModuleResolutionTests` proves it via `usedExternalModules`). Runtime composition also already works cross-package (`SkeletonCollector` in PackageToJS traverses package boundaries). The *only* missing piece is build-time **discovery**: `BridgeJSBuildPlugin.dependencySkeletons` filters out targets that are not in the current package (`:110`), so a package never sees a dependency package's skeleton. This phase lifts that filter and resolves dependency skeletons across package boundaries via their committed `Generated/JavaScript/BridgeJS.json`. + +**Tech Stack:** Swift (BridgeJS build + command plugins, core resolver), Swift Testing. + +--- + +## Context + +Independent of Phases 1 and 2. No developer-facing command change. New *expectation*: a package that exposes `@JS` types for cross-package use ships its committed `Generated/JavaScript/BridgeJS.json` (already the standard AoT recommendation for libraries). The consumer keeps running `swift package js` unchanged. + +> **Hazard (addressed by Task 6): stale committed skeleton.** Cross-package resolution reads the dependency's *committed* `BridgeJS.json`. If a dependency author edits `@JS` types but forgets to regenerate and commit, the consumer silently builds against a stale interface (wrong `abiName`/arity) — an ABI mismatch surfacing only at runtime. Task 6 adds drift detection (an input-hash stamp covering Swift + config + d.ts + dependency hashes + codegen version, plus a warning) and a CI regenerate-check so this fails loudly, not silently. + +**Verified anchors:** +- Engine already resolves external types: `CrossModuleResolutionTests.swift` (`usedExternalModules`, `.swiftStruct("Vector3D")` across modules); `ExternalModuleIndex(dependencies:)` — `ExternalModuleIndex.swift:26`. +- Build-plugin same-package filter (the blocker): `BridgeJSBuildPlugin.swift:106-116`, specifically `context.package.targets.contains(where: { $0.id == swiftTarget.id })` at `:110`, with a code comment (`:97-101`) explaining it's a discovery limitation. +- Build-plugin path uses `packageID: context.package.id` to locate the skeleton (`:123`) — must be generalized to the dependency's package. +- Command (AoT) plugin already reads dependency skeletons from committed `Generated/JavaScript/BridgeJS.json` (`BridgeJSCommandPlugin.swift:165-178`) — confirm/extend its cross-package coverage. +- PackageToJS runtime collection already crosses packages: `SkeletonCollector.visit` (`PackageToJSPlugin.swift:773-776`). + +--- + +## File Structure + +- **Modify** `Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift` — extend `dependencySkeletons` to include cross-package dependency targets and resolve their committed skeleton paths. +- **Modify** `Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift` — ensure AoT dependency-skeleton discovery walks cross-package dependencies (it already targets `Generated/JavaScript/BridgeJS.json`; verify package traversal). +- **Possibly modify** `Plugins/BridgeJS/Sources/BridgeJSPluginUtilities/PluginPaths.swift` — a helper to resolve a dependency target's committed skeleton URL given its owning package. +- **Tests**: extend `CrossModuleResolutionTests` for the cross-package shape (engine already supports it; assert the discovery wiring feeds it), plus an `Examples/` two-package fixture. + +--- + +## Task 1: Resolve a dependency target's committed skeleton URL across packages + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSPluginUtilities/PluginPaths.swift` +- Test: `Plugins/BridgeJS/Tests/BridgeJSToolTests/` (path-resolution unit test if `PluginPaths` is testable; otherwise covered by Task 3 integration) + +- [ ] **Step 1: Add a committed-skeleton path helper** + +In `PluginPaths.swift`, add a helper that, given a dependency `SwiftSourceModuleTarget`, returns the committed AoT skeleton URL: + +```swift +/// The committed (AoT) skeleton location a dependency package ships for +/// cross-package `@JS` type resolution. +static func committedSkeletonURL(targetDirectoryURL: URL) -> URL { + targetDirectoryURL + .appending(path: "Generated") + .appending(path: "JavaScript") + .appending(path: "BridgeJS.json") +} +``` + +> This mirrors the path the command plugin already reads (`BridgeJSCommandPlugin.swift:165-166`) and the `outputSkeletonURL` the tool writes (`BridgeJSTool.swift:264`). + +- [ ] **Step 2: Commit** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSPluginUtilities/PluginPaths.swift +git commit -m "feat(bridge-js): add committed-skeleton path helper for cross-package resolution" +``` + +--- + +## Task 2: Lift the same-package filter in the build plugin + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift:102-140` + +- [ ] **Step 1: Include cross-package dependency targets** + +Replace the `localTargets` filter at `:106-116` so it keeps **all** `SwiftSourceModuleTarget` recursive dependencies that have a `bridge-js.config.json`, regardless of owning package. The owning package is needed to locate the skeleton; build a `target.id -> package` map from `context.package.dependencies` (same approach `SkeletonCollector` uses at `PackageToJSPlugin.swift:766-771`). + +```swift +// Build target.id -> owning Package map across the dependency graph. +var packageByTargetID: [Target.ID: Package] = [:] +func indexTargets(of package: Package) { + for target in package.targets { packageByTargetID[target.id] = package } + for dep in package.dependencies { indexTargets(of: dep.package) } +} +indexTargets(of: context.package) + +let dependencyTargets: [(target: SwiftSourceModuleTarget, package: Package)] = + target.recursiveTargetDependencies.compactMap { dependency in + guard + let swiftTarget = dependency as? SwiftSourceModuleTarget, + FileManager.default.fileExists(atPath: pathToConfigFile(target: swiftTarget).path), + let owningPackage = packageByTargetID[swiftTarget.id] + else { return nil } + return (swiftTarget, owningPackage) + } +``` + +- [ ] **Step 2: Fix the input-file model for cross-package deps (H1)** + +**Why this matters.** The build plugin passes `--dependency-skeleton =` (the file actually *read*) but lists a **different** file as the tracked `inputFile` — currently always `skeleton.bridgeJSSwiftURL` (`BridgeJSBuildPlugin.swift:66-77`). For a **same-package** dep that's correct: the dep's `BridgeJS.swift` is an *output* of the dep's own plugin invocation, so listing it as an input creates the build-graph **ordering edge** (dep generates first). But for a **cross-package** dep: +> - its `BridgeJS.swift` is **not** produced by this package's plugin run, so a work-dir path for it is missing/stale (and the `packageID: owningPackage.id` + this package's `pluginWorkDirectoryURL` combination is nonsensical); +> - the file actually read — the committed `Generated/JavaScript/BridgeJS.json` — is **never tracked**, so edits to it don't retrigger. +> +> Fix: track the **committed skeleton itself** as the input for cross-package deps (it's a real committed file on disk, and no cross-package ordering edge is needed because the dependency package is already fully built). Keep `bridgeJSSwiftURL` as the input only for same-package ordering. + +Give `DependencySkeleton` (`:91-95`) an explicit `inputFileURL` and set it per origin: + +```swift +private struct DependencySkeleton { + let moduleName: String + let skeletonURL: URL // the file passed to --dependency-skeleton (the read input) + let inputFileURL: URL // the file tracked as a build inputFile (ordering / change-tracking) +} +``` + +**Module-name uniqueness (F2):** BridgeJS keys everything by the Swift module name — `ImportObjectBuilder` merges by `moduleName` string (`BridgeJSLink.swift:302`), Phase 2 scopes snippets by `snippets//…`, and closure thunks are named `...__...`. SwiftPM *permits* two packages to each define a target with the same name, which would silently merge/collide. A plain `seen.insert(name)` dedupe would silently drop one. **Detect and error instead:** + +```swift +var skeletons: [DependencySkeleton] = [] +var seenOwners: [String: Package.ID] = [:] // moduleName -> owning package +for (swiftTarget, owningPackage) in dependencyTargets { + if let existingOwner = seenOwners[swiftTarget.name] { + if existingOwner != owningPackage.id { + Diagnostics.error(""" + Duplicate BridgeJS module name '\(swiftTarget.name)' found in packages \ + '\(existingOwner)' and '\(owningPackage.id)'. BridgeJS keys bridge state, \ + snippets, and import objects by module name, so names must be unique across \ + the dependency graph. Rename one of the targets. + """) + } + continue // same target reached via two paths — skip + } + seenOwners[swiftTarget.name] = owningPackage.id + let skeletonURL: URL + let inputFileURL: URL + if owningPackage.id == context.package.id { + // Same package: skeleton lives in this plugin's work dir; track the generated + // BridgeJS.swift (an output of the dep's own invocation) for ordering. + skeletonURL = BridgeJSPluginPaths.skeletonURL( + targetName: swiftTarget.name, + packageID: context.package.id, + buildPluginWorkDirectoryURL: context.pluginWorkDirectoryURL + ) + inputFileURL = BridgeJSPluginPaths.bridgeJSSwiftURL( + targetName: swiftTarget.name, + packageID: context.package.id, + buildPluginWorkDirectoryURL: context.pluginWorkDirectoryURL + ) + } else { + // Cross package: read AND track the committed skeleton (a real on-disk file). + // No ordering edge needed — the dependency package is already built. + skeletonURL = BridgeJSPluginPaths.committedSkeletonURL( + targetDirectoryURL: swiftTarget.directoryURL + ) + inputFileURL = skeletonURL + } + skeletons.append(DependencySkeleton( + moduleName: swiftTarget.name, skeletonURL: skeletonURL, inputFileURL: inputFileURL + )) +} +return skeletons +``` + +> If a cross-package dependency lacks a committed skeleton, emit a clear diagnostic (mirror the command plugin's message at `BridgeJSCommandPlugin.swift:170`): "Dependency '' exposes `@JS` types across packages but has no committed Generated/JavaScript/BridgeJS.json. Run `swift package plugin bridge-js` in that package and commit the result." + +- [ ] **Step 3: Append the correct input file at the call site** + +At `BridgeJSBuildPlugin.swift:76`, replace `inputFiles.append(skeleton.bridgeJSSwiftURL)` with `inputFiles.append(skeleton.inputFileURL)`. The `--dependency-skeleton` argument keeps using `skeleton.skeletonURL.path` (`:69`). + +- [ ] **Step 4: Build to verify it compiles** + +Run: `swift build --package-path Plugins/BridgeJS` +Expected: builds. + +- [ ] **Step 5: Commit** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift +git commit -m "feat(bridge-js): discover dependency skeletons across packages with correct input tracking" +``` + +--- + +## Task 3: Verify the command (AoT) plugin crosses packages + +**Files:** +- Modify (if needed): `Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift:160-178` + +- [ ] **Step 1: Audit the AoT dependency walk** + +Read `:155-185`. It already builds `--dependency-skeleton =`. Confirm the loop iterates `recursiveTargetDependencies` (or the product graph) **including cross-package targets**, not only same-package ones. If it is restricted to `context.package`, apply the same generalization as Task 2 Step 1 (build a `target.id -> package` map and resolve `directoryURL`-based committed skeleton paths). + +- [ ] **Step 2: Ensure parity diagnostic** + +If a cross-package dependency lacks the committed skeleton, emit the same diagnostic wording as Task 2 Step 2 for a consistent developer experience. + +- [ ] **Step 3: Commit (if changed)** + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift +git commit -m "feat(bridge-js): discover dependency skeletons across packages (command plugin)" +``` + +--- + +## Task 4: Engine-level regression test for the cross-package shape + +**Files:** +- Modify: `Plugins/BridgeJS/Tests/BridgeJSToolTests/CrossModuleResolutionTests.swift` + +The resolver already supports this; this test locks the contract that a dependency skeleton loaded from another package resolves identically to a same-package one (the plugins only differ in *where they read the file from*). + +- [ ] **Step 1: Add a test asserting resolution is source-agnostic** + +```swift +@Test +func resolvesExternalTypeRegardlessOfSkeletonOrigin() throws { + // Same skeleton bytes a cross-package dependency would ship as committed BridgeJS.json. + let ui = try buildDependencySkeleton( + moduleName: "ElementaryUI", + source: """ + @JS public struct Color { + @JS public init(hex: String) { self.hex = hex } + public let hex: String + } + """ + ) + // Round-trip through JSON to simulate reading a committed skeleton file. + let encoded = try JSONEncoder().encode(ui) + let reloaded = try JSONDecoder().decode(BridgeJSSkeleton.self, from: encoded) + + let app = try resolveApp( + source: """ + import ElementaryUI + @JS public func brand() -> Color { Color(hex: "#000") } + """, + dependencies: [(moduleName: "ElementaryUI", skeleton: reloaded)] + ) + #expect(app.usedExternalModules == ["ElementaryUI"]) + let fn = try #require(app.exported?.functions.first(where: { $0.name == "brand" })) + #expect(fn.returnType == .swiftStruct("Color")) +} +``` + +> Use the existing `buildDependencySkeleton`/`resolveApp` helpers in this test file. If `BridgeJSSkeleton` isn't directly `Codable` at the top level, encode the value `buildDependencySkeleton` returns using the same encoder configuration the tool uses (`BridgeJSTool.swift:271-272`). + +- [ ] **Step 2: Run** + +Run: `swift test --package-path Plugins/BridgeJS --filter CrossModuleResolutionTests` +Expected: PASS. + +- [ ] **Step 3: Commit** + +```bash +git add Plugins/BridgeJS/Tests/BridgeJSToolTests/CrossModuleResolutionTests.swift +git commit -m "test(bridge-js): lock cross-package skeleton resolution parity" +``` + +--- + +## Task 5: Two-package integration fixture + +**Files:** +- Create: `Examples/CrossPackage/` (a library package + an app package depending on it) + +- [ ] **Step 1: Build the fixture** + +- Library package `ElementaryUIish` with a `@JS public struct` exported and committed `Generated/JavaScript/BridgeJS.json`. +- App package depending on it, with a `@JS public func` returning that struct. + +- [ ] **Step 2: Verify end-to-end** + +Run: +```bash +swift package --package-path Examples/CrossPackage/App --swift-sdk $SWIFT_SDK_ID js +``` +Expected: build succeeds; without Phase 3 the app build would fail to resolve the external type. Confirm the generated `bridge-js.d.ts` includes the composed surface. + +- [ ] **Step 3: Commit** + +```bash +git add Examples/CrossPackage +git commit -m "test(bridge-js): cross-package composition example" +``` + +--- + +## Task 6: Detect stale committed skeletons (drift guard for H2) + +**Files:** +- Modify: `Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift` (add an optional `inputHash` field) and `Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift` (compute + write it during `generate`) +- Add: a CI regenerate-check step (Makefile/CI script) and a docs note + +A dependency's committed `BridgeJS.json` can silently drift from its source. This task makes drift detectable. + +- [ ] **Step 1: Stamp the skeleton with an INPUT hash (write side) — must cover every input that affects the skeleton (M2)** + +Add an optional `public var inputHash: String?` to `BridgeJSSkeleton` (back-compatible: `decodeIfPresent`, omitted from old skeletons). In the `generate` subcommand, compute a stable hash over **all** inputs that can change `BridgeJS.json` — not just Swift sources. Hashing only Swift files would let a config-only edit silently stale the committed skeleton. Include: + +- **Swift source files** fed to `SwiftToSkeleton` (sorted by path; hash path + contents). +- **`bridge-js.config.json`** contents — `exposeToGlobal`, `identityMode`, etc. drive output (`BridgeJSTool.swift:132`, `:173-180`; `Misc.swift`). +- **TypeScript import inputs** — `bridge-js.d.ts` and `bridge-js.global.d.ts` (they generate `BridgeJS.Macros.swift`, which feeds the imported skeleton; `BridgeJSTool.swift:135-157, :164-173`). Hash the `.d.ts` contents (and/or the generated macro output). +- **Dependency skeleton identities** — the `moduleName` + each dependency's own `inputHash` (so a changed transitive dependency invalidates downstream). This makes the hash recursively sound. +- **A codegen/schema version constant** — bump it whenever the generator or skeleton schema changes, so a toolchain upgrade invalidates stale stamps even when inputs are unchanged. + +Compute as a sorted, delimited concatenation → SHA-256, set on the skeleton before writing `BridgeJS.json`. + +> Rationale anchors: config load/use at `BridgeJSTool.swift:132, :173-180`; d.ts→macros at `:135-157, :164-173`; dependency skeletons at `:125`. + +- [ ] **Step 2: Warn on drift (read side)** + +When a consumer's plugin loads a dependency's committed skeleton (Phase 3 Task 2/3), if the dependency target's *current* inputs are reachable in the build graph, recompute the `inputHash` (same input set as Step 1) and emit a **warning** (not an error — the dependency may be a prebuilt/binary distribution) when it differs from the committed `inputHash`: + +```text +"warning: dependency '' committed BridgeJS.json is stale (input hash mismatch). +Run `swift package plugin bridge-js` in that package and commit the result." +``` + +- [ ] **Step 3: Add a CI regenerate-check** + +Add a CI/Makefile target that runs `swift package plugin bridge-js` and fails if `git status --porcelain` shows changes under `Generated/` — guaranteeing committed skeletons are never stale in CI. Mirror any existing "generated code is up to date" check in the repo if present. + +- [ ] **Step 4: Tests + commit** + +Unit-test that (a) a changed Swift source, (b) a changed `bridge-js.config.json` (e.g. `exposeToGlobal` flip), and (c) a bumped codegen/schema version each produce a different `inputHash`; and assert the back-compat decode of a skeleton without `inputHash`. + +```bash +git add Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift Plugins/BridgeJS/Tests/BridgeJSToolTests Makefile +git commit -m "feat(bridge-js): stamp + drift-check committed skeletons for cross-package safety" +``` + +--- + +## Self-Review + +**Spec coverage:** cross-package type discovery + correct input tracking (Task 2 build plugin, Task 3 command plugin), path resolution (Task 1), resolution parity (Task 4), end-to-end (Task 5), stale-skeleton drift guard (Task 6). Runtime composition needs no change (already cross-package via `SkeletonCollector`). ✓ + +**Follow-up (F-series) fixes folded in:** +- **F2 (module-name uniqueness)** — discovery no longer silently dedupes by name; a duplicate module name from two different packages is a hard `Diagnostics.error` (BridgeJS keys import objects, snippets, and closure thunks by module name). Task 2 Step 2. + +**Second-review fixes folded in:** +- **H1 (cross-package input tracking)** — `DependencySkeleton` now carries a distinct `inputFileURL`: same-package deps track the generated `BridgeJS.swift` (ordering edge); cross-package deps track the **committed `BridgeJS.json` they actually read**. The call site at `BridgeJSBuildPlugin.swift:76` appends `inputFileURL`, not the bogus work-dir `bridgeJSSwiftURL`. Task 2 Steps 2–3. +- **M2 (incomplete drift hash)** — the stamp is renamed `inputHash` and now covers Swift sources **+** `bridge-js.config.json` **+** `bridge-js.d.ts`/`.global.d.ts` **+** dependency `inputHash`es **+** a codegen/schema version, so config-only or toolchain-only changes can't silently stale a committed skeleton. Task 6 Steps 1–2, 4. + +**Placeholder scan:** Task 3 is conditional ("if restricted, generalize") because the command plugin may already cross packages; Step 1 is an explicit audit with a concrete fallback referencing Task 2's code. Acceptable — it's a verify-then-apply task, not deferred design. + +**Type consistency:** `committedSkeletonURL(targetDirectoryURL:)`, `DependencySkeleton` (`moduleName`/`skeletonURL`/`inputFileURL`), `inputHash`, `packageByTargetID`, `usedExternalModules`, `resolveApp`, `buildDependencySkeleton` all reference existing or newly-defined symbols used consistently. The committed path is identical in Task 1 and the command-plugin read (`BridgeJSCommandPlugin.swift:165-166`). + +--- + +## Execution Handoff + +Subagent-Driven (recommended) or Inline. Execute Task 1→2 first, then audit Task 3, then the tests. diff --git a/docs/superpowers/plans/2026-06-17-bridgejs-phase4-per-target-modules.md b/docs/superpowers/plans/2026-06-17-bridgejs-phase4-per-target-modules.md new file mode 100644 index 000000000..01704c7c4 --- /dev/null +++ b/docs/superpowers/plans/2026-06-17-bridgejs-phase4-per-target-modules.md @@ -0,0 +1,188 @@ +# BridgeJS Per-Target Modules + Shared Runtime (Phase 4, OPTIONAL) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +> +> **STATUS: OPTIONAL / ARCHITECTURAL.** None of the three originally-identified problems require this phase — they ship on the existing single-link architecture via Phases 1–3. Pursue this only to gain per-target incremental rebuilds and a manual-wiring (Vite) loop. This plan is **staged with explicit design decisions and spikes**; it is intentionally not reduced to line-precise copy-paste code, because the work is a structural refactor of a 3,783-line file and the exact code falls out of the design tasks below. Each task still defines its interface, files, and acceptance test. +> +> **On "distribution" (precise wording — fixes overstatement M2):** this phase does *not* enable shipping a prebuilt JS module that composes across toolchain versions. Because the inter-module boundary is regenerated per codegen version, a consumer must have the dependency's **skeleton + snippets** present and **regenerate** the per-target JS. "Independent distribution" therefore means "ship skeleton + snippets, regenerate JS at app build" — not "ship prebuilt JS." (This is the deliberate consequence of not freezing a runtime ABI.) + +**Goal:** Generate per-target bridge modules (`bridge-js.js` per Swift target) that compose against a shared runtime helper, so targets rebuild incrementally and a manual-wiring (Vite) loop becomes possible — **while keeping the inter-module boundary an internal, codegen-version-stamped detail, NOT a frozen public ABI** (this is our one deliberate divergence from Yuta's design). Note: per-target JS is regenerated per codegen version (ship skeleton + snippets, regenerate JS), not shipped prebuilt across toolchain versions. + +**Architecture:** Split `BridgeJSLink` into (a) a shared runtime helper owning the per-instance `bjs` ABI state, and (b) per-target emission keyed on a `primary` skeleton with `dependencies` available for lookups only. The app build regenerates all per-target modules with a single codegen version, so no cross-version runtime ABI is ever frozen. Composition becomes a thin merge driven by `instantiate.js`. + +**Tech Stack:** Swift (BridgeJSLink, PackageToJS), JS runtime templates, Swift Testing + snapshot harness, Vitest for runtime behavior. + +--- + +## Context & guardrails + +**Verified anchors:** +- Shared per-instance state is isolated in one function: `generateVariableDeclarations()` — `BridgeJSLink.swift:329-358` (stacks, return scratch, decoders, `bjs`, `_exports`), referenced symbolically via `JSGlueVariableScope.reserved*`. +- The ABI builtins (`bjs[...] = function...`) are generated in `generateAddImports(...)` — `BridgeJSLink.swift:394+`. +- Emission currently iterates **all** skeletons: `collectLinkData()` (`:197, 268, 294`), `enumHelperAssignments` (`:1229`), `structHelperAssignments` (`:1245`), `renderSwiftClassWrappers` (`:1265`), `intrinsicRegistry.classNamespaces` (`:1212`). +- Current composition contract: `instantiate.js` imports `createInstantiator` from a single merged `bridge-js.js` and calls `addImports`/`setInstance`/`createExports` (`instantiate.js:18, 118, 146-181`). +- PackageToJS produces the single merged `bridge-js.js` in one `BridgeJSLink` pass — `PackageToJS.swift:597-617`. + +**Non-negotiable guardrails (the divergence):** +1. No `bridgeJSRuntimeRange`/semver ABI between independently-built modules. Per-target modules carry a **codegen version stamp**; the app build asserts all modules share one stamp and **regenerates** on mismatch. +2. The durable cross-version contract stays the **skeleton JSON schema**, versioned by its codec (extend the existing `addSkeletonFile` version handling at `BridgeJSLink.swift:43-59`). +3. Per-target modules must not read raw shared-runtime storage; they touch shared state only through a passed context object. + +--- + +## Spike findings (COMPLETED 2026-06-17) + +All three spikes were resolved by reading the code. **Verdict: the restructure is feasible with no fundamental blocker, but the dominant work is wider than the outer module split — see Spike C's "critical finding."** These findings update Tasks 2–3 below. + +### Spike A — Shared-state surface inventory ✅ + +Source of truth: `JSGlueVariableScope` (`JSGlueGen.swift:12-37`) + `generateVariableDeclarations()` (`BridgeJSLink.swift:329-358`). + +| `reserved*` symbol (JS name) | Classification | Notes | +|---|---|---| +| `swift`, `instance`, `memory` | **Runtime-owned** | Set in `setInstance` (`:1154-1155`). | +| `decodeString`, `setException` | **Runtime-owned** | Built in `setInstance` (`:1158-1175`). `decodeString` holds the **only** `sharedMemory` branch. | +| `textEncoder`, `textDecoder` | **Runtime-owned** | Module-scope read-only constants. | +| `strStack`, `i32Stack`, `i64Stack`, `f32Stack`, `f64Stack`, `ptrStack`, `taStack` | **Context (shared mutable)** | Pushed/popped by both the `bjs` builtins **and** per-target thunks (`JSGlueGen.swift:105-128`). | +| `tmpRetString`, `tmpRetBytes`, `tmpRetException`, `tmpRetOptional{Bool,Int,Float,Double,HeapObject}` | **Context (shared mutable)** | Side-channel return storage: written by `bjs` builtins, read by per-target thunks. | +| `enumHelpers`, `structHelpers` | **Context (shared, per-target-contributed)** | Each target registers + reads its own helpers. | +| `swiftClosureRegistry`, `makeClosure` | **Context (shared)** | Closure factory/registry used by per-target closure thunks. | +| `bjs`, `_exports` | **Context (shared)** | The ABI import namespace and the merged exports object. | + +**`sharedMemory`:** confirmed a single site — `BridgeJSLink.swift:1158` (the `setInstance` `decodeString` selection). It does **not** appear in any per-target thunk. Memory-mode handling moves cleanly into the runtime helper. + +### Spike B — Cross-target reference inventory ✅ + +**Purely primary (emit from the primary skeleton only):** +- Swift class wrappers — grouped by module (`renderSwiftClassWrappers :1265`). +- Closure `invoke`/`lower`/`make` functions — named `...__` (`:836, 843, 844`); collected per `unified` skeleton (`:762-766`). +- Struct/enum helper factories + assignments — per defining target. +- Imported-thunk `ImportObjectBuilder`s — per module. + +**Need `dependencies` as a lookup index during primary emission:** +- `intrinsicRegistry.classNamespaces` (`:1212`) — built from **all** skeletons' namespaced classes; a primary struct/enum helper may reference a class namespace defined in a dependency. → build from `primary + dependencies`. +- Parameter/return type rendering that references a type from another module → resolve via the dependency skeletons (the `ExternalModuleIndex` model already used at generate time). + +**Coordination wrinkles (new tasks/notes):** +- `bjs["swift_js_closure_unregister"]` is set as a no-op default (`:760`) then overridden when any module uses closures (`:828`). With separate per-target modules, the **runtime helper must own** the real implementation (or registration must be idempotent) — modules cannot each clobber it inconsistently. +- `intrinsicRegistry` helper factories: target-independent ones (closure registry/`makeClosure`) → **runtime helper**; type-specific ones (struct/enum helpers) → **per-target**. + +### Spike C — Composition shape ✅ (design validated; runnable PoC deferred) + +Current shape (`generateJavaScript :1056-1205`): `createInstantiator(options, swift)` is one closure holding all shared `let` state and returning `{ addImports, setInstance, createExports }`. The `bjs` builtins and every per-target thunk **close over that shared lexical scope** (e.g. `JSGlueGen.swift:122` emits a bare `strStack.pop()`). + +Proposed interface maps cleanly: + +| `createInstantiator` piece | Proposed home | +|---|---| +| shared `let` state (`generateVariableDeclarations`) | runtime helper `createModuleContext()` → the `context` object (stacks, `tmpRet*`, helpers, registry, `bjs`, `_exports`) | +| target-independent `bjs` builtins (push/pop/return/string) | runtime helper `createBridgeRuntime().addBuiltins(importObject, context)` | +| imported thunks + class wrappers + closure thunks (per module) | per-target `module.register(context, importObject, importsContext)` | +| `setInstance` (instance/memory/decodeString/setException; `sharedMemory @:1158`) | runtime helper `runtime.setInstance(instance)` sets `context` fields | +| `createExports` body (class defs, helper assignments, namespace init, property assignments) | per-target `module.createExports(context)` → merged via `Object.assign` | + +```ts +// runtime helper (bridge-js-runtime.js) +function createBridgeRuntime(swift, opts): { + createModuleContext(): BridgeModuleContext; // owns shared mutable ABI state + addBuiltins(importObject, context): void; // target-independent bjs.* + setInstance(instance): void; // sets context.instance/memory/decodeString/... +}; +// per-target module (Generated/JavaScript/bridge-js.js) +function createBridgeModule(): { + moduleName: string; + codegenVersion: string; // guardrail 1 stamp + register(context, importObject, importsContext): void; + createExports(context): Record; +}; +``` + +**CRITICAL FINDING (the real cost):** the heavy-lifting is **not** the outer structure — it's that per-target thunk/lowering codegen references shared state by **bare lexical name** (`strStack`, `tmpRetString`, `bjs`, `_exports`, …) via closure capture. For a thunk to live in a separate module, every such reference must go through the passed `context` (e.g. `context.strStack`) — or each `register`/`createExports` destructures the context at its top. This touches the lowering/lifting fragment generators in **`JSGlueGen.swift`** and `ImportedThunkBuilder`/`ImportObjectBuilder` pervasively. No blocker, but it is the dominant, wide edit — and exactly the internal (non-public, regenerable) contract our divergence relies on. **This is now the core of Task 3.** + +**Runnable two-module PoC: deferred.** It needs a build + sample wasm and only pays off if the restructure is greenlit. The design-level mapping above is sufficient to validate the architecture for the design discussion. When greenlit, build the PoC as Task 2.5 before committing the codegen rewrite. + +### Bottom line for the design discussion +The counter-proposal's thesis holds: per-target modules + shared runtime are achievable **without** a frozen public ABI. The cost is concentrated and predictable (context-threading through the lowering codegen), and it is exactly the surface our "regenerate per codegen version" stance keeps internal. Nothing here changes the recommendation; it sharpens the effort estimate (Task 3 is the big one). + +--- + +## Task 1: Introduce `BridgeJSLinkInput { primary; dependencies }` (no output change yet) + +**Files:** `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` + +- [ ] **Step 1: Add the input type and a `primary`-aware initializer** that defaults `primary = all, dependencies = []` so current callers are unchanged. +- [ ] **Step 2: Thread `primary` vs `all`** through `collectLinkData()` and the emission helpers per Spike B: emission loops iterate `primary`; lookup structures (`intrinsicRegistry.classNamespaces`, closure-signature collection, `ExternalModuleIndex`-style resolution) iterate `primary + dependencies`. +- [ ] **Step 3: Characterization test.** With `primary = all skeletons, dependencies = []`, assert `.link()` output is **byte-identical** to the pre-refactor snapshots. + +Run: `swift test --package-path Plugins/BridgeJS --filter BridgeJSLinkTests` +Expected: PASS with zero snapshot diffs (pure refactor). + +- [ ] **Step 4: Commit** — `refactor(bridge-js): introduce primary/dependencies link input (behavior-preserving)` + +--- + +## Task 2: Extract the shared runtime helper + +**Files:** `Plugins/BridgeJS/Sources/BridgeJSLink/` (new emitter for `bridge-js-runtime.js`), `Plugins/PackageToJS/Templates/` + +- [ ] **Step 1: Define the `BridgeModuleContext` shape** from Spike A's "Context (shared mutable)" rows — stacks (`strStack`/`i32Stack`/`i64Stack`/`f32Stack`/`f64Stack`/`ptrStack`/`taStack`), return scratch (`tmpRet*`), `enumHelpers`, `structHelpers`, `swiftClosureRegistry`, `makeClosure`, `bjs`, `_exports`, plus the runtime-owned `instance`/`memory`/`decodeString`/`setException` set later. This is the internal (regenerable, version-stamped) contract — not a public ABI. +- [ ] **Step 2: Emit `bridge-js-runtime.js`** exposing `createBridgeRuntime(swift, opts)` with `createModuleContext()` (allocates the context from Step 1), `addBuiltins(importObject, context)` (the target-independent `bjs.*` push/pop/return/string builtins from `generateAddImports`, now operating on `context.*`), and `setInstance(instance)` (sets `context.instance/memory/decodeString/setException`; the single `sharedMemory` branch from `:1158` lives here). Own the closure registry/`makeClosure` and the real `swift_js_closure_unregister` here (Spike B coordination wrinkle). +- [ ] **Step 3: Keep the legacy merged path working** by having the current `createInstantiator` build a context + delegate to the runtime helper internally (so existing `instantiate.js` keeps functioning during the transition). +- [ ] **Step 4: Vitest behavior test** — instantiate an existing example and assert exports behave identically (round-trip a string and a class method). +- [ ] **Step 5: Commit** — `refactor(bridge-js): extract shared bridge-js-runtime.js helper + context` + +--- + +## Task 3: Emit per-target modules from AoT + +**Files:** `Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift`, `Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift` + +> **This is the dominant task (Spike C critical finding).** The hard, pervasive work is rewriting the lowering/lifting codegen so per-target thunks reference shared state through the passed `context` instead of bare lexical names. + +- [ ] **Step 1: Thread `context` through the lowering primitives.** In `JSGlueGen.swift` (e.g. the bare `strStack.pop()` / `i32Stack.push(...)` at `:105-128`) and `ImportedThunkBuilder`/`ImportObjectBuilder`, route every `JSGlueVariableScope.reserved*` *Context* symbol (Spike A) through the context — either `context.strStack` references or a destructuring preamble (`const { strStack, tmpRetString, … } = context;`) at the top of each `register`/`createExports`. Add a unit test that a rendered thunk contains no bare shared-state identifier. +- [ ] **Step 2: Add a per-target emit entry** producing `Generated/JavaScript/bridge-js.js` for the `primary` skeleton (using `dependencies` for lookups), implementing the Spike C module interface (`createBridgeModule()` → `{ moduleName, codegenVersion, register, createExports }`), and writing a **codegen version stamp** (guardrail 1). +- [ ] **Step 3: Wire it into the `generate` subcommand** alongside the skeleton and the Phase-1 `.d.ts` (so a target emits skeleton + `.d.ts` + per-target `bridge-js.js` together). +- [ ] **Step 4: Snapshot per-target output** for a multi-target input; assert each target's module references only its own symbols + the shared `context`/runtime (never inlines a dependency's bridge code — guardrail 3). +- [ ] **Step 5: Commit** — `feat(bridge-js): emit per-target bridge modules from AoT` + +--- + +## Task 4: Compose per-target modules in PackageToJS + instantiate + +**Files:** `Plugins/PackageToJS/Sources/PackageToJS.swift`, `Plugins/PackageToJS/Templates/instantiate.js` + +- [ ] **Step 1: Replace the single-pass merge** (`PackageToJS.swift:597-617`) with collection of per-target modules (reuse the `SkeletonCollector` traversal to find each target's `bridge-js.js`) and a thin composition: `createBridgeRuntime` + `modules.map(register)` + `Object.assign(...modules.map(createExports))`. +- [ ] **Step 2: Version-stamp assertion** — PackageToJS verifies all collected modules share the codegen stamp; on mismatch it **regenerates**, per guardrail 1. No semver range, no compatibility matrix. **Regeneration target (F6):** dependency packages are read-only checkouts, so regenerated per-target modules are written to the **plugin work directory** (never into the checkout), and the composition step must prefer the work-dir regenerated module over the stale committed one (same shadowing model the build plugin already uses for same-package skeletons). Only if regeneration is impossible (no skeleton available) does it error with a "run bridge-js generate in " diagnostic. +- [ ] **Step 3: Keep a fallback** to the legacy single-link path behind a flag until parity is proven. +- [ ] **Step 4: End-to-end Vitest** on a multi-target example: identical observable behavior to the legacy path. +- [ ] **Step 5: Commit** — `feat(packagetojs): compose per-target bridge modules via shared runtime` + +--- + +## Task 5: Manual-wiring (Vite) ergonomics + +**Files:** docs + a `Examples/ViteManualWiring/` fixture + +- [ ] **Step 1: Document** importing per-target `bridge-js.js` + `bridge-js-runtime.js` directly in a Vite app, composing with the same thin merge PackageToJS uses. +- [ ] **Step 2: Fixture** proving a bundler resolves the per-target modules as plain ESM (no synthetic package names; subpath export or relative path). +- [ ] **Step 3: Commit** — `docs(bridge-js): manual-wiring composition guide + example` + +--- + +## Self-Review + +**Spec coverage:** primary/dependencies split (Task 1), shared runtime (Task 2), per-target emission (Task 3), thin composition + version stamping instead of frozen ABI (Task 4), manual-wiring (Task 5). Guardrails 1–3 are enforced in Tasks 3–4. ✓ + +**Follow-up (F-series) fix folded in:** F6 — stamp-mismatch regeneration writes to the **plugin work directory** (dependency checkouts are read-only) and composition prefers the regenerated module over the stale committed one. Task 4 Step 2. ✓ + +**Placeholder scan:** This plan intentionally specifies interfaces + acceptance tests rather than full code, and front-loads the uncertainty into Spikes A–C. That is appropriate for an architectural refactor and is explicitly flagged at the top; it is not hidden deferral. Before executing Tasks 1–4, the spikes must produce concrete outputs (state table, reference inventory, interface sketch + PoC). + +**Type consistency:** `BridgeJSLinkInput`, `createBridgeRuntime`, `createModuleContext`, `createBridgeModule`, `register`, `createExports`, and the codegen version stamp are named consistently across tasks and match the Spike C interface. + +**Relationship to Yuta's design:** Tasks 1–4 are the same structural moves Yuta proposes. The **only** divergence lives in Task 3 Step 1 and Task 4 Step 2 (codegen-version stamp + regenerate, instead of `bridgeJSRuntimeRange` semver) and guardrails 1–3. Everything else aligns. + +--- + +## Execution Handoff + +Do the spikes first. Then Subagent-Driven (recommended) per task with snapshot/Vitest parity gates between tasks. Keep the legacy path until Task 4 parity is proven.