From 507dae629819edf7cfdc77730668083aa54ccd3e Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Mon, 28 Sep 2026 21:12:15 +0200 Subject: [PATCH 1/5] BridgeJS: Support generic functions on exported Swift APIs Export half of generic function support (#398); the import half landed in #799 and this reuses its ABI: values cross on each type's existing stack layout, and a trailing i32 type ID per generic parameter selects the concrete type, so the per-function glue stays type-agnostic. @JS public func identity(_ value: T) -> T { value } const n = exports.identity(42, BridgeTypes.Int); // TS erases generics, const p = exports.identity(pt, BridgeTypes.Point); // so callers pass a token How it works: - The wasm entry point is a concrete @_expose thunk taking the trailing type IDs. Because a type ID is the address of the type's BridgeJSTypeHandle, the thunk recovers the concrete type directly via Unmanaged.fromOpaque and reifies T through an opened existential (nested opening chain for multiple parameters); the body then runs the same pop-call-push sequence a concrete export would. - Generic thunks always hoist stack-using arguments: the JS wrapper lowers self and every argument in declaration order, so the pops must run in reverse declaration order ahead of any self pop embedded in the callee expression. - The JS wrapper resolves the caller's BridgeTypes token through the map built during type-handle registration and throws a catchable TypeError for unknown tokens before anything is lowered, so the shared value stack stays balanced. A raw wasm caller passing a garbage type ID is undefined behavior; the generated wrapper is the only supported caller. - Tokens are unqualified type names, so linking two modules that define same-named @JS types fails the build while generics are in use. - Generics work on top-level functions and on instance and static methods of @JS classes, structs, and enums, with T bare or wrapped as [T], T?, or [String: T]. - Type recovery needs the handle's metatype storage (an existential), so exported generic thunks are fatalError stubs under Embedded Swift; imports remain Embedded-compatible. Unsupported forms (async or throws generics, where clauses, unused generic parameters, concrete non-Void returns, generic initializers, default parameter values, nested wrappings) are rejected with build-time diagnostics. --- Plugins/BridgeJS/README.md | 2 +- .../Sources/BridgeJSCore/ExportSwift.swift | 218 +++++++++++- .../BridgeJSCore/SwiftToSkeleton.swift | 161 ++++++++- .../Sources/BridgeJSLink/BridgeJSLink.swift | 329 ++++++++++++++---- .../Sources/BridgeJSLink/JSGlueGen.swift | 121 +++++++ .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 58 ++- .../GenericExportDiagnosticsTests.swift | 195 ++++++++++- .../BridgeJS-Internals/Design-Rationale.md | 10 + .../Exporting-Swift-Function.md | 93 ++++- .../Articles/BridgeJS/Supported-Types.md | 10 +- .../Articles/BridgeJS/Unsupported-Features.md | 19 + 11 files changed, 1096 insertions(+), 120 deletions(-) diff --git a/Plugins/BridgeJS/README.md b/Plugins/BridgeJS/README.md index 0905695c5..70eaebabf 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) | -| 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) | +| Generic function or method (`T`, `[T]`, `T?`, `[String: T]`) | `(value: T, type: BridgeType)` | Depends on `T` | ✅ | ### Import-specific (TypeScript -> Swift) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 6b5d3ef76..21cd35f6a 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -185,15 +185,28 @@ public class ExportSwift { var externDecls: [DeclSyntax] = [] let effects: Effects + /// Generic parameters of the exported function, in declaration order. + /// When non-empty, `render(abiName:)` wraps the standard body in a + /// generics prologue: the entry thunk takes one trailing type-ID ABI + /// parameter per generic, recovers the concrete types from their + /// `BridgeJSTypeHandle`s, and opens them into a chain of helper + /// functions; the innermost helper runs the standard body with the + /// generic parameters bound. + let genericParameterNames: [String] + /// The async return type settled through `_bjs_makePromise`'s `Promise_resolve_` /// helper. Set for every `async` thunk. var asyncResolveReturnType: BridgeType? var parameterBindings: [CodeBlockItemSyntax] = [] - init(effects: Effects, returnType: BridgeType) throws { + init(effects: Effects, returnType: BridgeType, genericParameterNames: [String] = []) throws { self.effects = effects + self.genericParameterNames = genericParameterNames guard effects.isAsync else { return } + guard genericParameterNames.isEmpty else { + throw BridgeJSCoreError("Generic exported functions cannot be 'async' yet") + } guard returnType.isAsyncResolvable else { throw BridgeJSCoreError( "Returning '\(returnType.swiftType)' from an async exported function is not yet supported" @@ -213,6 +226,18 @@ public class ExportSwift { func liftParameter(param: Parameter) throws { parameters.append(param) + if param.type.usesGenericParameter { + // Generic values cross the bridge on the shared value stack: + // they contribute no ABI parameters, and the lift is the + // opened type's own stack pop. + guard let popExpression = param.type.exportGenericStackPopExpression else { + throw BridgeJSCoreError( + "Unsupported generic parameter shape for '\(param.name)': \(param.type.swiftType)" + ) + } + parameterBindings.insert("let \(raw: param.name) = \(raw: popExpression)", at: 0) + return + } let liftingInfo = try param.type.liftParameterInfo() let argumentsToLift: [String] if liftingInfo.parameters.count == 1 { @@ -302,8 +327,11 @@ public class ExportSwift { return CodeBlockItemSyntax(item: .init(ExpressionStmtSyntax(expression: callExpr))) } else { let (prefix, suffix) = protocolCastSuffix(for: returnType) + // A generic result needs an explicit annotation so return-only + // generics (where nothing else pins `T`) infer correctly. + let annotation = returnType.usesGenericParameter ? ": \(returnType.swiftType)" : "" return CodeBlockItemSyntax( - item: .init(DeclSyntax("let ret = \(raw: prefix)\(raw: callExpr)\(raw: suffix)")) + item: .init(DeclSyntax("let ret\(raw: annotation) = \(raw: prefix)\(raw: callExpr)\(raw: suffix)")) ) } } @@ -371,6 +399,12 @@ public class ExportSwift { // The async return value is lowered by the generated `Promise_resolve_*` helper. return } + if let pushStatement = returnType.exportGenericStackPushStatement(value: "ret") { + // Generic results return on the shared value stack via the + // opened type's own stack push. + append("\(raw: pushStatement)") + return + } switch returnType { case .closure(_, useJSTypedClosure: false): @@ -423,6 +457,9 @@ public class ExportSwift { } func render(abiName: String) -> DeclSyntax { + if !genericParameterNames.isEmpty { + return renderGenericEntryThunk(abiName: abiName) + } var bindings = parameterBindings let body: CodeBlockItemListSyntax if effects.isAsync, let resolveType = asyncResolveReturnType { @@ -490,6 +527,130 @@ public class ExportSwift { private func returnPlaceholderStmt() -> String { return abiReturnType?.swiftReturnPlaceholderStmt ?? "return" } + + /// The constraint composition a generic parameter is opened under, e.g. + /// `BridgedSwiftGenericBridgeable`. A hook for protocol constraints. + func constraintComposition(_ genericName: String) -> String { + "BridgedSwiftGenericBridgeable" + } + + /// The existential metatype spelling used to thread a not-yet-opened + /// generic parameter through the open chain. + func existentialMetatype(_ genericName: String) -> String { + "any BridgedSwiftGenericBridgeable.Type" + } + + /// Renders the wasm entry point for a generic exported function. + /// + /// Generics are purely a prologue concern: the `@_expose` entry thunk + /// takes the concrete ABI parameters plus one trailing `Int32` type ID + /// per generic parameter, recovers each concrete type from its + /// `BridgeJSTypeHandle` (the ID is the handle object's address), and + /// opens the existential metatypes through `__openN` helpers. + /// The innermost helper runs the *standard* builder-emitted body + /// (liftParameter → call → lowerReturnValue) with every generic + /// parameter bound, so generic and concrete functions share the same + /// body-emission path. + /// + /// The handle's `type` storage is a metatype existential, which + /// Embedded Swift does not support, so generic exports compile to a + /// trapping stub under Embedded. + private func renderGenericEntryThunk(abiName: String) -> DeclSyntax { + let genericNames = genericParameterNames + let count = genericNames.count + func genericIndex(_ genericName: String) -> Int { genericNames.firstIndex(of: genericName)! } + func metatypeName(_ genericName: String) -> String { "_generic\(genericIndex(genericName))Type" } + func typeIdName(_ genericName: String) -> String { + ABINameGenerator.genericTypeIdParameterName(index: genericIndex(genericName)) + } + + let concreteABIParameters = abiParameterSignatures + let concreteABINames = concreteABIParameters.map { $0.name } + let thunkSignature = SwiftSignatureBuilder.buildABIFunctionSignature( + abiParameters: concreteABIParameters + genericNames.map { (name: typeIdName($0), type: .i32) }, + returnType: abiReturnType + ) + + let printer = CodeFragmentPrinter() + printer.write("#if hasFeature(Embedded)") + printer.write(SwiftCodePattern.buildExposeAttributes(abiName: abiName)) + printer.write("public func _\(abiName)\(thunkSignature) {") + printer.indent { + printer.write( + "fatalError(\"Generic @JS exported functions are not supported in Embedded Swift\")" + ) + } + printer.write("}") + printer.write("#else") + let entryDecl = SwiftCodePattern.buildExposedFunctionDecl( + abiName: abiName, + signature: thunkSignature + ) { printer in + for genericName in genericNames { + // The incoming type ID is the address of the type's unique + // `BridgeJSTypeHandle`; recover the type from it. A raw wasm + // caller passing a garbage ID is undefined behavior — the + // generated JS wrapper validates tokens before calling in. + printer.write( + "let \(metatypeName(genericName)) = " + + "Unmanaged.fromOpaque(" + + "UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: \(typeIdName(genericName)))))!" + + ").takeUnretainedValue().type" + ) + } + let open1Arguments = genericNames.map { metatypeName($0) } + concreteABINames + printer.write("_\(abiName)_open1(\(open1Arguments.joined(separator: ", ")))") + } + printer.write(multilineString: entryDecl.description) + + for k in 1...count { + let openName = "_\(abiName)_open\(k)" + let openedName = genericNames[k - 1] + let alreadyOpened = Array(genericNames[0..<(k - 1)]) + let remaining = Array(genericNames[k.." + + var params: [String] = [] + params.append("_ \(metatypeName(openedName)): \(openedName).Type") + for openedAlready in alreadyOpened { + params.append("as\(openedAlready) \(metatypeName(openedAlready)): \(openedAlready).Type") + } + for remainingName in remaining { + params.append("_ \(metatypeName(remainingName)): \(existentialMetatype(remainingName))") + } + for abiParam in concreteABIParameters { + params.append("_ \(abiParam.name): \(abiParam.type.swiftType)") + } + + printer.write("private func \(openName)\(genericClause)(\(params.joined(separator: ", "))) {") + printer.indent { + if k < count { + let nextOpenedName = genericNames[k] + var callArguments: [String] = [metatypeName(nextOpenedName)] + for threadedName in genericNames[0.. DeclSyntax { - let builder = try ExportedThunkBuilder(effects: function.effects, returnType: function.returnType) + let builder = try ExportedThunkBuilder( + effects: function.effects, + returnType: function.returnType, + genericParameterNames: function.genericParameterNames + ) for param in function.parameters { try builder.liftParameter(param: param) } @@ -655,7 +820,11 @@ public class ExportSwift { ownerTypeName: String, instanceSelfType: BridgeType ) throws -> DeclSyntax { - let builder = try ExportedThunkBuilder(effects: method.effects, returnType: method.returnType) + let builder = try ExportedThunkBuilder( + effects: method.effects, + returnType: method.returnType, + genericParameterNames: method.genericParameterNames + ) if !method.effects.isStatic { try builder.liftParameter(param: Parameter(label: nil, name: "_self", type: instanceSelfType)) } @@ -922,7 +1091,7 @@ struct StackCodegen { return "()" case .generic: fatalError( - "Generic parameters are only supported on imported declarations, not exported concrete-type codegen" + "Generic parameters cannot appear in struct fields or enum payloads; they are handled by the exported thunk builder" ) } } @@ -971,7 +1140,7 @@ struct StackCodegen { return lowerDictionaryStatements(valueType: valueType, accessor: accessor, varPrefix: varPrefix) case .generic: fatalError( - "Generic parameters are only supported on imported declarations, not exported concrete-type codegen" + "Generic parameters cannot appear in struct fields or enum payloads; they are handled by the exported thunk builder" ) } } @@ -1664,6 +1833,33 @@ extension BridgeType { } } + /// Stack expressions for every generic shape on the *export* side. + /// + /// Imports can fall back to their ordinary emission for `[T]` and + /// `[String: T]` because it already routes through the `Array` and + /// `Dictionary` stack conformances. An exported thunk cannot: its ordinary + /// emission lifts a dictionary parameter with `bridgeJSLiftParameter()` and + /// returns one with `bridgeJSLowerReturn()`, neither of which touches the + /// shared value stack the generic ABI is built on. So the export side spells + /// out all four shapes. + var exportGenericStackPopExpression: String? { + switch self { + case .generic(let name): return "\(name).bridgeJSStackPop()" + case .array(.generic(let name)): return "Array<\(name)>.bridgeJSStackPop()" + case .nullable(.generic(let name), _): return "Optional<\(name)>.bridgeJSStackPop()" + case .dictionary(.generic(let name)): return "Dictionary.bridgeJSStackPop()" + default: return nil + } + } + + func exportGenericStackPushStatement(value: String) -> String? { + switch self { + case .generic, .array(.generic), .nullable(.generic, _), .dictionary(.generic): + return "\(value).bridgeJSStackPush()" + default: return nil + } + } + var swiftType: String { switch self { case .bool: return "Bool" @@ -1768,9 +1964,8 @@ extension BridgeType { 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" - ) + // Generic values cross on the shared value stack; no ABI parameters. + return LiftingIntrinsicInfo(parameters: []) } } @@ -1825,9 +2020,8 @@ extension BridgeType { 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" - ) + // Generic results return on the shared value stack; no ABI return. + return LoweringIntrinsicInfo(returnType: nil) } } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index 0f9f79c06..7fda90fb9 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -1555,15 +1555,42 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { return .array(elements) } + /// Resolves a type that may reference one of the enclosing declaration's + /// generic parameters (bare `T` or wrapped `[T]`, `T?`, `[String: T]`), + /// falling back to the regular type lookup for concrete types. + private func lookupTypeWithGenerics( + for type: TypeSyntax, + genericParameterNames: [String], + errors: inout [DiagnosticError] + ) -> 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 parent.lookupType(for: type, errors: &errors) + } + } + /// Shared parameter parsing logic used by functions, initializers, and protocol methods private func parseParameters( from parameterClause: FunctionParameterClauseSyntax, - allowDefaults: Bool = true + allowDefaults: Bool = true, + genericParameterNames: [String] = [] ) -> [Parameter] { var parameters: [Parameter] = [] for param in parameterClause.parameters { - let resolvedType = withLookupErrors { self.parent.lookupType(for: param.type, errors: &$0) } + let resolvedType = withLookupErrors { + self.lookupTypeWithGenerics( + for: param.type, + genericParameterNames: genericParameterNames, + errors: &$0 + ) + } guard let type = resolvedType else { continue // Skip unsupported types } @@ -1655,13 +1682,41 @@ 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 + var genericParameterNames: [String] = [] + if let genericClause = node.genericParameterClause { + for genericParam in genericClause.parameters { + let constraint = genericParam.inheritedType?.trimmedDescription ?? "" + guard SwiftToSkeleton.isBridgeableGenericConstraint(constraint) else { + diagnose( + node: node, + message: + "Generic parameter '\(genericParam.name.text)' must be constrained to 'BridgedSwiftGenericBridgeable' to be used with @JS." + ) + return nil + } + genericParameterNames.append(genericParam.name.text) + } + if node.genericWhereClause != nil { + diagnose( + node: node, + message: "'where' clauses are not supported on generic @JS functions." + ) + return nil + } + if node.signature.effectSpecifiers?.asyncSpecifier != nil { + diagnose( + node: node, + message: "Generic @JS functions cannot be 'async' yet." + ) + return nil + } + if node.signature.effectSpecifiers?.throwsClause != nil { + diagnose( + node: node, + message: "Generic @JS functions cannot be 'throws' yet." + ) + return nil + } } let name = node.name.text @@ -1694,10 +1749,20 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { ) } - let parameters = parseParameters(from: node.signature.parameterClause, allowDefaults: true) + let parameters = parseParameters( + from: node.signature.parameterClause, + allowDefaults: true, + genericParameterNames: genericParameterNames + ) let returnType: BridgeType if let returnClause = node.signature.returnClause { - let resolvedType = withLookupErrors { self.parent.lookupType(for: returnClause.type, errors: &$0) } + let resolvedType = withLookupErrors { + self.lookupTypeWithGenerics( + for: returnClause.type, + genericParameterNames: genericParameterNames, + errors: &$0 + ) + } if let type = resolvedType, case .nullable(let wrappedType, _) = type, wrappedType.isOptional { diagnoseNestedOptional(node: returnClause.type, type: returnClause.type.trimmedDescription) @@ -1710,6 +1775,51 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { returnType = .void } + if !genericParameterNames.isEmpty { + // The generated JS wrapper appends one required `BridgeType` token + // parameter per generic parameter after the declared ones, so a + // defaulted parameter could never actually be omitted by a caller. + // Reject the combination instead of silently requiring the value. + if parameters.contains(where: { $0.defaultValue != nil }) { + diagnose( + node: node, + message: "Default parameter values are not supported on generic @JS functions.", + hint: + "JavaScript callers pass a trailing BridgeType token after the declared parameters, so a defaulted parameter could never be omitted. Remove the default value or provide a non-generic overload." + ) + return nil + } + let returnGenericName: String? + switch returnType { + case .void: + returnGenericName = nil + default: + guard let returnName = returnType.referencedGenericName, + genericParameterNames.contains(returnName) + else { + diagnose( + node: node, + message: + "A generic @JS function must return the generic type (optionally wrapped in '[T]', 'T?' or '[String: T]') or Void." + ) + return nil + } + returnGenericName = returnName + } + for genericName in genericParameterNames { + let usedInParameter = parameters.contains { $0.type.referencedGenericName == genericName } + let usedInReturn = returnGenericName == genericName + if !usedInParameter && !usedInReturn { + diagnose( + node: node, + message: + "The generic parameter '\(genericName)' must be used in at least one parameter or the return type of a generic @JS function." + ) + return nil + } + } + } + let abiName: String let staticContext: StaticContext? @@ -1772,7 +1882,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { effects: effects, namespace: finalNamespace, staticContext: staticContext, - documentation: extractDocumentation(from: node) + documentation: extractDocumentation(from: node), + genericParameters: genericParameterNames.isEmpty ? nil : genericParameterNames ) } @@ -1932,6 +2043,19 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { diagnoseUnsupportedJSName(from: jsAttribute) + // Without this early check, the generic parameter would fall through to + // type lookup and surface as a confusing "Unsupported type 'T'". Generic + // *imported* initializers are supported, so users will try the exported + // spelling. + if node.genericParameterClause != nil || node.genericWhereClause != nil { + diagnose( + node: node, + message: "Generic initializers are not supported on exported @JS types yet.", + hint: "Use a generic @JS method or a generic @JSFunction initializer on an imported @JSClass instead." + ) + return .skipChildren + } + switch state { case .classBody(_, let classKey): if extractNamespace(from: jsAttribute) != nil { @@ -2630,6 +2754,19 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { diagnoseUnsupportedJSName(from: jsAttribute) } + // Without this early check the generic parameter falls through to type + // lookup and surfaces as "Unsupported type 'T'", pointing away from the + // actual cause. + if node.genericParameterClause != nil || node.genericWhereClause != nil { + diagnose( + node: node, + message: "Generic requirements are not supported on @JS protocols yet.", + hint: + "Constrain a generic @JS function or method to the protocol instead: ''." + ) + return nil + } + let name = node.name.text let parameters = parseParameters(from: node.signature.parameterClause, allowDefaults: false) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 40b1c13c3..f6304b7e5 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -28,8 +28,12 @@ public struct BridgeJSLink { skeletons.compactMap(\.exported).compactMap(\.identityMode).first ?? "none" } + var hasGenericExports: Bool { + skeletons.contains { $0.exported?.hasGenericDeclarations ?? false } + } + var hasGenerics: Bool { - skeletons.contains { $0.imported?.hasGenericDeclarations ?? false } + hasGenericExports || skeletons.contains { $0.imported?.hasGenericDeclarations ?? false } } /// Whether a class should use identity caching based on its annotation and the config default. @@ -249,6 +253,12 @@ public struct BridgeJSLink { } } + if hasGenericExports { + let tokens = try generateBridgeTypeTokens() + data.topLevelTypeLines.append(contentsOf: tokens.js) + data.topLevelDtsTypeLines.append(contentsOf: tokens.dts) + } + // Process imported skeletons for unified in skeletons { guard let imported = unified.imported else { continue } @@ -341,6 +351,9 @@ public struct BridgeJSLink { ] if hasGenerics { declarations.append("const \(JSGlueVariableScope.reservedCodecByTypeId) = new Map();") + if hasGenericExports { + declarations.append("const \(JSGlueVariableScope.reservedTypeIdByToken) = new Map();") + } declarations.append("let __bjs_typeHandlesRegistered = false;") declarations.append("function __bjs_registerTypeHandles() {") declarations.append(" if (__bjs_typeHandlesRegistered) {") @@ -357,6 +370,9 @@ public struct BridgeJSLink { } declarations.append("}") declarations.append(contentsOf: GenericJSCodegen.runtimeHelperDeclarations()) + if hasGenericExports { + declarations.append(contentsOf: GenericJSCodegen.exportRuntimeHelperDeclarations()) + } } declarations.append(contentsOf: [ "", @@ -412,13 +428,25 @@ public struct BridgeJSLink { try ContainerCodecJS.codecExpression(for: type, context: makeCodecPrintContext(printer: printer)) } - private func writeTypeHandleRegistrationBody(into printer: CodeFragmentPrinter) { + /// Emits the shared tail of a type-handle registration hook: pair the wasm-provided + /// type IDs with the codec table built by the caller, index by index. + /// + /// When `tokens` is non-nil the same index-by-index pairing also binds the JS-facing + /// `BridgeTypes` tokens to their IDs. Generic export wrappers resolve tokens through + /// that map and throw before entering wasm when a token is unknown. + private func writeTypeHandleRegistrationBody(tokens: [String]?, into printer: CodeFragmentPrinter) { + if let tokens { + printer.write("const tokens = [\(tokens.map { "\"\($0)\"" }.joined(separator: ", "))];") + } 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]);") + if tokens != nil { + printer.write("\(JSGlueVariableScope.reservedTypeIdByToken).set(tokens[i], typeIds[i]);") + } } printer.write("}") } @@ -439,7 +467,10 @@ public struct BridgeJSLink { } } printer.write("];") - writeTypeHandleRegistrationBody(into: printer) + writeTypeHandleRegistrationBody( + tokens: hasGenericExports ? BridgeType.genericBridgeablePrimitives.map(\.token) : nil, + into: printer + ) } printer.write("}") } @@ -465,12 +496,71 @@ public struct BridgeJSLink { } } printer.write("];") - writeTypeHandleRegistrationBody(into: printer) + writeTypeHandleRegistrationBody( + tokens: hasGenericExports ? moduleEntries.map(\.token) : nil, + into: printer + ) } printer.write("}") } } + /// One `BridgeTypes` entry per bridgeable type across all linked modules. + /// Tokens are unqualified type names, so a name shared by two modules must + /// fail the build (the JS-facing token could not distinguish them). + private func bridgeTypeTokenEntries() throws -> [(token: String, tsType: String)] { + var entries: [(token: String, tsType: String)] = BridgeType.genericBridgeablePrimitives.map { + (token: $0.token, tsType: $0.type.tsType) + } + var definingModuleByToken: [String: String] = Dictionary( + uniqueKeysWithValues: BridgeType.genericBridgeablePrimitives.map { ($0.token, "JavaScriptKit") } + ) + func claim(_ token: String, module: String) throws { + if let existing = definingModuleByToken[token], existing != module { + throw BridgeJSLinkError( + message: + "Generic type token '\(token)' is defined by both '\(existing)' and '\(module)'; type names used with generics must be unique across linked modules" + ) + } + definingModuleByToken[token] = module + } + // Build the table from the same canonical list that drives type-handle + // registration, and resolve TypeScript names the way every other + // signature in the d.ts does (class interfaces are emitted at the top + // level even for namespaced classes, so a hand-qualified `API.Node` + // would reference a namespace member that is never declared). + for unified in skeletons { + guard let exported = unified.exported else { continue } + for entry in exported.genericBridgeableTypeEntries { + try claim(entry.token, module: unified.moduleName) + entries.append((token: entry.token, tsType: resolveTypeScriptType(entry.bridgeType))) + } + } + return entries + } + + /// The top-level `BridgeTypes` token map generic-export callers pass their + /// type selections through, plus its branded d.ts typing. + private func generateBridgeTypeTokens() throws -> (js: [String], dts: [String]) { + let entries = try bridgeTypeTokenEntries() + + let jsEntries = entries.map { "\($0.token): \"\($0.token)\"" } + let jsLines = ["export const BridgeTypes = { \(jsEntries.joined(separator: ", ")) };"] + + // A required `unique symbol` brand: a raw string literal such as "Int" + // must not satisfy `BridgeType`, only `BridgeTypes.Int` does. + // (An optional phantom property would let any string through.) + var dtsLines: [String] = [] + dtsLines.append("declare const bridgeTypeBrand: unique symbol;") + dtsLines.append( + "export type BridgeType = string & { readonly [bridgeTypeBrand]: (value: T) => void };" + ) + let dtsEntries = entries.map { "readonly \($0.token): BridgeType<\($0.tsType)>;" } + dtsLines.append("export const BridgeTypes: { \(dtsEntries.joined(separator: " ")) };") + + return (js: jsLines, dts: dtsLines) + } + private func generateAddImports(needsImportsObject: Bool) throws -> CodeFragmentPrinter { let printer = CodeFragmentPrinter() let allStructs = skeletons.flatMap { unified in @@ -1075,9 +1165,7 @@ public struct BridgeJSLink { parameters: function.parameters ) ) - printer.write( - "\(function.resolvedJSName)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" - ) + printer.write("\(renderExportMemberTSSignature(function: function));") } for property in enumDefinition.staticProperties { let readonly = property.isReadonly ? "readonly " : "" @@ -1129,9 +1217,7 @@ public struct BridgeJSLink { }, renderFunctionEntry: { function in return self.renderJSDoc(documentation: function.documentation, parameters: function.parameters) - + [ - "\(function.resolvedJSName)\(self.renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" - ] + + ["\(self.renderExportMemberTSSignature(function: function));"] }, renderPropertyEntry: { property in let readonly = property.isReadonly ? "readonly " : "" @@ -1564,6 +1650,15 @@ public struct BridgeJSLink { let effects: Effects let scope: JSGlueVariableScope let context: IntrinsicJSFragment.PrintCodeContext + /// Codec variable per generic parameter name, resolved from the + /// caller's `BridgeTypes` token before any value is lowered. + var genericCodecVariables: [String: String] = [:] + /// Type-ID variable per generic parameter name; forwarded to wasm as + /// the trailing type-ID ABI arguments. + var genericTypeIdVariables: [String: String] = [:] + /// JS parameter names for the trailing `BridgeType` tokens, in generic + /// parameter declaration order. + var genericTokenParameterNames: [String] = [] init(effects: Effects, hasDirectAccessToSwiftClass: Bool = true, intrinsicRegistry: JSIntrinsicRegistry) { self.effects = effects @@ -1594,6 +1689,83 @@ public struct BridgeJSLink { parameterForwardings.append(contentsOf: loweredValues) } + /// Lowers the parameters of a (possibly generic) exported function. + /// + /// Token resolution runs first, so an unknown `BridgeTypes` token + /// throws a `TypeError` before anything is lowered onto the shared + /// stacks or into wasm (balanced stack, catchable error). Values are + /// then lowered in declaration order — generic ones through the + /// resolved codec (possibly wrapped by a combinator), concrete ones + /// through the standard per-type fragments — matching the reverse + /// declaration-order pops of the Swift entry thunk. + func lowerParametersAndGenericTokens( + parameters: [Parameter], + genericParameters: [String] + ) throws { + guard !genericParameters.isEmpty else { + for param in parameters { + try lowerParameter(param: param) + } + return + } + // One source of truth for the token parameter names: the same + // helper the d.ts emission and the struct-instance path use, so + // the three emitters cannot drift apart. + genericTokenParameterNames = GenericJSCodegen.genericTokenParameterNames( + parameters: parameters, + genericNames: genericParameters, + scope: scope + ) + for (genericParam, tokenName) in zip(genericParameters, genericTokenParameterNames) { + let typeIdVariable = scope.variable("typeId\(genericParam)") + let codecVariable = scope.variable("codec\(genericParam)") + body.write("const \(typeIdVariable) = __bjs_typeIdForToken(\(tokenName));") + body.write("const \(codecVariable) = __bjs_codecForTypeId(\(typeIdVariable));") + genericTypeIdVariables[genericParam] = typeIdVariable + genericCodecVariables[genericParam] = codecVariable + } + // Lower under the declared parameter name: `parameterList(_:)` + // declares `param.name`, so that is the identifier in scope. + for param in parameters { + if let genericName = param.type.referencedGenericName { + guard let codecVariable = genericCodecVariables[genericName], + let lowerStatement = GenericJSCodegen.genericCodecLowerStatement( + type: param.type, + codec: codecVariable, + value: param.name + ) + else { + throw BridgeJSLinkError( + message: + "Generic codec for '\(genericName)' was not declared before lowering parameter '\(param.name)'" + ) + } + body.write(lowerStatement) + } else { + let loweringFragment = try IntrinsicJSFragment.lowerParameter(type: param.type) + let loweredValues = try loweringFragment.printCode([param.name], context) + parameterForwardings.append(contentsOf: loweredValues) + } + } + for genericParam in genericParameters { + if let typeIdVariable = genericTypeIdVariables[genericParam] { + parameterForwardings.append(typeIdVariable) + } + } + } + + /// The JS parameter list of the wrapper: the user-facing parameters + /// followed by one `BridgeType` token per generic parameter. + func parameterList(_ parameters: [Parameter]) -> String { + let base = DefaultValueUtils.formatParameterList( + parameters, + resolveTypeName: context.defaultValueTypeName + ) + guard !genericTokenParameterNames.isEmpty else { return base } + let tokens = genericTokenParameterNames.joined(separator: ", ") + return base.isEmpty ? tokens : "\(base), \(tokens)" + } + func lowerSelf() { parameterForwardings.append("this.pointer") } @@ -1608,6 +1780,20 @@ public struct BridgeJSLink { private func _call(abiName: String, returnType: BridgeType) throws -> String? { let call = "instance.exports.\(abiName)(\(parameterForwardings.joined(separator: ", ")))" + if let genericName = returnType.referencedGenericName { + guard let codecVariable = genericCodecVariables[genericName], + let liftExpression = GenericJSCodegen.genericCodecLiftExpression( + type: returnType, + codec: codecVariable + ) + else { + throw BridgeJSLinkError( + message: "Generic codec for return type '\(genericName)' was not declared before the call" + ) + } + body.write("\(call);") + return liftExpression + } let liftingFragment = try IntrinsicJSFragment.liftReturn(type: returnType) assert( liftingFragment.parameters.count <= 1, @@ -1667,10 +1853,7 @@ public struct BridgeJSLink { ) -> [String] { let printer = CodeFragmentPrinter() - let parameterList = DefaultValueUtils.formatParameterList( - parameters, - resolveTypeName: context.defaultValueTypeName - ) + let parameterList = self.parameterList(parameters) printer.write( "\(declarationPrefixKeyword.map { "\($0) "} ?? "")\(name)(\(parameterList)) {" @@ -1797,6 +1980,32 @@ public struct BridgeJSLink { return "<\(renderedParameters.joined(separator: ", "))>" } + /// The d.ts member signature of an exported function; generic functions + /// take one trailing `BridgeType` token per generic parameter, named to + /// match the generated wrapper's parameter list. + private func renderExportMemberTSSignature(function: ExportedFunction) -> String { + guard function.isGeneric else { + return + "\(function.resolvedJSName)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects))" + } + let genericNames = function.genericParameterNames + let tokenNames = GenericJSCodegen.genericTokenParameterNames( + parameters: function.parameters, + genericNames: genericNames, + scope: JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry) + ) + var parameterSignatures = function.parameters.map { param in + "\(param.name): \(resolveTypeScriptType(param.type))" + } + for (genericName, tokenName) in zip(genericNames, tokenNames) { + parameterSignatures.append("\(tokenName): BridgeType<\(genericName)>") + } + let genericClause = renderGenericClause(genericNames.map { GenericParameter(name: $0) }) + let returnSignature = resolveTypeScriptType(function.returnType) + return + "\(function.resolvedJSName)\(genericClause)(\(parameterSignatures.joined(separator: ", "))): \(returnSignature)" + } + 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 { @@ -1833,12 +2042,7 @@ public struct BridgeJSLink { parameters: method.parameters ) dtsTypePrinter.write(lines: jsDocLines) - let signature = renderTSSignature( - parameters: method.parameters, - returnType: method.returnType, - effects: method.effects - ) - dtsTypePrinter.write("\(method.resolvedJSName)\(signature);") + dtsTypePrinter.write("\(renderExportMemberTSSignature(function: method));") } } dtsTypePrinter.write("}") @@ -1866,9 +2070,7 @@ public struct BridgeJSLink { for method in structDefinition.methods where method.effects.isStatic { let jsDocLines = renderJSDoc(documentation: method.documentation, parameters: method.parameters) dtsExportEntryPrinter.write(lines: jsDocLines) - dtsExportEntryPrinter.write( - "\(method.resolvedJSName)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" - ) + dtsExportEntryPrinter.write("\(renderExportMemberTSSignature(function: method));") } return dtsExportEntryPrinter.lines @@ -2115,9 +2317,10 @@ extension BridgeJSLink { effects: function.effects, intrinsicRegistry: intrinsicRegistry ) - for param in function.parameters { - try thunkBuilder.lowerParameter(param: param) - } + try thunkBuilder.lowerParametersAndGenericTokens( + parameters: function.parameters, + genericParameters: function.genericParameterNames + ) let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( name: function.abiName, @@ -2129,9 +2332,7 @@ extension BridgeJSLink { dtsLines.append(contentsOf: renderJSDoc(documentation: function.documentation, parameters: function.parameters)) - dtsLines.append( - "\(function.resolvedJSName)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" - ) + dtsLines.append("\(renderExportMemberTSSignature(function: function));") return (funcLines, dtsLines) } @@ -2165,9 +2366,10 @@ extension BridgeJSLink { effects: function.effects, intrinsicRegistry: intrinsicRegistry ) - for param in function.parameters { - try thunkBuilder.lowerParameter(param: param) - } + try thunkBuilder.lowerParametersAndGenericTokens( + parameters: function.parameters, + genericParameters: function.genericParameterNames + ) let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( @@ -2181,9 +2383,7 @@ extension BridgeJSLink { dtsLines.append(contentsOf: renderJSDoc(documentation: function.documentation, parameters: function.parameters)) - dtsLines.append( - "static \(function.resolvedJSName)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" - ) + dtsLines.append("static \(renderExportMemberTSSignature(function: function));") return (funcLines, dtsLines) } @@ -2196,17 +2396,14 @@ extension BridgeJSLink { effects: function.effects, intrinsicRegistry: intrinsicRegistry ) - for param in function.parameters { - try thunkBuilder.lowerParameter(param: param) - } + try thunkBuilder.lowerParametersAndGenericTokens( + parameters: function.parameters, + genericParameters: function.genericParameterNames + ) let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) let printer = CodeFragmentPrinter() - let parameterList = DefaultValueUtils.formatParameterList( - function.parameters, - resolveTypeName: thunkBuilder.context.defaultValueTypeName - ) - printer.write("\(function.resolvedJSName)(\(parameterList)) {") + printer.write("\(function.resolvedJSName)(\(thunkBuilder.parameterList(function.parameters))) {") printer.indent { thunkBuilder.renderFunctionBody(into: printer, returnExpr: returnExpr) } @@ -2216,9 +2413,7 @@ extension BridgeJSLink { dtsLines.append(contentsOf: renderJSDoc(documentation: function.documentation, parameters: function.parameters)) - dtsLines.append( - "\(function.resolvedJSName)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" - ) + dtsLines.append("\(renderExportMemberTSSignature(function: function));") return (printer.lines, dtsLines) } @@ -2231,9 +2426,10 @@ extension BridgeJSLink { effects: function.effects, intrinsicRegistry: intrinsicRegistry ) - for param in function.parameters { - try thunkBuilder.lowerParameter(param: param) - } + try thunkBuilder.lowerParametersAndGenericTokens( + parameters: function.parameters, + genericParameters: function.genericParameterNames + ) let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( @@ -2256,9 +2452,10 @@ extension BridgeJSLink { effects: method.effects, intrinsicRegistry: intrinsicRegistry ) - for param in method.parameters { - try thunkBuilder.lowerParameter(param: param) - } + try thunkBuilder.lowerParametersAndGenericTokens( + parameters: method.parameters, + genericParameters: method.genericParameterNames + ) let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) let methodPrinter = CodeFragmentPrinter() @@ -2267,7 +2464,7 @@ extension BridgeJSLink { resolveTypeName: thunkBuilder.context.defaultValueTypeName ) methodPrinter.write( - "\(method.resolvedJSName): function(\(parameterList)) {" + "\(method.resolvedJSName): function(\(thunkBuilder.parameterList(method.parameters))) {" ) methodPrinter.indent { thunkBuilder.renderFunctionBody(into: methodPrinter, returnExpr: returnExpr) @@ -2396,9 +2593,10 @@ extension BridgeJSLink { effects: method.effects, intrinsicRegistry: intrinsicRegistry ) - for param in method.parameters { - try thunkBuilder.lowerParameter(param: param) - } + try thunkBuilder.lowerParametersAndGenericTokens( + parameters: method.parameters, + genericParameters: method.genericParameterNames + ) let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) jsPrinter.indent { @@ -2417,9 +2615,10 @@ extension BridgeJSLink { intrinsicRegistry: intrinsicRegistry ) thunkBuilder.lowerSelf() - for param in method.parameters { - try thunkBuilder.lowerParameter(param: param) - } + try thunkBuilder.lowerParametersAndGenericTokens( + parameters: method.parameters, + genericParameters: method.genericParameterNames + ) let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) jsPrinter.indent { @@ -2440,9 +2639,7 @@ extension BridgeJSLink { ) { dtsTypePrinter.write(line) } - dtsTypePrinter.write( - "\(method.resolvedJSName)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" - ) + dtsTypePrinter.write("\(renderExportMemberTSSignature(function: method));") } } } @@ -2478,9 +2675,7 @@ extension BridgeJSLink { } for method in klass.methods where method.effects.isStatic { printer.write(lines: renderJSDoc(documentation: method.documentation, parameters: method.parameters)) - printer.write( - "\(method.resolvedJSName)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" - ) + printer.write("\(renderExportMemberTSSignature(function: method));") } for property in klass.properties where property.isStatic { let readonly = property.isReadonly ? "readonly " : "" @@ -2510,9 +2705,7 @@ extension BridgeJSLink { for method in klass.methods.sorted(by: { $0.resolvedJSName < $1.resolvedJSName }) { let staticKeyword = method.effects.isStatic ? "static " : "" printer.write(lines: renderJSDoc(documentation: method.documentation, parameters: method.parameters)) - printer.write( - "\(staticKeyword)\(method.resolvedJSName)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" - ) + printer.write("\(staticKeyword)\(renderExportMemberTSSignature(function: method));") } for property in klass.properties.sorted(by: { $0.resolvedJSName < $1.resolvedJSName }) { let staticKeyword = property.isStatic ? "static " : "" diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 747c9f532..7856bd001 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -36,6 +36,7 @@ final class JSGlueVariableScope { static let reservedMakeSwiftClosure = "makeClosure" static let reservedTaStack = "taStack" static let reservedCodecByTypeId = "__bjs_codecByTypeId" + static let reservedTypeIdByToken = "__bjs_typeIdByToken" static let reservedPrimitiveCodecs = "__bjs_primitiveCodecs" static let reservedStringCodec = "__bjs_stringCodec" static let reservedTypeHandlesRegistered = "__bjs_typeHandlesRegistered" @@ -71,6 +72,7 @@ final class JSGlueVariableScope { reservedMakeSwiftClosure, reservedTaStack, reservedCodecByTypeId, + reservedTypeIdByToken, reservedPrimitiveCodecs, reservedStringCodec, reservedTypeHandlesRegistered, @@ -226,6 +228,45 @@ enum GenericJSCodegen { genericCodecExpression(type: type, codec: codec).map { "\($0).lift()" } } + /// JS parameter names for the trailing `BridgeType` tokens of a generic + /// exported function, deduplicated around the user-facing parameter names. + /// + /// The wrapper emission and the d.ts emission both derive the names from + /// this function (same fresh scope, same registration order), so the two + /// always agree. + static func genericTokenParameterNames( + parameters: [Parameter], + genericNames: [String], + scope: JSGlueVariableScope + ) -> [String] { + for parameter in parameters { + _ = scope.variable(parameter.name) + } + return genericNames.map { scope.variable("type\($0)") } + } + + /// Runtime helpers used only by generic *exports*: the JS wrapper resolves + /// the caller's `BridgeTypes` token to a runtime type ID before entering + /// wasm, throwing a catchable `TypeError` for unknown tokens (the wasm side + /// treats an invalid ID as undefined behavior, so it must never see one). + static func exportRuntimeHelperDeclarations() -> [String] { + let typeIdByToken = JSGlueVariableScope.reservedTypeIdByToken + return [ + "function __bjs_typeIdForToken(token) {", + " \(JSGlueVariableScope.reservedRegisterTypeHandles)();", + " const typeId = \(typeIdByToken).get(token);", + " if (typeId === undefined) {", + " throw new TypeError(\"BridgeJS: unknown BridgeType token '\" + token + \"'\");", + " }", + " return typeId;", + "}", + ] + } + + /// Shared generic runtime: a type-ID-keyed codec table plus codec + /// combinators. Each container shape's stack ABI is described once here and + /// instantiated with an element codec, instead of cloning the lowering and + /// lifting logic per shape. static func runtimeHelperDeclarations() -> [String] { let codecByTypeId = JSGlueVariableScope.reservedCodecByTypeId return [ @@ -2581,6 +2622,15 @@ struct IntrinsicJSFragment: Sendable { // Attach instance methods to the struct instance for method in structDef.methods where !method.effects.isStatic { + if method.isGeneric { + try attachGenericStructInstanceMethod( + method: method, + structDef: structDef, + instanceVar: instanceVar, + context: context + ) + continue + } let paramList = DefaultValueUtils.formatParameterList( method.parameters, resolveTypeName: context.defaultValueTypeName @@ -2611,6 +2661,77 @@ struct IntrinsicJSFragment: Sendable { } } + /// Attaches a generic instance method to a lifted struct instance. + /// + /// Mirrors `ExportedThunkBuilder.lowerParametersAndGenericTokens`: tokens + /// resolve first (an unknown token throws before anything is pushed), the + /// struct `self` lowers next, then the parameters in declaration order, + /// and the trailing type IDs select the concrete types on the Swift side. + private static func attachGenericStructInstanceMethod( + method: ExportedFunction, + structDef: ExportedStruct, + instanceVar: String, + context: IntrinsicJSFragment.PrintCodeContext + ) throws { + let printer = context.printer + let methodScope = context.scope.makeChildScope() + let genericNames = method.genericParameterNames + let tokenNames = GenericJSCodegen.genericTokenParameterNames( + parameters: method.parameters, + genericNames: genericNames, + scope: methodScope + ) + let methodContext = context.with(\.scope, methodScope) + + let paramList = (method.parameters.map { $0.name } + tokenNames).joined(separator: ", ") + printer.write("\(instanceVar).\(method.resolvedJSName) = function(\(paramList)) {") + try printer.indent { + var codecVariables: [String: String] = [:] + var typeIdVariables: [String: String] = [:] + for (genericName, tokenName) in zip(genericNames, tokenNames) { + let typeIdVariable = methodScope.variable("typeId\(genericName)") + let codecVariable = methodScope.variable("codec\(genericName)") + printer.write("const \(typeIdVariable) = __bjs_typeIdForToken(\(tokenName));") + printer.write("const \(codecVariable) = __bjs_codecForTypeId(\(typeIdVariable));") + typeIdVariables[genericName] = typeIdVariable + codecVariables[genericName] = codecVariable + } + printer.write( + "\(JSGlueVariableScope.reservedStructHelpers).\(context.scope.helperKey(forTypeNamed: structDef.swiftCallName)).lower(this);" + ) + var paramForwardings: [String] = [] + for param in method.parameters { + if let genericName = param.type.referencedGenericName { + if let codecVariable = codecVariables[genericName], + let lowerStatement = GenericJSCodegen.genericCodecLowerStatement( + type: param.type, + codec: codecVariable, + value: param.name + ) + { + printer.write(lowerStatement) + } + } else { + let fragment = try IntrinsicJSFragment.lowerParameter(type: param.type) + let loweredValues = try fragment.printCode([param.name], methodContext) + paramForwardings.append(contentsOf: loweredValues) + } + } + paramForwardings.append(contentsOf: genericNames.compactMap { typeIdVariables[$0] }) + printer.write("instance.exports.\(method.abiName)(\(paramForwardings.joined(separator: ", ")));") + if let returnGenericName = method.returnType.referencedGenericName, + let codecVariable = codecVariables[returnGenericName], + let liftExpression = GenericJSCodegen.genericCodecLiftExpression( + type: method.returnType, + codec: codecVariable + ) + { + printer.write("return \(liftExpression);") + } + } + printer.write("}.bind(\(instanceVar));") + } + private static func structFieldLowerFragment( type: BridgeType, fieldName: String, diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 8da9f2e4e..7e5582b19 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -312,6 +312,8 @@ extension BridgeType { } } + public var usesGenericParameter: Bool { referencedGenericName != nil } + public static let genericBridgeablePrimitives: [(token: String, type: BridgeType)] = [ ("Bool", .bool), ("Int", .integer(.int)), @@ -332,13 +334,23 @@ extension BridgeType { } +// 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); `token` is the JS-facing +/// `BridgeTypes` key a generic-export caller passes to select this type. public struct GenericBridgeableTypeEntry: Sendable { public let swiftName: String public let bridgeType: BridgeType + public let token: String - public init(swiftName: String, bridgeType: BridgeType) { + public init(swiftName: String, bridgeType: BridgeType, token: String) { self.swiftName = swiftName self.bridgeType = bridgeType + self.token = token } } @@ -366,7 +378,8 @@ extension ExportedSkeleton { entries.append( GenericBridgeableTypeEntry( swiftName: structDef.swiftCallName, - bridgeType: .swiftStruct(structDef.swiftCallName) + bridgeType: .swiftStruct(structDef.swiftCallName), + token: structDef.abiName ) ) } @@ -374,27 +387,50 @@ extension ExportedSkeleton { entries.append( GenericBridgeableTypeEntry( swiftName: klass.swiftCallName, - bridgeType: .swiftHeapObject(klass.swiftCallName) + bridgeType: .swiftHeapObject(klass.swiftCallName), + token: klass.abiName ) ) } for enumDef in enums { guard let bridgeType = enumDef.genericBridgeType else { continue } - entries.append(GenericBridgeableTypeEntry(swiftName: enumDef.swiftCallName, bridgeType: bridgeType)) + entries.append( + GenericBridgeableTypeEntry( + swiftName: enumDef.swiftCallName, + bridgeType: bridgeType, + token: enumDef.abiName + ) + ) } for proto in protocols where proto.isGenericBridgeable == true { entries.append( GenericBridgeableTypeEntry( swiftName: "Any\(proto.name)", - bridgeType: .swiftProtocol(proto.name) + bridgeType: .swiftProtocol(proto.name), + // Same shape as type tokens: the namespace path joined with '_'. + token: ((proto.namespace ?? []) + [proto.name]).joined(separator: "_") ) ) } return entries } + + public var hasGenericDeclarations: Bool { + functions.contains(where: \.isGeneric) + || classes.contains { $0.methods.contains(where: \.isGeneric) } + || structs.contains { $0.methods.contains(where: \.isGeneric) } + || enums.contains { $0.staticMethods.contains(where: \.isGeneric) } + } } extension BridgeJSSkeleton { + /// The ordered list of types this module registers type handles for, or `nil` + /// when the module does not emit a registration function. + /// + /// Every module that defines bridgeable `@JS` types registers them (a module + /// cannot know whether a dependent module will pass its types to a generic + /// function). Primitive handles are singletons owned by JavaScriptKit and are + /// registered once by the core hook, so they are not repeated here. public var typeRegistrationEntries: [GenericBridgeableTypeEntry]? { let exportedEntries = exported?.genericBridgeableTypeEntries ?? [] guard !exportedEntries.isEmpty else { return nil } @@ -1055,6 +1091,14 @@ public struct ExportedFunction: Codable, Equatable, Sendable { public var namespace: [String]? public var staticContext: StaticContext? public var documentation: String? + public var genericParameters: [String]? + public var genericParameterNames: [String] { genericParameters ?? [] } + public var isGeneric: Bool { !genericParameterNames.isEmpty } + /// Parameters whose type references a generic parameter; they cross the + /// bridge on the shared value stack via the type's codec. + public var genericValueParameters: [Parameter] { parameters.filter { $0.type.usesGenericParameter } } + /// Parameters with concrete types; they use the standard per-type ABI. + public var concreteParameters: [Parameter] { parameters.filter { !$0.type.usesGenericParameter } } public var resolvedJSName: String { jsName ?? name } @@ -1067,7 +1111,8 @@ public struct ExportedFunction: Codable, Equatable, Sendable { effects: Effects, namespace: [String]? = nil, staticContext: StaticContext? = nil, - documentation: String? = nil + documentation: String? = nil, + genericParameters: [String]? = nil ) { self.name = name self.jsName = jsName @@ -1078,6 +1123,7 @@ public struct ExportedFunction: Codable, Equatable, Sendable { self.namespace = namespace self.staticContext = staticContext self.documentation = documentation + self.genericParameters = genericParameters } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift index 37f2a0318..abb0f2d25 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift @@ -3,61 +3,218 @@ import Testing @Suite struct GenericExportDiagnosticsTests { @Test - func genericExportedFunctionRejected() { + func genericParameterRequiresBridgeableConstraint() { expectDiagnostic( source: """ - @JS public func identity(_ value: T) -> T { value } + @JS public func identity(_ value: T) -> T { value } """, - contains: "Generic parameters on exported @JS functions are not supported yet" + contains: "Generic parameter 'T' must be constrained to 'BridgedSwiftGenericBridgeable'" ) } @Test - func genericMethodOnExportedClassRejected() { + func genericExportedInitializerIsRejectedWithTargetedDiagnostic() { + // Falls through to "Unsupported type 'T'" without the early check; + // imported generic initializers work, so users will try this spelling. expectDiagnostic( source: """ - @JS final class Box { + @JS class Box { + @JS init(_ value: T) {} + } + """, + contains: "Generic initializers are not supported on exported @JS types yet." + ) + } + + @Test + func defaultParameterValueOnGenericExportIsRejected() { + // The JS wrapper appends required BridgeType token parameters after the + // declared ones, so a defaulted parameter could never be omitted. + expectDiagnostic( + source: """ + @JS public func tag(_ value: T, label: Int = 5) -> T { value } + """, + contains: "Default parameter values are not supported on generic @JS functions." + ) + } + + @Test + func genericWhereClauseUnsupported() { + expectDiagnostic( + source: """ + @JS public func identity(_ value: T) -> T where T: Sendable { value } + """, + contains: "'where' clauses are not supported" + ) + } + + @Test + func asyncGenericExportUnsupported() { + expectDiagnostic( + source: """ + @JS public func f(_ v: T) async -> T { v } + """, + contains: "Generic @JS functions cannot be 'async' yet." + ) + } + + @Test + func throwsGenericExportUnsupported() { + expectDiagnostic( + source: """ + @JS public func f(_ v: T) throws(JSException) -> T { v } + """, + contains: "Generic @JS functions cannot be 'throws' yet." + ) + } + + @Test(arguments: [ + ("[[T]]", "@JS public func f(_ v: [[T]]) {}"), + ("[T?]", "@JS public func f(_ v: [T?]) {}"), + ("T??", "@JS public func f(_ v: T??) {}"), + ("[Int: T]", "@JS public func f(_ v: [Int: T]) {}"), + ]) + func unsupportedGenericWrapperFormsInParameter(label: String, source: String) { + expectDiagnostic( + source: source, + contains: "may only be used as a bare type" + ) + } + + @Test(arguments: [ + ("[[T]]", "@JS public func f(_ v: T) -> [[T]] { [[v]] }"), + ("[T?]", "@JS public func f(_ v: T) -> [T?] { [v] }"), + ("T??", "@JS public func f(_ v: T) -> T?? { v }"), + ("[Int: T]", "@JS public func f(_ v: T) -> [Int: T] { [0: v] }"), + ]) + func unsupportedGenericWrapperFormsInReturn(label: String, source: String) { + expectDiagnostic( + source: source, + contains: "may only be used as a bare type" + ) + } + + @Test + func fullyUnusedGenericParameterRejected() { + expectDiagnostic( + source: """ + @JS public func combine(_ a: T) -> T { a } + """, + contains: "must be used in at least one parameter or the return type" + ) + } + + @Test + func genericConcreteReturnUnsupported() { + expectDiagnostic( + source: """ + @JS public func f(_ v: T) -> String { "" } + """, + contains: "must return the generic type" + ) + } + + @Test + func genericInstanceMethodAsyncIsRejected() { + expectDiagnostic( + source: """ + @JS class Box { @JS init() {} - @JS func wrap(_ value: T) -> T { value } + @JS func wrap(_ v: T) async -> T { v } } """, - contains: "Generic parameters on exported @JS functions are not supported yet" + contains: "Generic @JS functions cannot be 'async' yet." ) } @Test - func genericMethodOnExportedStructRejected() { + func genericInstanceMethodThrowsIsRejected() { expectDiagnostic( source: """ - @JS struct Pair { + @JS class Box { @JS init() {} - @JS func first(_ value: T) -> T { value } + @JS func wrap(_ v: T) throws(JSException) -> T { v } } """, - contains: "Generic parameters on exported @JS functions are not supported yet" + contains: "Generic @JS functions cannot be 'throws' yet." ) } @Test - func genericStaticMethodOnExportedEnumRejected() { + func genericMethodWhereClauseIsRejected() { expectDiagnostic( source: """ - @JS enum Factory { - case primary - @JS static func one(_ value: T) -> T { value } + @JS class Box { + @JS init() {} + @JS func wrap(_ v: T) -> T where T: Sendable { v } } """, - contains: "Generic parameters on exported @JS functions are not supported yet" + contains: "'where' clauses are not supported on generic @JS functions." ) } @Test - func unconstrainedGenericExportedFunctionRejected() { + func genericMethodUnconstrainedParamIsRejected() { expectDiagnostic( source: """ - @JS public func identity(_ value: T) -> T { value } + @JS class Box { + @JS init() {} + @JS func f(_ v: T) -> T { v } + } """, - contains: "Generic parameters on exported @JS functions are not supported yet" + contains: + "Generic parameter 'T' must be constrained to 'BridgedSwiftGenericBridgeable' to be used with @JS." + ) + } + + @Test + func genericMethodConcreteReturnIsRejected() { + expectDiagnostic( + source: """ + @JS class Box { + @JS init() {} + @JS func count(_ v: T) -> Int { 0 } + } + """, + contains: "must return the generic type" + ) + } + + @Test + func genericEnumInstanceMethodIsRejected() { + expectDiagnostic( + source: """ + @JS enum E { + case a + @JS func f(_ v: T) -> T { v } + } + """, + contains: "Only static functions are supported in enums" + ) + } + + @Test + func genericTypedPropertyIsRejectedAsUnsupportedType() { + expectDiagnostic( + source: """ + @JS final class Box { + @JS init() {} + @JS var value: T + } + """, + contains: "Unsupported type 'T'." + ) + } + + @Test + func returnOnlyGenericExportIsAccepted() throws { + let skeleton = try makeSkeleton( + """ + @JS public func load(_ key: String) -> T? { nil } + """ ) + let function = try #require(skeleton.exported?.functions.first) + #expect(function.genericParameterNames == ["T"]) + #expect(function.genericValueParameters.isEmpty) } } diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md index 8bfc5d302..b73c2d9e8 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md @@ -32,6 +32,16 @@ So even if you cache the property name (e.g. with `CachedJSStrings`), you are st BridgeJS avoids this by generating **separate** access paths per property or method. Each generated getter/setter or function call has a stable shape at the engine level, so the IC can stay monomorphic or polymorphic and the fast path is used. +## Generic imports + +An imported generic `@JSFunction` (`func parse(...)`) lets one piece of glue serve many concrete types. The generic value crosses using the type's own stack ABI, and a runtime type ID selects the matching JS codec, so the type-agnostic glue can lower and lift the right representation without a specialized path per call site. + +Type identity is pointer-based rather than name-based: every conforming type owns a unique `BridgeJSTypeHandle` instance, and the handle object's address is the type ID. This makes IDs unique across all linked modules for free (type-name strings would be fragile and could collide). During initialization each module's `bjs__register_type_handles` export lowers its handle IDs — in a canonical order shared with the JS glue — and the glue pairs them index-by-index with its codec array, so a generic call site resolves its codec with a single map lookup. Because this path avoids existentials entirely, it also works under Embedded Swift; the Embedded example (`Examples/Embedded`) exercises a generic import, including a `@JS struct` round-trip. + +## Generic exports + +An exported generic `@JS` function reuses the same stack ABI, codec table, and handle-based type identity, with the dispatch reversed. The WebAssembly entry point is a concrete `@_expose` thunk that takes the runtime type ID as a trailing `Int32`. Because the ID is the address of the type's `BridgeJSTypeHandle`, the thunk recovers the concrete type directly — `Unmanaged.fromOpaque(...).takeUnretainedValue().type` — with no per-module registry, reifies `T` through an opened existential, and runs the same unspecialized body a concrete export would run: pop the arguments from the stack, call your function, push the result. The JavaScript wrapper resolves the caller's `BridgeType` token through the map built during type-handle registration and passes the resulting ID; an unknown token throws a `TypeError` *before* entering wasm, so the shared value stack stays balanced and the error is catchable. Pointer recovery means the wasm entry point trusts its caller: a raw wasm caller passing a garbage type ID is undefined behavior, which is acceptable because the generated JS wrapper is the only supported caller and it validates every token first. Because recovery depends on the handle's metatype storage (an existential), generic exports are excluded under Embedded Swift. + ## What to read next - ABI and binary interface details will be documented in this section as they stabilize. diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md index 098a931af..c21d7ba74 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md @@ -157,6 +157,97 @@ export type Exports = { } ``` +### Generic functions + +A `@JS` function or method can be generic over a type parameter constrained to `BridgedSwiftGenericBridgeable`. The concrete type chosen at the JavaScript call site crosses the bridge, so one Swift implementation can transport many bridgeable types. A typical use is a typed payload slot: the Swift body stores and retrieves values opaquely while each call site keeps its concrete type: + +```swift +import JavaScriptKit + +@JS public final class GraphNode { + private var attributes: [String: any BridgedSwiftGenericBridgeable] = [:] + + @JS public init() {} + + @JS public func setAttribute(_ key: String, _ value: T) { + attributes[key] = value + } + + @JS public func attribute(_ key: String) -> T? { + attributes[key] as? T + } +} +``` + +`T` must be a bridgeable type: a supported primitive (`Bool`, any fixed-width integer such as `Int`/`UInt`/`Int8`…`UInt64`, `Float`, `Double`, `String`, or `JSValue`), or a `@JS` struct, `final @JS class`, or `@JS enum`. You do not write any conformance yourself; marking a type `@JS` makes it usable as `T` (see ). + +Because TypeScript erases generics, the JavaScript caller passes a `BridgeType` token as the last argument so the bridge can select the right type at runtime. The tokens come from a generated `BridgeTypes` map exported at the top level of `bridge-js.js`; import it directly rather than reading it from the `exports` object. The token for a type declared inside a namespace joins the path with underscores, so `API.Building` is `BridgeTypes.API_Building`: + +```javascript +import { BridgeTypes } from "./bridge-js.js"; + +const node = new exports.GraphNode(); +node.setAttribute("weight", 42, BridgeTypes.Int); +node.setAttribute("label", "primary", BridgeTypes.String); + +const weight = node.attribute("weight", BridgeTypes.Int); // 42 +const label = node.attribute("label", BridgeTypes.String); // "primary" +const missing = node.attribute("missing", BridgeTypes.Int); // null +``` + +Note that `attribute` uses its generic parameter only in the return type: the token alone tells the bridge which type to produce. This completes the store/load idiom — `setAttribute` consumes a generic value, `attribute` produces one. Passing a token that is not in `BridgeTypes` throws a `TypeError` before the call reaches Swift. + +Concrete parameters keep their positions; the token is always appended last. The non-generic parameters of a generic `@JS` function may be any supported bridged type, including value types such as `@JS` structs, arrays, dictionaries, and associated-value enums alongside the generic parameter. The generated TypeScript declarations look like: + +```typescript +export type BridgeType = string & { readonly __bridgeType?: (value: T) => void }; +export const BridgeTypes: { Bool: BridgeType; Int: BridgeType; Float: BridgeType; Double: BridgeType; String: BridgeType; MyPoint: BridgeType; }; +export interface GraphNode extends SwiftHeapObject { + setAttribute(key: string, value: T, typeT: BridgeType): void; + attribute(key: string, typeT: BridgeType): T | null; +} +``` + +The same works for a minimal top-level function — `identity` is the simplest possible shape: + +```swift +@JS public func identity(_ value: T) -> T { + return value +} +``` + +```typescript +export type Exports = { + identity(value: T, typeT: BridgeType): T; +} +``` + +A single `T` may be used in more than one parameter, and a function may declare multiple distinct generic parameters. Each distinct generic parameter takes its own `BridgeType` token, appended after the regular arguments in declaration order: + +```swift +@JS public func combine(_ a: T, _ b: U) -> T { + a +} +``` + +```typescript +export type Exports = { + combine(a: T, b: U, typeT: BridgeType, typeU: BridgeType): T; +} +``` + +The generic parameter may also be wrapped as `[T]`, `T?`, or `[String: T]` in parameters and the result: + +```swift +@JS public func firstOrNil(_ values: [T]) -> T? { + values.first +} +``` + +Every declared generic parameter must be used in at least one parameter or the return type; a fully unused generic parameter is rejected. The result must be one of the declared generic parameters (such as `T` or `U`), a supported wrapper of one (`[T]`, `T?`, `[String: T]`), or `Void` — returning a concrete non-`Void` type from a generic `@JS` function is not supported. Generic `@JS` functions must be synchronous (see ). + +Generics also work on methods. On a `@JS` class or struct they apply to both instance and static methods. A `@JS enum` (including a namespace-style enum) has no instance methods in BridgeJS, so generics there apply to static methods only. The constraint, the trailing `BridgeType` token, and the generic-or-`Void` return rule all carry over unchanged. + ## Supported Features | Swift Feature | Status | @@ -169,6 +260,6 @@ export type Exports = { | Throwing JS exception: `func x() throws(JSException)` | ✅ | | Throwing any exception: `func x() throws` | ❌ | | Async methods: `func x() async` | ✅ | -| Generics | ❌ | +| Generic parameter/result types (constrained to `BridgedSwiftGenericBridgeable`) | ✅ | | Opaque types: `func x() -> some P`, `func y(_: some P)` | ❌ | | Default parameter values: `func x(_ foo: String = "")` | ✅ (See ) | \ No newline at end of file diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md index 539f7ce15..f9225bf4b 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md @@ -33,7 +33,15 @@ 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. +A generic type parameter constrained to `BridgedSwiftGenericBridgeable` is supported as a parameter or result type in both directions: an imported `@JSFunction` (see ) and an exported `@JS` function (see ). The types that satisfy that constraint are: + +- `Bool`, `Float`, `Double`, `String`, and `JSValue` +- every fixed-width integer: `Int`, `UInt`, `Int8`/`Int16`/`Int32`/`Int64`, and `UInt8`/`UInt16`/`UInt32`/`UInt64` +- any `@JS` struct, `final @JS class`, or `@JS enum` (case, raw-value, or associated-value), including ones defined in another linked module + +You do not write the conformance by hand: marking a type `@JS` (or using a built-in primitive) is what makes it usable as the generic argument. + +The generic parameter may be used bare (`T`) or wrapped in `[T]`, `T?`, or `[String: T]`. Other or nested wrappings (for example `[T?]`, `[[T]]`, or `[Int: T]`) are not supported. `JSObject` cannot be used as the generic argument; use `JSValue` instead. ## See Also diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md index 74588b06b..5356869da 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md @@ -55,3 +55,22 @@ While using `@JS` types from another Swift module is supported, it is not possib ### Exporting Swift: types from another Swift package Types defined in a separate Swift package cannot yet be referenced from `@JS` declarations in your package. + +## Generics + +Generic functions are supported in both directions, through a type parameter constrained to `BridgedSwiftGenericBridgeable`: an imported `@JSFunction` (see ) and an exported `@JS` function (see ). Generics also work on methods of an exported `@JS` class or struct (both instance and static), on static methods of an exported `@JS` enum or namespace enum, and on imported `@JSClass` methods. A function may declare one or more distinct generic parameters, such as `combine(_ a: T, _ b: U) -> T`, and a return-only generic (such as `load(_ key: String) -> T?`) is supported in both directions. The following forms are not supported and produce build-time diagnostics: + +- `async` generic functions. +- `where` clauses on a generic declaration. +- An exported `@JS` generic function that is `throws`. (Imported `@JSFunction` generics may still use `throws(JSException)`.) +- An exported generic function with a declared generic parameter that is not used in any parameter or the return type. +- A generic initializer on an exported `@JS` class or struct. Use a generic `@JS` method, or a generic `@JSFunction` initializer on an imported `@JSClass`. +- Default parameter values on a generic exported `@JS` function. The trailing `BridgeType` token is always required, so a defaulted parameter could never be omitted. +- A generic requirement on a `@JS` protocol (`func map(_ v: T)`). Constrain a generic `@JS` function or method to the protocol instead. +- An exported generic function that returns a concrete, non-`Void` type. The result of an exported generic function must be one of the declared generic parameters (optionally wrapped in `[T]`, `T?`, or `[String: T]`) or `Void`. + +The generic parameter may be used bare (`T`) or wrapped in `[T]`, `T?`, or `[String: T]`. Nested or other wrappings, such as `[T?]`, `[[T]]`, `T??`, or `[Int: T]`, are not supported and produce build-time diagnostics. `JSObject` cannot be used as the generic argument (it is a non-final class); use `JSValue` instead. + +Passing an unknown `BridgeTypes` token to an exported generic function throws a catchable `TypeError` from the generated JavaScript wrapper before the call enters WebAssembly. + +Exported generic functions additionally require runtime existential support, so they are not available under Embedded Swift. Imported generics remain available there and are exercised by the Embedded example (`Examples/Embedded`). From c7565fb69523db61952b43e1b818d2b37ea9c596 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Mon, 28 Sep 2026 21:12:15 +0200 Subject: [PATCH 2/5] BridgeJS: Tests and fixtures for generic exports Codegen and link snapshots pin every distinct thunk shape: bare and wrapped generics, return-only generics, one generic used in several parameters, several distinct generic parameters, concrete parameters mixed in, case-colliding names (T vs t), and generic methods on each owning construct (class, struct, enum, and a method-only module). GenericMethodOnlyModuleCodegenTests pins the runtime-infrastructure gating: a module whose only generic declarations are exports over primitives emits no module registration hook (primitive handles are owned by JavaScriptKit's core hook), while modules with @JS types register their own; non-generic builds get no generic runtime at all. ExportGenericAPIs + ExportGenericTests.mjs round-trip every bridgeable type through real JS in both directions, including empty collections, nil, heap-object reference identity, and consecutive calls with different types. The unknown-token TypeError is asserted to be catchable and to leave the shared value stack balanced for the next call. --- .../BridgeJSToolTests/BridgeJSLinkTests.swift | 93 ++ .../CodegenTestSupport.swift | 25 + .../GenericExportDiagnosticsTests.swift | 13 + .../GenericMethodOnlyModuleCodegenTests.swift | 100 ++ .../Inputs/MacroSwift/GenericExports.swift | 170 ++ .../BridgeJSCodegenTests/GenericExports.json | 1278 +++++++++++++++ .../BridgeJSCodegenTests/GenericExports.swift | 1091 +++++++++++++ .../BridgeJSLinkTests/GenericExports.d.ts | 137 ++ .../BridgeJSLinkTests/GenericExports.js | 1167 ++++++++++++++ .../ExportGenericAPIs.swift | 174 +++ .../Generated/BridgeJS.swift | 943 +++++++++++ .../Generated/JavaScript/BridgeJS.json | 1379 +++++++++++++++-- .../JavaScript/ExportGenericTests.mjs | 184 +++ Tests/prelude.mjs | 8 + 14 files changed, 6662 insertions(+), 100 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericExports.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js create mode 100644 Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift create mode 100644 Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift index f96ec2d8d..488781832 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift @@ -157,6 +157,99 @@ import Testing return try bridgeJSLink.link().0 } + @Test + func genericRuntimeIsGatedToGenericBuilds() throws { + let genericJS = try linkedJS(forFixture: "GenericImports.swift") + #expect(genericJS.contains("__bjs_codecByTypeId")) + #expect(genericJS.contains("function __bjs_codecForTypeId(typeId) {")) + #expect(genericJS.contains("bjs[\"bjs_TestModule_register_type_handles\"] = function(base, count) {")) + #expect(genericJS.contains("instance.exports[\"bjs_TestModule_register_type_handles\"]();")) + + // 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 + // primitive codec table is not part of that runtime: the non-generic + // array and dictionary paths share it too. + let nonGenericJS = try linkedJS(forFixture: "SwiftStructImports.swift") + #expect(!nonGenericJS.contains("__bjs_codecByTypeId")) + #expect(!nonGenericJS.contains("__bjs_codecForTypeId")) + #expect(nonGenericJS.contains("bjs[\"bjs_core_register_type_handles\"] = function() {};")) + #expect(nonGenericJS.contains("bjs[\"bjs_TestModule_register_type_handles\"] = function() {};")) + #expect(!nonGenericJS.contains("instance.exports[\"bjs_TestModule_register_type_handles\"]();")) + } + + @Test + func sameTypeNameAcrossModulesLinksWithHandleIdentity() throws { + // Type identity is pointer-based (each type owns a BridgeJSTypeHandle), + // so two modules defining a same-named @JS type must link fine: each + // module registers its own handle IDs against its own codec array. + func makeSkeleton(moduleName: String, source: String) throws -> BridgeJSSkeleton { + let swiftAPI = SwiftToSkeleton( + progress: .silent, + moduleName: moduleName, + exposeToGlobal: false, + externalModuleIndex: .empty + ) + swiftAPI.addSourceFile(Parser.parse(source: source), inputFilePath: "\(moduleName).swift") + return try swiftAPI.finalize() + } + let structSource = """ + @JS public struct Point { + public var x: Int + @JS public init(x: Int) { self.x = x } + } + """ + let first = try makeSkeleton( + moduleName: "FirstModule", + source: structSource + """ + + @JSFunction func identity(_ value: T) throws(JSException) -> T + """ + ) + let second = try makeSkeleton(moduleName: "SecondModule", source: structSource) + let bridgeJSLink = BridgeJSLink(skeletons: [first, second]) + 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) {")) + } + + @Test + func sameTypeNameAcrossModulesFailsWithGenericExports() throws { + // Generic exports surface types to JS through unqualified `BridgeTypes` + // tokens, so a type name shared by two modules cannot be represented + // and must fail the build — unlike imports, where pointer-based handle + // identity keeps same-named types apart. + func makeSkeleton(moduleName: String, source: String) throws -> BridgeJSSkeleton { + let swiftAPI = SwiftToSkeleton( + progress: .silent, + moduleName: moduleName, + exposeToGlobal: false, + externalModuleIndex: .empty + ) + swiftAPI.addSourceFile(Parser.parse(source: source), inputFilePath: "\(moduleName).swift") + return try swiftAPI.finalize() + } + let structSource = """ + @JS public struct Point { + public var x: Int + @JS public init(x: Int) { self.x = x } + } + """ + let first = try makeSkeleton( + moduleName: "FirstModule", + source: structSource + """ + + @JS public func identity(_ value: T) -> T { value } + """ + ) + let second = try makeSkeleton(moduleName: "SecondModule", source: structSource) + let bridgeJSLink = BridgeJSLink(skeletons: [first, second]) + #expect(throws: (any Error).self) { + _ = try bridgeJSLink.link() + } + } + @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 index b66a5ba6f..ce409153c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/CodegenTestSupport.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/CodegenTestSupport.swift @@ -22,6 +22,31 @@ func makeSkeleton( return try swiftAPI.finalize() } +func renderExportGlue( + _ source: String, + moduleName: String = "TestModule" +) throws -> String { + let skeleton = try makeSkeleton(source, moduleName: moduleName) + let exported = try #require(skeleton.exported) + let exportSwift = ExportSwift( + progress: .silent, + moduleName: skeleton.moduleName, + skeleton: exported + ) + return try #require(try exportSwift.finalize()) +} + +func linkSource(_ source: String, moduleName: String = "TestModule") throws -> (js: String, dts: String) { + let skeleton = try makeSkeleton(source, moduleName: moduleName) + var bridgeJSLink = BridgeJSLink() + 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) +} + func expectDiagnostic( source: String, moduleName: String = "App", diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift index abb0f2d25..eed6497e0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift @@ -12,6 +12,19 @@ import Testing ) } + @Test + func genericProtocolRequirementIsRejectedWithTargetedDiagnostic() { + // Falls through to "Unsupported type 'T'" without the early check. + expectDiagnostic( + source: """ + @JS protocol Mapper { + func map(_ v: T) -> T + } + """, + contains: "Generic requirements are not supported on @JS protocols yet." + ) + } + @Test func genericExportedInitializerIsRejectedWithTargetedDiagnostic() { // Falls through to "Unsupported type 'T'" without the early check; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift new file mode 100644 index 000000000..ae8849c81 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift @@ -0,0 +1,100 @@ +import Testing + +@Suite struct GenericMethodOnlyModuleCodegenTests { + @Test + func exportClassMethodOnlyEmitsJSRuntimeInfrastructure() throws { + let js = try linkSource( + """ + @JS final class OnlyBox { + @JS init() {} + @JS func wrap(_ value: T) -> T { value } + } + """ + ).js + #expect(js.contains("const __bjs_codecByTypeId = new Map();")) + #expect(js.contains("const __bjs_typeIdByToken = new Map();")) + #expect(js.contains("function __bjs_typeIdForToken(token) {")) + #expect(js.contains("export const BridgeTypes = {")) + #expect(js.contains("bjs[\"bjs_TestModule_register_type_handles\"] = function(base, count) {")) + } + + @Test(arguments: [ + ( + """ + @JS final class OnlyBox { + @JS init() {} + @JS func wrap(_ value: T) -> T { value } + } + """, + "OnlyBox" + ), + ( + """ + @JS struct OnlyPair { + @JS init() {} + @JS func first(_ value: T) -> T { value } + } + """, + "OnlyPair" + ), + ( + """ + @JS enum OnlyFactory { + case primary + @JS static func one(_ value: T) -> T { value } + } + """, + "OnlyFactory" + ), + ]) + func exportMethodOnlyEmitsSwiftRuntimeInfrastructure(source: String, typeName: String) throws { + // Handle-based identity: the entry thunk recovers the concrete type + // straight from the incoming ID (the handle's address); there is no + // per-module type registry dictionary. + let swift = try renderExportGlue(source) + #expect(swift.contains("extension \(typeName): BridgedSwiftGenericBridgeable {")) + #expect(swift.contains("Unmanaged.fromOpaque(")) + #expect(!swift.contains("TypeRegistry")) + // Generic exports depend on the handle's metatype storage, which does + // not exist under Embedded Swift. + #expect(swift.contains("fatalError(\"Generic @JS exported functions are not supported in Embedded Swift\")")) + } + + @Test + func exportMethodOnlyModuleStillRegistersTypeHandles() throws { + // A module whose only generic declarations are exports over primitives + // defines no bridgeable @JS types of its own, so it emits no module + // registration hook. The primitive handles are owned by JavaScriptKit + // and registered by the core hook, which is where the token map gets + // populated before the first generic call. + let js = try linkSource( + """ + @JS public func onlyIdentity(_ value: T) -> T { value } + """ + ).js + #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_typeIdByToken.set(tokens[i], typeIds[i]);")) + #expect(!js.contains("bjs[\"bjs_TestModule_register_type_handles\"] = function(base, count) {")) + } + + @Test + func importMethodOnlyEmitsJSRuntimeInfrastructure() throws { + // A module with generic imports but no exported @JS types still needs a + // populated codec table for the primitives; those come from the core + // hook in JavaScriptKit rather than a per-module registration. + 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\"]();")) + // No exported @JS types means no module-level registration hook. + #expect(!js.contains("bjs[\"bjs_TestModule_register_type_handles\"] = function(base, count) {")) + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericExports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericExports.swift new file mode 100644 index 000000000..c6139b827 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericExports.swift @@ -0,0 +1,170 @@ +@JS struct ExportPoint { + var x: Int + var y: Int +} + +@JS enum ExportNamespace { + @JS struct Metadata { + var label: String + var count: Int + } + + @JS enum Level: Int { + case low = 1 + case high = 9 + } +} + +@JS enum ExportMode: String { + case on + case off +} + +@JS enum ExportColor { + case red + case green +} + +@JS enum ExportTagged { + case number(value: Int) + case text(value: String) +} + +@JS final class ExportBox { + @JS var value: Int + @JS init(value: Int) { + self.value = value + } + @JS func get() -> Int { + value + } +} + +@JS public func genericExportIdentity(_ value: T) -> T { + return value +} + +@JS public func genericExportArray(_ values: [T]) -> [T] { + return values +} + +@JS public func genericExportOptional(_ value: T?) -> T? { + return value +} + +@JS public func genericExportDictionary(_ values: [String: T]) -> [String: T] { + return values +} + +@JS public func genericExportEcho(_ value: T, tag: Int) -> T { + return value +} + +@JS public func genericExportStore(_ key: String, _ value: T) { +} + +@JS public func genericExportLoad(_ key: String) -> T? { + return nil +} + +@JS public func genericExportStructConcreteLeading(_ v: T, _ p: ExportPoint) -> T { + return v +} + +@JS public func genericExportStructAndScalar(_ p: ExportPoint, tag: Int, _ v: T) -> T +{ + return v +} + +@JS public func genericExportPair(_ a: T, _ b: T) -> T { + return a +} + +@JS public func genericExportCombine( + _ a: T, + _ b: U +) -> T { + return a +} + +@JS public func genericExportCombineReturnU( + _ a: T, + _ b: U +) -> U { + return b +} + +@JS +public func genericExportCaseDistinct< + T: BridgedSwiftGenericBridgeable, + t: BridgedSwiftGenericBridgeable +>(_ a: T, _ b: t) -> T { + return a +} + +@JS final class GenericBox { + @JS init() {} + + @JS func wrap(_ value: T) -> T { + return value + } + + @JS func combine(_ a: T, _ b: t) -> T { + return a + } + + @JS static func makeArray(_ value: T) -> [T] { + return [value] + } + + @JS func load(_ key: String) -> T? { + return nil + } +} + +@JS struct GenericPair { + @JS init() {} + + @JS func first(_ value: T) -> T { + return value + } + + @JS func combine(_ a: T, _ b: t) -> T { + return a + } + + @JS func maybe(_ value: T) -> T? { + return value + } + + @JS func dict(_ value: T) -> [String: T] { + return ["value": value] + } + + @JS static func wrap(_ value: T) -> [T] { + return [value] + } +} + +@JS enum GenericFactory { + case primary + + @JS static func one(_ value: T) -> T { + return value + } +} + +@JS enum GenericNamespace { + @JS static func make(_ value: T) -> T { + return value + } +} + +// A namespaced final class: its token is `ExportGenericNamespace_Handle`, but +// its d.ts interface is emitted at the top level, so the token table must not +// spell the type as `ExportGenericNamespace.Handle`. +@JS enum ExportGenericNamespace { + @JS final class Handle { + @JS init() {} + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json new file mode 100644 index 000000000..0d149056e --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json @@ -0,0 +1,1278 @@ +{ + "exported" : { + "aliases" : [ + + ], + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_ExportBox_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_ExportBox_get", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "get", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "name" : "ExportBox", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ExportBox" + }, + { + "constructor" : { + "abiName" : "bjs_GenericBox_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "isFinal" : true, + "methods" : [ + { + "abiName" : "bjs_GenericBox_wrap", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "wrap", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_GenericBox_combine", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T", + "t" + ], + "name" : "combine", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "t" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_GenericBox_static_makeArray", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "makeArray", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + }, + "staticContext" : { + "className" : { + "_0" : "GenericBox" + } + } + }, + { + "abiName" : "bjs_GenericBox_load", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "load", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + } + ], + "name" : "GenericBox", + "properties" : [ + + ], + "swiftCallName" : "GenericBox" + }, + { + "constructor" : { + "abiName" : "bjs_ExportGenericNamespace_Handle_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "isFinal" : true, + "methods" : [ + + ], + "name" : "Handle", + "namespace" : [ + "ExportGenericNamespace" + ], + "properties" : [ + + ], + "swiftCallName" : "ExportGenericNamespace.Handle" + } + ], + "enums" : [ + { + "cases" : [ + + ], + "emitStyle" : "const", + "name" : "ExportNamespace", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "ExportNamespace", + "tsFullPath" : "ExportNamespace" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "low", + "rawValue" : "1" + }, + { + "associatedValues" : [ + + ], + "name" : "high", + "rawValue" : "9" + } + ], + "emitStyle" : "const", + "name" : "Level", + "namespace" : [ + "ExportNamespace" + ], + "rawType" : "Int", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "ExportNamespace.Level", + "tsFullPath" : "ExportNamespace.Level" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "on" + }, + { + "associatedValues" : [ + + ], + "name" : "off" + } + ], + "emitStyle" : "const", + "name" : "ExportMode", + "rawType" : "String", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "ExportMode", + "tsFullPath" : "ExportMode" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "red" + }, + { + "associatedValues" : [ + + ], + "name" : "green" + } + ], + "emitStyle" : "const", + "name" : "ExportColor", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "ExportColor", + "tsFullPath" : "ExportColor" + }, + { + "cases" : [ + { + "associatedValues" : [ + { + "label" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "name" : "number" + }, + { + "associatedValues" : [ + { + "label" : "value", + "type" : { + "string" : { + + } + } + } + ], + "name" : "text" + } + ], + "emitStyle" : "const", + "name" : "ExportTagged", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "ExportTagged", + "tsFullPath" : "ExportTagged" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "primary" + } + ], + "emitStyle" : "const", + "name" : "GenericFactory", + "staticMethods" : [ + { + "abiName" : "bjs_GenericFactory_static_one", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "one", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + }, + "staticContext" : { + "enumName" : { + "_0" : "GenericFactory" + } + } + } + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericFactory", + "tsFullPath" : "GenericFactory" + }, + { + "cases" : [ + + ], + "emitStyle" : "const", + "name" : "GenericNamespace", + "staticMethods" : [ + { + "abiName" : "bjs_GenericNamespace_static_make", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "make", + "namespace" : [ + "GenericNamespace" + ], + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "GenericNamespace" + } + } + } + ], + "staticProperties" : [ + + ], + "swiftCallName" : "GenericNamespace", + "tsFullPath" : "GenericNamespace" + }, + { + "cases" : [ + + ], + "emitStyle" : "const", + "name" : "ExportGenericNamespace", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "ExportGenericNamespace", + "tsFullPath" : "ExportGenericNamespace" + } + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_genericExportIdentity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportIdentity", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_genericExportArray", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportArray", + "parameters" : [ + { + "label" : "_", + "name" : "values", + "type" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + }, + { + "abiName" : "bjs_genericExportOptional", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportOptional", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_genericExportDictionary", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportDictionary", + "parameters" : [ + { + "label" : "_", + "name" : "values", + "type" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + } + ], + "returnType" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + }, + { + "abiName" : "bjs_genericExportEcho", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportEcho", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_genericExportStore", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportStore", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + }, + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_genericExportLoad", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportLoad", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_genericExportStructConcreteLeading", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportStructConcreteLeading", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "p", + "type" : { + "swiftStruct" : { + "_0" : "ExportPoint" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_genericExportStructAndScalar", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportStructAndScalar", + "parameters" : [ + { + "label" : "_", + "name" : "p", + "type" : { + "swiftStruct" : { + "_0" : "ExportPoint" + } + } + }, + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "label" : "_", + "name" : "v", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_genericExportPair", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "genericExportPair", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_genericExportCombine", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T", + "U" + ], + "name" : "genericExportCombine", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_genericExportCombineReturnU", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T", + "U" + ], + "name" : "genericExportCombineReturnU", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "U" + } + } + }, + { + "abiName" : "bjs_genericExportCaseDistinct", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T", + "t" + ], + "name" : "genericExportCaseDistinct", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "t" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "protocols" : [ + + ], + "structs" : [ + { + "methods" : [ + + ], + "name" : "ExportPoint", + "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" : "ExportPoint" + }, + { + "methods" : [ + + ], + "name" : "Metadata", + "namespace" : [ + "ExportNamespace" + ], + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "label", + "namespace" : [ + "ExportNamespace" + ], + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "count", + "namespace" : [ + "ExportNamespace" + ], + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ExportNamespace.Metadata" + }, + { + "constructor" : { + "abiName" : "bjs_GenericPair_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_GenericPair_first", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "first", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_GenericPair_combine", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T", + "t" + ], + "name" : "combine", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "t" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_GenericPair_maybe", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "maybe", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_GenericPair_dict", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "dict", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + }, + { + "abiName" : "bjs_GenericPair_static_wrap", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "wrap", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + }, + "staticContext" : { + "structName" : { + "_0" : "GenericPair" + } + } + } + ], + "name" : "GenericPair", + "properties" : [ + + ], + "swiftCallName" : "GenericPair" + } + ] + }, + "moduleName" : "TestModule", + "usedExternalModules" : [ + + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.swift new file mode 100644 index 000000000..1bd4bb95e --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.swift @@ -0,0 +1,1091 @@ +extension ExportNamespace.Level: _BridgedSwiftEnumNoPayload, _BridgedSwiftRawValueEnum { +} + +extension ExportMode: _BridgedSwiftEnumNoPayload, _BridgedSwiftRawValueEnum { +} + +extension ExportColor: _BridgedSwiftCaseEnum { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> ExportColor { + return bridgeJSLiftParameter(value) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> ExportColor { + return ExportColor(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 ExportTagged: _BridgedSwiftAssociatedValueEnum { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPopPayload(_ caseId: Int32) -> ExportTagged { + switch caseId { + case 0: + return .number(value: Int.bridgeJSStackPop()) + case 1: + return .text(value: String.bridgeJSStackPop()) + default: + fatalError("Unknown ExportTagged 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 GenericFactory: _BridgedSwiftCaseEnum { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> GenericFactory { + return bridgeJSLiftParameter(value) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> GenericFactory { + return GenericFactory(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSLowerParameter() + } + + @_spi(BridgeJS) @usableFromInline init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .primary + default: + return nil + } + } + + @_spi(BridgeJS) @usableFromInline var bridgeJSRawValue: Int32 { + switch self { + case .primary: + return 0 + } + } +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericFactory_static_one") +@_cdecl("bjs_GenericFactory_static_one") +public func _bjs_GenericFactory_static_one(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericFactory_static_one") +@_cdecl("bjs_GenericFactory_static_one") +public func _bjs_GenericFactory_static_one(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericFactory_static_one_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericFactory_static_one_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: T = GenericFactory.one(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericNamespace_static_make") +@_cdecl("bjs_GenericNamespace_static_make") +public func _bjs_GenericNamespace_static_make(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericNamespace_static_make") +@_cdecl("bjs_GenericNamespace_static_make") +public func _bjs_GenericNamespace_static_make(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericNamespace_static_make_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericNamespace_static_make_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: T = GenericNamespace.make(_: value) + ret.bridgeJSStackPush() +} +#endif + +extension ExportPoint: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> ExportPoint { + let y = Int.bridgeJSStackPop() + let x = Int.bridgeJSStackPop() + return ExportPoint(x: x, y: y) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.x.bridgeJSStackPush() + self.y.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_ExportPoint(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_ExportPoint())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_ExportPoint") +fileprivate func _bjs_struct_lower_ExportPoint_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_ExportPoint_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_ExportPoint(_ objectId: Int32) -> Void { + return _bjs_struct_lower_ExportPoint_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_ExportPoint") +fileprivate func _bjs_struct_lift_ExportPoint_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_ExportPoint_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_ExportPoint() -> Int32 { + return _bjs_struct_lift_ExportPoint_extern() +} + +extension ExportNamespace.Metadata: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> ExportNamespace.Metadata { + let count = Int.bridgeJSStackPop() + let label = String.bridgeJSStackPop() + return ExportNamespace.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_ExportNamespace_Metadata(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_ExportNamespace_Metadata())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_ExportNamespace_Metadata") +fileprivate func _bjs_struct_lower_ExportNamespace_Metadata_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_ExportNamespace_Metadata_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_ExportNamespace_Metadata(_ objectId: Int32) -> Void { + return _bjs_struct_lower_ExportNamespace_Metadata_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_ExportNamespace_Metadata") +fileprivate func _bjs_struct_lift_ExportNamespace_Metadata_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_ExportNamespace_Metadata_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_ExportNamespace_Metadata() -> Int32 { + return _bjs_struct_lift_ExportNamespace_Metadata_extern() +} + +extension GenericPair: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> GenericPair { + return GenericPair() + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_GenericPair(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_GenericPair())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_GenericPair") +fileprivate func _bjs_struct_lower_GenericPair_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_GenericPair_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_GenericPair(_ objectId: Int32) -> Void { + return _bjs_struct_lower_GenericPair_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_GenericPair") +fileprivate func _bjs_struct_lift_GenericPair_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_GenericPair_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_GenericPair() -> Int32 { + return _bjs_struct_lift_GenericPair_extern() +} + +@_expose(wasm, "bjs_GenericPair_init") +@_cdecl("bjs_GenericPair_init") +public func _bjs_GenericPair_init() -> Void { + #if arch(wasm32) + let ret = GenericPair() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericPair_first") +@_cdecl("bjs_GenericPair_first") +public func _bjs_GenericPair_first(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericPair_first") +@_cdecl("bjs_GenericPair_first") +public func _bjs_GenericPair_first(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericPair_first_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericPair_first_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let _self = GenericPair.bridgeJSLiftParameter() + let ret: T = _self.first(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericPair_combine") +@_cdecl("bjs_GenericPair_combine") +public func _bjs_GenericPair_combine(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericPair_combine") +@_cdecl("bjs_GenericPair_combine") +public func _bjs_GenericPair_combine(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_GenericPair_combine_open1(_generic0Type, _generic1Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericPair_combine_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_GenericPair_combine_open2(_generic1Type, asT: T.self) +} +private func _bjs_GenericPair_combine_open2(_ _generic1Type: t.Type, asT _generic0Type: T.Type) { + let b = t.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let _self = GenericPair.bridgeJSLiftParameter() + let ret: T = _self.combine(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericPair_maybe") +@_cdecl("bjs_GenericPair_maybe") +public func _bjs_GenericPair_maybe(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericPair_maybe") +@_cdecl("bjs_GenericPair_maybe") +public func _bjs_GenericPair_maybe(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericPair_maybe_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericPair_maybe_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let _self = GenericPair.bridgeJSLiftParameter() + let ret: Optional = _self.maybe(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericPair_dict") +@_cdecl("bjs_GenericPair_dict") +public func _bjs_GenericPair_dict(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericPair_dict") +@_cdecl("bjs_GenericPair_dict") +public func _bjs_GenericPair_dict(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericPair_dict_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericPair_dict_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let _self = GenericPair.bridgeJSLiftParameter() + let ret: [String: T] = _self.dict(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericPair_static_wrap") +@_cdecl("bjs_GenericPair_static_wrap") +public func _bjs_GenericPair_static_wrap(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericPair_static_wrap") +@_cdecl("bjs_GenericPair_static_wrap") +public func _bjs_GenericPair_static_wrap(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericPair_static_wrap_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericPair_static_wrap_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: [T] = GenericPair.wrap(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportIdentity") +@_cdecl("bjs_genericExportIdentity") +public func _bjs_genericExportIdentity(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportIdentity") +@_cdecl("bjs_genericExportIdentity") +public func _bjs_genericExportIdentity(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportIdentity_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportIdentity_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: T = genericExportIdentity(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportArray") +@_cdecl("bjs_genericExportArray") +public func _bjs_genericExportArray(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportArray") +@_cdecl("bjs_genericExportArray") +public func _bjs_genericExportArray(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportArray_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportArray_open1(_ _generic0Type: T.Type) { + let values = Array.bridgeJSStackPop() + let ret: [T] = genericExportArray(_: values) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportOptional") +@_cdecl("bjs_genericExportOptional") +public func _bjs_genericExportOptional(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportOptional") +@_cdecl("bjs_genericExportOptional") +public func _bjs_genericExportOptional(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportOptional_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportOptional_open1(_ _generic0Type: T.Type) { + let value = Optional.bridgeJSStackPop() + let ret: Optional = genericExportOptional(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportDictionary") +@_cdecl("bjs_genericExportDictionary") +public func _bjs_genericExportDictionary(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportDictionary") +@_cdecl("bjs_genericExportDictionary") +public func _bjs_genericExportDictionary(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportDictionary_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportDictionary_open1(_ _generic0Type: T.Type) { + let values = Dictionary.bridgeJSStackPop() + let ret: [String: T] = genericExportDictionary(_: values) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportEcho") +@_cdecl("bjs_genericExportEcho") +public func _bjs_genericExportEcho(_ tag: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportEcho") +@_cdecl("bjs_genericExportEcho") +public func _bjs_genericExportEcho(_ tag: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportEcho_open1(_generic0Type, tag) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportEcho_open1(_ _generic0Type: T.Type, _ tag: Int32) { + let tag = Int.bridgeJSLiftParameter(tag) + let value = T.bridgeJSStackPop() + let ret: T = genericExportEcho(_: value, tag: tag) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportStore") +@_cdecl("bjs_genericExportStore") +public func _bjs_genericExportStore(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportStore") +@_cdecl("bjs_genericExportStore") +public func _bjs_genericExportStore(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportStore_open1(_generic0Type, keyBytes, keyLength) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportStore_open1(_ _generic0Type: T.Type, _ keyBytes: Int32, _ keyLength: Int32) { + let value = T.bridgeJSStackPop() + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + genericExportStore(_: key, _: value) +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportLoad") +@_cdecl("bjs_genericExportLoad") +public func _bjs_genericExportLoad(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportLoad") +@_cdecl("bjs_genericExportLoad") +public func _bjs_genericExportLoad(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportLoad_open1(_generic0Type, keyBytes, keyLength) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportLoad_open1(_ _generic0Type: T.Type, _ keyBytes: Int32, _ keyLength: Int32) { + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + let ret: Optional = genericExportLoad(_: key) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportStructConcreteLeading") +@_cdecl("bjs_genericExportStructConcreteLeading") +public func _bjs_genericExportStructConcreteLeading(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportStructConcreteLeading") +@_cdecl("bjs_genericExportStructConcreteLeading") +public func _bjs_genericExportStructConcreteLeading(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportStructConcreteLeading_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportStructConcreteLeading_open1(_ _generic0Type: T.Type) { + let p = ExportPoint.bridgeJSLiftParameter() + let v = T.bridgeJSStackPop() + let ret: T = genericExportStructConcreteLeading(_: v, _: p) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportStructAndScalar") +@_cdecl("bjs_genericExportStructAndScalar") +public func _bjs_genericExportStructAndScalar(_ tag: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportStructAndScalar") +@_cdecl("bjs_genericExportStructAndScalar") +public func _bjs_genericExportStructAndScalar(_ tag: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportStructAndScalar_open1(_generic0Type, tag) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportStructAndScalar_open1(_ _generic0Type: T.Type, _ tag: Int32) { + let v = T.bridgeJSStackPop() + let tag = Int.bridgeJSLiftParameter(tag) + let p = ExportPoint.bridgeJSLiftParameter() + let ret: T = genericExportStructAndScalar(_: p, tag: tag, _: v) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportPair") +@_cdecl("bjs_genericExportPair") +public func _bjs_genericExportPair(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportPair") +@_cdecl("bjs_genericExportPair") +public func _bjs_genericExportPair(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_genericExportPair_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportPair_open1(_ _generic0Type: T.Type) { + let b = T.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: T = genericExportPair(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportCombine") +@_cdecl("bjs_genericExportCombine") +public func _bjs_genericExportCombine(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportCombine") +@_cdecl("bjs_genericExportCombine") +public func _bjs_genericExportCombine(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_genericExportCombine_open1(_generic0Type, _generic1Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportCombine_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_genericExportCombine_open2(_generic1Type, asT: T.self) +} +private func _bjs_genericExportCombine_open2(_ _generic1Type: U.Type, asT _generic0Type: T.Type) { + let b = U.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: T = genericExportCombine(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportCombineReturnU") +@_cdecl("bjs_genericExportCombineReturnU") +public func _bjs_genericExportCombineReturnU(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportCombineReturnU") +@_cdecl("bjs_genericExportCombineReturnU") +public func _bjs_genericExportCombineReturnU(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_genericExportCombineReturnU_open1(_generic0Type, _generic1Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportCombineReturnU_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_genericExportCombineReturnU_open2(_generic1Type, asT: T.self) +} +private func _bjs_genericExportCombineReturnU_open2(_ _generic1Type: U.Type, asT _generic0Type: T.Type) { + let b = U.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: U = genericExportCombineReturnU(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_genericExportCaseDistinct") +@_cdecl("bjs_genericExportCaseDistinct") +public func _bjs_genericExportCaseDistinct(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericExportCaseDistinct") +@_cdecl("bjs_genericExportCaseDistinct") +public func _bjs_genericExportCaseDistinct(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_genericExportCaseDistinct_open1(_generic0Type, _generic1Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericExportCaseDistinct_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_genericExportCaseDistinct_open2(_generic1Type, asT: T.self) +} +private func _bjs_genericExportCaseDistinct_open2(_ _generic1Type: t.Type, asT _generic0Type: T.Type) { + let b = t.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: T = genericExportCaseDistinct(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +@_expose(wasm, "bjs_ExportBox_init") +@_cdecl("bjs_ExportBox_init") +public func _bjs_ExportBox_init(_ value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let value = Int.bridgeJSLiftParameter(value) + let ret = ExportBox(value: value) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExportBox_get") +@_cdecl("bjs_ExportBox_get") +public func _bjs_ExportBox_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let _self = ExportBox.bridgeJSLiftParameter(_self) + let ret = _self.get() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExportBox_value_get") +@_cdecl("bjs_ExportBox_value_get") +public func _bjs_ExportBox_value_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let _self = ExportBox.bridgeJSLiftParameter(_self) + let ret = _self.value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExportBox_value_set") +@_cdecl("bjs_ExportBox_value_set") +public func _bjs_ExportBox_value_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + let value = Int.bridgeJSLiftParameter(value) + let _self = ExportBox.bridgeJSLiftParameter(_self) + _self.value = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExportBox_deinit") +@_cdecl("bjs_ExportBox_deinit") +public func _bjs_ExportBox_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ExportBox: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ExportBox_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ExportBox_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ExportBox_wrap") +fileprivate func _bjs_ExportBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ExportBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ExportBox_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ExportBox_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_GenericBox_init") +@_cdecl("bjs_GenericBox_init") +public func _bjs_GenericBox_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = GenericBox() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericBox_wrap") +@_cdecl("bjs_GenericBox_wrap") +public func _bjs_GenericBox_wrap(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericBox_wrap") +@_cdecl("bjs_GenericBox_wrap") +public func _bjs_GenericBox_wrap(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericBox_wrap_open1(_generic0Type, _self) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericBox_wrap_open1(_ _generic0Type: T.Type, _ _self: UnsafeMutableRawPointer) { + let value = T.bridgeJSStackPop() + let _self = GenericBox.bridgeJSLiftParameter(_self) + let ret: T = _self.wrap(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericBox_combine") +@_cdecl("bjs_GenericBox_combine") +public func _bjs_GenericBox_combine(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericBox_combine") +@_cdecl("bjs_GenericBox_combine") +public func _bjs_GenericBox_combine(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_GenericBox_combine_open1(_generic0Type, _generic1Type, _self) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericBox_combine_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type, _ _self: UnsafeMutableRawPointer) { + _bjs_GenericBox_combine_open2(_generic1Type, asT: T.self, _self) +} +private func _bjs_GenericBox_combine_open2(_ _generic1Type: t.Type, asT _generic0Type: T.Type, _ _self: UnsafeMutableRawPointer) { + let b = t.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let _self = GenericBox.bridgeJSLiftParameter(_self) + let ret: T = _self.combine(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericBox_static_makeArray") +@_cdecl("bjs_GenericBox_static_makeArray") +public func _bjs_GenericBox_static_makeArray(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericBox_static_makeArray") +@_cdecl("bjs_GenericBox_static_makeArray") +public func _bjs_GenericBox_static_makeArray(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericBox_static_makeArray_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericBox_static_makeArray_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: [T] = GenericBox.makeArray(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericBox_load") +@_cdecl("bjs_GenericBox_load") +public func _bjs_GenericBox_load(_ _self: UnsafeMutableRawPointer, _ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericBox_load") +@_cdecl("bjs_GenericBox_load") +public func _bjs_GenericBox_load(_ _self: UnsafeMutableRawPointer, _ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericBox_load_open1(_generic0Type, _self, keyBytes, keyLength) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericBox_load_open1(_ _generic0Type: T.Type, _ _self: UnsafeMutableRawPointer, _ keyBytes: Int32, _ keyLength: Int32) { + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + let _self = GenericBox.bridgeJSLiftParameter(_self) + let ret: Optional = _self.load(_: key) + ret.bridgeJSStackPush() +} +#endif + +@_expose(wasm, "bjs_GenericBox_deinit") +@_cdecl("bjs_GenericBox_deinit") +public func _bjs_GenericBox_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension GenericBox: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_GenericBox_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_GenericBox_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_GenericBox_wrap") +fileprivate func _bjs_GenericBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_GenericBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_GenericBox_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_GenericBox_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_ExportGenericNamespace_Handle_init") +@_cdecl("bjs_ExportGenericNamespace_Handle_init") +public func _bjs_ExportGenericNamespace_Handle_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ExportGenericNamespace.Handle() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExportGenericNamespace_Handle_deinit") +@_cdecl("bjs_ExportGenericNamespace_Handle_deinit") +public func _bjs_ExportGenericNamespace_Handle_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ExportGenericNamespace.Handle: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ExportGenericNamespace_Handle_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ExportGenericNamespace_Handle_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ExportGenericNamespace_Handle_wrap") +fileprivate func _bjs_ExportGenericNamespace_Handle_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ExportGenericNamespace_Handle_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ExportGenericNamespace_Handle_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ExportGenericNamespace_Handle_wrap_extern(pointer) +} + +extension ExportPoint: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportPoint.bridgeJSMakeTypeHandle() +} + +extension ExportNamespace.Metadata: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportNamespace.Metadata.bridgeJSMakeTypeHandle() +} + +extension GenericPair: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericPair.bridgeJSMakeTypeHandle() +} + +extension ExportBox: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportBox.bridgeJSMakeTypeHandle() +} + +extension GenericBox: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericBox.bridgeJSMakeTypeHandle() +} + +extension ExportGenericNamespace.Handle: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericNamespace.Handle.bridgeJSMakeTypeHandle() +} + +extension ExportNamespace.Level: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportNamespace.Level.bridgeJSMakeTypeHandle() +} + +extension ExportMode: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportMode.bridgeJSMakeTypeHandle() +} + +extension ExportColor: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportColor.bridgeJSMakeTypeHandle() +} + +extension ExportTagged: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportTagged.bridgeJSMakeTypeHandle() +} + +extension GenericFactory: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericFactory.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] = [ + ExportPoint.bridgeJSTypeID, + ExportNamespace.Metadata.bridgeJSTypeID, + GenericPair.bridgeJSTypeID, + ExportBox.bridgeJSTypeID, + GenericBox.bridgeJSTypeID, + ExportGenericNamespace.Handle.bridgeJSTypeID, + ExportNamespace.Level.bridgeJSTypeID, + ExportMode.bridgeJSTypeID, + ExportColor.bridgeJSTypeID, + ExportTagged.bridgeJSTypeID, + GenericFactory.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/GenericExports.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.d.ts new file mode 100644 index 000000000..e6ddcf9c2 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.d.ts @@ -0,0 +1,137 @@ +// 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 ExportModeValues: { + readonly On: "on"; + readonly Off: "off"; +}; +export type ExportModeTag = typeof ExportModeValues[keyof typeof ExportModeValues]; + +export const ExportColorValues: { + readonly Red: 0; + readonly Green: 1; +}; +export type ExportColorTag = typeof ExportColorValues[keyof typeof ExportColorValues]; + +export const ExportTaggedValues: { + readonly Tag: { + readonly Number: 0; + readonly Text: 1; + }; +}; + +export type ExportTaggedTag = + { tag: typeof ExportTaggedValues.Tag.Number; value: number } | { tag: typeof ExportTaggedValues.Tag.Text; value: string } + +export const GenericFactoryValues: { + readonly Primary: 0; +}; +export type GenericFactoryTag = typeof GenericFactoryValues[keyof typeof GenericFactoryValues]; + +export interface ExportPoint { + x: number; + y: number; +} +export interface GenericPair { + first(value: T, typeT: BridgeType): T; + combine(a: T, b: t, typeT: BridgeType, typet: BridgeType): T; + maybe(value: T, typeT: BridgeType): T | null; + dict(value: T, typeT: BridgeType): Record; +} +declare const bridgeTypeBrand: unique symbol; +export type BridgeType = string & { readonly [bridgeTypeBrand]: (value: T) => void }; +export const BridgeTypes: { readonly Bool: BridgeType; readonly Int: BridgeType; readonly Int8: BridgeType; readonly UInt8: BridgeType; readonly Int16: BridgeType; readonly UInt16: BridgeType; readonly Int32: BridgeType; readonly UInt32: BridgeType; readonly UInt: BridgeType; readonly Int64: BridgeType; readonly UInt64: BridgeType; readonly Float: BridgeType; readonly Double: BridgeType; readonly String: BridgeType; readonly JSValue: BridgeType; readonly ExportPoint: BridgeType; readonly ExportNamespace_Metadata: BridgeType; readonly GenericPair: BridgeType; readonly ExportBox: BridgeType; readonly GenericBox: BridgeType; readonly ExportGenericNamespace_Handle: BridgeType; readonly ExportNamespace_Level: BridgeType; readonly ExportMode: BridgeType; readonly ExportColor: BridgeType; readonly ExportTagged: BridgeType; readonly GenericFactory: BridgeType; }; +export type LevelObject = typeof ExportNamespace.LevelValues; + +export type ExportModeObject = typeof ExportModeValues; + +export type ExportColorObject = typeof ExportColorValues; + +export type ExportTaggedObject = typeof ExportTaggedValues; + +export type GenericFactoryObject = typeof GenericFactoryValues & { + one(value: T, typeT: BridgeType): T; +}; + +export namespace ExportNamespace { + const LevelValues: { + readonly Low: 1; + readonly High: 9; + }; + type LevelTag = typeof LevelValues[keyof typeof LevelValues]; + export interface Metadata { + label: string; + count: number; + } +} +/// 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 ExportBox extends SwiftHeapObject { + get(): number; + value: number; +} +export interface GenericBox extends SwiftHeapObject { + wrap(value: T, typeT: BridgeType): T; + combine(a: T, b: t, typeT: BridgeType, typet: BridgeType): T; + load(key: string, typeT: BridgeType): T | null; +} +export interface Handle extends SwiftHeapObject { +} +export type Exports = { + genericExportIdentity(value: T, typeT: BridgeType): T; + genericExportArray(values: T[], typeT: BridgeType): T[]; + genericExportOptional(value: T | null, typeT: BridgeType): T | null; + genericExportDictionary(values: Record, typeT: BridgeType): Record; + genericExportEcho(value: T, tag: number, typeT: BridgeType): T; + genericExportStore(key: string, value: T, typeT: BridgeType): void; + genericExportLoad(key: string, typeT: BridgeType): T | null; + genericExportStructConcreteLeading(v: T, p: ExportPoint, typeT: BridgeType): T; + genericExportStructAndScalar(p: ExportPoint, tag: number, v: T, typeT: BridgeType): T; + genericExportPair(a: T, b: T, typeT: BridgeType): T; + genericExportCombine(a: T, b: U, typeT: BridgeType, typeU: BridgeType): T; + genericExportCombineReturnU(a: T, b: U, typeT: BridgeType, typeU: BridgeType): U; + genericExportCaseDistinct(a: T, b: t, typeT: BridgeType, typet: BridgeType): T; + ExportMode: ExportModeObject + ExportColor: ExportColorObject + ExportTagged: ExportTaggedObject + GenericFactory: GenericFactoryObject + ExportBox: { + new(value: number): ExportBox; + }, + ExportGenericNamespace: { + Handle: { + new(): Handle; + }, + }, + ExportNamespace: { + Level: LevelObject + }, + GenericBox: { + new(): GenericBox; + makeArray(value: T, typeT: BridgeType): T[]; + }, + GenericNamespace: { + make(value: T, typeT: BridgeType): T; + }, + GenericPair: { + init(): GenericPair; + wrap(value: T, typeT: BridgeType): T[]; + }, +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js new file mode 100644 index 000000000..70bb1abd0 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js @@ -0,0 +1,1167 @@ +// 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 LevelValues = { + Low: 1, + High: 9, +}; + +export const ExportModeValues = { + On: "on", + Off: "off", +}; + +export const ExportColorValues = { + Red: 0, + Green: 1, +}; + +export const ExportTaggedValues = { + Tag: { + Number: 0, + Text: 1, + }, +}; +export const GenericFactoryValues = { + Primary: 0, +}; + +export const BridgeTypes = { Bool: "Bool", Int: "Int", Int8: "Int8", UInt8: "UInt8", Int16: "Int16", UInt16: "UInt16", Int32: "Int32", UInt32: "UInt32", UInt: "UInt", Int64: "Int64", UInt64: "UInt64", Float: "Float", Double: "Double", String: "String", JSValue: "JSValue", ExportPoint: "ExportPoint", ExportNamespace_Metadata: "ExportNamespace_Metadata", GenericPair: "GenericPair", ExportBox: "ExportBox", GenericBox: "GenericBox", ExportGenericNamespace_Handle: "ExportGenericNamespace_Handle", ExportNamespace_Level: "ExportNamespace_Level", ExportMode: "ExportMode", ExportColor: "ExportColor", ExportTagged: "ExportTagged", GenericFactory: "GenericFactory" }; +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(); + const __bjs_typeIdByToken = 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; + } + function __bjs_typeIdForToken(token) { + __bjs_registerTypeHandles(); + const typeId = __bjs_typeIdByToken.get(token); + if (typeId === undefined) { + throw new TypeError("BridgeJS: unknown BridgeType token '" + token + "'"); + } + return typeId; + } + + 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_M10TestModuleT11ExportPoint = { + lower: (v) => { + structHelpers.M10TestModuleT11ExportPoint.lower(v); + }, + lift: () => { + const struct = structHelpers.M10TestModuleT11ExportPoint.lift(); + return struct; + }, + }; + const __bjs_codec_M10TestModuleT15ExportNamespaceT8Metadata = { + lower: (v) => { + structHelpers.M10TestModuleT15ExportNamespaceT8Metadata.lower(v); + }, + lift: () => { + const struct = structHelpers.M10TestModuleT15ExportNamespaceT8Metadata.lift(); + return struct; + }, + }; + const __bjs_codec_M10TestModuleT11GenericPair = { + lower: (v) => { + structHelpers.M10TestModuleT11GenericPair.lower(v); + }, + lift: () => { + const struct = structHelpers.M10TestModuleT11GenericPair.lift(); + return struct; + }, + }; + const __bjs_codec_M10TestModuleT9ExportBox = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports['ExportBox'].__construct(ptr); + return obj; + }, + }; + const __bjs_codec_M10TestModuleT10GenericBox = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports['GenericBox'].__construct(ptr); + return obj; + }, + }; + const __bjs_codec_T22ExportGenericNamespaceT6Handle = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports.ExportGenericNamespace.Handle.__construct(ptr); + return obj; + }, + }; + const __bjs_codec_M10TestModuleT15ExportNamespaceT5Level = { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const rawValue = i32Stack.pop(); + return rawValue; + }, + }; + const __bjs_codec_M10TestModuleT11ExportColor = { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const caseId = i32Stack.pop(); + return caseId; + }, + }; + const __bjs_codec_M10TestModuleT12ExportTagged = { + lower: (v) => { + const caseId = enumHelpers.M10TestModuleT12ExportTagged.lower(v); + i32Stack.push(caseId); + }, + lift: () => { + const enumValue = enumHelpers.M10TestModuleT12ExportTagged.lift(i32Stack.pop()); + return enumValue; + }, + }; + const __bjs_codec_M10TestModuleT14GenericFactory = { + lower: (v) => { + i32Stack.push((v | 0)); + }, + lift: () => { + const caseId = i32Stack.pop(); + return caseId; + }, + }; + + const __bjs_createStructHelpers_M10TestModuleT11ExportPoint = () => ({ + 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_createStructHelpers_M10TestModuleT15ExportNamespaceT8Metadata = () => ({ + lower: (value) => { + const bytes = textEncoder.encode(value.label); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + i32Stack.push((value.count | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + const string = strStack.pop(); + return { label: string, count: int }; + } + }); + const __bjs_createStructHelpers_M10TestModuleT11GenericPair = () => ({ + lower: (value) => { + }, + lift: () => { + const instance1 = { }; + instance1.first = function(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + structHelpers.M10TestModuleT11GenericPair.lower(this); + codecT.lower(value); + instance.exports.bjs_GenericPair_first(typeIdT); + return codecT.lift(); + }.bind(instance1); + instance1.combine = function(a, b, typeT, typet) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + const typeIdt = __bjs_typeIdForToken(typet); + const codect = __bjs_codecForTypeId(typeIdt); + structHelpers.M10TestModuleT11GenericPair.lower(this); + codecT.lower(a); + codect.lower(b); + instance.exports.bjs_GenericPair_combine(typeIdT, typeIdt); + return codecT.lift(); + }.bind(instance1); + instance1.maybe = function(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + structHelpers.M10TestModuleT11GenericPair.lower(this); + codecT.lower(value); + instance.exports.bjs_GenericPair_maybe(typeIdT); + return __bjs_optionalCodec(codecT).lift(); + }.bind(instance1); + instance1.dict = function(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + structHelpers.M10TestModuleT11GenericPair.lower(this); + codecT.lower(value); + instance.exports.bjs_GenericPair_dict(typeIdT); + return __bjs_dictCodec(codecT).lift(); + }.bind(instance1); + return instance1; + } + }); + const __bjs_createEnumHelpers_M10TestModuleT12ExportTagged = () => ({ + lower: (value) => { + const enumTag = value.tag; + switch (enumTag) { + case ExportTaggedValues.Tag.Number: { + i32Stack.push((value.value | 0)); + return ExportTaggedValues.Tag.Number; + } + case ExportTaggedValues.Tag.Text: { + const bytes = textEncoder.encode(value.value); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + return ExportTaggedValues.Tag.Text; + } + default: throw new Error("Unknown ExportTaggedValues tag: " + String(enumTag)); + } + }, + lift: (tag) => { + tag = tag | 0; + switch (tag) { + case ExportTaggedValues.Tag.Number: { + const int = i32Stack.pop(); + return { tag: ExportTaggedValues.Tag.Number, value: int }; + } + case ExportTaggedValues.Tag.Text: { + const string = strStack.pop(); + return { tag: ExportTaggedValues.Tag.Text, value: string }; + } + default: throw new Error("Unknown ExportTaggedValues tag returned from Swift: " + String(tag)); + } + } + }); + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + 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_ExportPoint"] = function(objectId) { + structHelpers.M10TestModuleT11ExportPoint.lower(swift.memory.getObject(objectId)); + } + bjs["swift_js_struct_lift_ExportPoint"] = function() { + const value = structHelpers.M10TestModuleT11ExportPoint.lift(); + return swift.memory.retain(value); + } + bjs["swift_js_struct_lower_ExportNamespace_Metadata"] = function(objectId) { + structHelpers.M10TestModuleT15ExportNamespaceT8Metadata.lower(swift.memory.getObject(objectId)); + } + bjs["swift_js_struct_lift_ExportNamespace_Metadata"] = function() { + const value = structHelpers.M10TestModuleT15ExportNamespaceT8Metadata.lift(); + return swift.memory.retain(value); + } + bjs["swift_js_struct_lower_GenericPair"] = function(objectId) { + structHelpers.M10TestModuleT11GenericPair.lower(swift.memory.getObject(objectId)); + } + bjs["swift_js_struct_lift_GenericPair"] = function() { + const value = structHelpers.M10TestModuleT11GenericPair.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, + ]; + const tokens = ["Bool", "Int", "Int8", "UInt8", "Int16", "UInt16", "Int32", "UInt32", "UInt", "Int64", "UInt64", "Float", "Double", "String", "JSValue"]; + 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_typeIdByToken.set(tokens[i], typeIds[i]); + } + } + bjs["bjs_TestModule_register_type_handles"] = function(base, count) { + const codecs = [ + __bjs_codec_M10TestModuleT11ExportPoint, + __bjs_codec_M10TestModuleT15ExportNamespaceT8Metadata, + __bjs_codec_M10TestModuleT11GenericPair, + __bjs_codec_M10TestModuleT9ExportBox, + __bjs_codec_M10TestModuleT10GenericBox, + __bjs_codec_T22ExportGenericNamespaceT6Handle, + __bjs_codec_M10TestModuleT15ExportNamespaceT5Level, + __bjs_stringCodec, + __bjs_codec_M10TestModuleT11ExportColor, + __bjs_codec_M10TestModuleT12ExportTagged, + __bjs_codec_M10TestModuleT14GenericFactory, + ]; + const tokens = ["ExportPoint", "ExportNamespace_Metadata", "GenericPair", "ExportBox", "GenericBox", "ExportGenericNamespace_Handle", "ExportNamespace_Level", "ExportMode", "ExportColor", "ExportTagged", "GenericFactory"]; + 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_typeIdByToken.set(tokens[i], typeIds[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_ExportBox_wrap"] = function(pointer) { + const obj = _exports['ExportBox'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_GenericBox_wrap"] = function(pointer) { + const obj = _exports['GenericBox'].__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_ExportGenericNamespace_Handle_wrap"] = function(pointer) { + const obj = _exports.ExportGenericNamespace.Handle.__construct(pointer); + return swift.memory.retain(obj); + }; + }, + 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) + } + }, + /** @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 ExportBox extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_ExportBox_deinit, ExportBox.prototype, null); + } + + constructor(value) { + const ret = instance.exports.bjs_ExportBox_init(value); + return ExportBox.__construct(ret); + } + get() { + const ret = instance.exports.bjs_ExportBox_get(this.pointer); + return ret; + } + get value() { + const ret = instance.exports.bjs_ExportBox_value_get(this.pointer); + return ret; + } + set value(value) { + instance.exports.bjs_ExportBox_value_set(this.pointer, value); + } + } + class GenericBox extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_GenericBox_deinit, GenericBox.prototype, null); + } + + constructor() { + const ret = instance.exports.bjs_GenericBox_init(); + return GenericBox.__construct(ret); + } + wrap(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(value); + instance.exports.bjs_GenericBox_wrap(this.pointer, typeIdT); + return codecT.lift(); + } + combine(a, b, typeT, typet) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + const typeIdt = __bjs_typeIdForToken(typet); + const codect = __bjs_codecForTypeId(typeIdt); + codecT.lower(a); + codect.lower(b); + instance.exports.bjs_GenericBox_combine(this.pointer, typeIdT, typeIdt); + return codecT.lift(); + } + static makeArray(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(value); + instance.exports.bjs_GenericBox_static_makeArray(typeIdT); + return __bjs_arrayCodec(codecT).lift(); + } + load(key, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + const keyBytes = textEncoder.encode(key); + const keyId = swift.memory.retain(keyBytes); + instance.exports.bjs_GenericBox_load(this.pointer, keyId, keyBytes.length, typeIdT); + return __bjs_optionalCodec(codecT).lift(); + } + } + class Handle extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_ExportGenericNamespace_Handle_deinit, Handle.prototype, null); + } + + constructor() { + const ret = instance.exports.bjs_ExportGenericNamespace_Handle_init(); + return Handle.__construct(ret); + } + } + const __bjs_helpers_M10TestModuleT11ExportPoint = __bjs_createStructHelpers_M10TestModuleT11ExportPoint(); + structHelpers.M10TestModuleT11ExportPoint = __bjs_helpers_M10TestModuleT11ExportPoint; + + const __bjs_helpers_M10TestModuleT15ExportNamespaceT8Metadata = __bjs_createStructHelpers_M10TestModuleT15ExportNamespaceT8Metadata(); + structHelpers.M10TestModuleT15ExportNamespaceT8Metadata = __bjs_helpers_M10TestModuleT15ExportNamespaceT8Metadata; + + const __bjs_helpers_M10TestModuleT11GenericPair = __bjs_createStructHelpers_M10TestModuleT11GenericPair(); + structHelpers.M10TestModuleT11GenericPair = __bjs_helpers_M10TestModuleT11GenericPair; + + const __bjs_helpers_M10TestModuleT12ExportTagged = __bjs_createEnumHelpers_M10TestModuleT12ExportTagged(); + enumHelpers.M10TestModuleT12ExportTagged = __bjs_helpers_M10TestModuleT12ExportTagged; + + const exports = { + genericExportIdentity: function bjs_genericExportIdentity(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(value); + instance.exports.bjs_genericExportIdentity(typeIdT); + return codecT.lift(); + }, + genericExportArray: function bjs_genericExportArray(values, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + __bjs_arrayCodec(codecT).lower(values); + instance.exports.bjs_genericExportArray(typeIdT); + return __bjs_arrayCodec(codecT).lift(); + }, + genericExportOptional: function bjs_genericExportOptional(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + __bjs_optionalCodec(codecT).lower(value); + instance.exports.bjs_genericExportOptional(typeIdT); + return __bjs_optionalCodec(codecT).lift(); + }, + genericExportDictionary: function bjs_genericExportDictionary(values, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + __bjs_dictCodec(codecT).lower(values); + instance.exports.bjs_genericExportDictionary(typeIdT); + return __bjs_dictCodec(codecT).lift(); + }, + genericExportEcho: function bjs_genericExportEcho(value, tag, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(value); + instance.exports.bjs_genericExportEcho(tag, typeIdT); + return codecT.lift(); + }, + genericExportStore: function bjs_genericExportStore(key, value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + const keyBytes = textEncoder.encode(key); + const keyId = swift.memory.retain(keyBytes); + codecT.lower(value); + instance.exports.bjs_genericExportStore(keyId, keyBytes.length, typeIdT); + }, + genericExportLoad: function bjs_genericExportLoad(key, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + const keyBytes = textEncoder.encode(key); + const keyId = swift.memory.retain(keyBytes); + instance.exports.bjs_genericExportLoad(keyId, keyBytes.length, typeIdT); + return __bjs_optionalCodec(codecT).lift(); + }, + genericExportStructConcreteLeading: function bjs_genericExportStructConcreteLeading(v, p, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(v); + structHelpers.M10TestModuleT11ExportPoint.lower(p); + instance.exports.bjs_genericExportStructConcreteLeading(typeIdT); + return codecT.lift(); + }, + genericExportStructAndScalar: function bjs_genericExportStructAndScalar(p, tag, v, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + structHelpers.M10TestModuleT11ExportPoint.lower(p); + codecT.lower(v); + instance.exports.bjs_genericExportStructAndScalar(tag, typeIdT); + return codecT.lift(); + }, + genericExportPair: function bjs_genericExportPair(a, b, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(a); + codecT.lower(b); + instance.exports.bjs_genericExportPair(typeIdT); + return codecT.lift(); + }, + genericExportCombine: function bjs_genericExportCombine(a, b, typeT, typeU) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + const typeIdU = __bjs_typeIdForToken(typeU); + const codecU = __bjs_codecForTypeId(typeIdU); + codecT.lower(a); + codecU.lower(b); + instance.exports.bjs_genericExportCombine(typeIdT, typeIdU); + return codecT.lift(); + }, + genericExportCombineReturnU: function bjs_genericExportCombineReturnU(a, b, typeT, typeU) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + const typeIdU = __bjs_typeIdForToken(typeU); + const codecU = __bjs_codecForTypeId(typeIdU); + codecT.lower(a); + codecU.lower(b); + instance.exports.bjs_genericExportCombineReturnU(typeIdT, typeIdU); + return codecU.lift(); + }, + genericExportCaseDistinct: function bjs_genericExportCaseDistinct(a, b, typeT, typet) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + const typeIdt = __bjs_typeIdForToken(typet); + const codect = __bjs_codecForTypeId(typeIdt); + codecT.lower(a); + codect.lower(b); + instance.exports.bjs_genericExportCaseDistinct(typeIdT, typeIdt); + return codecT.lift(); + }, + ExportMode: ExportModeValues, + ExportColor: ExportColorValues, + ExportTagged: ExportTaggedValues, + GenericFactory: { + ...GenericFactoryValues, + one: function(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(value); + instance.exports.bjs_GenericFactory_static_one(typeIdT); + return codecT.lift(); + } + }, + ExportBox, + ExportGenericNamespace: { + Handle, + }, + ExportNamespace: { + Level: LevelValues, + }, + GenericBox, + GenericNamespace: { + make: function bjs_GenericNamespace_static_make(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(value); + instance.exports.bjs_GenericNamespace_static_make(typeIdT); + return codecT.lift(); + }, + }, + GenericPair: { + init: function() { + instance.exports.bjs_GenericPair_init(); + const structValue = structHelpers.M10TestModuleT11GenericPair.lift(); + return structValue; + }, + wrap: function(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(value); + instance.exports.bjs_GenericPair_static_wrap(typeIdT); + return __bjs_arrayCodec(codecT).lift(); + }, + }, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift b/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift new file mode 100644 index 000000000..8e63e0964 --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift @@ -0,0 +1,174 @@ +import Testing +import JavaScriptKit + +@JS public struct ExportGenericPoint { + public var x: Int + public var y: Int + + @JS public init(x: Int, y: Int) { + self.x = x + self.y = y + } +} + +@JS public final class ExportGenericBox { + @JS public var value: Int + @JS public init(value: Int) { + self.value = value + } + @JS public func get() -> Int { + value + } +} + +@JS public func exportGenericIdentity(_ value: T) -> T { + value +} + +@JS public func exportGenericEcho(_ value: T, tag: Int) -> T { + value +} + +@JS public func exportGenericPickFirst(_ a: T, _ b: T) -> T { + a +} + +@JS public func exportGenericPickSecond(_ a: T, _ b: T) -> T { + b +} + +@JS public enum ExportGenericOutcome { + case ok(value: Int) + case fail(reason: String) +} + +@JS public func makeExportGenericOutcome(_ value: Int) -> ExportGenericOutcome { + .ok(value: value) +} + +@JS public func exportGenericOutcomeValue(_ outcome: ExportGenericOutcome) -> Int { + if case .ok(let value) = outcome { + return value + } + return -1 +} + +@JS public func exportGenericArrayIdentity(_ values: [T]) -> [T] { + values +} + +@JS public func exportGenericOptionalIdentity(_ value: T?) -> T? { + value +} + +@JS public func exportGenericDictIdentity(_ values: [String: T]) -> [String: T] { + values +} + +nonisolated(unsafe) var _lastWrappedPoint = ExportGenericPoint(x: 0, y: 0) +nonisolated(unsafe) var _lastTag = 0 + +@JS public func exportGenericWrapPointAndTag( + _ p: ExportGenericPoint, + tag: Int, + _ value: T +) -> T { + _lastWrappedPoint = p + _lastTag = tag + return value +} + +@JS +public func exportGenericCombineFirst< + T: BridgedSwiftGenericBridgeable, + U: BridgedSwiftGenericBridgeable +>(_ a: T, _ b: U) -> T { + a +} + +@JS +public func exportGenericCombineSecond< + T: BridgedSwiftGenericBridgeable, + U: BridgedSwiftGenericBridgeable +>(_ a: T, _ b: U) -> U { + b +} + +@JS +public func exportGenericCombineTripleLast< + T: BridgedSwiftGenericBridgeable, + U: BridgedSwiftGenericBridgeable, + V: BridgedSwiftGenericBridgeable +>(_ a: T, _ b: U, _ c: V) -> V { + c +} + +@JS public final class ExportGenericMethodBox { + @JS public init() {} + @JS public func echo(_ value: T) -> T { + value + } + @JS + public func combine< + T: BridgedSwiftGenericBridgeable, + U: BridgedSwiftGenericBridgeable + >(_ a: T, _ b: U) -> U { + b + } + @JS public static func wrapArray(_ value: T) -> [T] { + [value] + } +} + +@JS public struct ExportGenericMethodPair { + @JS public init() {} + @JS public func first(_ value: T) -> T { + value + } + @JS public func maybe(_ value: T, present: Bool) -> T? { + present ? value : nil + } + @JS public func dict(_ value: T) -> [String: T] { + ["value": value] + } + @JS public static func wrap(_ value: T) -> [T] { + [value] + } +} + +@JS public enum ExportGenericMethodFactory { + case primary + @JS public static func one(_ value: T) -> T { + value + } +} + +@JS public enum ExportGenericMethodNamespace { + @JS public static func make(_ value: T) -> T { + value + } +} + +nonisolated(unsafe) private var _genericStore: [String: any BridgedSwiftGenericBridgeable] = [:] + +@JS public func exportGenericStore(_ key: String, _ value: T) { + _genericStore[key] = value +} + +@JS public func exportGenericLoad(_ key: String) -> T? { + _genericStore[key] as? T +} + +@JS public func lastWrappedPointX() -> Int { _lastWrappedPoint.x } +@JS public func lastWrappedPointY() -> Int { _lastWrappedPoint.y } +@JS public func lastTag() -> Int { _lastTag } + +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "runExportGenericTests") +@_extern(c) +func runExportGenericTests() -> Void + +@Suite struct ExportGenericAPITests { + @Test func exportGenerics() { + runExportGenericTests() + } +} diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index acb3e23f1..4115baf29 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -6864,6 +6864,109 @@ public func _bjs_NestedStructGroupB_static_roundtripMetadata() -> Void { extension NestedTypeHost.Variant: _BridgedSwiftEnumNoPayload, _BridgedSwiftRawValueEnum { } +extension ExportGenericOutcome: _BridgedSwiftAssociatedValueEnum { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPopPayload(_ caseId: Int32) -> ExportGenericOutcome { + switch caseId { + case 0: + return .ok(value: Int.bridgeJSStackPop()) + case 1: + return .fail(reason: String.bridgeJSStackPop()) + default: + fatalError("Unknown ExportGenericOutcome case ID: \(caseId)") + } + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPushPayload() -> Int32 { + switch self { + case .ok(let value): + value.bridgeJSStackPush() + return Int32(0) + case .fail(let reason): + reason.bridgeJSStackPush() + return Int32(1) + } + } +} + +extension ExportGenericMethodFactory: _BridgedSwiftCaseEnum { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> ExportGenericMethodFactory { + return bridgeJSLiftParameter(value) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> ExportGenericMethodFactory { + return ExportGenericMethodFactory(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSLowerParameter() + } + + @_spi(BridgeJS) @usableFromInline init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .primary + default: + return nil + } + } + + @_spi(BridgeJS) @usableFromInline var bridgeJSRawValue: Int32 { + switch self { + case .primary: + return 0 + } + } +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_ExportGenericMethodFactory_static_one") +@_cdecl("bjs_ExportGenericMethodFactory_static_one") +public func _bjs_ExportGenericMethodFactory_static_one(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_ExportGenericMethodFactory_static_one") +@_cdecl("bjs_ExportGenericMethodFactory_static_one") +public func _bjs_ExportGenericMethodFactory_static_one(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_ExportGenericMethodFactory_static_one_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_ExportGenericMethodFactory_static_one_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: T = ExportGenericMethodFactory.one(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_ExportGenericMethodNamespace_static_make") +@_cdecl("bjs_ExportGenericMethodNamespace_static_make") +public func _bjs_ExportGenericMethodNamespace_static_make(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_ExportGenericMethodNamespace_static_make") +@_cdecl("bjs_ExportGenericMethodNamespace_static_make") +public func _bjs_ExportGenericMethodNamespace_static_make(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_ExportGenericMethodNamespace_static_make_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_ExportGenericMethodNamespace_static_make_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: T = ExportGenericMethodNamespace.make(_: value) + ret.bridgeJSStackPush() +} +#endif + extension Message: _BridgedSwiftAssociatedValueEnum { @_spi(BridgeJS) @_transparent public static func bridgeJSStackPopPayload(_ caseId: Int32) -> Message { switch caseId { @@ -7909,6 +8012,222 @@ public func _bjs_NestedTypeHost_Label_static_untitled() -> Void { #endif } +extension ExportGenericPoint: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> ExportGenericPoint { + let y = Int.bridgeJSStackPop() + let x = Int.bridgeJSStackPop() + return ExportGenericPoint(x: x, y: y) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.x.bridgeJSStackPush() + self.y.bridgeJSStackPush() + } + + public init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_ExportGenericPoint(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + public func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_ExportGenericPoint())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_ExportGenericPoint") +fileprivate func _bjs_struct_lower_ExportGenericPoint_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_ExportGenericPoint_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_ExportGenericPoint(_ objectId: Int32) -> Void { + return _bjs_struct_lower_ExportGenericPoint_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_ExportGenericPoint") +fileprivate func _bjs_struct_lift_ExportGenericPoint_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_ExportGenericPoint_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_ExportGenericPoint() -> Int32 { + return _bjs_struct_lift_ExportGenericPoint_extern() +} + +@_expose(wasm, "bjs_ExportGenericPoint_init") +@_cdecl("bjs_ExportGenericPoint_init") +public func _bjs_ExportGenericPoint_init(_ x: Int32, _ y: Int32) -> Void { + #if arch(wasm32) + let y = Int.bridgeJSLiftParameter(y) + let x = Int.bridgeJSLiftParameter(x) + let ret = ExportGenericPoint(x: x, y: y) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ExportGenericMethodPair: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> ExportGenericMethodPair { + return ExportGenericMethodPair() + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + } + + public init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_ExportGenericMethodPair(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + public func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_ExportGenericMethodPair())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_ExportGenericMethodPair") +fileprivate func _bjs_struct_lower_ExportGenericMethodPair_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_ExportGenericMethodPair_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_ExportGenericMethodPair(_ objectId: Int32) -> Void { + return _bjs_struct_lower_ExportGenericMethodPair_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_ExportGenericMethodPair") +fileprivate func _bjs_struct_lift_ExportGenericMethodPair_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_ExportGenericMethodPair_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_ExportGenericMethodPair() -> Int32 { + return _bjs_struct_lift_ExportGenericMethodPair_extern() +} + +@_expose(wasm, "bjs_ExportGenericMethodPair_init") +@_cdecl("bjs_ExportGenericMethodPair_init") +public func _bjs_ExportGenericMethodPair_init() -> Void { + #if arch(wasm32) + let ret = ExportGenericMethodPair() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_ExportGenericMethodPair_first") +@_cdecl("bjs_ExportGenericMethodPair_first") +public func _bjs_ExportGenericMethodPair_first(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_ExportGenericMethodPair_first") +@_cdecl("bjs_ExportGenericMethodPair_first") +public func _bjs_ExportGenericMethodPair_first(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_ExportGenericMethodPair_first_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_ExportGenericMethodPair_first_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let _self = ExportGenericMethodPair.bridgeJSLiftParameter() + let ret: T = _self.first(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_ExportGenericMethodPair_maybe") +@_cdecl("bjs_ExportGenericMethodPair_maybe") +public func _bjs_ExportGenericMethodPair_maybe(_ present: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_ExportGenericMethodPair_maybe") +@_cdecl("bjs_ExportGenericMethodPair_maybe") +public func _bjs_ExportGenericMethodPair_maybe(_ present: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_ExportGenericMethodPair_maybe_open1(_generic0Type, present) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_ExportGenericMethodPair_maybe_open1(_ _generic0Type: T.Type, _ present: Int32) { + let present = Bool.bridgeJSLiftParameter(present) + let value = T.bridgeJSStackPop() + let _self = ExportGenericMethodPair.bridgeJSLiftParameter() + let ret: Optional = _self.maybe(_: value, present: present) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_ExportGenericMethodPair_dict") +@_cdecl("bjs_ExportGenericMethodPair_dict") +public func _bjs_ExportGenericMethodPair_dict(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_ExportGenericMethodPair_dict") +@_cdecl("bjs_ExportGenericMethodPair_dict") +public func _bjs_ExportGenericMethodPair_dict(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_ExportGenericMethodPair_dict_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_ExportGenericMethodPair_dict_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let _self = ExportGenericMethodPair.bridgeJSLiftParameter() + let ret: [String: T] = _self.dict(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_ExportGenericMethodPair_static_wrap") +@_cdecl("bjs_ExportGenericMethodPair_static_wrap") +public func _bjs_ExportGenericMethodPair_static_wrap(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_ExportGenericMethodPair_static_wrap") +@_cdecl("bjs_ExportGenericMethodPair_static_wrap") +public func _bjs_ExportGenericMethodPair_static_wrap(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_ExportGenericMethodPair_static_wrap_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_ExportGenericMethodPair_static_wrap_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: [T] = ExportGenericMethodPair.wrap(_: value) + ret.bridgeJSStackPush() +} +#endif + extension Library.Shelf: _BridgedSwiftStruct { @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> Library.Shelf { let label = String.bridgeJSStackPop() @@ -11509,6 +11828,400 @@ public func _bjs_makeAdder(_ base: Int32) -> Int32 { #endif } +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericIdentity") +@_cdecl("bjs_exportGenericIdentity") +public func _bjs_exportGenericIdentity(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericIdentity") +@_cdecl("bjs_exportGenericIdentity") +public func _bjs_exportGenericIdentity(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericIdentity_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericIdentity_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: T = exportGenericIdentity(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericEcho") +@_cdecl("bjs_exportGenericEcho") +public func _bjs_exportGenericEcho(_ tag: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericEcho") +@_cdecl("bjs_exportGenericEcho") +public func _bjs_exportGenericEcho(_ tag: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericEcho_open1(_generic0Type, tag) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericEcho_open1(_ _generic0Type: T.Type, _ tag: Int32) { + let tag = Int.bridgeJSLiftParameter(tag) + let value = T.bridgeJSStackPop() + let ret: T = exportGenericEcho(_: value, tag: tag) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericPickFirst") +@_cdecl("bjs_exportGenericPickFirst") +public func _bjs_exportGenericPickFirst(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericPickFirst") +@_cdecl("bjs_exportGenericPickFirst") +public func _bjs_exportGenericPickFirst(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericPickFirst_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericPickFirst_open1(_ _generic0Type: T.Type) { + let b = T.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: T = exportGenericPickFirst(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericPickSecond") +@_cdecl("bjs_exportGenericPickSecond") +public func _bjs_exportGenericPickSecond(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericPickSecond") +@_cdecl("bjs_exportGenericPickSecond") +public func _bjs_exportGenericPickSecond(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericPickSecond_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericPickSecond_open1(_ _generic0Type: T.Type) { + let b = T.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: T = exportGenericPickSecond(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +@_expose(wasm, "bjs_makeExportGenericOutcome") +@_cdecl("bjs_makeExportGenericOutcome") +public func _bjs_makeExportGenericOutcome(_ value: Int32) -> Void { + #if arch(wasm32) + let value = Int.bridgeJSLiftParameter(value) + let ret = makeExportGenericOutcome(_: value) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_exportGenericOutcomeValue") +@_cdecl("bjs_exportGenericOutcomeValue") +public func _bjs_exportGenericOutcomeValue(_ outcome: Int32) -> Int32 { + #if arch(wasm32) + let outcome = ExportGenericOutcome.bridgeJSLiftParameter(outcome) + let ret = exportGenericOutcomeValue(_: outcome) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericArrayIdentity") +@_cdecl("bjs_exportGenericArrayIdentity") +public func _bjs_exportGenericArrayIdentity(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericArrayIdentity") +@_cdecl("bjs_exportGenericArrayIdentity") +public func _bjs_exportGenericArrayIdentity(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericArrayIdentity_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericArrayIdentity_open1(_ _generic0Type: T.Type) { + let values = Array.bridgeJSStackPop() + let ret: [T] = exportGenericArrayIdentity(_: values) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericOptionalIdentity") +@_cdecl("bjs_exportGenericOptionalIdentity") +public func _bjs_exportGenericOptionalIdentity(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericOptionalIdentity") +@_cdecl("bjs_exportGenericOptionalIdentity") +public func _bjs_exportGenericOptionalIdentity(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericOptionalIdentity_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericOptionalIdentity_open1(_ _generic0Type: T.Type) { + let value = Optional.bridgeJSStackPop() + let ret: Optional = exportGenericOptionalIdentity(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericDictIdentity") +@_cdecl("bjs_exportGenericDictIdentity") +public func _bjs_exportGenericDictIdentity(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericDictIdentity") +@_cdecl("bjs_exportGenericDictIdentity") +public func _bjs_exportGenericDictIdentity(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericDictIdentity_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericDictIdentity_open1(_ _generic0Type: T.Type) { + let values = Dictionary.bridgeJSStackPop() + let ret: [String: T] = exportGenericDictIdentity(_: values) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericWrapPointAndTag") +@_cdecl("bjs_exportGenericWrapPointAndTag") +public func _bjs_exportGenericWrapPointAndTag(_ tag: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericWrapPointAndTag") +@_cdecl("bjs_exportGenericWrapPointAndTag") +public func _bjs_exportGenericWrapPointAndTag(_ tag: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericWrapPointAndTag_open1(_generic0Type, tag) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericWrapPointAndTag_open1(_ _generic0Type: T.Type, _ tag: Int32) { + let value = T.bridgeJSStackPop() + let tag = Int.bridgeJSLiftParameter(tag) + let p = ExportGenericPoint.bridgeJSLiftParameter() + let ret: T = exportGenericWrapPointAndTag(_: p, tag: tag, _: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericCombineFirst") +@_cdecl("bjs_exportGenericCombineFirst") +public func _bjs_exportGenericCombineFirst(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericCombineFirst") +@_cdecl("bjs_exportGenericCombineFirst") +public func _bjs_exportGenericCombineFirst(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericCombineFirst_open1(_generic0Type, _generic1Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericCombineFirst_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_exportGenericCombineFirst_open2(_generic1Type, asT: T.self) +} +private func _bjs_exportGenericCombineFirst_open2(_ _generic1Type: U.Type, asT _generic0Type: T.Type) { + let b = U.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: T = exportGenericCombineFirst(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericCombineSecond") +@_cdecl("bjs_exportGenericCombineSecond") +public func _bjs_exportGenericCombineSecond(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericCombineSecond") +@_cdecl("bjs_exportGenericCombineSecond") +public func _bjs_exportGenericCombineSecond(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericCombineSecond_open1(_generic0Type, _generic1Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericCombineSecond_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_exportGenericCombineSecond_open2(_generic1Type, asT: T.self) +} +private func _bjs_exportGenericCombineSecond_open2(_ _generic1Type: U.Type, asT _generic0Type: T.Type) { + let b = U.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: U = exportGenericCombineSecond(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericCombineTripleLast") +@_cdecl("bjs_exportGenericCombineTripleLast") +public func _bjs_exportGenericCombineTripleLast(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32, _ _generic2TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericCombineTripleLast") +@_cdecl("bjs_exportGenericCombineTripleLast") +public func _bjs_exportGenericCombineTripleLast(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32, _ _generic2TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + let _generic2Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic2TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericCombineTripleLast_open1(_generic0Type, _generic1Type, _generic2Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericCombineTripleLast_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type, _ _generic2Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_exportGenericCombineTripleLast_open2(_generic1Type, asT: T.self, _generic2Type) +} +private func _bjs_exportGenericCombineTripleLast_open2(_ _generic1Type: U.Type, asT _generic0Type: T.Type, _ _generic2Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_exportGenericCombineTripleLast_open3(_generic2Type, asT: T.self, asU: U.self) +} +private func _bjs_exportGenericCombineTripleLast_open3(_ _generic2Type: V.Type, asT _generic0Type: T.Type, asU _generic1Type: U.Type) { + let c = V.bridgeJSStackPop() + let b = U.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: V = exportGenericCombineTripleLast(_: a, _: b, _: c) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericStore") +@_cdecl("bjs_exportGenericStore") +public func _bjs_exportGenericStore(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericStore") +@_cdecl("bjs_exportGenericStore") +public func _bjs_exportGenericStore(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericStore_open1(_generic0Type, keyBytes, keyLength) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericStore_open1(_ _generic0Type: T.Type, _ keyBytes: Int32, _ keyLength: Int32) { + let value = T.bridgeJSStackPop() + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + exportGenericStore(_: key, _: value) +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericLoad") +@_cdecl("bjs_exportGenericLoad") +public func _bjs_exportGenericLoad(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericLoad") +@_cdecl("bjs_exportGenericLoad") +public func _bjs_exportGenericLoad(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericLoad_open1(_generic0Type, keyBytes, keyLength) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericLoad_open1(_ _generic0Type: T.Type, _ keyBytes: Int32, _ keyLength: Int32) { + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + let ret: Optional = exportGenericLoad(_: key) + ret.bridgeJSStackPush() +} +#endif + +@_expose(wasm, "bjs_lastWrappedPointX") +@_cdecl("bjs_lastWrappedPointX") +public func _bjs_lastWrappedPointX() -> Int32 { + #if arch(wasm32) + let ret = lastWrappedPointX() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_lastWrappedPointY") +@_cdecl("bjs_lastWrappedPointY") +public func _bjs_lastWrappedPointY() -> Int32 { + #if arch(wasm32) + let ret = lastWrappedPointY() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_lastTag") +@_cdecl("bjs_lastTag") +public func _bjs_lastTag() -> Int32 { + #if arch(wasm32) + let ret = lastTag() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripMessage") @_cdecl("bjs_roundTripMessage") public func _bjs_roundTripMessage(_ message: Int32) -> Void { @@ -15289,6 +16002,206 @@ fileprivate func _bjs_NestedTypeHost_wrap_extern(_ pointer: UnsafeMutableRawPoin return _bjs_NestedTypeHost_wrap_extern(pointer) } +@_expose(wasm, "bjs_ExportGenericBox_init") +@_cdecl("bjs_ExportGenericBox_init") +public func _bjs_ExportGenericBox_init(_ value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let value = Int.bridgeJSLiftParameter(value) + let ret = ExportGenericBox(value: value) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExportGenericBox_get") +@_cdecl("bjs_ExportGenericBox_get") +public func _bjs_ExportGenericBox_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let _self = ExportGenericBox.bridgeJSLiftParameter(_self) + let ret = _self.get() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExportGenericBox_value_get") +@_cdecl("bjs_ExportGenericBox_value_get") +public func _bjs_ExportGenericBox_value_get(_ _self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let _self = ExportGenericBox.bridgeJSLiftParameter(_self) + let ret = _self.value + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExportGenericBox_value_set") +@_cdecl("bjs_ExportGenericBox_value_set") +public func _bjs_ExportGenericBox_value_set(_ _self: UnsafeMutableRawPointer, _ value: Int32) -> Void { + #if arch(wasm32) + let value = Int.bridgeJSLiftParameter(value) + let _self = ExportGenericBox.bridgeJSLiftParameter(_self) + _self.value = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_ExportGenericBox_deinit") +@_cdecl("bjs_ExportGenericBox_deinit") +public func _bjs_ExportGenericBox_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ExportGenericBox: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + public var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ExportGenericBox_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + public consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ExportGenericBox_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ExportGenericBox_wrap") +fileprivate func _bjs_ExportGenericBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ExportGenericBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ExportGenericBox_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ExportGenericBox_wrap_extern(pointer) +} + +@_expose(wasm, "bjs_ExportGenericMethodBox_init") +@_cdecl("bjs_ExportGenericMethodBox_init") +public func _bjs_ExportGenericMethodBox_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = ExportGenericMethodBox() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_ExportGenericMethodBox_echo") +@_cdecl("bjs_ExportGenericMethodBox_echo") +public func _bjs_ExportGenericMethodBox_echo(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_ExportGenericMethodBox_echo") +@_cdecl("bjs_ExportGenericMethodBox_echo") +public func _bjs_ExportGenericMethodBox_echo(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_ExportGenericMethodBox_echo_open1(_generic0Type, _self) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_ExportGenericMethodBox_echo_open1(_ _generic0Type: T.Type, _ _self: UnsafeMutableRawPointer) { + let value = T.bridgeJSStackPop() + let _self = ExportGenericMethodBox.bridgeJSLiftParameter(_self) + let ret: T = _self.echo(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_ExportGenericMethodBox_combine") +@_cdecl("bjs_ExportGenericMethodBox_combine") +public func _bjs_ExportGenericMethodBox_combine(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_ExportGenericMethodBox_combine") +@_cdecl("bjs_ExportGenericMethodBox_combine") +public func _bjs_ExportGenericMethodBox_combine(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_ExportGenericMethodBox_combine_open1(_generic0Type, _generic1Type, _self) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_ExportGenericMethodBox_combine_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type, _ _self: UnsafeMutableRawPointer) { + _bjs_ExportGenericMethodBox_combine_open2(_generic1Type, asT: T.self, _self) +} +private func _bjs_ExportGenericMethodBox_combine_open2(_ _generic1Type: U.Type, asT _generic0Type: T.Type, _ _self: UnsafeMutableRawPointer) { + let b = U.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let _self = ExportGenericMethodBox.bridgeJSLiftParameter(_self) + let ret: U = _self.combine(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_ExportGenericMethodBox_static_wrapArray") +@_cdecl("bjs_ExportGenericMethodBox_static_wrapArray") +public func _bjs_ExportGenericMethodBox_static_wrapArray(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_ExportGenericMethodBox_static_wrapArray") +@_cdecl("bjs_ExportGenericMethodBox_static_wrapArray") +public func _bjs_ExportGenericMethodBox_static_wrapArray(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_ExportGenericMethodBox_static_wrapArray_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_ExportGenericMethodBox_static_wrapArray_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let ret: [T] = ExportGenericMethodBox.wrapArray(_: value) + ret.bridgeJSStackPush() +} +#endif + +@_expose(wasm, "bjs_ExportGenericMethodBox_deinit") +@_cdecl("bjs_ExportGenericMethodBox_deinit") +public func _bjs_ExportGenericMethodBox_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ExportGenericMethodBox: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + public var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_ExportGenericMethodBox_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + public consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_ExportGenericMethodBox_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ExportGenericMethodBox_wrap") +fileprivate func _bjs_ExportGenericMethodBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_ExportGenericMethodBox_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_ExportGenericMethodBox_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_ExportGenericMethodBox_wrap_extern(pointer) +} + @_expose(wasm, "bjs_Library_init") @_cdecl("bjs_Library_init") public func _bjs_Library_init(_ nameBytes: Int32, _ nameLength: Int32) -> UnsafeMutableRawPointer { @@ -15921,6 +16834,14 @@ extension NestedTypeHost.Label: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = NestedTypeHost.Label.bridgeJSMakeTypeHandle() } +extension ExportGenericPoint: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericPoint.bridgeJSMakeTypeHandle() +} + +extension ExportGenericMethodPair: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericMethodPair.bridgeJSMakeTypeHandle() +} + extension Library.Shelf: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = Library.Shelf.bridgeJSMakeTypeHandle() } @@ -16037,6 +16958,14 @@ extension PriorityReference: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = PriorityReference.bridgeJSMakeTypeHandle() } +extension ExportGenericBox: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericBox.bridgeJSMakeTypeHandle() +} + +extension ExportGenericMethodBox: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericMethodBox.bridgeJSMakeTypeHandle() +} + extension ImportGenericBox: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = ImportGenericBox.bridgeJSMakeTypeHandle() } @@ -16157,6 +17086,14 @@ extension NestedTypeHost.Variant: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = NestedTypeHost.Variant.bridgeJSMakeTypeHandle() } +extension ExportGenericOutcome: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericOutcome.bridgeJSMakeTypeHandle() +} + +extension ExportGenericMethodFactory: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericMethodFactory.bridgeJSMakeTypeHandle() +} + extension Message: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = Message.bridgeJSMakeTypeHandle() } @@ -21296,6 +22233,8 @@ public func _bjs_BridgeJSRuntimeTests_register_type_handles() { NestedStructGroupA.Metadata.bridgeJSTypeID, NestedStructGroupB.Metadata.bridgeJSTypeID, NestedTypeHost.Label.bridgeJSTypeID, + ExportGenericPoint.bridgeJSTypeID, + ExportGenericMethodPair.bridgeJSTypeID, Library.Shelf.bridgeJSTypeID, Library.Shelf.Divider.bridgeJSTypeID, GenericRTPoint.bridgeJSTypeID, @@ -21325,6 +22264,8 @@ public func _bjs_BridgeJSRuntimeTests_register_type_handles() { TagReference.bridgeJSTypeID, TagHolderReference.bridgeJSTypeID, PriorityReference.bridgeJSTypeID, + ExportGenericBox.bridgeJSTypeID, + ExportGenericMethodBox.bridgeJSTypeID, ImportGenericBox.bridgeJSTypeID, Severity.bridgeJSTypeID, Shape.bridgeJSTypeID, @@ -21355,6 +22296,8 @@ public func _bjs_BridgeJSRuntimeTests_register_type_handles() { StaticCalculator.bridgeJSTypeID, StaticPropertyEnum.bridgeJSTypeID, NestedTypeHost.Variant.bridgeJSTypeID, + ExportGenericOutcome.bridgeJSTypeID, + ExportGenericMethodFactory.bridgeJSTypeID, Message.bridgeJSTypeID, Library.Genre.bridgeJSTypeID, Message.Update.bridgeJSTypeID, diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 4749c6eb3..ba2545854 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -4952,6 +4952,196 @@ ], "swiftCallName" : "NestedTypeHost" }, + { + "constructor" : { + "abiName" : "bjs_ExportGenericBox_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "explicitAccessControl" : "public", + "isFinal" : true, + "methods" : [ + { + "abiName" : "bjs_ExportGenericBox_get", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "get", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "name" : "ExportGenericBox", + "properties" : [ + { + "isReadonly" : false, + "isStatic" : false, + "name" : "value", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ExportGenericBox" + }, + { + "constructor" : { + "abiName" : "bjs_ExportGenericMethodBox_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "explicitAccessControl" : "public", + "isFinal" : true, + "methods" : [ + { + "abiName" : "bjs_ExportGenericMethodBox_echo", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "echo", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_ExportGenericMethodBox_combine", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T", + "U" + ], + "name" : "combine", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "U" + } + } + }, + { + "abiName" : "bjs_ExportGenericMethodBox_static_wrapArray", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "wrapArray", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + }, + "staticContext" : { + "className" : { + "_0" : "ExportGenericMethodBox" + } + } + } + ], + "name" : "ExportGenericMethodBox", + "properties" : [ + + ], + "swiftCallName" : "ExportGenericMethodBox" + }, { "constructor" : { "abiName" : "bjs_Library_init", @@ -10577,32 +10767,44 @@ { "associatedValues" : [ { + "label" : "value", "type" : { - "caseEnum" : { - "_0" : "Message.Update" + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } } } } ], - "name" : "update" + "name" : "ok" }, { "associatedValues" : [ + { + "label" : "reason", + "type" : { + "string" : { + } + } + } ], - "name" : "delete" + "name" : "fail" } ], "emitStyle" : "const", - "name" : "Message", + "explicitAccessControl" : "public", + "name" : "ExportGenericOutcome", "staticMethods" : [ ], "staticProperties" : [ ], - "swiftCallName" : "Message", - "tsFullPath" : "Message" + "swiftCallName" : "ExportGenericOutcome", + "tsFullPath" : "ExportGenericOutcome" }, { "cases" : [ @@ -10610,53 +10812,189 @@ "associatedValues" : [ ], - "name" : "fiction" - }, - { - "associatedValues" : [ - - ], - "name" : "reference" + "name" : "primary" } ], "emitStyle" : "const", - "name" : "Genre", - "namespace" : [ - "Library" - ], - "rawType" : "String", + "explicitAccessControl" : "public", + "name" : "ExportGenericMethodFactory", "staticMethods" : [ - + { + "abiName" : "bjs_ExportGenericMethodFactory_static_one", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "one", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + }, + "staticContext" : { + "enumName" : { + "_0" : "ExportGenericMethodFactory" + } + } + } ], "staticProperties" : [ ], - "swiftCallName" : "Library.Genre", - "tsFullPath" : "Library.Genre" + "swiftCallName" : "ExportGenericMethodFactory", + "tsFullPath" : "ExportGenericMethodFactory" }, { "cases" : [ - { - "associatedValues" : [ - - ], - "name" : "flip" - }, - { - "associatedValues" : [ - ], - "name" : "rotate" - } ], "emitStyle" : "const", - "name" : "Update", + "explicitAccessControl" : "public", + "name" : "ExportGenericMethodNamespace", + "staticMethods" : [ + { + "abiName" : "bjs_ExportGenericMethodNamespace_static_make", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "make", + "namespace" : [ + "ExportGenericMethodNamespace" + ], + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "ExportGenericMethodNamespace" + } + } + } + ], + "staticProperties" : [ + + ], + "swiftCallName" : "ExportGenericMethodNamespace", + "tsFullPath" : "ExportGenericMethodNamespace" + }, + { + "cases" : [ + { + "associatedValues" : [ + { + "type" : { + "caseEnum" : { + "_0" : "Message.Update" + } + } + } + ], + "name" : "update" + }, + { + "associatedValues" : [ + + ], + "name" : "delete" + } + ], + "emitStyle" : "const", + "name" : "Message", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "Message", + "tsFullPath" : "Message" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "fiction" + }, + { + "associatedValues" : [ + + ], + "name" : "reference" + } + ], + "emitStyle" : "const", + "name" : "Genre", + "namespace" : [ + "Library" + ], + "rawType" : "String", + "staticMethods" : [ + + ], + "staticProperties" : [ + + ], + "swiftCallName" : "Library.Genre", + "tsFullPath" : "Library.Genre" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "flip" + }, + { + "associatedValues" : [ + + ], + "name" : "rotate" + } + ], + "emitStyle" : "const", + "name" : "Update", "staticMethods" : [ ], - "staticProperties" : [ - - ], + "staticProperties" : [ + + ], "swiftCallName" : "Message.Update", "tsFullPath" : "Update" }, @@ -17647,202 +17985,813 @@ } }, { - "abiName" : "bjs_roundTripMessage", + "abiName" : "bjs_exportGenericIdentity", "effects" : { "isAsync" : false, "isStatic" : false, "isThrows" : false }, - "name" : "roundTripMessage", + "genericParameters" : [ + "T" + ], + "name" : "exportGenericIdentity", "parameters" : [ { "label" : "_", - "name" : "message", + "name" : "value", "type" : { - "associatedValueEnum" : { - "_0" : "Message" + "generic" : { + "_0" : "T" } } } ], "returnType" : { - "associatedValueEnum" : { - "_0" : "Message" + "generic" : { + "_0" : "T" } } }, { - "abiName" : "bjs_describeRefinedEntity", + "abiName" : "bjs_exportGenericEcho", "effects" : { "isAsync" : false, "isStatic" : false, "isThrows" : false }, - "name" : "describeRefinedEntity", + "genericParameters" : [ + "T" + ], + "name" : "exportGenericEcho", "parameters" : [ { "label" : "_", - "name" : "entity", + "name" : "value", "type" : { - "swiftProtocol" : { - "_0" : "RefineEntity" + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } } } } ], "returnType" : { - "string" : { - + "generic" : { + "_0" : "T" } } }, { - "abiName" : "bjs_describeTaggedEntity", + "abiName" : "bjs_exportGenericPickFirst", "effects" : { "isAsync" : false, "isStatic" : false, "isThrows" : false }, - "name" : "describeTaggedEntity", + "genericParameters" : [ + "T" + ], + "name" : "exportGenericPickFirst", "parameters" : [ { "label" : "_", - "name" : "entity", + "name" : "a", "type" : { - "swiftProtocol" : { - "_0" : "RefineTagged" + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "T" } } } ], "returnType" : { - "string" : { - + "generic" : { + "_0" : "T" } } }, { - "abiName" : "bjs_renamedEcho", + "abiName" : "bjs_exportGenericPickSecond", "effects" : { "isAsync" : false, "isStatic" : false, "isThrows" : false }, - "jsName" : "renamedEcho", - "name" : "jsNameEcho", + "genericParameters" : [ + "T" + ], + "name" : "exportGenericPickSecond", "parameters" : [ { "label" : "_", - "name" : "value", + "name" : "a", "type" : { - "string" : { - + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "T" } } } ], "returnType" : { - "string" : { - + "generic" : { + "_0" : "T" } } }, { - "abiName" : "bjs_greetName", + "abiName" : "bjs_makeExportGenericOutcome", "effects" : { "isAsync" : false, "isStatic" : false, "isThrows" : false }, - "jsName" : "greetName", - "name" : "greet", + "name" : "makeExportGenericOutcome", "parameters" : [ { "label" : "_", - "name" : "name", + "name" : "value", "type" : { - "string" : { - + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } } } } ], "returnType" : { - "string" : { - + "associatedValueEnum" : { + "_0" : "ExportGenericOutcome" } } }, { - "abiName" : "bjs_greetCount", + "abiName" : "bjs_exportGenericOutcomeValue", "effects" : { "isAsync" : false, "isStatic" : false, "isThrows" : false }, - "jsName" : "greetCount", - "name" : "greet", + "name" : "exportGenericOutcomeValue", "parameters" : [ { "label" : "_", - "name" : "count", + "name" : "outcome", "type" : { - "integer" : { - "_0" : { - "isSigned" : true, - "width" : "word" - } + "associatedValueEnum" : { + "_0" : "ExportGenericOutcome" } } } ], "returnType" : { - "string" : { - + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } } } }, { - "abiName" : "bjs_roundTripPointerFields", + "abiName" : "bjs_exportGenericArrayIdentity", "effects" : { "isAsync" : false, "isStatic" : false, "isThrows" : false }, - "name" : "roundTripPointerFields", + "genericParameters" : [ + "T" + ], + "name" : "exportGenericArrayIdentity", "parameters" : [ { "label" : "_", - "name" : "value", + "name" : "values", "type" : { - "swiftStruct" : { - "_0" : "PointerFields" + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } } } } ], "returnType" : { - "swiftStruct" : { - "_0" : "PointerFields" + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } } } }, { - "abiName" : "bjs_testStructDefault", + "abiName" : "bjs_exportGenericOptionalIdentity", "effects" : { "isAsync" : false, "isStatic" : false, "isThrows" : false }, - "name" : "testStructDefault", + "genericParameters" : [ + "T" + ], + "name" : "exportGenericOptionalIdentity", "parameters" : [ { - "defaultValue" : { - "structLiteral" : { - "_0" : "DataPoint", - "_1" : [ - { - "name" : "x", + "label" : "_", + "name" : "value", + "type" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_exportGenericDictIdentity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "exportGenericDictIdentity", + "parameters" : [ + { + "label" : "_", + "name" : "values", + "type" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + } + ], + "returnType" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + }, + { + "abiName" : "bjs_exportGenericWrapPointAndTag", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "exportGenericWrapPointAndTag", + "parameters" : [ + { + "label" : "_", + "name" : "p", + "type" : { + "swiftStruct" : { + "_0" : "ExportGenericPoint" + } + } + }, + { + "label" : "tag", + "name" : "tag", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_exportGenericCombineFirst", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T", + "U" + ], + "name" : "exportGenericCombineFirst", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_exportGenericCombineSecond", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T", + "U" + ], + "name" : "exportGenericCombineSecond", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "U" + } + } + }, + { + "abiName" : "bjs_exportGenericCombineTripleLast", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T", + "U", + "V" + ], + "name" : "exportGenericCombineTripleLast", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "U" + } + } + }, + { + "label" : "_", + "name" : "c", + "type" : { + "generic" : { + "_0" : "V" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "V" + } + } + }, + { + "abiName" : "bjs_exportGenericStore", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "exportGenericStore", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + }, + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_exportGenericLoad", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "exportGenericLoad", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_lastWrappedPointX", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "lastWrappedPointX", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_lastWrappedPointY", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "lastWrappedPointY", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_lastTag", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "lastTag", + "parameters" : [ + + ], + "returnType" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "abiName" : "bjs_roundTripMessage", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripMessage", + "parameters" : [ + { + "label" : "_", + "name" : "message", + "type" : { + "associatedValueEnum" : { + "_0" : "Message" + } + } + } + ], + "returnType" : { + "associatedValueEnum" : { + "_0" : "Message" + } + } + }, + { + "abiName" : "bjs_describeRefinedEntity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "describeRefinedEntity", + "parameters" : [ + { + "label" : "_", + "name" : "entity", + "type" : { + "swiftProtocol" : { + "_0" : "RefineEntity" + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_describeTaggedEntity", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "describeTaggedEntity", + "parameters" : [ + { + "label" : "_", + "name" : "entity", + "type" : { + "swiftProtocol" : { + "_0" : "RefineTagged" + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_renamedEcho", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "jsName" : "renamedEcho", + "name" : "jsNameEcho", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_greetName", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "jsName" : "greetName", + "name" : "greet", + "parameters" : [ + { + "label" : "_", + "name" : "name", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_greetCount", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "jsName" : "greetCount", + "name" : "greet", + "parameters" : [ + { + "label" : "_", + "name" : "count", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_roundTripPointerFields", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "roundTripPointerFields", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "swiftStruct" : { + "_0" : "PointerFields" + } + } + } + ], + "returnType" : { + "swiftStruct" : { + "_0" : "PointerFields" + } + } + }, + { + "abiName" : "bjs_testStructDefault", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "testStructDefault", + "parameters" : [ + { + "defaultValue" : { + "structLiteral" : { + "_0" : "DataPoint", + "_1" : [ + { + "name" : "x", "value" : { "float" : { "_0" : 1 @@ -19470,6 +20419,236 @@ ], "swiftCallName" : "NestedTypeHost.Label" }, + { + "constructor" : { + "abiName" : "bjs_ExportGenericPoint_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "x", + "name" : "x", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + }, + { + "label" : "y", + "name" : "y", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "explicitAccessControl" : "public", + "methods" : [ + + ], + "name" : "ExportGenericPoint", + "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" : "ExportGenericPoint" + }, + { + "constructor" : { + "abiName" : "bjs_ExportGenericMethodPair_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "explicitAccessControl" : "public", + "methods" : [ + { + "abiName" : "bjs_ExportGenericMethodPair_first", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "first", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_ExportGenericMethodPair_maybe", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "maybe", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "present", + "name" : "present", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_ExportGenericMethodPair_dict", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "dict", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "dictionary" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + } + }, + { + "abiName" : "bjs_ExportGenericMethodPair_static_wrap", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "genericParameters" : [ + "T" + ], + "name" : "wrap", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "generic" : { + "_0" : "T" + } + } + } + }, + "staticContext" : { + "structName" : { + "_0" : "ExportGenericMethodPair" + } + } + } + ], + "name" : "ExportGenericMethodPair", + "properties" : [ + + ], + "swiftCallName" : "ExportGenericMethodPair" + }, { "constructor" : { "abiName" : "bjs_Library_Shelf_init", diff --git a/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs b/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs new file mode 100644 index 000000000..e120ba271 --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs @@ -0,0 +1,184 @@ +// @ts-check + +import assert from 'node:assert'; +import { BridgeTypes } from '../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.js'; + +/** @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ +export function runExportGenericTests(exports) { + // The import-direction tests sweep every supported T through the same codecs; + // the export direction checks representatives of each codec family. + assert.equal(exports.exportGenericIdentity(42, BridgeTypes.Int), 42); + assert.equal(exports.exportGenericIdentity(-7, BridgeTypes.Int), -7); + assert.equal(exports.exportGenericIdentity(true, BridgeTypes.Bool), true); + assert.equal(exports.exportGenericIdentity(3000000000, BridgeTypes.UInt32), 3000000000); + assert.equal(exports.exportGenericIdentity(-9000000000n, BridgeTypes.Int64), -9000000000n); + assert.equal(exports.exportGenericIdentity(Math.fround(1.25), BridgeTypes.Float), Math.fround(1.25)); + assert.equal(exports.exportGenericIdentity(3.5, BridgeTypes.Double), 3.5); + assert.equal(exports.exportGenericIdentity("hi", BridgeTypes.String), "hi"); + assert.equal(exports.exportGenericIdentity("", BridgeTypes.String), ""); + + assert.equal(exports.exportGenericIdentity(3.5, BridgeTypes.JSValue), 3.5); + assert.equal(exports.exportGenericIdentity("hey", BridgeTypes.JSValue), "hey"); + assert.equal(exports.exportGenericIdentity(true, BridgeTypes.JSValue), true); + assert.equal(exports.exportGenericIdentity(null, BridgeTypes.JSValue), null); + assert.equal(exports.exportGenericIdentity(undefined, BridgeTypes.JSValue), undefined); + const jsValueObject = exports.exportGenericIdentity({ tag: 5 }, BridgeTypes.JSValue); + assert.equal(jsValueObject.tag, 5); + + assert.equal(exports.exportGenericIdentity(0, BridgeTypes.GenericRTColor), 0); + assert.equal(exports.exportGenericIdentity(2, BridgeTypes.GenericRTColor), 2); + assert.equal(exports.exportGenericIdentity("dark", BridgeTypes.GenericRTMode), "dark"); + assert.equal(exports.exportGenericIdentity(9, BridgeTypes.GenericRTLevel), 9); + + const outcome = exports.makeExportGenericOutcome(42); + const sameOutcome = exports.exportGenericIdentity(outcome, BridgeTypes.ExportGenericOutcome); + assert.equal(exports.exportGenericOutcomeValue(sameOutcome), 42); + + const point = exports.exportGenericIdentity({ x: 1, y: 2 }, BridgeTypes.ExportGenericPoint); + assert.equal(point.x, 1); + assert.equal(point.y, 2); + + assert.equal(exports.exportGenericEcho(9, 0, BridgeTypes.Int), 9); + assert.equal(exports.exportGenericEcho(-3, 1, BridgeTypes.Int), -3); + assert.equal(exports.exportGenericEcho("tagged", 5, BridgeTypes.String), "tagged"); + + const echoedPoint = exports.exportGenericEcho({ x: 4, y: 5 }, 7, BridgeTypes.ExportGenericPoint); + assert.equal(echoedPoint.x, 4); + assert.equal(echoedPoint.y, 5); + + assert.equal(exports.exportGenericPickFirst(1, 2, BridgeTypes.Int), 1); + assert.equal(exports.exportGenericPickSecond(1, 2, BridgeTypes.Int), 2); + + assert.deepEqual(exports.exportGenericArrayIdentity([1, 2, 3], BridgeTypes.Int), [1, 2, 3]); + assert.deepEqual(exports.exportGenericArrayIdentity(["a", "b"], BridgeTypes.String), ["a", "b"]); + assert.deepEqual(exports.exportGenericArrayIdentity([], BridgeTypes.Int), []); + assert.equal(exports.exportGenericOptionalIdentity(7, BridgeTypes.Int), 7); + assert.equal(exports.exportGenericOptionalIdentity(null, BridgeTypes.Int), null); + assert.equal(exports.exportGenericOptionalIdentity("hi", BridgeTypes.String), "hi"); + assert.deepEqual(exports.exportGenericDictIdentity({ x: 1, y: 2 }, BridgeTypes.Int), { x: 1, y: 2 }); + + // Stack ordering with side effects: struct + scalar + generic, across a scalar and a struct value. + assert.equal(exports.exportGenericWrapPointAndTag({ x: 1, y: 1 }, 7, "z", BridgeTypes.String), "z"); + assert.equal(exports.lastTag(), 7); + assert.equal(exports.lastWrappedPointX(), 1); + assert.equal(exports.lastWrappedPointY(), 1); + + const wrappedStruct = exports.exportGenericWrapPointAndTag({ x: 5, y: 6 }, 8, { x: 7, y: 8 }, BridgeTypes.ExportGenericPoint); + assert.equal(wrappedStruct.x, 7); + assert.equal(wrappedStruct.y, 8); + assert.equal(exports.lastWrappedPointX(), 5); + assert.equal(exports.lastWrappedPointY(), 6); + assert.equal(exports.lastTag(), 8); + + // Multiple distinct generic parameters: T and U cross independently via their own codecs. + assert.equal(exports.exportGenericCombineFirst(7, "hello", BridgeTypes.Int, BridgeTypes.String), 7); + assert.equal(exports.exportGenericCombineSecond(7, "hello", BridgeTypes.Int, BridgeTypes.String), "hello"); + + // Swapped concrete types prove the type ids and codecs are not mixed up. + assert.equal(exports.exportGenericCombineFirst("hello", 7, BridgeTypes.String, BridgeTypes.Int), "hello"); + assert.equal(exports.exportGenericCombineSecond("hello", 7, BridgeTypes.String, BridgeTypes.Int), 7); + + // Struct as one of the generics mixed with a scalar. + const combinedStructFirst = exports.exportGenericCombineFirst( + { x: 1, y: 2 }, + 9, + BridgeTypes.ExportGenericPoint, + BridgeTypes.Int + ); + assert.equal(combinedStructFirst.x, 1); + assert.equal(combinedStructFirst.y, 2); + assert.equal(exports.exportGenericCombineSecond({ x: 1, y: 2 }, 9, BridgeTypes.ExportGenericPoint, BridgeTypes.Int), 9); + + // T and U are the SAME concrete type but distinct values: ensure no swap. + assert.equal(exports.exportGenericCombineFirst(1, 2, BridgeTypes.Int, BridgeTypes.Int), 1); + assert.equal(exports.exportGenericCombineSecond(1, 2, BridgeTypes.Int, BridgeTypes.Int), 2); + + // Three distinct generic parameters prove the nested-open chain. + assert.equal( + exports.exportGenericCombineTripleLast(11, "two", true, BridgeTypes.Int, BridgeTypes.String, BridgeTypes.Bool), + true + ); + + // Return-only generic: the token alone selects the type the bridge produces. + exports.exportGenericStore("answer", 42, BridgeTypes.Int); + assert.equal(exports.exportGenericLoad("answer", BridgeTypes.Int), 42); + assert.equal(exports.exportGenericLoad("missing", BridgeTypes.Int), null); + exports.exportGenericStore("name", "hi", BridgeTypes.String); + assert.equal(exports.exportGenericLoad("name", BridgeTypes.String), "hi"); + // Loading under a different token than the stored type yields null (as? cast fails). + assert.equal(exports.exportGenericLoad("answer", BridgeTypes.String), null); + exports.exportGenericStore("point", { x: 3, y: 4 }, BridgeTypes.ExportGenericPoint); + const loadedPoint = exports.exportGenericLoad("point", BridgeTypes.ExportGenericPoint); + assert.equal(loadedPoint.x, 3); + assert.equal(loadedPoint.y, 4); + + const box = new exports.ExportGenericBox(123); + assert.equal(box.get(), 123); + const sameBox = exports.exportGenericIdentity(box, BridgeTypes.ExportGenericBox); + assert.equal(sameBox.get(), 123); + sameBox.release(); + + const firstBox = new exports.ExportGenericBox(7); + const secondBox = new exports.ExportGenericBox(9); + const pickedFirstBox = exports.exportGenericPickFirst(firstBox, secondBox, BridgeTypes.ExportGenericBox); + assert.equal(pickedFirstBox.get(), 7); + const pickedSecondBox = exports.exportGenericPickSecond(firstBox, secondBox, BridgeTypes.ExportGenericBox); + assert.equal(pickedSecondBox.get(), 9); + pickedFirstBox.release(); + pickedSecondBox.release(); + firstBox.release(); + secondBox.release(); + box.release(); + + const methodBox = new exports.ExportGenericMethodBox(); + assert.equal(methodBox.echo(42, BridgeTypes.Int), 42); + assert.equal(methodBox.echo(-7, BridgeTypes.Int), -7); + assert.equal(methodBox.echo("hi", BridgeTypes.String), "hi"); + assert.equal(methodBox.echo(true, BridgeTypes.Bool), true); + assert.equal(methodBox.echo(false, BridgeTypes.Bool), false); + + const methodOutcome = exports.makeExportGenericOutcome(77); + const echoedOutcome = methodBox.echo(methodOutcome, BridgeTypes.ExportGenericOutcome); + assert.equal(exports.exportGenericOutcomeValue(echoedOutcome), 77); + + assert.equal(methodBox.combine(7, "hello", BridgeTypes.Int, BridgeTypes.String), "hello"); + assert.equal(methodBox.combine("hello", 7, BridgeTypes.String, BridgeTypes.Int), 7); + methodBox.release(); + + assert.deepEqual(exports.ExportGenericMethodBox.wrapArray(5, BridgeTypes.Int), [5]); + assert.deepEqual(exports.ExportGenericMethodBox.wrapArray("x", BridgeTypes.String), ["x"]); + assert.deepEqual(exports.ExportGenericMethodBox.wrapArray(true, BridgeTypes.Bool), [true]); + + const methodPair = exports.ExportGenericMethodPair.init(); + assert.equal(methodPair.first(9, BridgeTypes.Int), 9); + assert.equal(methodPair.first("p", BridgeTypes.String), "p"); + assert.equal(methodPair.first(true, BridgeTypes.Bool), true); + + assert.equal(methodPair.maybe(42, true, BridgeTypes.Int), 42); + assert.equal(methodPair.maybe(42, false, BridgeTypes.Int), null); + assert.equal(methodPair.maybe("present", true, BridgeTypes.String), "present"); + assert.equal(methodPair.maybe("present", false, BridgeTypes.String), null); + + assert.deepEqual(methodPair.dict(7, BridgeTypes.Int), { value: 7 }); + assert.deepEqual(methodPair.dict("d", BridgeTypes.String), { value: "d" }); + + assert.deepEqual(exports.ExportGenericMethodPair.wrap(3, BridgeTypes.Int), [3]); + assert.deepEqual(exports.ExportGenericMethodPair.wrap("q", BridgeTypes.String), ["q"]); + + assert.equal(exports.ExportGenericMethodFactory.one(11, BridgeTypes.Int), 11); + assert.equal(exports.ExportGenericMethodFactory.one("e", BridgeTypes.String), "e"); + assert.equal(exports.ExportGenericMethodFactory.one(false, BridgeTypes.Bool), false); + + assert.equal(exports.ExportGenericMethodNamespace.make(13, BridgeTypes.Int), 13); + assert.equal(exports.ExportGenericMethodNamespace.make("n", BridgeTypes.String), "n"); + assert.equal(exports.ExportGenericMethodNamespace.make(true, BridgeTypes.Bool), true); + + // An unknown BridgeTypes token throws a catchable TypeError from the JS + // wrapper BEFORE the call enters wasm, so the shared value stacks stay + // balanced and later calls are unaffected. + assert.throws( + () => exports.exportGenericIdentity(1, "NotARealToken"), + TypeError + ); + assert.equal(exports.exportGenericIdentity(7, BridgeTypes.Int), 7); +} diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 5287df791..17c7e4818 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -6,6 +6,7 @@ import { import { ImportedFoo } from './BridgeJSRuntimeTests/JavaScript/Types.mjs'; import { runJsOptionalSupportTests } from './BridgeJSRuntimeTests/JavaScript/OptionalSupportTests.mjs'; import { runAliasWorks, getImports as getAliasImports, Surface } from './BridgeJSRuntimeTests/JavaScript/AliasTests.mjs'; +import { runExportGenericTests } from './BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs'; import { getImports as getClosureSupportImports } from './BridgeJSRuntimeTests/JavaScript/ClosureSupportTests.mjs'; import { getImports as getClosureThrowsImports } from './BridgeJSRuntimeTests/JavaScript/ClosureThrowsTests.mjs'; import { getImports as getClosureAsyncImports } from './BridgeJSRuntimeTests/JavaScript/ClosureAsyncTests.mjs'; @@ -250,6 +251,13 @@ export async function setupOptions(options, context) { } runAliasWorks(exports); } + bridgeJSRuntimeTests["runExportGenericTests"] = () => { + const exports = getExports(); + if (!exports) { + throw new Error("No exports!?"); + } + return runExportGenericTests(exports); + } const bridgeJSGlobalTests = importObject["BridgeJSGlobalTests"] || {}; bridgeJSGlobalTests["runJsWorksGlobal"] = () => { return BridgeJSGlobalTests_runJsWorksGlobal(); From e04f4e086b5ed4e76c8645818e6b0368a971587f Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Mon, 28 Sep 2026 21:12:15 +0200 Subject: [PATCH 3/5] BridgeJS: Support @JS protocol constraints on generic exports The same protocol compositions now work on exported generics: @JS public func storeNode(_ node: T) -> T { node } Unlike imports, the Swift compiler cannot check a JavaScript caller, so the constraint is enforced at the boundary: the link layer records every @JS type's conformed @JS protocols (declared on the type or via an extension) in a token-to-conformances map, and the generated wrapper resolves the caller's BridgeTypes token against the constraint before anything is lowered. A non-conforming token throws a catchable TypeError without entering wasm, keeping the shared value stack balanced; the Swift-side generic signature then guarantees the constraint holds inside the thunk. The d.ts wrapper signature carries 'T extends GraphNode', so TypeScript users get the check at compile time as well. --- .../Sources/BridgeJSCore/ExportSwift.swift | 56 +- .../BridgeJSCore/SwiftToSkeleton.swift | 112 ++- .../Sources/BridgeJSLink/BridgeJSLink.swift | 118 ++- .../Sources/BridgeJSLink/JSGlueGen.swift | 30 +- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 27 +- .../BridgeJSToolTests/DiagnosticsTests.swift | 10 + .../GenericExportDiagnosticsTests.swift | 189 ++++ .../GenericMethodOnlyModuleCodegenTests.swift | 2 +- .../GenericConstrainedExports.swift | 64 ++ .../GenericConstrainedExports.json | 388 ++++++++ .../GenericConstrainedExports.swift | 403 +++++++++ .../BridgeJSCodegenTests/GenericExports.json | 116 ++- .../GenericConstrainedExports.d.ts | 55 ++ .../GenericConstrainedExports.js | 832 ++++++++++++++++++ .../GenericExports.callsites.check.ts | 37 + .../BridgeJSLinkTests/GenericExports.js | 11 +- .../BridgeJSLinkTests/tsconfig.json | 11 +- .../BridgeJS-Internals/Design-Rationale.md | 6 + .../Exporting-Swift/Exporting-Swift-Class.md | 4 +- .../Exporting-Swift/Exporting-Swift-Enum.md | 3 +- .../Exporting-Swift-Function.md | 37 +- .../Exporting-Swift-Protocols.md | 1 + .../Exporting-Swift-Static-Functions.md | 2 +- .../Exporting-Swift/Exporting-Swift-Struct.md | 4 +- .../Importing-JS-Function.md | 31 +- .../Articles/BridgeJS/Supported-Types.md | 2 + .../Articles/BridgeJS/Unsupported-Features.md | 3 +- .../ExportGenericAPIs.swift | 75 ++ .../Generated/BridgeJS.swift | 343 ++++++++ .../Generated/JavaScript/BridgeJS.json | 487 +++++++++- .../JavaScript/ExportGenericTests.mjs | 76 ++ 31 files changed, 3411 insertions(+), 124 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericConstrainedExports.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericConstrainedExports.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericConstrainedExports.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericConstrainedExports.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericConstrainedExports.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.callsites.check.ts diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 21cd35f6a..15f40fe8b 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -189,10 +189,12 @@ public class ExportSwift { /// When non-empty, `render(abiName:)` wraps the standard body in a /// generics prologue: the entry thunk takes one trailing type-ID ABI /// parameter per generic, recovers the concrete types from their - /// `BridgeJSTypeHandle`s, and opens them into a chain of helper + /// `BridgeJSTypeHandle`s (enforcing any `@JS protocol` constraints via + /// a conditional metatype cast), and opens them into a chain of helper /// functions; the innermost helper runs the standard body with the /// generic parameters bound. - let genericParameterNames: [String] + let genericParameters: [GenericParameter] + var genericParameterNames: [String] { genericParameters.map(\.name) } /// The async return type settled through `_bjs_makePromise`'s `Promise_resolve_` /// helper. Set for every `async` thunk. @@ -200,11 +202,11 @@ public class ExportSwift { var parameterBindings: [CodeBlockItemSyntax] = [] - init(effects: Effects, returnType: BridgeType, genericParameterNames: [String] = []) throws { + init(effects: Effects, returnType: BridgeType, genericParameters: [GenericParameter] = []) throws { self.effects = effects - self.genericParameterNames = genericParameterNames + self.genericParameters = genericParameters guard effects.isAsync else { return } - guard genericParameterNames.isEmpty else { + guard genericParameters.isEmpty else { throw BridgeJSCoreError("Generic exported functions cannot be 'async' yet") } guard returnType.isAsyncResolvable else { @@ -528,16 +530,29 @@ public class ExportSwift { return abiReturnType?.swiftReturnPlaceholderStmt ?? "return" } + /// Swift spellings of a generic parameter's `@JS protocol` constraints, + /// qualified as written in source (`GraphKit.Node`) so the generated + /// generic clause resolves; the JS-side check keys on the bare names. + func protocolConstraints(_ genericName: String) -> [String] { + guard let parameter = genericParameters.first(where: { $0.name == genericName }) else { return [] } + return parameter.swiftConstraints ?? parameter.constraints + } + /// The constraint composition a generic parameter is opened under, e.g. - /// `BridgedSwiftGenericBridgeable`. A hook for protocol constraints. + /// `BridgedSwiftGenericBridgeable & GraphNode`. func constraintComposition(_ genericName: String) -> String { - "BridgedSwiftGenericBridgeable" + (["BridgedSwiftGenericBridgeable"] + protocolConstraints(genericName)).joined(separator: " & ") } /// The existential metatype spelling used to thread a not-yet-opened - /// generic parameter through the open chain. + /// generic parameter through the open chain, e.g. + /// `any (BridgedSwiftGenericBridgeable & GraphNode).Type`. func existentialMetatype(_ genericName: String) -> String { - "any BridgedSwiftGenericBridgeable.Type" + let constraints = protocolConstraints(genericName) + if constraints.isEmpty { + return "any BridgedSwiftGenericBridgeable.Type" + } + return "any (\(constraintComposition(genericName))).Type" } /// Renders the wasm entry point for a generic exported function. @@ -591,12 +606,29 @@ public class ExportSwift { // `BridgeJSTypeHandle`; recover the type from it. A raw wasm // caller passing a garbage ID is undefined behavior — the // generated JS wrapper validates tokens before calling in. + let constraints = protocolConstraints(genericName) + let recoveredName = + constraints.isEmpty ? metatypeName(genericName) : "\(metatypeName(genericName))Base" printer.write( - "let \(metatypeName(genericName)) = " + "let \(recoveredName) = " + "Unmanaged.fromOpaque(" + "UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: \(typeIdName(genericName)))))!" + ").takeUnretainedValue().type" ) + if !constraints.isEmpty { + // Defense-in-depth: the generated JS wrapper already + // rejects non-conforming tokens with a TypeError, so + // this cast can only fail for a raw wasm caller. + printer.write( + "guard let \(metatypeName(genericName)) = \(recoveredName) as? \(existentialMetatype(genericName)) else {" + ) + printer.indent { + printer.write( + "fatalError(\"BridgeJS: type '\\(\(recoveredName))' does not conform to required protocol(s): \(constraints.joined(separator: ", "))\")" + ) + } + printer.write("}") + } } let open1Arguments = genericNames.map { metatypeName($0) } + concreteABINames printer.write("_\(abiName)_open1(\(open1Arguments.joined(separator: ", ")))") @@ -776,7 +808,7 @@ public class ExportSwift { let builder = try ExportedThunkBuilder( effects: function.effects, returnType: function.returnType, - genericParameterNames: function.genericParameterNames + genericParameters: function.genericParameters ?? [] ) for param in function.parameters { try builder.liftParameter(param: param) @@ -823,7 +855,7 @@ public class ExportSwift { let builder = try ExportedThunkBuilder( effects: method.effects, returnType: method.returnType, - genericParameterNames: method.genericParameterNames + genericParameters: method.genericParameters ?? [] ) if !method.effects.isStatic { try builder.liftParameter(param: Parameter(label: nil, name: "_self", type: instanceSelfType)) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index 7fda90fb9..6385ae8d9 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -300,6 +300,8 @@ public final class SwiftToSkeleton { collector.finalize(&exported) } + mergeExtensionDeclaredJSProtocolConformances(into: &exported) + perSourceErrors.append(contentsOf: diagnoseProtocolConformances(in: exported)) if !perSourceErrors.isEmpty { @@ -418,6 +420,50 @@ public final class SwiftToSkeleton { return diagnostics } + /// Folds `extension Foo: SomeJSProtocol {}` conformances into the exported + /// skeleton's `conformedJSProtocols` lists. + /// + /// The per-declaration collectors only see the type's own inheritance + /// clause, but declaring the conformance in an extension is common Swift + /// style, and the JS-side generic constraint check + /// (`__bjs_tokenConformances`) must not reject a type that genuinely + /// conforms. + private func mergeExtensionDeclaredJSProtocolConformances(into exported: inout ExportedSkeleton) { + var protocolsByTarget: [String: [String]] = [:] + for (sourceFile, _) in sourceFiles { + let collector = ExtensionConformanceCollector(parent: self) + collector.walk(sourceFile) + for (target, protocols) in collector.protocolsByTarget { + protocolsByTarget[target, default: []].append(contentsOf: protocols) + } + } + guard !protocolsByTarget.isEmpty else { return } + + func merge(_ existing: inout [String]?, additions: [String]) { + var result = existing ?? [] + for name in additions where !result.contains(name) { + result.append(name) + } + existing = result.isEmpty ? nil : result + } + + for index in exported.structs.indices { + if let additions = protocolsByTarget[exported.structs[index].swiftCallName] { + merge(&exported.structs[index].conformedJSProtocols, additions: additions) + } + } + for index in exported.classes.indices { + if let additions = protocolsByTarget[exported.classes[index].swiftCallName] { + merge(&exported.classes[index].conformedJSProtocols, additions: additions) + } + } + for index in exported.enums.indices { + if let additions = protocolsByTarget[exported.enums[index].swiftCallName] { + merge(&exported.enums[index].conformedJSProtocols, additions: additions) + } + } + } + private static let jsTypedArrayTypealiasNames: [String: String] = [ "Int8": "JSInt8Array", "UInt8": "JSUint8Array", @@ -1575,6 +1621,20 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { } } + /// Names of the `@JS protocol`s a type's inheritance clause declares + /// conformance to. Non-protocol entries (raw types, plain Swift protocols) + /// are ignored. + private func collectConformedJSProtocols(from inheritanceClause: InheritanceClauseSyntax?) -> [String]? { + guard let inheritanceClause else { return nil } + var names: [String] = [] + for inherited in inheritanceClause.inheritedTypes { + if let name = parent.resolveJSProtocolConstraint(for: inherited.type), !names.contains(name) { + names.append(name) + } + } + return names.isEmpty ? nil : names + } + /// Shared parameter parsing logic used by functions, initializers, and protocol methods private func parseParameters( from parameterClause: FunctionParameterClauseSyntax, @@ -1682,19 +1742,16 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { return nil } - var genericParameterNames: [String] = [] + var genericParameters: [GenericParameter] = [] if let genericClause = node.genericParameterClause { for genericParam in genericClause.parameters { - let constraint = genericParam.inheritedType?.trimmedDescription ?? "" - guard SwiftToSkeleton.isBridgeableGenericConstraint(constraint) else { - diagnose( - node: node, - message: - "Generic parameter '\(genericParam.name.text)' must be constrained to 'BridgedSwiftGenericBridgeable' to be used with @JS." - ) + switch parent.parseGenericParameterConstraints(genericParam, attributeName: "@JS") { + case .failed(let message): + diagnose(node: node, message: message) return nil + case .parsed(let genericParameter): + genericParameters.append(genericParameter) } - genericParameterNames.append(genericParam.name.text) } if node.genericWhereClause != nil { diagnose( @@ -1719,6 +1776,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { } } + let genericParameterNames = genericParameters.map(\.name) + let name = node.name.text let jsName = extractValidatedJSName(from: jsAttribute) @@ -1883,7 +1942,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { namespace: finalNamespace, staticContext: staticContext, documentation: extractDocumentation(from: node), - genericParameters: genericParameterNames.isEmpty ? nil : genericParameterNames + genericParameters: genericParameters.isEmpty ? nil : genericParameters ) } @@ -2275,7 +2334,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { jsNamespace: namespaceResult.jsNamespace, identityMode: classIdentityMode, documentation: extractDocumentation(from: node), - isFinal: isFinal + isFinal: isFinal, + conformedJSProtocols: collectConformedJSProtocols(from: node.inheritanceClause) ) let uniqueKey = makeKey(name: name, namespace: effectiveNamespace) @@ -2440,7 +2500,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { emitStyle: emitStyle, staticMethods: [], staticProperties: [], - documentation: extractDocumentation(from: node) + documentation: extractDocumentation(from: node), + conformedJSProtocols: collectConformedJSProtocols(from: node.inheritanceClause) ) let enumUniqueKey = makeKey(name: name, namespace: effectiveNamespace) @@ -2693,7 +2754,8 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { methods: [], namespace: effectiveNamespace, jsNamespace: namespaceResult.jsNamespace, - documentation: extractDocumentation(from: node) + documentation: extractDocumentation(from: node), + conformedJSProtocols: collectConformedJSProtocols(from: node.inheritanceClause) ) exportedStructByName[structUniqueKey] = exportedStruct @@ -4076,3 +4138,27 @@ extension GenericArgumentListSyntax { #endif } } + +/// Collects `@JS protocol` conformances declared through extensions, keyed by +/// the extended type's (possibly qualified) name as written in source, which +/// matches the skeleton's `swiftCallName` for both top-level and nested types. +private final class ExtensionConformanceCollector: SyntaxVisitor { + private let parent: SwiftToSkeleton + var protocolsByTarget: [String: [String]] = [:] + + init(parent: SwiftToSkeleton) { + self.parent = parent + super.init(viewMode: .sourceAccurate) + } + + override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { + guard let inheritanceClause = node.inheritanceClause else { return .visitChildren } + let target = node.extendedType.trimmedDescription + for inherited in inheritanceClause.inheritedTypes { + if let name = parent.resolveJSProtocolConstraint(for: inherited.type) { + protocolsByTarget[target, default: []].append(name) + } + } + return .visitChildren + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index f6304b7e5..c64993ca8 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -371,6 +371,7 @@ public struct BridgeJSLink { declarations.append("}") declarations.append(contentsOf: GenericJSCodegen.runtimeHelperDeclarations()) if hasGenericExports { + declarations.append(generateTokenConformancesDeclaration()) declarations.append(contentsOf: GenericJSCodegen.exportRuntimeHelperDeclarations()) } } @@ -505,6 +506,65 @@ public struct BridgeJSLink { } } + /// The token → `@JS protocol` conformance map used by + /// `__bjs_typeIdForToken` to reject non-conforming tokens with a catchable + /// `TypeError` before a constrained generic export enters wasm. The link + /// layer knows every type's declared conformances from the skeletons, so + /// the check needs no runtime metadata. + /// + /// Conformance sets are closed over protocol refinement: a type conforming + /// to `@JS protocol Refined: Base` satisfies a constraint on `Base` in + /// Swift, so the JS-side check must agree. + private func generateTokenConformancesDeclaration() -> String { + var inheritedByProtocol: [String: [String]] = [:] + for unified in skeletons { + for protocolDef in unified.exported?.protocols ?? [] { + if let inherited = protocolDef.inheritedJSProtocols, !inherited.isEmpty { + inheritedByProtocol[protocolDef.name] = inherited + } + } + } + func transitiveClosure(of protocols: [String]) -> [String] { + var seen: [String] = [] + var worklist = protocols + while let name = worklist.popLast() { + guard !seen.contains(name) else { continue } + seen.append(name) + worklist.append(contentsOf: inheritedByProtocol[name] ?? []) + } + return seen + } + var entries: [String] = [] + func append(token: String, protocols: [String]?) { + guard let protocols, !protocols.isEmpty else { return } + let list = transitiveClosure(of: protocols).map { "\"\($0)\"" }.joined(separator: ", ") + entries.append("\"\(token)\": [\(list)]") + } + for unified in skeletons { + guard let exported = unified.exported else { continue } + for structDef in exported.structs { + append(token: structDef.abiName, protocols: structDef.conformedJSProtocols) + } + for klass in exported.classes where klass.isFinal == true { + append(token: klass.abiName, protocols: klass.conformedJSProtocols) + } + for enumDef in exported.enums where enumDef.genericBridgeType != nil { + append(token: enumDef.abiName, protocols: enumDef.conformedJSProtocols) + } + // A bridgeable protocol's own token selects its `Any

` wrapper, + // which conforms to `P` (and, through the closure, to everything + // `P` refines), so the JS-side check must accept it where the + // Swift metatype cast would. + for proto in exported.protocols where proto.isGenericBridgeable == true { + append( + token: ((proto.namespace ?? []) + [proto.name]).joined(separator: "_"), + protocols: [proto.name] + ) + } + } + return "const __bjs_tokenConformances = { \(entries.joined(separator: ", ")) };" + } + /// One `BridgeTypes` entry per bridgeable type across all linked modules. /// Tokens are unqualified type names, so a name shared by two modules must /// fail the build (the JS-facing token could not distinguish them). @@ -536,6 +596,22 @@ public struct BridgeJSLink { entries.append((token: entry.token, tsType: resolveTypeScriptType(entry.bridgeType))) } } + // The JS-side constraint check identifies @JS protocols by unqualified + // name, like tokens identify types. Two modules defining a same-named + // @JS protocol would let a conformer of one pass the check for the + // other and reach the Swift metatype-cast trap, so reject it here. + var definingModuleByProtocol: [String: String] = [:] + for unified in skeletons { + for protocolDef in unified.exported?.protocols ?? [] { + if let existing = definingModuleByProtocol[protocolDef.name], existing != unified.moduleName { + throw BridgeJSLinkError( + message: + "@JS protocol '\(protocolDef.name)' is defined by both '\(existing)' and '\(unified.moduleName)'; protocol names used as generic constraints must be unique across linked modules" + ) + } + definingModuleByProtocol[protocolDef.name] = unified.moduleName + } + } return entries } @@ -1700,7 +1776,7 @@ public struct BridgeJSLink { /// declaration-order pops of the Swift entry thunk. func lowerParametersAndGenericTokens( parameters: [Parameter], - genericParameters: [String] + genericParameters: [GenericParameter] ) throws { guard !genericParameters.isEmpty else { for param in parameters { @@ -1713,16 +1789,18 @@ public struct BridgeJSLink { // the three emitters cannot drift apart. genericTokenParameterNames = GenericJSCodegen.genericTokenParameterNames( parameters: parameters, - genericNames: genericParameters, + genericNames: genericParameters.map(\.name), scope: scope ) for (genericParam, tokenName) in zip(genericParameters, genericTokenParameterNames) { - let typeIdVariable = scope.variable("typeId\(genericParam)") - let codecVariable = scope.variable("codec\(genericParam)") - body.write("const \(typeIdVariable) = __bjs_typeIdForToken(\(tokenName));") + let typeIdVariable = scope.variable("typeId\(genericParam.name)") + let codecVariable = scope.variable("codec\(genericParam.name)") + body.write( + "const \(typeIdVariable) = __bjs_typeIdForToken(\(tokenName)\(Self.requiredProtocolsArgument(genericParam)));" + ) body.write("const \(codecVariable) = __bjs_codecForTypeId(\(typeIdVariable));") - genericTypeIdVariables[genericParam] = typeIdVariable - genericCodecVariables[genericParam] = codecVariable + genericTypeIdVariables[genericParam.name] = typeIdVariable + genericCodecVariables[genericParam.name] = codecVariable } // Lower under the declared parameter name: `parameterList(_:)` // declares `param.name`, so that is the identifier in scope. @@ -1748,12 +1826,20 @@ public struct BridgeJSLink { } } for genericParam in genericParameters { - if let typeIdVariable = genericTypeIdVariables[genericParam] { + if let typeIdVariable = genericTypeIdVariables[genericParam.name] { parameterForwardings.append(typeIdVariable) } } } + /// The trailing `requiredProtocols` argument of `__bjs_typeIdForToken` + /// for a constrained generic parameter, or empty when unconstrained. + static func requiredProtocolsArgument(_ genericParameter: GenericParameter) -> String { + guard !genericParameter.constraints.isEmpty else { return "" } + let list = genericParameter.constraints.map { "\"\($0)\"" }.joined(separator: ", ") + return ", [\(list)]" + } + /// The JS parameter list of the wrapper: the user-facing parameters /// followed by one `BridgeType` token per generic parameter. func parameterList(_ parameters: [Parameter]) -> String { @@ -2000,7 +2086,7 @@ public struct BridgeJSLink { for (genericName, tokenName) in zip(genericNames, tokenNames) { parameterSignatures.append("\(tokenName): BridgeType<\(genericName)>") } - let genericClause = renderGenericClause(genericNames.map { GenericParameter(name: $0) }) + let genericClause = renderGenericClause(function.genericParameters ?? []) let returnSignature = resolveTypeScriptType(function.returnType) return "\(function.resolvedJSName)\(genericClause)(\(parameterSignatures.joined(separator: ", "))): \(returnSignature)" @@ -2319,7 +2405,7 @@ extension BridgeJSLink { ) try thunkBuilder.lowerParametersAndGenericTokens( parameters: function.parameters, - genericParameters: function.genericParameterNames + genericParameters: function.genericParameters ?? [] ) let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( @@ -2368,7 +2454,7 @@ extension BridgeJSLink { ) try thunkBuilder.lowerParametersAndGenericTokens( parameters: function.parameters, - genericParameters: function.genericParameterNames + genericParameters: function.genericParameters ?? [] ) let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) @@ -2398,7 +2484,7 @@ extension BridgeJSLink { ) try thunkBuilder.lowerParametersAndGenericTokens( parameters: function.parameters, - genericParameters: function.genericParameterNames + genericParameters: function.genericParameters ?? [] ) let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) @@ -2428,7 +2514,7 @@ extension BridgeJSLink { ) try thunkBuilder.lowerParametersAndGenericTokens( parameters: function.parameters, - genericParameters: function.genericParameterNames + genericParameters: function.genericParameters ?? [] ) let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) @@ -2454,7 +2540,7 @@ extension BridgeJSLink { ) try thunkBuilder.lowerParametersAndGenericTokens( parameters: method.parameters, - genericParameters: method.genericParameterNames + genericParameters: method.genericParameters ?? [] ) let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) @@ -2595,7 +2681,7 @@ extension BridgeJSLink { ) try thunkBuilder.lowerParametersAndGenericTokens( parameters: method.parameters, - genericParameters: method.genericParameterNames + genericParameters: method.genericParameters ?? [] ) let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) @@ -2617,7 +2703,7 @@ extension BridgeJSLink { thunkBuilder.lowerSelf() try thunkBuilder.lowerParametersAndGenericTokens( parameters: method.parameters, - genericParameters: method.genericParameterNames + genericParameters: method.genericParameters ?? [] ) let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 7856bd001..349382a7d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -247,17 +247,28 @@ enum GenericJSCodegen { /// Runtime helpers used only by generic *exports*: the JS wrapper resolves /// the caller's `BridgeTypes` token to a runtime type ID before entering - /// wasm, throwing a catchable `TypeError` for unknown tokens (the wasm side - /// treats an invalid ID as undefined behavior, so it must never see one). + /// wasm, throwing a catchable `TypeError` for unknown tokens and for tokens + /// whose type does not conform to the generic parameter's `@JS protocol` + /// constraints (the wasm side treats an invalid ID as undefined behavior + /// and enforces constraints only as a defense-in-depth trap, so neither + /// must be reachable from JS). static func exportRuntimeHelperDeclarations() -> [String] { let typeIdByToken = JSGlueVariableScope.reservedTypeIdByToken return [ - "function __bjs_typeIdForToken(token) {", + "function __bjs_typeIdForToken(token, requiredProtocols) {", " \(JSGlueVariableScope.reservedRegisterTypeHandles)();", " const typeId = \(typeIdByToken).get(token);", " if (typeId === undefined) {", " throw new TypeError(\"BridgeJS: unknown BridgeType token '\" + token + \"'\");", " }", + " if (requiredProtocols) {", + " const conformances = __bjs_tokenConformances[token] || [];", + " for (const requiredProtocol of requiredProtocols) {", + " if (!conformances.includes(requiredProtocol)) {", + " throw new TypeError(\"BridgeJS: type '\" + token + \"' does not conform to required protocol '\" + requiredProtocol + \"'\");", + " }", + " }", + " }", " return typeId;", "}", ] @@ -2675,10 +2686,10 @@ struct IntrinsicJSFragment: Sendable { ) throws { let printer = context.printer let methodScope = context.scope.makeChildScope() - let genericNames = method.genericParameterNames + let genericParameters = method.genericParameters ?? [] let tokenNames = GenericJSCodegen.genericTokenParameterNames( parameters: method.parameters, - genericNames: genericNames, + genericNames: method.genericParameterNames, scope: methodScope ) let methodContext = context.with(\.scope, methodScope) @@ -2688,10 +2699,13 @@ struct IntrinsicJSFragment: Sendable { try printer.indent { var codecVariables: [String: String] = [:] var typeIdVariables: [String: String] = [:] - for (genericName, tokenName) in zip(genericNames, tokenNames) { + for (genericParameter, tokenName) in zip(genericParameters, tokenNames) { + let genericName = genericParameter.name let typeIdVariable = methodScope.variable("typeId\(genericName)") let codecVariable = methodScope.variable("codec\(genericName)") - printer.write("const \(typeIdVariable) = __bjs_typeIdForToken(\(tokenName));") + printer.write( + "const \(typeIdVariable) = __bjs_typeIdForToken(\(tokenName)\(BridgeJSLink.ExportedThunkBuilder.requiredProtocolsArgument(genericParameter)));" + ) printer.write("const \(codecVariable) = __bjs_codecForTypeId(\(typeIdVariable));") typeIdVariables[genericName] = typeIdVariable codecVariables[genericName] = codecVariable @@ -2717,7 +2731,7 @@ struct IntrinsicJSFragment: Sendable { paramForwardings.append(contentsOf: loweredValues) } } - paramForwardings.append(contentsOf: genericNames.compactMap { typeIdVariables[$0] }) + paramForwardings.append(contentsOf: method.genericParameterNames.compactMap { typeIdVariables[$0] }) printer.write("instance.exports.\(method.abiName)(\(paramForwardings.joined(separator: ", ")));") if let returnGenericName = method.returnType.referencedGenericName, let codecVariable = codecVariables[returnGenericName], diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 7e5582b19..54ad43e6f 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -876,6 +876,9 @@ public struct ExportedStruct: Codable, Equatable, Sendable, NamespacedExportedTy public let namespace: [String]? public let jsNamespace: [String]? public var documentation: String? + /// Names of the `@JS protocol`s this type declares conformance to, used by + /// the JS link layer to validate constrained generic-export tokens. + public var conformedJSProtocols: [String]? public init( name: String, @@ -887,7 +890,8 @@ public struct ExportedStruct: Codable, Equatable, Sendable, NamespacedExportedTy methods: [ExportedFunction] = [], namespace: [String]?, jsNamespace: [String]? = nil, - documentation: String? = nil + documentation: String? = nil, + conformedJSProtocols: [String]? = nil ) { self.name = name self.jsName = jsName @@ -899,6 +903,7 @@ public struct ExportedStruct: Codable, Equatable, Sendable, NamespacedExportedTy self.namespace = namespace self.jsNamespace = jsNamespace self.documentation = documentation + self.conformedJSProtocols = conformedJSProtocols } } @@ -971,6 +976,9 @@ public struct ExportedEnum: Codable, Equatable, Sendable, NamespacedExportedType public var staticMethods: [ExportedFunction] public var staticProperties: [ExportedProperty] = [] public var documentation: String? + /// Names of the `@JS protocol`s this type declares conformance to, used by + /// the JS link layer to validate constrained generic-export tokens. + public var conformedJSProtocols: [String]? public var enumType: EnumType { if cases.isEmpty { return .namespace @@ -1001,7 +1009,8 @@ public struct ExportedEnum: Codable, Equatable, Sendable, NamespacedExportedType emitStyle: EnumEmitStyle, staticMethods: [ExportedFunction] = [], staticProperties: [ExportedProperty] = [], - documentation: String? = nil + documentation: String? = nil, + conformedJSProtocols: [String]? = nil ) { self.name = name self.jsName = jsName @@ -1016,6 +1025,7 @@ public struct ExportedEnum: Codable, Equatable, Sendable, NamespacedExportedType self.staticMethods = staticMethods self.staticProperties = staticProperties self.documentation = documentation + self.conformedJSProtocols = conformedJSProtocols } } @@ -1091,8 +1101,8 @@ public struct ExportedFunction: Codable, Equatable, Sendable { public var namespace: [String]? public var staticContext: StaticContext? public var documentation: String? - public var genericParameters: [String]? - public var genericParameterNames: [String] { genericParameters ?? [] } + public var genericParameters: [GenericParameter]? + public var genericParameterNames: [String] { (genericParameters ?? []).map(\.name) } public var isGeneric: Bool { !genericParameterNames.isEmpty } /// Parameters whose type references a generic parameter; they cross the /// bridge on the shared value stack via the type's codec. @@ -1112,7 +1122,7 @@ public struct ExportedFunction: Codable, Equatable, Sendable { namespace: [String]? = nil, staticContext: StaticContext? = nil, documentation: String? = nil, - genericParameters: [String]? = nil + genericParameters: [GenericParameter]? = nil ) { self.name = name self.jsName = jsName @@ -1140,6 +1150,9 @@ public struct ExportedClass: Codable, NamespacedExportedType { public var identityMode: Bool? // nil = use config default, true/false = override public var documentation: String? public var isFinal: Bool? + /// Names of the `@JS protocol`s this type declares conformance to, used by + /// the JS link layer to validate constrained generic-export tokens. + public var conformedJSProtocols: [String]? public init( name: String, @@ -1153,7 +1166,8 @@ public struct ExportedClass: Codable, NamespacedExportedType { jsNamespace: [String]? = nil, identityMode: Bool? = nil, documentation: String? = nil, - isFinal: Bool? = nil + isFinal: Bool? = nil, + conformedJSProtocols: [String]? = nil ) { self.name = name self.jsName = jsName @@ -1167,6 +1181,7 @@ public struct ExportedClass: Codable, NamespacedExportedType { self.identityMode = identityMode self.documentation = documentation self.isFinal = isFinal + self.conformedJSProtocols = conformedJSProtocols } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/DiagnosticsTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/DiagnosticsTests.swift index 580360bb7..936153322 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/DiagnosticsTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/DiagnosticsTests.swift @@ -833,6 +833,16 @@ import Testing ) } + @Test func constraintOnlyJSProtocolConformer() throws { + _ = try makeSkeleton( + """ + @JS protocol GraphNode { var id: String { get } } + @JS struct Building: GraphNode { var id: String } + @JS func store(_ node: T) -> T { node } + """ + ) + } + @Test func protocolRefinementIsNotAConcreteConformance() throws { _ = try makeSkeleton( """ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift index eed6497e0..29f2cbe4d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift @@ -1,5 +1,10 @@ +import Foundation import Testing +@testable import BridgeJSCore +@testable import BridgeJSLink +@testable import BridgeJSSkeleton + @Suite struct GenericExportDiagnosticsTests { @Test @@ -51,6 +56,167 @@ import Testing ) } + @Test + func refinedProtocolConformerSatisfiesBaseConstraint() throws { + // Swift accepts a conformer of a refined protocol for a constraint on + // the base protocol, so the JS-side token conformance map must agree: + // the link layer closes conformance sets over protocol refinement. + let (js, _) = try linkSource( + """ + @JS protocol Base { var id: String { get } } + @JS protocol Refined: Base { var label: String { get } } + @JS struct Building: Refined { + var id: String + var label: String + } + @JS public func store(_ node: T) -> T { node } + """ + ) + #expect(js.contains(#"const __bjs_tokenConformances = { "Building": ["Refined", "Base"] };"#)) + } + + @Test + func qualifiedExternalConstraintKeepsItsSpellingInTheExportThunk() throws { + // The generated Swift generic clause must use the constraint as + // written (`GraphKit.Node`), while the JS-side check keys on the bare + // protocol name. + let graphKit = try makeSkeleton( + """ + @JS public protocol Node { var id: String { get } } + """, + moduleName: "GraphKit" + ) + let app = try makeSkeleton( + """ + import GraphKit + @JS public func store(_ n: T) -> T { n } + """, + moduleName: "App", + dependencies: [(moduleName: "GraphKit", skeleton: graphKit)] + ) + let exported = try #require(app.exported) + let glue = try #require( + try ExportSwift(progress: .silent, moduleName: app.moduleName, skeleton: exported).finalize() + ) + #expect(glue.contains("")) + #expect(glue.contains("any (BridgedSwiftGenericBridgeable & GraphKit.Node).Type")) + var link = BridgeJSLink() + let encoder = JSONEncoder() + try link.addSkeletonFile(data: encoder.encode(graphKit)) + try link.addSkeletonFile(data: encoder.encode(app)) + let js = try link.link().outputJs + #expect(js.contains(#"__bjs_typeIdForToken(typeT, ["Node"])"#)) + } + + @Test + func bridgeableProtocolTokenSatisfiesItsOwnAndInheritedConstraints() throws { + // `BridgeTypes.Labeled` selects the JS-implemented `AnyLabeled` wrapper, + // which Swift accepts for `T: Labeled` and (via refinement) `T: Base`; + // the JS-side check must agree instead of throwing a TypeError. + let (js, _) = try linkSource( + """ + @JS protocol Base { var id: String { get } } + @JS protocol Labeled: Base, BridgedSwiftGenericBridgeable { var label: String { get } } + @JS public func store(_ node: T) -> T { node } + """ + ) + #expect(js.contains(#""Labeled": ["Labeled", "Base"]"#)) + } + + @Test + func legacyStringGenericParametersDecodeOnExportedFunctions() throws { + // Skeletons committed by earlier plugins encoded generic parameters as + // bare name strings; the export-side field must keep decoding them. + let legacy = + #"{"name":"identity","abiName":"bjs_identity","parameters":[],"returnType":{"generic":{"_0":"T"}},"effects":{"isAsync":false,"isThrows":false,"isStatic":false},"genericParameters":["T"]}"# + let function = try JSONDecoder().decode(ExportedFunction.self, from: Data(legacy.utf8)) + #expect(function.genericParameters == [GenericParameter(name: "T")]) + } + + @Test + func sameNamedJSProtocolsAcrossModulesFailTheLink() throws { + // The JS-side constraint check identifies protocols by unqualified + // name. If two modules each defined `GraphNode`, ModuleA's conformer + // would pass the check for ModuleB's constraint and reach the Swift + // metatype-cast trap instead of a catchable TypeError, so the link + // must reject the combination up front (mirroring the token rule). + let moduleA = try makeSkeleton( + """ + @JS protocol GraphNode { var id: String { get } } + @JS public struct Building: GraphNode { + public var id: String + @JS public init(id: String) { self.id = id } + } + """, + moduleName: "ModuleA" + ) + let moduleB = try makeSkeleton( + """ + @JS protocol GraphNode { var label: String { get } } + @JS public func store(_ n: T) -> T { n } + """, + moduleName: "ModuleB" + ) + var link = BridgeJSLink() + let encoder = JSONEncoder() + try link.addSkeletonFile(data: encoder.encode(moduleA)) + try link.addSkeletonFile(data: encoder.encode(moduleB)) + #expect(throws: BridgeJSLinkError.self) { + _ = try link.link() + } + } + + @Test + func compositionConstraintIsParsed() throws { + let skeleton = try makeSkeleton( + """ + @JS protocol GraphNode { + var id: String { get } + } + @JS public func store(_ node: T) -> T { node } + """, + moduleName: "App" + ) + let exported = try #require(skeleton.exported) + let function = try #require(exported.functions.first { $0.name == "store" }) + #expect(function.genericParameters == [GenericParameter(name: "T", constraints: ["GraphNode"])]) + } + + @Test + func bareJSProtocolConstraintSuggestsComposition() { + expectDiagnostic( + source: """ + @JS protocol GraphNode { + var id: String { get } + } + @JS public func store(_ node: T) -> T { node } + """, + contains: + "Generic parameter 'T' is missing the 'BridgedSwiftGenericBridgeable' constraint. " + + "Write 'T: BridgedSwiftGenericBridgeable & GraphNode'" + ) + } + + @Test + func unknownProtocolInCompositionIsRejected() { + expectDiagnostic( + source: """ + @JS public func store(_ node: T) -> T { node } + """, + contains: "Generic parameter 'T' has unsupported constraint 'Nonexistent'" + ) + } + + @Test + func nonJSProtocolInCompositionIsRejected() { + expectDiagnostic( + source: """ + @JS public func store(_ node: T) -> T { node } + """, + contains: "Generic parameter 'T' has unsupported constraint 'Comparable'" + ) + } + @Test func genericWhereClauseUnsupported() { expectDiagnostic( @@ -230,4 +396,27 @@ import Testing #expect(function.genericParameterNames == ["T"]) #expect(function.genericValueParameters.isEmpty) } + + @Test + func extensionDeclaredConformanceReachesTokenConformances() throws { + // Declaring the @JS protocol conformance in an extension is common + // Swift style; the constraint check on the JS side must see it, or a + // genuinely conforming type would be rejected with a TypeError. + let skeleton = try makeSkeleton( + """ + @JS protocol GraphNode { + var id: String { get } + } + @JS struct Building { + var id: String + } + extension Building: GraphNode {} + + @JS public func store(_ node: T) -> T { node } + """ + ) + let structDef = try #require(skeleton.exported?.structs.first { $0.name == "Building" }) + #expect(structDef.conformedJSProtocols == ["GraphNode"]) + } + } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift index ae8849c81..4e2d637a1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericMethodOnlyModuleCodegenTests.swift @@ -13,7 +13,7 @@ import Testing ).js #expect(js.contains("const __bjs_codecByTypeId = new Map();")) #expect(js.contains("const __bjs_typeIdByToken = new Map();")) - #expect(js.contains("function __bjs_typeIdForToken(token) {")) + #expect(js.contains("function __bjs_typeIdForToken(token, requiredProtocols) {")) #expect(js.contains("export const BridgeTypes = {")) #expect(js.contains("bjs[\"bjs_TestModule_register_type_handles\"] = function(base, count) {")) } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericConstrainedExports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericConstrainedExports.swift new file mode 100644 index 000000000..d3c72379b --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericConstrainedExports.swift @@ -0,0 +1,64 @@ +@JS protocol ExportGraphNode { + var id: String { get } +} + +@JS protocol ExportLabeled { + var label: String { get } +} + +@JS struct ExportGraphBuilding: ExportGraphNode { + var id: String + var floors: Int +} + +@JS public func storeGraphNode(_ node: T) -> T { + return node +} + +// The bridging protocol may appear anywhere in the composition. +@JS +public func storeLabeledGraphNode< + T: ExportGraphNode & BridgedSwiftGenericBridgeable & ExportLabeled +>(_ node: T) -> T { + return node +} + +@JS +public func pairGraphNodes< + T: BridgedSwiftGenericBridgeable & ExportGraphNode, + U: BridgedSwiftGenericBridgeable +>(_ a: T, _ b: U) -> U { + return b +} + +// Return-only constrained generic: T appears only in the result, so the entry +// thunk performs the composed-existential cast and pops no generic argument. +@JS +public func loadGraphNode< + T: BridgedSwiftGenericBridgeable & ExportGraphNode +>(_ key: String) -> T? { + return nil +} + +@JS final class GraphRegistry { + @JS init() {} + + @JS func register(_ node: T) -> T { + return node + } + + @JS static func pin(_ node: T) -> T { + return node + } +} + +// A bridgeable protocol gets its own token (its `Any

` wrapper) and satisfies +// constraints on itself and on everything it refines, on both the Swift and the +// JS side of the check. +@JS protocol ExportLabeledNode: ExportGraphNode, BridgedSwiftGenericBridgeable { + var label: String { get } +} + +@JS public func storeAnyLabeled(_ node: T) -> T { + return node +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericConstrainedExports.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericConstrainedExports.json new file mode 100644 index 000000000..52a3d6d87 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericConstrainedExports.json @@ -0,0 +1,388 @@ +{ + "exported" : { + "aliases" : [ + + ], + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_GraphRegistry_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "isFinal" : true, + "methods" : [ + { + "abiName" : "bjs_GraphRegistry_register", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGraphNode" + ], + "name" : "T" + } + ], + "name" : "register", + "parameters" : [ + { + "label" : "_", + "name" : "node", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_GraphRegistry_static_pin", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGraphNode" + ], + "name" : "T" + } + ], + "name" : "pin", + "parameters" : [ + { + "label" : "_", + "name" : "node", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + }, + "staticContext" : { + "className" : { + "_0" : "GraphRegistry" + } + } + } + ], + "name" : "GraphRegistry", + "properties" : [ + + ], + "swiftCallName" : "GraphRegistry" + } + ], + "enums" : [ + + ], + "exposeToGlobal" : false, + "functions" : [ + { + "abiName" : "bjs_storeGraphNode", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGraphNode" + ], + "name" : "T" + } + ], + "name" : "storeGraphNode", + "parameters" : [ + { + "label" : "_", + "name" : "node", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_storeLabeledGraphNode", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGraphNode", + "ExportLabeled" + ], + "name" : "T" + } + ], + "name" : "storeLabeledGraphNode", + "parameters" : [ + { + "label" : "_", + "name" : "node", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_pairGraphNodes", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGraphNode" + ], + "name" : "T" + }, + { + "name" : "U" + } + ], + "name" : "pairGraphNodes", + "parameters" : [ + { + "label" : "_", + "name" : "a", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "b", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "U" + } + } + }, + { + "abiName" : "bjs_loadGraphNode", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGraphNode" + ], + "name" : "T" + } + ], + "name" : "loadGraphNode", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_storeAnyLabeled", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGraphNode" + ], + "name" : "T" + } + ], + "name" : "storeAnyLabeled", + "parameters" : [ + { + "label" : "_", + "name" : "node", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "protocols" : [ + { + "methods" : [ + + ], + "name" : "ExportGraphNode", + "properties" : [ + { + "isReadonly" : true, + "name" : "id", + "type" : { + "string" : { + + } + } + } + ] + }, + { + "methods" : [ + + ], + "name" : "ExportLabeled", + "properties" : [ + { + "isReadonly" : true, + "name" : "label", + "type" : { + "string" : { + + } + } + } + ] + }, + { + "inheritedJSProtocols" : [ + "ExportGraphNode" + ], + "isGenericBridgeable" : true, + "methods" : [ + + ], + "name" : "ExportLabeledNode", + "properties" : [ + { + "isReadonly" : true, + "name" : "label", + "type" : { + "string" : { + + } + } + } + ] + } + ], + "structs" : [ + { + "conformedJSProtocols" : [ + "ExportGraphNode" + ], + "methods" : [ + + ], + "name" : "ExportGraphBuilding", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "floors", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ExportGraphBuilding" + } + ] + }, + "moduleName" : "TestModule", + "usedExternalModules" : [ + + ] +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericConstrainedExports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericConstrainedExports.swift new file mode 100644 index 000000000..593971334 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericConstrainedExports.swift @@ -0,0 +1,403 @@ +extension ExportGraphNode where Self: _BridgedSwiftProtocolWrapper { + var id: String { + get { + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = bjs_ExportGraphNode_id_get(jsObjectValue) + return String.bridgeJSLiftReturn(ret) + } + } +} + +struct AnyExportGraphNode: ExportGraphNode, _BridgedSwiftProtocolWrapper { + let jsObject: JSObject + + static func bridgeJSLiftParameter(_ value: Int32) -> Self { + return AnyExportGraphNode(jsObject: JSObject(id: UInt32(bitPattern: value))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ExportGraphNode_id_get") +fileprivate func bjs_ExportGraphNode_id_get_extern(_ jsObject: Int32) -> Int32 +#else +fileprivate func bjs_ExportGraphNode_id_get_extern(_ jsObject: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ExportGraphNode_id_get(_ jsObject: Int32) -> Int32 { + return bjs_ExportGraphNode_id_get_extern(jsObject) +} + +extension ExportLabeled where Self: _BridgedSwiftProtocolWrapper { + var label: String { + get { + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = bjs_ExportLabeled_label_get(jsObjectValue) + return String.bridgeJSLiftReturn(ret) + } + } +} + +struct AnyExportLabeled: ExportLabeled, _BridgedSwiftProtocolWrapper { + let jsObject: JSObject + + static func bridgeJSLiftParameter(_ value: Int32) -> Self { + return AnyExportLabeled(jsObject: JSObject(id: UInt32(bitPattern: value))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ExportLabeled_label_get") +fileprivate func bjs_ExportLabeled_label_get_extern(_ jsObject: Int32) -> Int32 +#else +fileprivate func bjs_ExportLabeled_label_get_extern(_ jsObject: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ExportLabeled_label_get(_ jsObject: Int32) -> Int32 { + return bjs_ExportLabeled_label_get_extern(jsObject) +} + +extension ExportLabeledNode where Self: _BridgedSwiftProtocolWrapper { + var label: String { + get { + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = bjs_ExportLabeledNode_label_get(jsObjectValue) + return String.bridgeJSLiftReturn(ret) + } + } +} + +struct AnyExportLabeledNode: ExportLabeledNode, _BridgedSwiftProtocolWrapper { + let jsObject: JSObject + + static func bridgeJSLiftParameter(_ value: Int32) -> Self { + return AnyExportLabeledNode(jsObject: JSObject(id: UInt32(bitPattern: value))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_ExportLabeledNode_label_get") +fileprivate func bjs_ExportLabeledNode_label_get_extern(_ jsObject: Int32) -> Int32 +#else +fileprivate func bjs_ExportLabeledNode_label_get_extern(_ jsObject: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ExportLabeledNode_label_get(_ jsObject: Int32) -> Int32 { + return bjs_ExportLabeledNode_label_get_extern(jsObject) +} + +extension ExportGraphBuilding: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> ExportGraphBuilding { + let floors = Int.bridgeJSStackPop() + let id = String.bridgeJSStackPop() + return ExportGraphBuilding(id: id, floors: floors) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.id.bridgeJSStackPush() + self.floors.bridgeJSStackPush() + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_ExportGraphBuilding(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_ExportGraphBuilding())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_ExportGraphBuilding") +fileprivate func _bjs_struct_lower_ExportGraphBuilding_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_ExportGraphBuilding_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_ExportGraphBuilding(_ objectId: Int32) -> Void { + return _bjs_struct_lower_ExportGraphBuilding_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_ExportGraphBuilding") +fileprivate func _bjs_struct_lift_ExportGraphBuilding_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_ExportGraphBuilding_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_ExportGraphBuilding() -> Int32 { + return _bjs_struct_lift_ExportGraphBuilding_extern() +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_storeGraphNode") +@_cdecl("bjs_storeGraphNode") +public func _bjs_storeGraphNode(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_storeGraphNode") +@_cdecl("bjs_storeGraphNode") +public func _bjs_storeGraphNode(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGraphNode") + } + _bjs_storeGraphNode_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_storeGraphNode_open1(_ _generic0Type: T.Type) { + let node = T.bridgeJSStackPop() + let ret: T = storeGraphNode(_: node) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_storeLabeledGraphNode") +@_cdecl("bjs_storeLabeledGraphNode") +public func _bjs_storeLabeledGraphNode(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_storeLabeledGraphNode") +@_cdecl("bjs_storeLabeledGraphNode") +public func _bjs_storeLabeledGraphNode(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGraphNode & ExportLabeled).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGraphNode, ExportLabeled") + } + _bjs_storeLabeledGraphNode_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_storeLabeledGraphNode_open1(_ _generic0Type: T.Type) { + let node = T.bridgeJSStackPop() + let ret: T = storeLabeledGraphNode(_: node) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_pairGraphNodes") +@_cdecl("bjs_pairGraphNodes") +public func _bjs_pairGraphNodes(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_pairGraphNodes") +@_cdecl("bjs_pairGraphNodes") +public func _bjs_pairGraphNodes(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGraphNode") + } + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_pairGraphNodes_open1(_generic0Type, _generic1Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_pairGraphNodes_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_pairGraphNodes_open2(_generic1Type, asT: T.self) +} +private func _bjs_pairGraphNodes_open2(_ _generic1Type: U.Type, asT _generic0Type: T.Type) { + let b = U.bridgeJSStackPop() + let a = T.bridgeJSStackPop() + let ret: U = pairGraphNodes(_: a, _: b) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_loadGraphNode") +@_cdecl("bjs_loadGraphNode") +public func _bjs_loadGraphNode(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_loadGraphNode") +@_cdecl("bjs_loadGraphNode") +public func _bjs_loadGraphNode(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGraphNode") + } + _bjs_loadGraphNode_open1(_generic0Type, keyBytes, keyLength) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_loadGraphNode_open1(_ _generic0Type: T.Type, _ keyBytes: Int32, _ keyLength: Int32) { + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + let ret: Optional = loadGraphNode(_: key) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_storeAnyLabeled") +@_cdecl("bjs_storeAnyLabeled") +public func _bjs_storeAnyLabeled(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_storeAnyLabeled") +@_cdecl("bjs_storeAnyLabeled") +public func _bjs_storeAnyLabeled(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGraphNode") + } + _bjs_storeAnyLabeled_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_storeAnyLabeled_open1(_ _generic0Type: T.Type) { + let node = T.bridgeJSStackPop() + let ret: T = storeAnyLabeled(_: node) + ret.bridgeJSStackPush() +} +#endif + +@_expose(wasm, "bjs_GraphRegistry_init") +@_cdecl("bjs_GraphRegistry_init") +public func _bjs_GraphRegistry_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = GraphRegistry() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GraphRegistry_register") +@_cdecl("bjs_GraphRegistry_register") +public func _bjs_GraphRegistry_register(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GraphRegistry_register") +@_cdecl("bjs_GraphRegistry_register") +public func _bjs_GraphRegistry_register(_ _self: UnsafeMutableRawPointer, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGraphNode") + } + _bjs_GraphRegistry_register_open1(_generic0Type, _self) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GraphRegistry_register_open1(_ _generic0Type: T.Type, _ _self: UnsafeMutableRawPointer) { + let node = T.bridgeJSStackPop() + let _self = GraphRegistry.bridgeJSLiftParameter(_self) + let ret: T = _self.register(_: node) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GraphRegistry_static_pin") +@_cdecl("bjs_GraphRegistry_static_pin") +public func _bjs_GraphRegistry_static_pin(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GraphRegistry_static_pin") +@_cdecl("bjs_GraphRegistry_static_pin") +public func _bjs_GraphRegistry_static_pin(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGraphNode") + } + _bjs_GraphRegistry_static_pin_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GraphRegistry_static_pin_open1(_ _generic0Type: T.Type) { + let node = T.bridgeJSStackPop() + let ret: T = GraphRegistry.pin(_: node) + ret.bridgeJSStackPush() +} +#endif + +@_expose(wasm, "bjs_GraphRegistry_deinit") +@_cdecl("bjs_GraphRegistry_deinit") +public func _bjs_GraphRegistry_deinit(_ pointer: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(pointer).release() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension GraphRegistry: ConvertibleToJSValue, _BridgedSwiftHeapObject, _BridgedSwiftProtocolExportable { + var jsValue: JSValue { + return .object(JSObject(id: UInt32(bitPattern: _bjs_GraphRegistry_wrap(Unmanaged.passRetained(self).toOpaque())))) + } + consuming func bridgeJSLowerAsProtocolReturn() -> Int32 { + _bjs_GraphRegistry_wrap(Unmanaged.passRetained(self).toOpaque()) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "TestModule", name: "bjs_GraphRegistry_wrap") +fileprivate func _bjs_GraphRegistry_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 +#else +fileprivate func _bjs_GraphRegistry_wrap_extern(_ pointer: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_GraphRegistry_wrap(_ pointer: UnsafeMutableRawPointer) -> Int32 { + return _bjs_GraphRegistry_wrap_extern(pointer) +} + +extension ExportGraphBuilding: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGraphBuilding.bridgeJSMakeTypeHandle() +} + +extension GraphRegistry: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GraphRegistry.bridgeJSMakeTypeHandle() +} + +extension AnyExportLabeledNode: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = AnyExportLabeledNode.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] = [ + ExportGraphBuilding.bridgeJSTypeID, + GraphRegistry.bridgeJSTypeID, + AnyExportLabeledNode.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/GenericExports.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json index 0d149056e..43f6a6fe1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json @@ -90,7 +90,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "wrap", "parameters" : [ @@ -118,8 +120,12 @@ "isThrows" : false }, "genericParameters" : [ - "T", - "t" + { + "name" : "T" + }, + { + "name" : "t" + } ], "name" : "combine", "parameters" : [ @@ -156,7 +162,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "makeArray", "parameters" : [ @@ -193,7 +201,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "load", "parameters" : [ @@ -415,7 +425,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "one", "parameters" : [ @@ -462,7 +474,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "make", "namespace" : [ @@ -523,7 +537,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportIdentity", "parameters" : [ @@ -551,7 +567,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportArray", "parameters" : [ @@ -587,7 +605,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportOptional", "parameters" : [ @@ -625,7 +645,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportDictionary", "parameters" : [ @@ -661,7 +683,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportEcho", "parameters" : [ @@ -701,7 +725,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportStore", "parameters" : [ @@ -738,7 +764,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportLoad", "parameters" : [ @@ -771,7 +799,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportStructConcreteLeading", "parameters" : [ @@ -808,7 +838,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportStructAndScalar", "parameters" : [ @@ -857,7 +889,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "genericExportPair", "parameters" : [ @@ -894,8 +928,12 @@ "isThrows" : false }, "genericParameters" : [ - "T", - "U" + { + "name" : "T" + }, + { + "name" : "U" + } ], "name" : "genericExportCombine", "parameters" : [ @@ -932,8 +970,12 @@ "isThrows" : false }, "genericParameters" : [ - "T", - "U" + { + "name" : "T" + }, + { + "name" : "U" + } ], "name" : "genericExportCombineReturnU", "parameters" : [ @@ -970,8 +1012,12 @@ "isThrows" : false }, "genericParameters" : [ - "T", - "t" + { + "name" : "T" + }, + { + "name" : "t" + } ], "name" : "genericExportCaseDistinct", "parameters" : [ @@ -1102,7 +1148,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "first", "parameters" : [ @@ -1130,8 +1178,12 @@ "isThrows" : false }, "genericParameters" : [ - "T", - "t" + { + "name" : "T" + }, + { + "name" : "t" + } ], "name" : "combine", "parameters" : [ @@ -1168,7 +1220,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "maybe", "parameters" : [ @@ -1201,7 +1255,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "dict", "parameters" : [ @@ -1233,7 +1289,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "wrap", "parameters" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericConstrainedExports.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericConstrainedExports.d.ts new file mode 100644 index 000000000..f6765bbe6 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericConstrainedExports.d.ts @@ -0,0 +1,55 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export interface ExportGraphNode { + readonly id: string; +} + +export interface ExportLabeled { + readonly label: string; +} + +export interface ExportLabeledNode extends ExportGraphNode { + readonly label: string; +} + +export interface ExportGraphBuilding { + id: string; + floors: number; +} +declare const bridgeTypeBrand: unique symbol; +export type BridgeType = string & { readonly [bridgeTypeBrand]: (value: T) => void }; +export const BridgeTypes: { readonly Bool: BridgeType; readonly Int: BridgeType; readonly Int8: BridgeType; readonly UInt8: BridgeType; readonly Int16: BridgeType; readonly UInt16: BridgeType; readonly Int32: BridgeType; readonly UInt32: BridgeType; readonly UInt: BridgeType; readonly Int64: BridgeType; readonly UInt64: BridgeType; readonly Float: BridgeType; readonly Double: BridgeType; readonly String: BridgeType; readonly JSValue: BridgeType; readonly ExportGraphBuilding: BridgeType; readonly GraphRegistry: BridgeType; readonly ExportLabeledNode: BridgeType; }; +/// 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 GraphRegistry extends SwiftHeapObject { + register(node: T, typeT: BridgeType): T; +} +export type Exports = { + storeGraphNode(node: T, typeT: BridgeType): T; + storeLabeledGraphNode(node: T, typeT: BridgeType): T; + pairGraphNodes(a: T, b: U, typeT: BridgeType, typeU: BridgeType): U; + loadGraphNode(key: string, typeT: BridgeType): T | null; + storeAnyLabeled(node: T, typeT: BridgeType): T; + GraphRegistry: { + new(): GraphRegistry; + pin(node: T, typeT: BridgeType): T; + }, +} +export type Imports = { +} +export function createInstantiator(options: { + imports: Imports; +}, swift: any): Promise<{ + addImports: (importObject: WebAssembly.Imports) => void; + setInstance: (instance: WebAssembly.Instance) => void; + createExports: (instance: WebAssembly.Instance) => Exports; +}>; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericConstrainedExports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericConstrainedExports.js new file mode 100644 index 000000000..7ec4acede --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericConstrainedExports.js @@ -0,0 +1,832 @@ +// 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 BridgeTypes = { Bool: "Bool", Int: "Int", Int8: "Int8", UInt8: "UInt8", Int16: "Int16", UInt16: "UInt16", Int32: "Int32", UInt32: "UInt32", UInt: "UInt", Int64: "Int64", UInt64: "UInt64", Float: "Float", Double: "Double", String: "String", JSValue: "JSValue", ExportGraphBuilding: "ExportGraphBuilding", GraphRegistry: "GraphRegistry", ExportLabeledNode: "ExportLabeledNode" }; +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(); + const __bjs_typeIdByToken = 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; + } + const __bjs_tokenConformances = { "ExportGraphBuilding": ["ExportGraphNode"], "ExportLabeledNode": ["ExportLabeledNode", "ExportGraphNode"] }; + function __bjs_typeIdForToken(token, requiredProtocols) { + __bjs_registerTypeHandles(); + const typeId = __bjs_typeIdByToken.get(token); + if (typeId === undefined) { + throw new TypeError("BridgeJS: unknown BridgeType token '" + token + "'"); + } + if (requiredProtocols) { + const conformances = __bjs_tokenConformances[token] || []; + for (const requiredProtocol of requiredProtocols) { + if (!conformances.includes(requiredProtocol)) { + throw new TypeError("BridgeJS: type '" + token + "' does not conform to required protocol '" + requiredProtocol + "'"); + } + } + } + return typeId; + } + + 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_M10TestModuleT19ExportGraphBuilding = { + lower: (v) => { + structHelpers.M10TestModuleT19ExportGraphBuilding.lower(v); + }, + lift: () => { + const struct = structHelpers.M10TestModuleT19ExportGraphBuilding.lift(); + return struct; + }, + }; + const __bjs_codec_M10TestModuleT13GraphRegistry = { + lower: (v) => { + ptrStack.push(v.pointer); + }, + lift: () => { + const ptr = ptrStack.pop(); + const obj = _exports['GraphRegistry'].__construct(ptr); + return obj; + }, + }; + const __bjs_codec_M10TestModuleT17ExportLabeledNode = { + 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_createStructHelpers_M10TestModuleT19ExportGraphBuilding = () => ({ + lower: (value) => { + const bytes = textEncoder.encode(value.id); + const id = swift.memory.retain(bytes); + i32Stack.push(bytes.length); + i32Stack.push(id); + i32Stack.push((value.floors | 0)); + }, + lift: () => { + const int = i32Stack.pop(); + const string = strStack.pop(); + return { id: string, floors: int }; + } + }); + + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + bjs = {}; + importObject["bjs"] = bjs; + 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_ExportGraphBuilding"] = function(objectId) { + structHelpers.M10TestModuleT19ExportGraphBuilding.lower(swift.memory.getObject(objectId)); + } + bjs["swift_js_struct_lift_ExportGraphBuilding"] = function() { + const value = structHelpers.M10TestModuleT19ExportGraphBuilding.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, + ]; + const tokens = ["Bool", "Int", "Int8", "UInt8", "Int16", "UInt16", "Int32", "UInt32", "UInt", "Int64", "UInt64", "Float", "Double", "String", "JSValue"]; + 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_typeIdByToken.set(tokens[i], typeIds[i]); + } + } + bjs["bjs_TestModule_register_type_handles"] = function(base, count) { + const codecs = [ + __bjs_codec_M10TestModuleT19ExportGraphBuilding, + __bjs_codec_M10TestModuleT13GraphRegistry, + __bjs_codec_M10TestModuleT17ExportLabeledNode, + ]; + const tokens = ["ExportGraphBuilding", "GraphRegistry", "ExportLabeledNode"]; + 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_typeIdByToken.set(tokens[i], typeIds[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_GraphRegistry_wrap"] = function(pointer) { + const obj = _exports['GraphRegistry'].__construct(pointer); + return swift.memory.retain(obj); + }; + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; + TestModule["bjs_ExportGraphNode_id_get"] = function bjs_ExportGraphNode_id_get(self) { + try { + let ret = swift.memory.getObject(self).id; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_ExportLabeled_label_get"] = function bjs_ExportLabeled_label_get(self) { + try { + let ret = swift.memory.getObject(self).label; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_ExportLabeledNode_label_get"] = function bjs_ExportLabeledNode_label_get(self) { + try { + let ret = swift.memory.getObject(self).label; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } 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) + } + }, + /** @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 GraphRegistry extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_GraphRegistry_deinit, GraphRegistry.prototype, null); + } + + constructor() { + const ret = instance.exports.bjs_GraphRegistry_init(); + return GraphRegistry.__construct(ret); + } + register(node, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT, ["ExportGraphNode"]); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(node); + instance.exports.bjs_GraphRegistry_register(this.pointer, typeIdT); + return codecT.lift(); + } + static pin(node, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT, ["ExportGraphNode"]); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(node); + instance.exports.bjs_GraphRegistry_static_pin(typeIdT); + return codecT.lift(); + } + } + const __bjs_helpers_M10TestModuleT19ExportGraphBuilding = __bjs_createStructHelpers_M10TestModuleT19ExportGraphBuilding(); + structHelpers.M10TestModuleT19ExportGraphBuilding = __bjs_helpers_M10TestModuleT19ExportGraphBuilding; + + const exports = { + storeGraphNode: function bjs_storeGraphNode(node, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT, ["ExportGraphNode"]); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(node); + instance.exports.bjs_storeGraphNode(typeIdT); + return codecT.lift(); + }, + storeLabeledGraphNode: function bjs_storeLabeledGraphNode(node, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT, ["ExportGraphNode", "ExportLabeled"]); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(node); + instance.exports.bjs_storeLabeledGraphNode(typeIdT); + return codecT.lift(); + }, + pairGraphNodes: function bjs_pairGraphNodes(a, b, typeT, typeU) { + const typeIdT = __bjs_typeIdForToken(typeT, ["ExportGraphNode"]); + const codecT = __bjs_codecForTypeId(typeIdT); + const typeIdU = __bjs_typeIdForToken(typeU); + const codecU = __bjs_codecForTypeId(typeIdU); + codecT.lower(a); + codecU.lower(b); + instance.exports.bjs_pairGraphNodes(typeIdT, typeIdU); + return codecU.lift(); + }, + loadGraphNode: function bjs_loadGraphNode(key, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT, ["ExportGraphNode"]); + const codecT = __bjs_codecForTypeId(typeIdT); + const keyBytes = textEncoder.encode(key); + const keyId = swift.memory.retain(keyBytes); + instance.exports.bjs_loadGraphNode(keyId, keyBytes.length, typeIdT); + return __bjs_optionalCodec(codecT).lift(); + }, + storeAnyLabeled: function bjs_storeAnyLabeled(node, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT, ["ExportGraphNode"]); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(node); + instance.exports.bjs_storeAnyLabeled(typeIdT); + return codecT.lift(); + }, + GraphRegistry, + }; + _exports = exports; + return exports; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.callsites.check.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.callsites.check.ts new file mode 100644 index 000000000..041e40562 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.callsites.check.ts @@ -0,0 +1,37 @@ +// Call-site typing fixtures for generic exports. Compiled by `check:bridgejs-dts` +// alongside the generated declarations; the `@ts-expect-error` lines assert +// that TypeScript rejects the call, so a regression in the declarations (for +// example a brand that lets raw strings through) fails the check. +import type { BridgeTypes as ExportTokens, Exports as GenericExports } from "./GenericExports.d.ts"; +import type { + BridgeTypes as ConstrainedTokens, + Exports as ConstrainedExports, +} from "./GenericConstrainedExports.d.ts"; + +declare const exports: GenericExports; +declare const BridgeTypes: typeof ExportTokens; +declare const constrained: ConstrainedExports; +declare const ConstrainedBridgeTypes: typeof ConstrainedTokens; + +// T is inferred from the token and checked against the value. +const n: number = exports.genericExportIdentity(42, BridgeTypes.Int); +const s: string = exports.genericExportIdentity("hi", BridgeTypes.String); +const p = exports.genericExportIdentity({ x: 1, y: 2 }, BridgeTypes.ExportPoint); +const px: number = p.x; + +// A raw string is not a token: the brand is required, not a phantom optional. +// @ts-expect-error +exports.genericExportIdentity(42, "Int"); + +// Token and value must agree. +// @ts-expect-error +exports.genericExportIdentity("hi", BridgeTypes.Int); + +// Constrained generics: the token's type must satisfy the constraint. +const b = constrained.storeGraphNode({ id: "b", floors: 3 }, ConstrainedBridgeTypes.ExportGraphBuilding); +const bid: string = b.id; + +// @ts-expect-error +constrained.storeGraphNode(5, ConstrainedBridgeTypes.Int); + +export { n, s, px, bid }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js index 70bb1abd0..45aab11aa 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js @@ -73,12 +73,21 @@ export async function createInstantiator(options, swift) { } return codec; } - function __bjs_typeIdForToken(token) { + const __bjs_tokenConformances = { }; + function __bjs_typeIdForToken(token, requiredProtocols) { __bjs_registerTypeHandles(); const typeId = __bjs_typeIdByToken.get(token); if (typeId === undefined) { throw new TypeError("BridgeJS: unknown BridgeType token '" + token + "'"); } + if (requiredProtocols) { + const conformances = __bjs_tokenConformances[token] || []; + for (const requiredProtocol of requiredProtocols) { + if (!conformances.includes(requiredProtocol)) { + throw new TypeError("BridgeJS: type '" + token + "' does not conform to required protocol '" + requiredProtocol + "'"); + } + } + } return typeId; } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/tsconfig.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/tsconfig.json index 277b2c578..6e15507c6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/tsconfig.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/tsconfig.json @@ -6,7 +6,14 @@ "target": "ES2020", "module": "ES2020", "moduleResolution": "node", - "lib": ["ES2020", "DOM"] + "lib": [ + "ES2020", + "DOM" + ], + "allowImportingTsExtensions": true }, - "include": ["*.d.ts"] + "include": [ + "*.d.ts", + "*.check.ts" + ] } diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md index b73c2d9e8..c320a3e1b 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md @@ -42,6 +42,12 @@ Type identity is pointer-based rather than name-based: every conforming type own An exported generic `@JS` function reuses the same stack ABI, codec table, and handle-based type identity, with the dispatch reversed. The WebAssembly entry point is a concrete `@_expose` thunk that takes the runtime type ID as a trailing `Int32`. Because the ID is the address of the type's `BridgeJSTypeHandle`, the thunk recovers the concrete type directly — `Unmanaged.fromOpaque(...).takeUnretainedValue().type` — with no per-module registry, reifies `T` through an opened existential, and runs the same unspecialized body a concrete export would run: pop the arguments from the stack, call your function, push the result. The JavaScript wrapper resolves the caller's `BridgeType` token through the map built during type-handle registration and passes the resulting ID; an unknown token throws a `TypeError` *before* entering wasm, so the shared value stack stays balanced and the error is catchable. Pointer recovery means the wasm entry point trusts its caller: a raw wasm caller passing a garbage type ID is undefined behavior, which is acceptable because the generated JS wrapper is the only supported caller and it validates every token first. Because recovery depends on the handle's metatype storage (an existential), generic exports are excluded under Embedded Swift. +## Generic `@JS` protocol constraints + +A generic parameter may compose `BridgedSwiftGenericBridgeable` with `@JS` protocols (``). The protocols contribute nothing at the ABI level — values still cross as the concrete `T` via the stack codecs — they exist so the Swift body (exports) or Swift call site (imports) can use the protocol requirements, and so the emitted `.d.ts` can declare `T extends GraphNode` against the generated protocol interfaces for compile-time checking in TypeScript. The `@JS protocol` machinery is reused purely for static knowledge of which protocols exist; the runtime witness plumbing that backs protocol-typed *values* is not involved. + +On imports the Swift compiler proves the constraint at every call site, so the generated thunk merely repeats the user's constraint list. On exports the constraint must be re-checked at runtime because `BridgeType` tokens are erased in JavaScript. The primary check lives in the generated JS wrapper: the link layer knows every `@JS` type's declared protocol conformances from the skeletons, so the wrapper validates the token against the required protocols and throws a catchable `TypeError` before the call enters wasm — the shared value stack stays balanced, and the same mechanism will compose with throwing exports later. As defense-in-depth for raw wasm callers, the entry thunk additionally casts the recovered handle type to the composed existential metatype (`as? any (BridgedSwiftGenericBridgeable & GraphNode).Type`) before opening it and traps with a descriptive fatal error when the cast fails; through the generated wrapper this path is unreachable. `BridgedSwiftGenericBridgeable` must always appear in the composition: imports need it statically to lower and lift `T` at all, and exports keep the same spelling for symmetry. + ## What to read next - ABI and binary interface details will be documented in this section as they stabilize. diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md index 09bc51d79..3e6228e3b 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md @@ -234,4 +234,6 @@ Identity mode improves performance for reuse-heavy workloads (same objects cross | Static/class methods: `static func`, `class func` | ✅ (See ) | | Extension methods/properties | ✅ | | Subscripts: `subscript()` | ❌ | -| Generics | ❌ | +| Generic methods (instance and static): `func wrap(_ v: T) -> T` (see ) | ✅ | +| Generic classes: `class Box` | ❌ | +| Generic initializers | ❌ | diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md index 6d7af6799..842e0d1d3 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md @@ -544,4 +544,5 @@ This differs from classes, which use reference semantics and share state across | Associated values: Arrays | ✅ | | Associated values: Optionals of all supported types | ✅ | | Extension static functions/properties | ✅ | -| Generics | ❌ | +| Generic static methods (see ) | ✅ | +| Generic enums: `enum Result` | ❌ | diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md index c21d7ba74..836789610 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md @@ -179,7 +179,7 @@ import JavaScriptKit } ``` -`T` must be a bridgeable type: a supported primitive (`Bool`, any fixed-width integer such as `Int`/`UInt`/`Int8`…`UInt64`, `Float`, `Double`, `String`, or `JSValue`), or a `@JS` struct, `final @JS class`, or `@JS enum`. You do not write any conformance yourself; marking a type `@JS` makes it usable as `T` (see ). +`T` must be a bridgeable type: a supported primitive (`Bool`, any fixed-width integer such as `Int`/`UInt`/`Int8`…`UInt64`, `Float`, `Double`, `String`, or `JSValue`), or a `@JS` struct, `final @JS class`, or `@JS enum`. A `@JS protocol` that inherits `BridgedSwiftGenericBridgeable` is bridgeable too: its token (`BridgeTypes.SceneNode`) selects the generated JavaScript-backed wrapper, so a plain JavaScript object satisfying the protocol can be passed where `T` is constrained to it. You do not write any conformance yourself; marking a type `@JS` makes it usable as `T` (see ). Because TypeScript erases generics, the JavaScript caller passes a `BridgeType` token as the last argument so the bridge can select the right type at runtime. The tokens come from a generated `BridgeTypes` map exported at the top level of `bridge-js.js`; import it directly rather than reading it from the `exports` object. The token for a type declared inside a namespace joins the path with underscores, so `API.Building` is `BridgeTypes.API_Building`: @@ -200,8 +200,9 @@ Note that `attribute` uses its generic parameter only in the return type: the to Concrete parameters keep their positions; the token is always appended last. The non-generic parameters of a generic `@JS` function may be any supported bridged type, including value types such as `@JS` structs, arrays, dictionaries, and associated-value enums alongside the generic parameter. The generated TypeScript declarations look like: ```typescript -export type BridgeType = string & { readonly __bridgeType?: (value: T) => void }; -export const BridgeTypes: { Bool: BridgeType; Int: BridgeType; Float: BridgeType; Double: BridgeType; String: BridgeType; MyPoint: BridgeType; }; +declare const bridgeTypeBrand: unique symbol; +export type BridgeType = string & { readonly [bridgeTypeBrand]: (value: T) => void }; +export const BridgeTypes: { readonly Bool: BridgeType; readonly Int: BridgeType; readonly Float: BridgeType; readonly Double: BridgeType; readonly String: BridgeType; readonly MyPoint: BridgeType; }; export interface GraphNode extends SwiftHeapObject { setAttribute(key: string, value: T, typeT: BridgeType): void; attribute(key: string, typeT: BridgeType): T | null; @@ -246,6 +247,36 @@ The generic parameter may also be wrapped as `[T]`, `T?`, or `[String: T]` in pa Every declared generic parameter must be used in at least one parameter or the return type; a fully unused generic parameter is rejected. The result must be one of the declared generic parameters (such as `T` or `U`), a supported wrapper of one (`[T]`, `T?`, `[String: T]`), or `Void` — returning a concrete non-`Void` type from a generic `@JS` function is not supported. Generic `@JS` functions must be synchronous (see ). +#### Constraining a generic parameter to `@JS` protocols + +A generic parameter may additionally be constrained to one or more `@JS` protocols by composing them with `BridgedSwiftGenericBridgeable` (in any order). The function body can then use the protocol requirements of the value: + +```swift +@JS protocol GraphNode { + var id: String { get } +} + +@JS struct Building: GraphNode { + var id: String + var floors: Int +} + +@JS public func store(_ node: T) -> T { + print(node.id) // GraphNode requirements are available on `node` + return node +} +``` + +Every protocol in the composition must be a `@JS protocol` declared in the same module or in a dependency module that applies the BridgeJS plugin. `BridgedSwiftGenericBridgeable` must be reachable from the composition: either spelled directly (``) or inherited by one of the protocols (`@JS protocol SceneNode: BridgedSwiftGenericBridgeable` lets you write ``). The generated TypeScript declaration carries the constraint, so TypeScript callers get compile-time checking against the generated protocol interfaces: + +```typescript +export type Exports = { + store(node: T, typeT: BridgeType): T; +} +``` + +TypeScript checks the constraint at compile time, but `BridgeType` tokens are erased at runtime, so the generated JavaScript wrapper enforces it again when the call crosses the bridge: the token's type is validated against the required protocols and a non-conforming token throws a catchable `TypeError` *before* the call enters WebAssembly, e.g. `BridgeJS: type 'Int' does not conform to required protocol 'GraphNode'`. Because nothing has been lowered yet, the shared value stack stays balanced and later calls are unaffected. (The Swift entry thunk repeats the check with a conditional metatype cast as defense-in-depth, but through the generated wrapper that trap is unreachable.) + Generics also work on methods. On a `@JS` class or struct they apply to both instance and static methods. A `@JS enum` (including a namespace-style enum) has no instance methods in BridgeJS, so generics there apply to static methods only. The constraint, the trailing `BridgeType` token, and the generic-or-`Void` return rule all carry over unchanged. ## Supported Features diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Protocols.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Protocols.md index 4c91ba749..e77633864 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Protocols.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Protocols.md @@ -227,5 +227,6 @@ struct AnyCounter: Counter, _BridgedSwiftProtocolWrapper { | Existential protocol composition: `any Protocol1 & Protocol2` | ❌ | | Generic requirements | ❌ | | Protocol-valued properties, parameters, or results in requirements | ❌ | +| Use as a generic constraint: `` (see ) | ✅ | > Note: For supported requirement types, see , , and . diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Static-Functions.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Static-Functions.md index 1c4b22abe..d0c89c682 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Static-Functions.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Static-Functions.md @@ -162,5 +162,5 @@ const result: string = Utils.String.uppercase("world"); | Class `class func` | ✅ | | Enum `static func` | ✅ | | Namespace enum `static func` | ✅ | -| Generic static functions | ❌ | +| Generic static functions: `static func make(_ v: T) -> T` (see ) | ✅ | | Protocol static requirements | ❌ | diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md index 208c80a25..46f9da3c3 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Struct.md @@ -169,7 +169,9 @@ This differs from classes, which use reference semantics and share state across | Static properties | ✅ | | Extension methods/properties | ✅ | | Property observers (`willSet`, `didSet`) | ❌ | -| Generics | ❌ | +| Generic methods (instance and static) (see ) | ✅ | +| Generic structs: `struct Pair` | ❌ | +| Generic initializers | ❌ | | Conformances | ❌ | ## See Also 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 02f0a349d..d80eb713d 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 @@ -87,7 +87,36 @@ A `@JSFunction` can be generic over a type parameter constrained to `BridgedSwif 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. +`T` must be a bridgeable type: a supported primitive (`Bool`, any fixed-width integer such as `Int`/`UInt`/`Int8`…`UInt64`, `Float`, `Double`, `String`, or `JSValue`), or a `@JS` struct, `final @JS class`, or `@JS enum`. You do not write any conformance yourself; marking a type `@JS` makes it usable as `T` (see ). Generics are not supported for `async` functions or `where` clauses (see ). + +A generic type parameter may be used in more than one parameter, an imported function may declare more than one distinct generic parameter, and a generic result type may be used on a function that takes no generic parameters (the JavaScript implementation produces the value): + +```swift +@JSFunction func pickFirst(_ a: T, _ b: T) throws(JSException) -> T + +@JSFunction func makeValue() throws(JSException) -> T + +@JSFunction func combine(_ a: T, _ b: U) throws(JSException) -> U +``` + +The generic parameter may also be wrapped as `[T]`, `T?`, or `[String: T]` in parameters and the result: + +```swift +@JSFunction func roundTrip(_ values: [T]) throws(JSException) -> [T] + +@JSFunction func lookup(_ values: [String: T]) throws(JSException) -> T? +``` + +### Generic methods on imported classes + +An imported `@JSClass` type can also have generic initializers, instance methods, and static methods. The same constraint applies. The Swift to JavaScript bridge resolves the concrete type through an internal type id, and the JavaScript implementation is called with only the method's declared arguments: + +```swift +@JSClass struct Store { + @JSFunction func identity(_ value: T) throws(JSException) -> T + @JSFunction static func box(_ value: T) throws(JSException) -> T +} +``` Generic imports can also constrain `T` to `@JS` protocols, as in `T: BridgedSwiftGenericBridgeable & P & Q`. If `P` inherits `BridgedSwiftGenericBridgeable`, use `T: P`: diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md index f9225bf4b..cc181801e 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md @@ -43,6 +43,8 @@ You do not write the conformance by hand: marking a type `@JS` (or using a built The generic parameter may be used bare (`T`) or wrapped in `[T]`, `T?`, or `[String: T]`. Other or nested wrappings (for example `[T?]`, `[[T]]`, or `[Int: T]`) are not supported. `JSObject` cannot be used as the generic argument; use `JSValue` instead. +A generic parameter may additionally be constrained to one or more `@JS` protocols by composing them with `BridgedSwiftGenericBridgeable`, for example ``. See and for details, including the runtime enforcement behavior on exports. + ## See Also - diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md index 5356869da..6a5149573 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md @@ -58,10 +58,11 @@ Types defined in a separate Swift package cannot yet be referenced from `@JS` de ## Generics -Generic functions are supported in both directions, through a type parameter constrained to `BridgedSwiftGenericBridgeable`: an imported `@JSFunction` (see ) and an exported `@JS` function (see ). Generics also work on methods of an exported `@JS` class or struct (both instance and static), on static methods of an exported `@JS` enum or namespace enum, and on imported `@JSClass` methods. A function may declare one or more distinct generic parameters, such as `combine(_ a: T, _ b: U) -> T`, and a return-only generic (such as `load(_ key: String) -> T?`) is supported in both directions. The following forms are not supported and produce build-time diagnostics: +Generic functions are supported in both directions, through a type parameter constrained to `BridgedSwiftGenericBridgeable`: an imported `@JSFunction` (see ) and an exported `@JS` function (see ). Generics also work on methods of an exported `@JS` class or struct (both instance and static), on static methods of an exported `@JS` enum or namespace enum, and on imported `@JSClass` methods. A function may declare one or more distinct generic parameters, such as `combine(_ a: T, _ b: U) -> T`, and a return-only generic (such as `load(_ key: String) -> T?`) is supported in both directions. A generic parameter may additionally be constrained to one or more `@JS` protocols by composing them with `BridgedSwiftGenericBridgeable`, such as ``. The following forms are not supported and produce build-time diagnostics: - `async` generic functions. - `where` clauses on a generic declaration. +- A generic parameter constrained to a bare protocol without `BridgedSwiftGenericBridgeable` in the composition (such as ``), or composed with a protocol that is not a `@JS protocol` (such as `Comparable`). - An exported `@JS` generic function that is `throws`. (Imported `@JSFunction` generics may still use `throws(JSException)`.) - An exported generic function with a declared generic parameter that is not used in any parameter or the return type. - A generic initializer on an exported `@JS` class or struct. Use a generic `@JS` method, or a generic `@JSFunction` initializer on an imported `@JSClass`. diff --git a/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift b/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift index 8e63e0964..d053e7df2 100644 --- a/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift +++ b/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift @@ -163,6 +163,81 @@ nonisolated(unsafe) private var _genericStore: [String: any BridgedSwiftGenericB @JS public func lastWrappedPointY() -> Int { _lastWrappedPoint.y } @JS public func lastTag() -> Int { _lastTag } +@JS public protocol ExportGenericGraphNode { + var id: String { get } +} + +@JS public struct ExportGenericBuilding: ExportGenericGraphNode { + public var id: String + public var floors: Int + + @JS public init(id: String, floors: Int) { + self.id = id + self.floors = floors + } +} + +nonisolated(unsafe) var _lastStoredNodeID = "" + +@JS public func exportGenericStoreNode(_ node: T) -> T { + // The protocol constraint is usable in the function body. + _lastStoredNodeID = node.id + return node +} + +// The bridging protocol may appear anywhere in the composition. +@JS +public func exportGenericNodeRoundTrip< + T: ExportGenericGraphNode & BridgedSwiftGenericBridgeable +>(_ node: T) -> T { + return node +} + +@JS +public func exportGenericStoreNodeWithExtra< + T: BridgedSwiftGenericBridgeable & ExportGenericGraphNode, + U: BridgedSwiftGenericBridgeable +>(_ node: T, _ extra: U) -> U { + _lastStoredNodeID = node.id + return extra +} + +nonisolated(unsafe) private var _nodeStore: [String: any BridgedSwiftGenericBridgeable] = [:] + +// Return-only generic export with a protocol constraint: T appears only in the +// result, so the thunk pops nothing for it and the JS caller picks T via the token. +@JS +public func exportGenericLoadNode< + T: BridgedSwiftGenericBridgeable & ExportGenericGraphNode +>(_ key: String) -> T? { + _nodeStore[key] as? T +} + +@JS +public func exportGenericSaveNode< + T: BridgedSwiftGenericBridgeable & ExportGenericGraphNode +>(_ key: String, _ node: T) { + _nodeStore[key] = node +} + +@JS public func lastStoredNodeID() -> String { _lastStoredNodeID } + +// Protocol refinement: a conformer of the refined protocol must satisfy a +// constraint on the base protocol, both in Swift and in the JS-side token check. +@JS public protocol ExportGenericSite: ExportGenericGraphNode { + var region: String { get } +} + +@JS public struct ExportGenericCampus: ExportGenericSite { + public var id: String + public var region: String + + @JS public init(id: String, region: String) { + self.id = id + self.region = region + } +} + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "runExportGenericTests") @_extern(c) func runExportGenericTests() -> Void diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index 4115baf29..bdfd606f2 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -4862,6 +4862,66 @@ fileprivate func bjs_DataProcessor_optionalHelper_set_extern(_ jsObject: Int32, return bjs_DataProcessor_optionalHelper_set_extern(jsObject, newValueIsSome, newValuePointer) } +extension ExportGenericGraphNode where Self: _BridgedSwiftProtocolWrapper { + var id: String { + get { + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = bjs_ExportGenericGraphNode_id_get(jsObjectValue) + return String.bridgeJSLiftReturn(ret) + } + } +} + +struct AnyExportGenericGraphNode: ExportGenericGraphNode, _BridgedSwiftProtocolWrapper { + let jsObject: JSObject + + static func bridgeJSLiftParameter(_ value: Int32) -> Self { + return AnyExportGenericGraphNode(jsObject: JSObject(id: UInt32(bitPattern: value))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ExportGenericGraphNode_id_get") +fileprivate func bjs_ExportGenericGraphNode_id_get_extern(_ jsObject: Int32) -> Int32 +#else +fileprivate func bjs_ExportGenericGraphNode_id_get_extern(_ jsObject: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ExportGenericGraphNode_id_get(_ jsObject: Int32) -> Int32 { + return bjs_ExportGenericGraphNode_id_get_extern(jsObject) +} + +extension ExportGenericSite where Self: _BridgedSwiftProtocolWrapper { + var region: String { + get { + let jsObjectValue = jsObject.bridgeJSLowerParameter() + let ret = bjs_ExportGenericSite_region_get(jsObjectValue) + return String.bridgeJSLiftReturn(ret) + } + } +} + +struct AnyExportGenericSite: ExportGenericSite, _BridgedSwiftProtocolWrapper { + let jsObject: JSObject + + static func bridgeJSLiftParameter(_ value: Int32) -> Self { + return AnyExportGenericSite(jsObject: JSObject(id: UInt32(bitPattern: value))) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ExportGenericSite_region_get") +fileprivate func bjs_ExportGenericSite_region_get_extern(_ jsObject: Int32) -> Int32 +#else +fileprivate func bjs_ExportGenericSite_region_get_extern(_ jsObject: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_ExportGenericSite_region_get(_ jsObject: Int32) -> Int32 { + return bjs_ExportGenericSite_region_get_extern(jsObject) +} + extension GenericRTPosition where Self: _BridgedSwiftProtocolWrapper { func sum() -> Int { let jsObjectValue = jsObject.bridgeJSLowerParameter() @@ -8228,6 +8288,128 @@ private func _bjs_ExportGenericMethodPair_static_wrap_open1 ExportGenericBuilding { + let floors = Int.bridgeJSStackPop() + let id = String.bridgeJSStackPop() + return ExportGenericBuilding(id: id, floors: floors) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.id.bridgeJSStackPush() + self.floors.bridgeJSStackPush() + } + + public init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_ExportGenericBuilding(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + public func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_ExportGenericBuilding())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_ExportGenericBuilding") +fileprivate func _bjs_struct_lower_ExportGenericBuilding_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_ExportGenericBuilding_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_ExportGenericBuilding(_ objectId: Int32) -> Void { + return _bjs_struct_lower_ExportGenericBuilding_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_ExportGenericBuilding") +fileprivate func _bjs_struct_lift_ExportGenericBuilding_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_ExportGenericBuilding_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_ExportGenericBuilding() -> Int32 { + return _bjs_struct_lift_ExportGenericBuilding_extern() +} + +@_expose(wasm, "bjs_ExportGenericBuilding_init") +@_cdecl("bjs_ExportGenericBuilding_init") +public func _bjs_ExportGenericBuilding_init(_ idBytes: Int32, _ idLength: Int32, _ floors: Int32) -> Void { + #if arch(wasm32) + let floors = Int.bridgeJSLiftParameter(floors) + let id = String.bridgeJSLiftParameter(idBytes, idLength) + let ret = ExportGenericBuilding(id: id, floors: floors) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +extension ExportGenericCampus: _BridgedSwiftStruct { + @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> ExportGenericCampus { + let region = String.bridgeJSStackPop() + let id = String.bridgeJSStackPop() + return ExportGenericCampus(id: id, region: region) + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + self.id.bridgeJSStackPush() + self.region.bridgeJSStackPush() + } + + public init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_ExportGenericCampus(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + public func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_ExportGenericCampus())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_ExportGenericCampus") +fileprivate func _bjs_struct_lower_ExportGenericCampus_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_ExportGenericCampus_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_ExportGenericCampus(_ objectId: Int32) -> Void { + return _bjs_struct_lower_ExportGenericCampus_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_ExportGenericCampus") +fileprivate func _bjs_struct_lift_ExportGenericCampus_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_ExportGenericCampus_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_ExportGenericCampus() -> Int32 { + return _bjs_struct_lift_ExportGenericCampus_extern() +} + +@_expose(wasm, "bjs_ExportGenericCampus_init") +@_cdecl("bjs_ExportGenericCampus_init") +public func _bjs_ExportGenericCampus_init(_ idBytes: Int32, _ idLength: Int32, _ regionBytes: Int32, _ regionLength: Int32) -> Void { + #if arch(wasm32) + let region = String.bridgeJSLiftParameter(regionBytes, regionLength) + let id = String.bridgeJSLiftParameter(idBytes, idLength) + let ret = ExportGenericCampus(id: id, region: region) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + extension Library.Shelf: _BridgedSwiftStruct { @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> Library.Shelf { let label = String.bridgeJSStackPop() @@ -12222,6 +12404,157 @@ public func _bjs_lastTag() -> Int32 { #endif } +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericStoreNode") +@_cdecl("bjs_exportGenericStoreNode") +public func _bjs_exportGenericStoreNode(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericStoreNode") +@_cdecl("bjs_exportGenericStoreNode") +public func _bjs_exportGenericStoreNode(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGenericGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGenericGraphNode") + } + _bjs_exportGenericStoreNode_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericStoreNode_open1(_ _generic0Type: T.Type) { + let node = T.bridgeJSStackPop() + let ret: T = exportGenericStoreNode(_: node) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericNodeRoundTrip") +@_cdecl("bjs_exportGenericNodeRoundTrip") +public func _bjs_exportGenericNodeRoundTrip(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericNodeRoundTrip") +@_cdecl("bjs_exportGenericNodeRoundTrip") +public func _bjs_exportGenericNodeRoundTrip(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGenericGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGenericGraphNode") + } + _bjs_exportGenericNodeRoundTrip_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericNodeRoundTrip_open1(_ _generic0Type: T.Type) { + let node = T.bridgeJSStackPop() + let ret: T = exportGenericNodeRoundTrip(_: node) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericStoreNodeWithExtra") +@_cdecl("bjs_exportGenericStoreNodeWithExtra") +public func _bjs_exportGenericStoreNodeWithExtra(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericStoreNodeWithExtra") +@_cdecl("bjs_exportGenericStoreNodeWithExtra") +public func _bjs_exportGenericStoreNodeWithExtra(_ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGenericGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGenericGraphNode") + } + let _generic1Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic1TypeId)))!).takeUnretainedValue().type + _bjs_exportGenericStoreNodeWithExtra_open1(_generic0Type, _generic1Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericStoreNodeWithExtra_open1(_ _generic0Type: T.Type, _ _generic1Type: any BridgedSwiftGenericBridgeable.Type) { + _bjs_exportGenericStoreNodeWithExtra_open2(_generic1Type, asT: T.self) +} +private func _bjs_exportGenericStoreNodeWithExtra_open2(_ _generic1Type: U.Type, asT _generic0Type: T.Type) { + let extra = U.bridgeJSStackPop() + let node = T.bridgeJSStackPop() + let ret: U = exportGenericStoreNodeWithExtra(_: node, _: extra) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericLoadNode") +@_cdecl("bjs_exportGenericLoadNode") +public func _bjs_exportGenericLoadNode(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericLoadNode") +@_cdecl("bjs_exportGenericLoadNode") +public func _bjs_exportGenericLoadNode(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGenericGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGenericGraphNode") + } + _bjs_exportGenericLoadNode_open1(_generic0Type, keyBytes, keyLength) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericLoadNode_open1(_ _generic0Type: T.Type, _ keyBytes: Int32, _ keyLength: Int32) { + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + let ret: Optional = exportGenericLoadNode(_: key) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericSaveNode") +@_cdecl("bjs_exportGenericSaveNode") +public func _bjs_exportGenericSaveNode(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericSaveNode") +@_cdecl("bjs_exportGenericSaveNode") +public func _bjs_exportGenericSaveNode(_ keyBytes: Int32, _ keyLength: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0TypeBase = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + guard let _generic0Type = _generic0TypeBase as? any (BridgedSwiftGenericBridgeable & ExportGenericGraphNode).Type else { + fatalError("BridgeJS: type '\(_generic0TypeBase)' does not conform to required protocol(s): ExportGenericGraphNode") + } + _bjs_exportGenericSaveNode_open1(_generic0Type, keyBytes, keyLength) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericSaveNode_open1(_ _generic0Type: T.Type, _ keyBytes: Int32, _ keyLength: Int32) { + let node = T.bridgeJSStackPop() + let key = String.bridgeJSLiftParameter(keyBytes, keyLength) + exportGenericSaveNode(_: key, _: node) +} +#endif + +@_expose(wasm, "bjs_lastStoredNodeID") +@_cdecl("bjs_lastStoredNodeID") +public func _bjs_lastStoredNodeID() -> Void { + #if arch(wasm32) + let ret = lastStoredNodeID() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_roundTripMessage") @_cdecl("bjs_roundTripMessage") public func _bjs_roundTripMessage(_ message: Int32) -> Void { @@ -16842,6 +17175,14 @@ extension ExportGenericMethodPair: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericMethodPair.bridgeJSMakeTypeHandle() } +extension ExportGenericBuilding: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericBuilding.bridgeJSMakeTypeHandle() +} + +extension ExportGenericCampus: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportGenericCampus.bridgeJSMakeTypeHandle() +} + extension Library.Shelf: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = Library.Shelf.bridgeJSMakeTypeHandle() } @@ -22235,6 +22576,8 @@ public func _bjs_BridgeJSRuntimeTests_register_type_handles() { NestedTypeHost.Label.bridgeJSTypeID, ExportGenericPoint.bridgeJSTypeID, ExportGenericMethodPair.bridgeJSTypeID, + ExportGenericBuilding.bridgeJSTypeID, + ExportGenericCampus.bridgeJSTypeID, Library.Shelf.bridgeJSTypeID, Library.Shelf.Divider.bridgeJSTypeID, GenericRTPoint.bridgeJSTypeID, diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index ba2545854..49e98c374 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -3052,6 +3052,9 @@ "swiftCallName" : "DataProcessorManager" }, { + "conformedJSProtocols" : [ + "DataProcessor" + ], "constructor" : { "abiName" : "bjs_SwiftDataProcessor_init", "effects" : { @@ -5040,7 +5043,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "echo", "parameters" : [ @@ -5068,8 +5073,12 @@ "isThrows" : false }, "genericParameters" : [ - "T", - "U" + { + "name" : "T" + }, + { + "name" : "U" + } ], "name" : "combine", "parameters" : [ @@ -5106,7 +5115,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "wrapArray", "parameters" : [ @@ -10827,7 +10838,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "one", "parameters" : [ @@ -10875,7 +10888,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "make", "namespace" : [ @@ -17992,7 +18007,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericIdentity", "parameters" : [ @@ -18020,7 +18037,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericEcho", "parameters" : [ @@ -18060,7 +18079,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericPickFirst", "parameters" : [ @@ -18097,7 +18118,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericPickSecond", "parameters" : [ @@ -18190,7 +18213,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericArrayIdentity", "parameters" : [ @@ -18226,7 +18251,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericOptionalIdentity", "parameters" : [ @@ -18264,7 +18291,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericDictIdentity", "parameters" : [ @@ -18300,7 +18329,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericWrapPointAndTag", "parameters" : [ @@ -18349,8 +18380,12 @@ "isThrows" : false }, "genericParameters" : [ - "T", - "U" + { + "name" : "T" + }, + { + "name" : "U" + } ], "name" : "exportGenericCombineFirst", "parameters" : [ @@ -18387,8 +18422,12 @@ "isThrows" : false }, "genericParameters" : [ - "T", - "U" + { + "name" : "T" + }, + { + "name" : "U" + } ], "name" : "exportGenericCombineSecond", "parameters" : [ @@ -18425,9 +18464,15 @@ "isThrows" : false }, "genericParameters" : [ - "T", - "U", - "V" + { + "name" : "T" + }, + { + "name" : "U" + }, + { + "name" : "V" + } ], "name" : "exportGenericCombineTripleLast", "parameters" : [ @@ -18473,7 +18518,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericStore", "parameters" : [ @@ -18510,7 +18557,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "exportGenericLoad", "parameters" : [ @@ -18595,6 +18644,214 @@ } } }, + { + "abiName" : "bjs_exportGenericStoreNode", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGenericGraphNode" + ], + "name" : "T" + } + ], + "name" : "exportGenericStoreNode", + "parameters" : [ + { + "label" : "_", + "name" : "node", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_exportGenericNodeRoundTrip", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGenericGraphNode" + ], + "name" : "T" + } + ], + "name" : "exportGenericNodeRoundTrip", + "parameters" : [ + { + "label" : "_", + "name" : "node", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_exportGenericStoreNodeWithExtra", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGenericGraphNode" + ], + "name" : "T" + }, + { + "name" : "U" + } + ], + "name" : "exportGenericStoreNodeWithExtra", + "parameters" : [ + { + "label" : "_", + "name" : "node", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "extra", + "type" : { + "generic" : { + "_0" : "U" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "U" + } + } + }, + { + "abiName" : "bjs_exportGenericLoadNode", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGenericGraphNode" + ], + "name" : "T" + } + ], + "name" : "exportGenericLoadNode", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "generic" : { + "_0" : "T" + } + }, + "_1" : "null" + } + } + }, + { + "abiName" : "bjs_exportGenericSaveNode", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "constraints" : [ + "ExportGenericGraphNode" + ], + "name" : "T" + } + ], + "name" : "exportGenericSaveNode", + "parameters" : [ + { + "label" : "_", + "name" : "key", + "type" : { + "string" : { + + } + } + }, + { + "label" : "_", + "name" : "node", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_lastStoredNodeID", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "name" : "lastStoredNodeID", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + }, { "abiName" : "bjs_roundTripMessage", "effects" : { @@ -19983,6 +20240,43 @@ } ] }, + { + "methods" : [ + + ], + "name" : "ExportGenericGraphNode", + "properties" : [ + { + "isReadonly" : true, + "name" : "id", + "type" : { + "string" : { + + } + } + } + ] + }, + { + "inheritedJSProtocols" : [ + "ExportGenericGraphNode" + ], + "methods" : [ + + ], + "name" : "ExportGenericSite", + "properties" : [ + { + "isReadonly" : true, + "name" : "region", + "type" : { + "string" : { + + } + } + } + ] + }, { "isGenericBridgeable" : true, "methods" : [ @@ -20511,7 +20805,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "first", "parameters" : [ @@ -20539,7 +20835,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "maybe", "parameters" : [ @@ -20581,7 +20879,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "dict", "parameters" : [ @@ -20613,7 +20913,9 @@ "isThrows" : false }, "genericParameters" : [ - "T" + { + "name" : "T" + } ], "name" : "wrap", "parameters" : [ @@ -20649,6 +20951,134 @@ ], "swiftCallName" : "ExportGenericMethodPair" }, + { + "conformedJSProtocols" : [ + "ExportGenericGraphNode" + ], + "constructor" : { + "abiName" : "bjs_ExportGenericBuilding_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "id", + "name" : "id", + "type" : { + "string" : { + + } + } + }, + { + "label" : "floors", + "name" : "floors", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ] + }, + "explicitAccessControl" : "public", + "methods" : [ + + ], + "name" : "ExportGenericBuilding", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "floors", + "type" : { + "integer" : { + "_0" : { + "isSigned" : true, + "width" : "word" + } + } + } + } + ], + "swiftCallName" : "ExportGenericBuilding" + }, + { + "conformedJSProtocols" : [ + "ExportGenericSite" + ], + "constructor" : { + "abiName" : "bjs_ExportGenericCampus_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "id", + "name" : "id", + "type" : { + "string" : { + + } + } + }, + { + "label" : "region", + "name" : "region", + "type" : { + "string" : { + + } + } + } + ] + }, + "explicitAccessControl" : "public", + "methods" : [ + + ], + "name" : "ExportGenericCampus", + "properties" : [ + { + "isReadonly" : true, + "isStatic" : false, + "name" : "id", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "isStatic" : false, + "name" : "region", + "type" : { + "string" : { + + } + } + } + ], + "swiftCallName" : "ExportGenericCampus" + }, { "constructor" : { "abiName" : "bjs_Library_Shelf_init", @@ -20780,6 +21210,9 @@ "swiftCallName" : "Library.Shelf.Divider" }, { + "conformedJSProtocols" : [ + "GenericRTNode" + ], "methods" : [ { "abiName" : "bjs_GenericRTPoint_sum", diff --git a/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs b/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs index e120ba271..c371b5134 100644 --- a/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs +++ b/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs @@ -181,4 +181,80 @@ export function runExportGenericTests(exports) { TypeError ); assert.equal(exports.exportGenericIdentity(7, BridgeTypes.Int), 7); + + // Generic exports constrained to a @JS protocol composition: the Swift thunk + // reifies T through `any (BridgedSwiftGenericBridgeable & P).Type` and the + // function body can use the protocol members of the value. + const storedBuilding = exports.exportGenericStoreNode( + { id: "hq", floors: 3 }, + BridgeTypes.ExportGenericBuilding + ); + assert.equal(storedBuilding.id, "hq"); + assert.equal(storedBuilding.floors, 3); + assert.equal(exports.lastStoredNodeID(), "hq"); + + const roundTrippedBuilding = exports.exportGenericNodeRoundTrip( + { id: "annex", floors: 1 }, + BridgeTypes.ExportGenericBuilding + ); + assert.equal(roundTrippedBuilding.id, "annex"); + assert.equal(roundTrippedBuilding.floors, 1); + + // Mixed constrained + unconstrained generic parameters. + assert.equal( + exports.exportGenericStoreNodeWithExtra( + { id: "depot", floors: 2 }, + 42, + BridgeTypes.ExportGenericBuilding, + BridgeTypes.Int + ), + 42 + ); + assert.equal(exports.lastStoredNodeID(), "depot"); + + // Return-only constrained generic export: T appears only in the result, so + // the thunk performs the composed-existential cast, opens T, pops no generic + // argument, and lowers the optional return via the token's codec. + exports.exportGenericSaveNode( + "vault", + { id: "vault", floors: 9 }, + BridgeTypes.ExportGenericBuilding + ); + const loadedNode = exports.exportGenericLoadNode( + "vault", + BridgeTypes.ExportGenericBuilding + ); + assert.equal(loadedNode.id, "vault"); + assert.equal(loadedNode.floors, 9); + assert.equal( + exports.exportGenericLoadNode("missing", BridgeTypes.ExportGenericBuilding), + null + ); + + // A token for a type that does NOT conform to the required protocols throws + // a catchable TypeError from the JS wrapper BEFORE entering wasm: the link + // layer knows every type's @JS protocol conformances, so the check runs + // against the token and nothing is lowered (balanced stack, no wasm trap). + // The Swift-side conditional cast still exists as defense-in-depth, but it + // is unreachable through the generated wrapper (only a raw wasm caller + // could hit it), so there is no trap-based test here. + assert.throws( + () => exports.exportGenericStoreNode(5, BridgeTypes.Int), + TypeError + ); + // The failed call left the stacks balanced; constrained calls still work. + assert.equal( + exports.exportGenericNodeRoundTrip({ id: "after", floors: 4 }, BridgeTypes.ExportGenericBuilding).id, + "after" + ); + + // Protocol refinement: ExportGenericCampus conforms to ExportGenericSite, + // which refines ExportGenericGraphNode, so its token satisfies constraints + // on the base protocol too - exactly as the Swift type system does. + const campus = exports.exportGenericStoreNode( + { id: "campus-1", region: "north" }, + BridgeTypes.ExportGenericCampus + ); + assert.equal(campus.region, "north"); + assert.equal(exports.lastStoredNodeID(), "campus-1"); } From ff403eb694193eeedb20e2867224164ce0067845 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Mon, 28 Sep 2026 21:12:15 +0200 Subject: [PATCH 4/5] BridgeJS: Support throws(JSException) on generic exports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generic exported functions and methods may now be throws(JSException), matching what generic imports already supported: @JS public func pickOrThrow(_ value: T, _ fail: Bool) throws(JSException) -> T The concrete entry thunk wraps the existential-opening call chain in the same do/catch every throwing export uses — the exception crosses through the _swift_js_throw side channel — and the open-chain helpers become throws(JSException) so the typed error propagates without erasure. On the JavaScript side the wrapper rethrows the exception right after the wasm call and before lifting the result, so a thrown call reads nothing back and leaves the shared value stack balanced; the wrappers emitted through the thunk builder get this from the existing effects-driven exception check, and the struct-instance method path emits the same sequence explicitly. async generic exports remain rejected with a diagnostic. There is no compiler obstacle — the wasm32 typed-throws closure issues are already worked around by the forced-capture emission (#760) and JSException storage boxing (#766) — but promise settlement is per-type today: each async export settles through a Promise_resolve_ helper paired with a JS settle handler that lifts a concrete value. A generic result needs a codec-driven settlement path (stash the call's codec with the promise's settlers, settle through the value stack), which is its own ABI addition and lands separately. Runtime tests cover the happy path, the surfaced exception, and that a thrown call leaves the stacks balanced for the next generic call. --- .../Sources/BridgeJSCore/ExportSwift.swift | 41 ++++- .../BridgeJSCore/SwiftToSkeleton.swift | 7 - .../Sources/BridgeJSLink/JSGlueGen.swift | 30 +++- .../GenericExportDiagnosticsTests.swift | 34 ++-- .../Inputs/MacroSwift/GenericExports.swift | 26 +++ .../BridgeJSCodegenTests/GenericExports.json | 120 +++++++++++++ .../BridgeJSCodegenTests/GenericExports.swift | 163 ++++++++++++++++++ .../BridgeJSLinkTests/GenericExports.d.ts | 10 +- .../BridgeJSLinkTests/GenericExports.js | 74 +++++++- .../BridgeJS-Internals/Design-Rationale.md | 2 +- .../Exporting-Swift-Function.md | 22 ++- .../Articles/BridgeJS/Unsupported-Features.md | 5 +- .../ExportGenericAPIs.swift | 12 ++ .../Generated/BridgeJS.swift | 39 +++++ .../Generated/JavaScript/BridgeJS.json | 39 +++++ .../JavaScript/ExportGenericTests.mjs | 29 ++++ 16 files changed, 617 insertions(+), 36 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 15f40fe8b..2fe022bb6 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -631,7 +631,38 @@ public class ExportSwift { } } let open1Arguments = genericNames.map { metatypeName($0) } + concreteABINames - printer.write("_\(abiName)_open1(\(open1Arguments.joined(separator: ", ")))") + let open1Call = "_\(abiName)_open1(\(open1Arguments.joined(separator: ", ")))" + if effects.isThrows { + // Same throw-to-JS convention as concrete throwing exports: + // the exception crosses through the side channel and the JS + // wrapper rethrows it before lifting anything, so a thrown + // call leaves the shared value stack balanced. + printer.write("do {") + printer.indent { + printer.write("try \(open1Call)") + } + printer.write("} catch let error {") + printer.indent { + printer.write( + multilineString: """ + if let error = error.thrownValue.object { + withExtendedLifetime(error) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } else { + let jsError = JSError(message: error.description) + withExtendedLifetime(jsError.jsObject) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } + \(returnPlaceholderStmt()) + """ + ) + } + printer.write("}") + } else { + printer.write(open1Call) + } } printer.write(multilineString: entryDecl.description) @@ -657,7 +688,10 @@ public class ExportSwift { params.append("_ \(abiParam.name): \(abiParam.type.swiftType)") } - printer.write("private func \(openName)\(genericClause)(\(params.joined(separator: ", "))) {") + let effectsClause = effects.isThrows ? " throws(JSException)" : "" + printer.write( + "private func \(openName)\(genericClause)(\(params.joined(separator: ", ")))\(effectsClause) {" + ) printer.indent { if k < count { let nextOpenedName = genericNames[k] @@ -669,7 +703,8 @@ public class ExportSwift { callArguments.append(metatypeName(remainingName)) } callArguments.append(contentsOf: concreteABINames) - printer.write("_\(abiName)_open\(k + 1)(\(callArguments.joined(separator: ", ")))") + let tryPrefix = effects.isThrows ? "try " : "" + printer.write("\(tryPrefix)_\(abiName)_open\(k + 1)(\(callArguments.joined(separator: ", ")))") } else { // The standard builder-emitted body: `T` behaves like // any other bridged type via its protocol requirements. diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index 6385ae8d9..06050c6ef 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -1767,13 +1767,6 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor { ) return nil } - if node.signature.effectSpecifiers?.throwsClause != nil { - diagnose( - node: node, - message: "Generic @JS functions cannot be 'throws' yet." - ) - return nil - } } let genericParameterNames = genericParameters.map(\.name) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 349382a7d..656de3b53 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -274,6 +274,21 @@ enum GenericJSCodegen { ] } + /// The rethrow sequence a throwing export's wrapper runs right after the + /// wasm call, mirroring `ExportedThunkBuilder.checkExceptionLines()` for + /// glue emitted outside the thunk builder. + static func checkExceptionLines() -> [String] { + let exceptionVariable = JSGlueVariableScope.reservedStorageToReturnException + return [ + "if (\(exceptionVariable)) {", + " const error = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(exceptionVariable));", + " \(JSGlueVariableScope.reservedSwift).memory.release(\(exceptionVariable));", + " \(exceptionVariable) = undefined;", + " throw error;", + "}", + ] + } + /// Shared generic runtime: a type-ID-keyed codec table plus codec /// combinators. Each container shape's stack ABI is described once here and /// instantiated with an element codec, instead of cloning the lowering and @@ -2716,15 +2731,19 @@ struct IntrinsicJSFragment: Sendable { var paramForwardings: [String] = [] for param in method.parameters { if let genericName = param.type.referencedGenericName { - if let codecVariable = codecVariables[genericName], + guard let codecVariable = codecVariables[genericName], let lowerStatement = GenericJSCodegen.genericCodecLowerStatement( type: param.type, codec: codecVariable, value: param.name ) - { - printer.write(lowerStatement) + else { + throw BridgeJSLinkError( + message: + "Generic codec for '\(genericName)' was not declared before lowering parameter '\(param.name)'" + ) } + printer.write(lowerStatement) } else { let fragment = try IntrinsicJSFragment.lowerParameter(type: param.type) let loweredValues = try fragment.printCode([param.name], methodContext) @@ -2733,6 +2752,11 @@ struct IntrinsicJSFragment: Sendable { } paramForwardings.append(contentsOf: method.genericParameterNames.compactMap { typeIdVariables[$0] }) printer.write("instance.exports.\(method.abiName)(\(paramForwardings.joined(separator: ", ")));") + if method.effects.isThrows { + // Rethrow before lifting: a thrown call pushed nothing onto the + // shared value stack, so the lift below must not run. + printer.write(lines: GenericJSCodegen.checkExceptionLines()) + } if let returnGenericName = method.returnType.referencedGenericName, let codecVariable = codecVariables[returnGenericName], let liftExpression = GenericJSCodegen.genericCodecLiftExpression( diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift index 29f2cbe4d..e183538aa 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift @@ -238,13 +238,15 @@ import Testing } @Test - func throwsGenericExportUnsupported() { - expectDiagnostic( - source: """ - @JS public func f(_ v: T) throws(JSException) -> T { v } - """, - contains: "Generic @JS functions cannot be 'throws' yet." + func throwsGenericExportIsAccepted() throws { + let skeleton = try makeSkeleton( + """ + @JS public func f(_ v: T) throws(JSException) -> T { v } + """ ) + let function = try #require(skeleton.exported?.functions.first) + #expect(function.effects.isThrows) + #expect(function.genericParameterNames == ["T"]) } @Test(arguments: [ @@ -307,16 +309,18 @@ import Testing } @Test - func genericInstanceMethodThrowsIsRejected() { - expectDiagnostic( - source: """ - @JS class Box { - @JS init() {} - @JS func wrap(_ v: T) throws(JSException) -> T { v } - } - """, - contains: "Generic @JS functions cannot be 'throws' yet." + func genericInstanceMethodThrowsIsAccepted() throws { + let skeleton = try makeSkeleton( + """ + @JS class Box { + @JS init() {} + @JS func wrap(_ v: T) throws(JSException) -> T { v } + } + """ ) + let method = try #require(skeleton.exported?.classes.first?.methods.first) + #expect(method.effects.isThrows) + #expect(method.genericParameterNames == ["T"]) } @Test diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericExports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericExports.swift index c6139b827..d0f1c7ecd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericExports.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/GenericExports.swift @@ -160,6 +160,32 @@ public func genericExportCaseDistinct< } } +// Throwing generic exports: the exception crosses through the side channel and +// the JS wrapper rethrows it before lifting anything from the value stack. +@JS public func genericPickOrThrow( + _ value: T, + _ shouldThrow: Bool +) throws(JSException) -> T { + if shouldThrow { + throw JSException(JSError(message: "generic pick failed").jsValue) + } + return value +} + +@JS struct GenericThrowingBox { + @JS init() {} + + @JS func reject(_ value: T) throws(JSException) -> T { + throw JSException(JSError(message: "boxed rejection").jsValue) + } + + // A renamed generic struct method must be attached under its JS name, the + // same name the d.ts declares. + @JS("passThrough") func forward(_ value: T) -> T { + value + } +} + // A namespaced final class: its token is `ExportGenericNamespace_Handle`, but // its d.ts interface is emitted at the top level, so the token table must not // spell the type as `ExportGenericNamespace.Handle`. diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json index 43f6a6fe1..bc44cd03a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.json @@ -1045,6 +1045,45 @@ "_0" : "T" } } + }, + { + "abiName" : "bjs_genericPickOrThrow", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + { + "name" : "T" + } + ], + "name" : "genericPickOrThrow", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "label" : "_", + "name" : "shouldThrow", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } } ], "protocols" : [ @@ -1326,6 +1365,87 @@ ], "swiftCallName" : "GenericPair" + }, + { + "constructor" : { + "abiName" : "bjs_GenericThrowingBox_init", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_GenericThrowingBox_reject", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + { + "name" : "T" + } + ], + "name" : "reject", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, + { + "abiName" : "bjs_GenericThrowingBox_passThrough", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : false + }, + "genericParameters" : [ + { + "name" : "T" + } + ], + "jsName" : "passThrough", + "name" : "forward", + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "name" : "GenericThrowingBox", + "properties" : [ + + ], + "swiftCallName" : "GenericThrowingBox" } ] }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.swift index 1bd4bb95e..5f2040a9c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericExports.swift @@ -422,6 +422,125 @@ private func _bjs_GenericPair_static_wrap_open1 GenericThrowingBox { + return GenericThrowingBox() + } + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSStackPush() { + } + + init(unsafelyCopying jsObject: JSObject) { + _bjs_struct_lower_GenericThrowingBox(jsObject.bridgeJSLowerParameter()) + self = Self.bridgeJSStackPop() + } + + func toJSObject() -> JSObject { + let __bjs_self = self + __bjs_self.bridgeJSStackPush() + return JSObject(id: UInt32(bitPattern: _bjs_struct_lift_GenericThrowingBox())) + } +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lower_GenericThrowingBox") +fileprivate func _bjs_struct_lower_GenericThrowingBox_extern(_ objectId: Int32) -> Void +#else +fileprivate func _bjs_struct_lower_GenericThrowingBox_extern(_ objectId: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lower_GenericThrowingBox(_ objectId: Int32) -> Void { + return _bjs_struct_lower_GenericThrowingBox_extern(objectId) +} + +#if arch(wasm32) +@_extern(wasm, module: "bjs", name: "swift_js_struct_lift_GenericThrowingBox") +fileprivate func _bjs_struct_lift_GenericThrowingBox_extern() -> Int32 +#else +fileprivate func _bjs_struct_lift_GenericThrowingBox_extern() -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func _bjs_struct_lift_GenericThrowingBox() -> Int32 { + return _bjs_struct_lift_GenericThrowingBox_extern() +} + +@_expose(wasm, "bjs_GenericThrowingBox_init") +@_cdecl("bjs_GenericThrowingBox_init") +public func _bjs_GenericThrowingBox_init() -> Void { + #if arch(wasm32) + let ret = GenericThrowingBox() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericThrowingBox_reject") +@_cdecl("bjs_GenericThrowingBox_reject") +public func _bjs_GenericThrowingBox_reject(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericThrowingBox_reject") +@_cdecl("bjs_GenericThrowingBox_reject") +public func _bjs_GenericThrowingBox_reject(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + do { + try _bjs_GenericThrowingBox_reject_open1(_generic0Type) + } catch let error { + if let error = error.thrownValue.object { + withExtendedLifetime(error) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } else { + let jsError = JSError(message: error.description) + withExtendedLifetime(jsError.jsObject) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } + return + } + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericThrowingBox_reject_open1(_ _generic0Type: T.Type) throws(JSException) { + let value = T.bridgeJSStackPop() + let _self = GenericThrowingBox.bridgeJSLiftParameter() + let ret: T = try _self.reject(_: value) + ret.bridgeJSStackPush() +} +#endif + +#if hasFeature(Embedded) +@_expose(wasm, "bjs_GenericThrowingBox_passThrough") +@_cdecl("bjs_GenericThrowingBox_passThrough") +public func _bjs_GenericThrowingBox_passThrough(_ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_GenericThrowingBox_passThrough") +@_cdecl("bjs_GenericThrowingBox_passThrough") +public func _bjs_GenericThrowingBox_passThrough(_ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + _bjs_GenericThrowingBox_passThrough_open1(_generic0Type) + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_GenericThrowingBox_passThrough_open1(_ _generic0Type: T.Type) { + let value = T.bridgeJSStackPop() + let _self = GenericThrowingBox.bridgeJSLiftParameter() + let ret: T = _self.forward(_: value) + ret.bridgeJSStackPush() +} +#endif + #if hasFeature(Embedded) @_expose(wasm, "bjs_genericExportIdentity") @_cdecl("bjs_genericExportIdentity") @@ -754,6 +873,45 @@ private func _bjs_genericExportCaseDistinct_open2 Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_genericPickOrThrow") +@_cdecl("bjs_genericPickOrThrow") +public func _bjs_genericPickOrThrow(_ shouldThrow: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + do { + try _bjs_genericPickOrThrow_open1(_generic0Type, shouldThrow) + } catch let error { + if let error = error.thrownValue.object { + withExtendedLifetime(error) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } else { + let jsError = JSError(message: error.description) + withExtendedLifetime(jsError.jsObject) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } + return + } + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_genericPickOrThrow_open1(_ _generic0Type: T.Type, _ shouldThrow: Int32) throws(JSException) { + let shouldThrow = Bool.bridgeJSLiftParameter(shouldThrow) + let value = T.bridgeJSStackPop() + let ret: T = try genericPickOrThrow(_: value, _: shouldThrow) + ret.bridgeJSStackPush() +} +#endif + @_expose(wasm, "bjs_ExportBox_init") @_cdecl("bjs_ExportBox_init") public func _bjs_ExportBox_init(_ value: Int32) -> UnsafeMutableRawPointer { @@ -1033,6 +1191,10 @@ extension GenericPair: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericPair.bridgeJSMakeTypeHandle() } +extension GenericThrowingBox: BridgedSwiftGenericBridgeable { + @_spi(BridgeJS) public static let bridgeJSTypeHandle = GenericThrowingBox.bridgeJSMakeTypeHandle() +} + extension ExportBox: BridgedSwiftGenericBridgeable { @_spi(BridgeJS) public static let bridgeJSTypeHandle = ExportBox.bridgeJSMakeTypeHandle() } @@ -1075,6 +1237,7 @@ public func _bjs_TestModule_register_type_handles() { ExportPoint.bridgeJSTypeID, ExportNamespace.Metadata.bridgeJSTypeID, GenericPair.bridgeJSTypeID, + GenericThrowingBox.bridgeJSTypeID, ExportBox.bridgeJSTypeID, GenericBox.bridgeJSTypeID, ExportGenericNamespace.Handle.bridgeJSTypeID, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.d.ts index e6ddcf9c2..07a2403ce 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.d.ts @@ -41,9 +41,13 @@ export interface GenericPair { maybe(value: T, typeT: BridgeType): T | null; dict(value: T, typeT: BridgeType): Record; } +export interface GenericThrowingBox { + reject(value: T, typeT: BridgeType): T; + passThrough(value: T, typeT: BridgeType): T; +} declare const bridgeTypeBrand: unique symbol; export type BridgeType = string & { readonly [bridgeTypeBrand]: (value: T) => void }; -export const BridgeTypes: { readonly Bool: BridgeType; readonly Int: BridgeType; readonly Int8: BridgeType; readonly UInt8: BridgeType; readonly Int16: BridgeType; readonly UInt16: BridgeType; readonly Int32: BridgeType; readonly UInt32: BridgeType; readonly UInt: BridgeType; readonly Int64: BridgeType; readonly UInt64: BridgeType; readonly Float: BridgeType; readonly Double: BridgeType; readonly String: BridgeType; readonly JSValue: BridgeType; readonly ExportPoint: BridgeType; readonly ExportNamespace_Metadata: BridgeType; readonly GenericPair: BridgeType; readonly ExportBox: BridgeType; readonly GenericBox: BridgeType; readonly ExportGenericNamespace_Handle: BridgeType; readonly ExportNamespace_Level: BridgeType; readonly ExportMode: BridgeType; readonly ExportColor: BridgeType; readonly ExportTagged: BridgeType; readonly GenericFactory: BridgeType; }; +export const BridgeTypes: { readonly Bool: BridgeType; readonly Int: BridgeType; readonly Int8: BridgeType; readonly UInt8: BridgeType; readonly Int16: BridgeType; readonly UInt16: BridgeType; readonly Int32: BridgeType; readonly UInt32: BridgeType; readonly UInt: BridgeType; readonly Int64: BridgeType; readonly UInt64: BridgeType; readonly Float: BridgeType; readonly Double: BridgeType; readonly String: BridgeType; readonly JSValue: BridgeType; readonly ExportPoint: BridgeType; readonly ExportNamespace_Metadata: BridgeType; readonly GenericPair: BridgeType; readonly GenericThrowingBox: BridgeType; readonly ExportBox: BridgeType; readonly GenericBox: BridgeType; readonly ExportGenericNamespace_Handle: BridgeType; readonly ExportNamespace_Level: BridgeType; readonly ExportMode: BridgeType; readonly ExportColor: BridgeType; readonly ExportTagged: BridgeType; readonly GenericFactory: BridgeType; }; export type LevelObject = typeof ExportNamespace.LevelValues; export type ExportModeObject = typeof ExportModeValues; @@ -99,6 +103,7 @@ export type Exports = { genericExportCombine(a: T, b: U, typeT: BridgeType, typeU: BridgeType): T; genericExportCombineReturnU(a: T, b: U, typeT: BridgeType, typeU: BridgeType): U; genericExportCaseDistinct(a: T, b: t, typeT: BridgeType, typet: BridgeType): T; + genericPickOrThrow(value: T, shouldThrow: boolean, typeT: BridgeType): T; ExportMode: ExportModeObject ExportColor: ExportColorObject ExportTagged: ExportTaggedObject @@ -125,6 +130,9 @@ export type Exports = { init(): GenericPair; wrap(value: T, typeT: BridgeType): T[]; }, + GenericThrowingBox: { + init(): GenericThrowingBox; + }, } export type Imports = { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js index 45aab11aa..6826bfb55 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericExports.js @@ -29,7 +29,7 @@ export const GenericFactoryValues = { Primary: 0, }; -export const BridgeTypes = { Bool: "Bool", Int: "Int", Int8: "Int8", UInt8: "UInt8", Int16: "Int16", UInt16: "UInt16", Int32: "Int32", UInt32: "UInt32", UInt: "UInt", Int64: "Int64", UInt64: "UInt64", Float: "Float", Double: "Double", String: "String", JSValue: "JSValue", ExportPoint: "ExportPoint", ExportNamespace_Metadata: "ExportNamespace_Metadata", GenericPair: "GenericPair", ExportBox: "ExportBox", GenericBox: "GenericBox", ExportGenericNamespace_Handle: "ExportGenericNamespace_Handle", ExportNamespace_Level: "ExportNamespace_Level", ExportMode: "ExportMode", ExportColor: "ExportColor", ExportTagged: "ExportTagged", GenericFactory: "GenericFactory" }; +export const BridgeTypes = { Bool: "Bool", Int: "Int", Int8: "Int8", UInt8: "UInt8", Int16: "Int16", UInt16: "UInt16", Int32: "Int32", UInt32: "UInt32", UInt: "UInt", Int64: "Int64", UInt64: "UInt64", Float: "Float", Double: "Double", String: "String", JSValue: "JSValue", ExportPoint: "ExportPoint", ExportNamespace_Metadata: "ExportNamespace_Metadata", GenericPair: "GenericPair", GenericThrowingBox: "GenericThrowingBox", ExportBox: "ExportBox", GenericBox: "GenericBox", ExportGenericNamespace_Handle: "ExportGenericNamespace_Handle", ExportNamespace_Level: "ExportNamespace_Level", ExportMode: "ExportMode", ExportColor: "ExportColor", ExportTagged: "ExportTagged", GenericFactory: "GenericFactory" }; export async function createInstantiator(options, swift) { let instance; let memory; @@ -443,6 +443,15 @@ export async function createInstantiator(options, swift) { return struct; }, }; + const __bjs_codec_M10TestModuleT18GenericThrowingBox = { + lower: (v) => { + structHelpers.M10TestModuleT18GenericThrowingBox.lower(v); + }, + lift: () => { + const struct = structHelpers.M10TestModuleT18GenericThrowingBox.lift(); + return struct; + }, + }; const __bjs_codec_M10TestModuleT9ExportBox = { lower: (v) => { ptrStack.push(v.pointer); @@ -579,6 +588,36 @@ export async function createInstantiator(options, swift) { return instance1; } }); + const __bjs_createStructHelpers_M10TestModuleT18GenericThrowingBox = () => ({ + lower: (value) => { + }, + lift: () => { + const instance1 = { }; + instance1.reject = function(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + structHelpers.M10TestModuleT18GenericThrowingBox.lower(this); + codecT.lower(value); + instance.exports.bjs_GenericThrowingBox_reject(typeIdT); + if (tmpRetException) { + const error = swift.memory.getObject(tmpRetException); + swift.memory.release(tmpRetException); + tmpRetException = undefined; + throw error; + } + return codecT.lift(); + }.bind(instance1); + instance1.passThrough = function(value, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + structHelpers.M10TestModuleT18GenericThrowingBox.lower(this); + codecT.lower(value); + instance.exports.bjs_GenericThrowingBox_passThrough(typeIdT); + return codecT.lift(); + }.bind(instance1); + return instance1; + } + }); const __bjs_createEnumHelpers_M10TestModuleT12ExportTagged = () => ({ lower: (value) => { const enumTag = value.tag; @@ -708,6 +747,13 @@ export async function createInstantiator(options, swift) { const value = structHelpers.M10TestModuleT11GenericPair.lift(); return swift.memory.retain(value); } + bjs["swift_js_struct_lower_GenericThrowingBox"] = function(objectId) { + structHelpers.M10TestModuleT18GenericThrowingBox.lower(swift.memory.getObject(objectId)); + } + bjs["swift_js_struct_lift_GenericThrowingBox"] = function() { + const value = structHelpers.M10TestModuleT18GenericThrowingBox.lift(); + return swift.memory.retain(value); + } bjs["bjs_core_register_type_handles"] = function(base, count) { const codecs = [ __bjs_primitiveCodecs.Bool, @@ -738,6 +784,7 @@ export async function createInstantiator(options, swift) { __bjs_codec_M10TestModuleT11ExportPoint, __bjs_codec_M10TestModuleT15ExportNamespaceT8Metadata, __bjs_codec_M10TestModuleT11GenericPair, + __bjs_codec_M10TestModuleT18GenericThrowingBox, __bjs_codec_M10TestModuleT9ExportBox, __bjs_codec_M10TestModuleT10GenericBox, __bjs_codec_T22ExportGenericNamespaceT6Handle, @@ -747,7 +794,7 @@ export async function createInstantiator(options, swift) { __bjs_codec_M10TestModuleT12ExportTagged, __bjs_codec_M10TestModuleT14GenericFactory, ]; - const tokens = ["ExportPoint", "ExportNamespace_Metadata", "GenericPair", "ExportBox", "GenericBox", "ExportGenericNamespace_Handle", "ExportNamespace_Level", "ExportMode", "ExportColor", "ExportTagged", "GenericFactory"]; + const tokens = ["ExportPoint", "ExportNamespace_Metadata", "GenericPair", "GenericThrowingBox", "ExportBox", "GenericBox", "ExportGenericNamespace_Handle", "ExportNamespace_Level", "ExportMode", "ExportColor", "ExportTagged", "GenericFactory"]; const typeIds = new Int32Array(memory.buffer, base >>> 0, count >>> 0); for (let i = 0; i < count; i++) { __bjs_codecByTypeId.set(typeIds[i], codecs[i]); @@ -1015,6 +1062,9 @@ export async function createInstantiator(options, swift) { const __bjs_helpers_M10TestModuleT11GenericPair = __bjs_createStructHelpers_M10TestModuleT11GenericPair(); structHelpers.M10TestModuleT11GenericPair = __bjs_helpers_M10TestModuleT11GenericPair; + const __bjs_helpers_M10TestModuleT18GenericThrowingBox = __bjs_createStructHelpers_M10TestModuleT18GenericThrowingBox(); + structHelpers.M10TestModuleT18GenericThrowingBox = __bjs_helpers_M10TestModuleT18GenericThrowingBox; + const __bjs_helpers_M10TestModuleT12ExportTagged = __bjs_createEnumHelpers_M10TestModuleT12ExportTagged(); enumHelpers.M10TestModuleT12ExportTagged = __bjs_helpers_M10TestModuleT12ExportTagged; @@ -1124,6 +1174,19 @@ export async function createInstantiator(options, swift) { instance.exports.bjs_genericExportCaseDistinct(typeIdT, typeIdt); return codecT.lift(); }, + genericPickOrThrow: function bjs_genericPickOrThrow(value, shouldThrow, typeT) { + const typeIdT = __bjs_typeIdForToken(typeT); + const codecT = __bjs_codecForTypeId(typeIdT); + codecT.lower(value); + instance.exports.bjs_genericPickOrThrow(shouldThrow, typeIdT); + if (tmpRetException) { + const error = swift.memory.getObject(tmpRetException); + swift.memory.release(tmpRetException); + tmpRetException = undefined; + throw error; + } + return codecT.lift(); + }, ExportMode: ExportModeValues, ExportColor: ExportColorValues, ExportTagged: ExportTaggedValues, @@ -1168,6 +1231,13 @@ export async function createInstantiator(options, swift) { return __bjs_arrayCodec(codecT).lift(); }, }, + GenericThrowingBox: { + init: function() { + instance.exports.bjs_GenericThrowingBox_init(); + const structValue = structHelpers.M10TestModuleT18GenericThrowingBox.lift(); + return structValue; + }, + }, }; _exports = exports; return exports; diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md index c320a3e1b..a7183e2d1 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Internals/Design-Rationale.md @@ -46,7 +46,7 @@ An exported generic `@JS` function reuses the same stack ABI, codec table, and h A generic parameter may compose `BridgedSwiftGenericBridgeable` with `@JS` protocols (``). The protocols contribute nothing at the ABI level — values still cross as the concrete `T` via the stack codecs — they exist so the Swift body (exports) or Swift call site (imports) can use the protocol requirements, and so the emitted `.d.ts` can declare `T extends GraphNode` against the generated protocol interfaces for compile-time checking in TypeScript. The `@JS protocol` machinery is reused purely for static knowledge of which protocols exist; the runtime witness plumbing that backs protocol-typed *values* is not involved. -On imports the Swift compiler proves the constraint at every call site, so the generated thunk merely repeats the user's constraint list. On exports the constraint must be re-checked at runtime because `BridgeType` tokens are erased in JavaScript. The primary check lives in the generated JS wrapper: the link layer knows every `@JS` type's declared protocol conformances from the skeletons, so the wrapper validates the token against the required protocols and throws a catchable `TypeError` before the call enters wasm — the shared value stack stays balanced, and the same mechanism will compose with throwing exports later. As defense-in-depth for raw wasm callers, the entry thunk additionally casts the recovered handle type to the composed existential metatype (`as? any (BridgedSwiftGenericBridgeable & GraphNode).Type`) before opening it and traps with a descriptive fatal error when the cast fails; through the generated wrapper this path is unreachable. `BridgedSwiftGenericBridgeable` must always appear in the composition: imports need it statically to lower and lift `T` at all, and exports keep the same spelling for symmetry. +On imports the Swift compiler proves the constraint at every call site, so the generated thunk merely repeats the user's constraint list. On exports the constraint must be re-checked at runtime because `BridgeType` tokens are erased in JavaScript. The primary check lives in the generated JS wrapper: the link layer knows every `@JS` type's declared protocol conformances from the skeletons, so the wrapper validates the token against the required protocols and throws a catchable `TypeError` before the call enters wasm — the shared value stack stays balanced, which is also what lets a throwing generic export rethrow from the wrapper before anything is lifted. As defense-in-depth for raw wasm callers, the entry thunk additionally casts the recovered handle type to the composed existential metatype (`as? any (BridgedSwiftGenericBridgeable & GraphNode).Type`) before opening it and traps with a descriptive fatal error when the cast fails; through the generated wrapper this path is unreachable. `BridgedSwiftGenericBridgeable` must be reachable from the composition, directly or through a refining `@JS` protocol: imports need it statically to lower and lift `T` at all, and exports keep the same rule for symmetry. ## What to read next diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md index 836789610..51730dfde 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md @@ -245,7 +245,27 @@ The generic parameter may also be wrapped as `[T]`, `T?`, or `[String: T]` in pa } ``` -Every declared generic parameter must be used in at least one parameter or the return type; a fully unused generic parameter is rejected. The result must be one of the declared generic parameters (such as `T` or `U`), a supported wrapper of one (`[T]`, `T?`, `[String: T]`), or `Void` — returning a concrete non-`Void` type from a generic `@JS` function is not supported. Generic `@JS` functions must be synchronous (see ). +Every declared generic parameter must be used in at least one parameter or the return type; a fully unused generic parameter is rejected. The result must be one of the declared generic parameters (such as `T` or `U`), a supported wrapper of one (`[T]`, `T?`, `[String: T]`), or `Void` — returning a concrete non-`Void` type from a generic `@JS` function is not supported. Generic `@JS` functions may be `throws(JSException)` but not `async` (see ). + +A throwing generic function follows the same error convention as any other throwing export — the exception surfaces as a catchable JavaScript error, thrown by the generated wrapper before any result is read back: + +```swift +@JS public func pickOrThrow(_ value: T, _ fail: Bool) throws(JSException) -> T { + if fail { + throw JSException(JSError(message: "pick failed").jsValue) + } + return value +} +``` + +```javascript +exports.pickOrThrow(42, false, BridgeTypes.Int); // 42 +try { + exports.pickOrThrow(42, true, BridgeTypes.Int); +} catch (error) { + console.log(error.message); // "pick failed" +} +``` #### Constraining a generic parameter to `@JS` protocols diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md index 6a5149573..d8499a7bb 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Unsupported-Features.md @@ -58,12 +58,11 @@ Types defined in a separate Swift package cannot yet be referenced from `@JS` de ## Generics -Generic functions are supported in both directions, through a type parameter constrained to `BridgedSwiftGenericBridgeable`: an imported `@JSFunction` (see ) and an exported `@JS` function (see ). Generics also work on methods of an exported `@JS` class or struct (both instance and static), on static methods of an exported `@JS` enum or namespace enum, and on imported `@JSClass` methods. A function may declare one or more distinct generic parameters, such as `combine(_ a: T, _ b: U) -> T`, and a return-only generic (such as `load(_ key: String) -> T?`) is supported in both directions. A generic parameter may additionally be constrained to one or more `@JS` protocols by composing them with `BridgedSwiftGenericBridgeable`, such as ``. The following forms are not supported and produce build-time diagnostics: +Generic functions are supported in both directions, through a type parameter constrained to `BridgedSwiftGenericBridgeable`: an imported `@JSFunction` (see ) and an exported `@JS` function (see ). Generics also work on methods of an exported `@JS` class or struct (both instance and static), on static methods of an exported `@JS` enum or namespace enum, and on imported `@JSClass` methods. A function may declare one or more distinct generic parameters, such as `combine(_ a: T, _ b: U) -> T`, and a return-only generic (such as `load(_ key: String) -> T?`) is supported in both directions. A generic parameter may additionally be constrained to one or more `@JS` protocols by composing them with `BridgedSwiftGenericBridgeable`, such as ``; a conformer of a refined protocol (`@JS protocol Refined: Base`) satisfies constraints on the protocols it inherits from. Generic declarations may be `throws(JSException)` in both directions. The following forms are not supported and produce build-time diagnostics: -- `async` generic functions. +- `async` generic functions. (`throws(JSException)` is supported; plain `throws` is unsupported on every export, generic or not.) - `where` clauses on a generic declaration. - A generic parameter constrained to a bare protocol without `BridgedSwiftGenericBridgeable` in the composition (such as ``), or composed with a protocol that is not a `@JS protocol` (such as `Comparable`). -- An exported `@JS` generic function that is `throws`. (Imported `@JSFunction` generics may still use `throws(JSException)`.) - An exported generic function with a declared generic parameter that is not used in any parameter or the return type. - A generic initializer on an exported `@JS` class or struct. Use a generic `@JS` method, or a generic `@JSFunction` initializer on an imported `@JSClass`. - Default parameter values on a generic exported `@JS` function. The trailing `BridgeType` token is always required, so a defaulted parameter could never be omitted. diff --git a/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift b/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift index d053e7df2..745f79cdf 100644 --- a/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift +++ b/Tests/BridgeJSRuntimeTests/ExportGenericAPIs.swift @@ -222,6 +222,18 @@ public func exportGenericSaveNode< @JS public func lastStoredNodeID() -> String { _lastStoredNodeID } +// Throwing generic exports: the exception crosses through the side channel and +// the JS wrapper rethrows it before lifting anything from the value stack. +@JS public func exportGenericThrowOrRoundTrip( + _ shouldThrow: Bool, + _ value: T +) throws(JSException) -> T { + if shouldThrow { + throw JSException(JSError(message: "ExportTestError").jsValue) + } + return value +} + // Protocol refinement: a conformer of the refined protocol must satisfy a // constraint on the base protocol, both in Swift and in the JS-side token check. @JS public protocol ExportGenericSite: ExportGenericGraphNode { diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index bdfd606f2..9d4817675 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -12555,6 +12555,45 @@ public func _bjs_lastStoredNodeID() -> Void { #endif } +#if hasFeature(Embedded) +@_expose(wasm, "bjs_exportGenericThrowOrRoundTrip") +@_cdecl("bjs_exportGenericThrowOrRoundTrip") +public func _bjs_exportGenericThrowOrRoundTrip(_ shouldThrow: Int32, _ _generic0TypeId: Int32) -> Void { + fatalError("Generic @JS exported functions are not supported in Embedded Swift") +} +#else +@_expose(wasm, "bjs_exportGenericThrowOrRoundTrip") +@_cdecl("bjs_exportGenericThrowOrRoundTrip") +public func _bjs_exportGenericThrowOrRoundTrip(_ shouldThrow: Int32, _ _generic0TypeId: Int32) -> Void { + #if arch(wasm32) + let _generic0Type = Unmanaged.fromOpaque(UnsafeRawPointer(bitPattern: UInt(UInt32(bitPattern: _generic0TypeId)))!).takeUnretainedValue().type + do { + try _bjs_exportGenericThrowOrRoundTrip_open1(_generic0Type, shouldThrow) + } catch let error { + if let error = error.thrownValue.object { + withExtendedLifetime(error) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } else { + let jsError = JSError(message: error.description) + withExtendedLifetime(jsError.jsObject) { + _swift_js_throw(Int32(bitPattern: $0.id)) + } + } + return + } + #else + fatalError("Only available on WebAssembly") + #endif +} +private func _bjs_exportGenericThrowOrRoundTrip_open1(_ _generic0Type: T.Type, _ shouldThrow: Int32) throws(JSException) { + let value = T.bridgeJSStackPop() + let shouldThrow = Bool.bridgeJSLiftParameter(shouldThrow) + let ret: T = try exportGenericThrowOrRoundTrip(_: shouldThrow, _: value) + ret.bridgeJSStackPush() +} +#endif + @_expose(wasm, "bjs_roundTripMessage") @_cdecl("bjs_roundTripMessage") public func _bjs_roundTripMessage(_ message: Int32) -> Void { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 49e98c374..f08895bcf 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -18852,6 +18852,45 @@ } } }, + { + "abiName" : "bjs_exportGenericThrowOrRoundTrip", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "genericParameters" : [ + { + "name" : "T" + } + ], + "name" : "exportGenericThrowOrRoundTrip", + "parameters" : [ + { + "label" : "_", + "name" : "shouldThrow", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "_", + "name" : "value", + "type" : { + "generic" : { + "_0" : "T" + } + } + } + ], + "returnType" : { + "generic" : { + "_0" : "T" + } + } + }, { "abiName" : "bjs_roundTripMessage", "effects" : { diff --git a/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs b/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs index c371b5134..5fc69910b 100644 --- a/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs +++ b/Tests/BridgeJSRuntimeTests/JavaScript/ExportGenericTests.mjs @@ -257,4 +257,33 @@ export function runExportGenericTests(exports) { ); assert.equal(campus.region, "north"); assert.equal(exports.lastStoredNodeID(), "campus-1"); + + // Throwing generic exports: the Swift exception surfaces as a catchable JS + // error from the wrapper, thrown before any lift runs. + assert.equal( + exports.exportGenericThrowOrRoundTrip(false, 42, BridgeTypes.Int), + 42 + ); + assert.equal( + exports.exportGenericThrowOrRoundTrip( + false, + { x: 5, y: 6 }, + BridgeTypes.ExportGenericPoint + ).y, + 6 + ); + assert.throws( + () => exports.exportGenericThrowOrRoundTrip(true, 7, BridgeTypes.Int), + /ExportTestError/ + ); + // A thrown call must not strand values on the shared stacks: the next + // generic call has to read its own arguments and result back. + assert.equal( + exports.exportGenericThrowOrRoundTrip( + false, + { x: 8, y: 9 }, + BridgeTypes.ExportGenericPoint + ).x, + 8 + ); } From d8c198af90fea35b5cd08cb19f15a8b63e0ae984 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Mon, 28 Sep 2026 21:12:15 +0200 Subject: [PATCH 5/5] BridgeJS: Recognize cross-module extension conformances to @JS protocols A module may declare that a @JS type exported by one of its dependencies conforms to a @JS protocol it defines: // ModuleB, depending on ModuleA @JS protocol GraphNode { var id: String { get } } extension Building: GraphNode {} // Building is ModuleA's @JS struct @JS public func store(_ n: T) -> T { n } The Swift compiler sees the retroactive conformance directly, so the generic thunk already accepted ModuleA's type - but the JS-side token check did not: the conformance was declared in ModuleB's sources while the token belongs to ModuleA, and neither skeleton recorded it, so a valid call threw a spurious TypeError. Extension conformances whose target does not resolve in-module are now resolved against the external module index and recorded in the skeleton (externalJSProtocolConformances, keyed by the conformer's Swift dot path, e.g. `Models.Site`). The link layer sees every module's skeleton, maps that dot path to the defining module's token (`Models_Site`), merges the record into that token's entry, and the refinement closure applies on top. --- .../BridgeJSCore/SwiftToSkeleton.swift | 63 ++++++++++++++++--- .../Sources/BridgeJSLink/BridgeJSLink.swift | 28 ++++++++- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 7 +++ .../GenericExportDiagnosticsTests.swift | 42 +++++++++++++ .../Exporting-Swift-Function.md | 17 +++++ 5 files changed, 147 insertions(+), 10 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift index 06050c6ef..feedd6ae6 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift @@ -428,16 +428,25 @@ public final class SwiftToSkeleton { /// style, and the JS-side generic constraint check /// (`__bjs_tokenConformances`) must not reject a type that genuinely /// conforms. + /// + /// An extension may also target a `@JS` type exported by a *dependency* + /// module. The Swift compiler sees that retroactive conformance directly, + /// so the generic thunk accepts the type; those conformances are recorded + /// in `externalJSProtocolConformances` (keyed by the type's Swift dot + /// path) for the link layer to merge into the defining module's token + /// entry. private func mergeExtensionDeclaredJSProtocolConformances(into exported: inout ExportedSkeleton) { - var protocolsByTarget: [String: [String]] = [:] + var targets: [(name: String, type: TypeSyntax, protocols: [String])] = [] for (sourceFile, _) in sourceFiles { let collector = ExtensionConformanceCollector(parent: self) collector.walk(sourceFile) - for (target, protocols) in collector.protocolsByTarget { - protocolsByTarget[target, default: []].append(contentsOf: protocols) - } + targets.append(contentsOf: collector.targets) + } + guard !targets.isEmpty else { return } + var protocolsByTarget: [String: [String]] = [:] + for target in targets { + protocolsByTarget[target.name, default: []].append(contentsOf: target.protocols) } - guard !protocolsByTarget.isEmpty else { return } func merge(_ existing: inout [String]?, additions: [String]) { var result = existing ?? [] @@ -447,21 +456,54 @@ public final class SwiftToSkeleton { existing = result.isEmpty ? nil : result } + var matchedTargets: Set = [] for index in exported.structs.indices { if let additions = protocolsByTarget[exported.structs[index].swiftCallName] { merge(&exported.structs[index].conformedJSProtocols, additions: additions) + matchedTargets.insert(exported.structs[index].swiftCallName) } } for index in exported.classes.indices { if let additions = protocolsByTarget[exported.classes[index].swiftCallName] { merge(&exported.classes[index].conformedJSProtocols, additions: additions) + matchedTargets.insert(exported.classes[index].swiftCallName) } } for index in exported.enums.indices { if let additions = protocolsByTarget[exported.enums[index].swiftCallName] { merge(&exported.enums[index].conformedJSProtocols, additions: additions) + matchedTargets.insert(exported.enums[index].swiftCallName) + } + } + + // Whatever did not match an in-module type may be a bridged type from a + // dependency module; record the conformance under the dependency type's + // Swift dot path for the link layer to resolve to its token. + var externalConformances: [String: [String]] = [:] + for target in targets where !matchedTargets.contains(target.name) { + var scratchErrors: [DiagnosticError] = [] + guard let externalType = resolveExternal(for: target.type, errors: &scratchErrors) else { continue } + // Key by the dependency type's Swift dot path (its `swiftCallName`); + // the link layer resolves that to the type's token, which for a + // namespaced type (`Models.Building` -> `Models_Building`) is not + // the last name component. + let externalName: String? + switch externalType { + case .swiftStruct(let name), .swiftHeapObject(let name), + .caseEnum(let name), .rawValueEnum(let name, _), .associatedValueEnum(let name): + externalName = name + default: + externalName = nil + } + guard let externalName else { continue } + for protocolName in target.protocols + where !(externalConformances[externalName] ?? []).contains(protocolName) { + externalConformances[externalName, default: []].append(protocolName) } } + if !externalConformances.isEmpty { + exported.externalJSProtocolConformances = externalConformances + } } private static let jsTypedArrayTypealiasNames: [String: String] = [ @@ -4137,7 +4179,7 @@ extension GenericArgumentListSyntax { /// matches the skeleton's `swiftCallName` for both top-level and nested types. private final class ExtensionConformanceCollector: SyntaxVisitor { private let parent: SwiftToSkeleton - var protocolsByTarget: [String: [String]] = [:] + var targets: [(name: String, type: TypeSyntax, protocols: [String])] = [] init(parent: SwiftToSkeleton) { self.parent = parent @@ -4146,12 +4188,17 @@ private final class ExtensionConformanceCollector: SyntaxVisitor { override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { guard let inheritanceClause = node.inheritanceClause else { return .visitChildren } - let target = node.extendedType.trimmedDescription + var protocols: [String] = [] for inherited in inheritanceClause.inheritedTypes { if let name = parent.resolveJSProtocolConstraint(for: inherited.type) { - protocolsByTarget[target, default: []].append(name) + protocols.append(name) } } + if !protocols.isEmpty { + targets.append( + (name: node.extendedType.trimmedDescription, type: node.extendedType, protocols: protocols) + ) + } return .visitChildren } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index c64993ca8..89ab3ce3e 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -517,12 +517,32 @@ public struct BridgeJSLink { /// Swift, so the JS-side check must agree. private func generateTokenConformancesDeclaration() -> String { var inheritedByProtocol: [String: [String]] = [:] + // Every bridgeable type's token, keyed by its Swift dot path: external + // conformance records name the type by dot path (the only spelling the + // declaring module knows), and only the defining module's skeleton can + // turn that into the token (`Models.Building` -> `Models_Building`). + var tokenBySwiftCallName: [String: String] = [:] + for unified in skeletons { + guard let exported = unified.exported else { continue } + for structDef in exported.structs { tokenBySwiftCallName[structDef.swiftCallName] = structDef.abiName } + for klass in exported.classes { tokenBySwiftCallName[klass.swiftCallName] = klass.abiName } + for enumDef in exported.enums { tokenBySwiftCallName[enumDef.swiftCallName] = enumDef.abiName } + } + // Conformances a module declares through extensions on another + // module's @JS types, keyed by the conformer's token. + var externalByToken: [String: [String]] = [:] for unified in skeletons { for protocolDef in unified.exported?.protocols ?? [] { if let inherited = protocolDef.inheritedJSProtocols, !inherited.isEmpty { inheritedByProtocol[protocolDef.name] = inherited } } + for (swiftCallName, protocols) in unified.exported?.externalJSProtocolConformances ?? [:] { + guard let token = tokenBySwiftCallName[swiftCallName] else { continue } + for protocolName in protocols where !(externalByToken[token] ?? []).contains(protocolName) { + externalByToken[token, default: []].append(protocolName) + } + } } func transitiveClosure(of protocols: [String]) -> [String] { var seen: [String] = [] @@ -536,8 +556,12 @@ public struct BridgeJSLink { } var entries: [String] = [] func append(token: String, protocols: [String]?) { - guard let protocols, !protocols.isEmpty else { return } - let list = transitiveClosure(of: protocols).map { "\"\($0)\"" }.joined(separator: ", ") + var combined = protocols ?? [] + for protocolName in externalByToken[token] ?? [] where !combined.contains(protocolName) { + combined.append(protocolName) + } + guard !combined.isEmpty else { return } + let list = transitiveClosure(of: combined).map { "\"\($0)\"" }.joined(separator: ", ") entries.append("\"\(token)\": [\(list)]") } for unified in skeletons { diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 54ad43e6f..812dc5ced 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -1304,6 +1304,13 @@ public struct ExportedSkeleton: Codable { /// When `"none"` or `nil`, no identity tracking is performed. public var identityMode: String? + /// `@JS protocol` conformances this module declares through extensions on + /// `@JS` types exported by *other* modules, keyed by the conformer's Swift + /// dot path (`swiftCallName`, e.g. `Models.Building`). The Swift compiler + /// sees the retroactive conformance directly; this record lets the link + /// layer teach the JS-side token conformance check about it too. + public var externalJSProtocolConformances: [String: [String]]? + public init( functions: [ExportedFunction], classes: [ExportedClass], diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift index e183538aa..63bb76a27 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift @@ -166,6 +166,48 @@ import Testing } } + @Test + func crossModuleExtensionConformanceReachesTokenConformances() throws { + // A conformance declared in this module on a @JS type exported by a + // dependency is a real retroactive conformance in Swift, so the + // JS-side token check must accept the dependency type's token. + let moduleA = try makeSkeleton( + """ + @JS public struct Building { + public var id: String + @JS public init(id: String) { self.id = id } + } + @JS(namespace: "Models") public struct Site { + public var id: String + @JS public init(id: String) { self.id = id } + } + """, + moduleName: "ModuleA" + ) + let moduleB = try makeSkeleton( + """ + @JS protocol GraphNode { var id: String { get } } + extension Building: GraphNode {} + // `Models` is a JS namespace, not a Swift scope: the Swift type is + // `Site`, but its token is the ABI name `Models_Site`. + extension Site: GraphNode {} + @JS public func store(_ n: T) -> T { n } + """, + moduleName: "ModuleB", + dependencies: [(moduleName: "ModuleA", skeleton: moduleA)] + ) + var link = BridgeJSLink() + let encoder = JSONEncoder() + try link.addSkeletonFile(data: encoder.encode(moduleA)) + try link.addSkeletonFile(data: encoder.encode(moduleB)) + let js = try link.link().outputJs + #expect( + js.contains( + #"const __bjs_tokenConformances = { "Building": ["GraphNode"], "Models_Site": ["GraphNode"] };"# + ) + ) + } + @Test func compositionConstraintIsParsed() throws { let skeleton = try makeSkeleton( diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md index 51730dfde..21ae669ee 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md @@ -297,6 +297,23 @@ export type Exports = { TypeScript checks the constraint at compile time, but `BridgeType` tokens are erased at runtime, so the generated JavaScript wrapper enforces it again when the call crosses the bridge: the token's type is validated against the required protocols and a non-conforming token throws a catchable `TypeError` *before* the call enters WebAssembly, e.g. `BridgeJS: type 'Int' does not conform to required protocol 'GraphNode'`. Because nothing has been lowered yet, the shared value stack stays balanced and later calls are unaffected. (The Swift entry thunk repeats the check with a conditional metatype cast as defense-in-depth, but through the generated wrapper that trap is unreachable.) +Conformances count no matter how they are declared: on the type itself, in an extension in the same module, or in an extension on a `@JS` type exported by a dependency module (a retroactive conformance). Protocol refinement is honored too — a conformer of `@JS protocol Refined: Base` satisfies a constraint on `Base`, exactly as it does in Swift (see ): + +```swift +@JS protocol Site: GraphNode { // Site refines GraphNode + var region: String { get } +} + +@JS struct Campus: Site { // conforms to Site, and therefore GraphNode + var id: String + var region: String +} +``` + +```javascript +exports.store({ id: "campus-1", region: "north" }, BridgeTypes.Campus); // satisfies T: GraphNode +``` + Generics also work on methods. On a `@JS` class or struct they apply to both instance and static methods. A `@JS enum` (including a namespace-style enum) has no instance methods in BridgeJS, so generics there apply to static methods only. The constraint, the trailing `BridgeType` token, and the generic-or-`Void` return rule all carry over unchanged. ## Supported Features