From 82048d541089973fb48963a0df3d8563ea343101 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 15 Aug 2025 21:16:43 +0900 Subject: [PATCH 01/61] ./Utilities/format.swift with DEVELOPMENT-SNAPSHOT-2025-08-04-a --- .../JavaScriptEventLoop+LegacyHooks.swift | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift index bcab9a3d..9353cf34 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift @@ -7,9 +7,10 @@ extension JavaScriptEventLoop { static func installByLegacyHook() { #if compiler(>=5.9) - typealias swift_task_asyncMainDrainQueue_hook_Fn = @convention(thin) ( - swift_task_asyncMainDrainQueue_original, swift_task_asyncMainDrainQueue_override - ) -> Void + typealias swift_task_asyncMainDrainQueue_hook_Fn = + @convention(thin) ( + swift_task_asyncMainDrainQueue_original, swift_task_asyncMainDrainQueue_override + ) -> Void let swift_task_asyncMainDrainQueue_hook_impl: swift_task_asyncMainDrainQueue_hook_Fn = { _, _ in swjs_unsafe_event_loop_yield() } @@ -19,7 +20,8 @@ extension JavaScriptEventLoop { ) #endif - typealias swift_task_enqueueGlobal_hook_Fn = @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) + typealias swift_task_enqueueGlobal_hook_Fn = + @convention(thin) (UnownedJob, swift_task_enqueueGlobal_original) -> Void let swift_task_enqueueGlobal_hook_impl: swift_task_enqueueGlobal_hook_Fn = { job, original in JavaScriptEventLoop.shared.unsafeEnqueue(job) @@ -29,9 +31,10 @@ extension JavaScriptEventLoop { to: UnsafeMutableRawPointer?.self ) - typealias swift_task_enqueueGlobalWithDelay_hook_Fn = @convention(thin) ( - UInt64, UnownedJob, swift_task_enqueueGlobalWithDelay_original - ) -> Void + typealias swift_task_enqueueGlobalWithDelay_hook_Fn = + @convention(thin) ( + UInt64, UnownedJob, swift_task_enqueueGlobalWithDelay_original + ) -> Void let swift_task_enqueueGlobalWithDelay_hook_impl: swift_task_enqueueGlobalWithDelay_hook_Fn = { nanoseconds, job, @@ -45,9 +48,10 @@ extension JavaScriptEventLoop { ) #if compiler(>=5.7) - typealias swift_task_enqueueGlobalWithDeadline_hook_Fn = @convention(thin) ( - Int64, Int64, Int64, Int64, Int32, UnownedJob, swift_task_enqueueGlobalWithDelay_original - ) -> Void + typealias swift_task_enqueueGlobalWithDeadline_hook_Fn = + @convention(thin) ( + Int64, Int64, Int64, Int64, Int32, UnownedJob, swift_task_enqueueGlobalWithDelay_original + ) -> Void let swift_task_enqueueGlobalWithDeadline_hook_impl: swift_task_enqueueGlobalWithDeadline_hook_Fn = { sec, nsec, @@ -64,9 +68,10 @@ extension JavaScriptEventLoop { ) #endif - typealias swift_task_enqueueMainExecutor_hook_Fn = @convention(thin) ( - UnownedJob, swift_task_enqueueMainExecutor_original - ) -> Void + typealias swift_task_enqueueMainExecutor_hook_Fn = + @convention(thin) ( + UnownedJob, swift_task_enqueueMainExecutor_original + ) -> Void let swift_task_enqueueMainExecutor_hook_impl: swift_task_enqueueMainExecutor_hook_Fn = { job, original in JavaScriptEventLoop.shared.unsafeEnqueue(job) } From f5d74de764453f76166d4b77fac186587b7ff642 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 15 Aug 2025 22:04:23 +0900 Subject: [PATCH 02/61] BridgeJS: Improve Xcode editing experience by removing symlinks Xcode does not handle symlinks well, especially when showing errors in the editor. This commit removes the symlinks in the BridgeJS plugin package and use SwiftPM's native target structure. We still use symlinks for the actual BridgeJSTool executable target in the main package as a build-time optimization, but it should not affect the Xcode editing experience on the main package. --- Package.swift | 1 - Plugins/BridgeJS/Package.swift | 42 ++++- .../BridgeJSCore/BridgeJSCoreError.swift | 6 +- .../Sources/BridgeJSCore/ExportSwift.swift | 11 +- .../Sources/BridgeJSCore/ImportTS.swift | 15 +- .../BridgeJSCore/ProgressReporting.swift | 8 +- .../Sources/BridgeJSLink/BridgeJSLink.swift | 3 + .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 172 ++++++++++++------ .../Sources/BridgeJSTool/BridgeJSCore | 1 - .../Sources/BridgeJSTool/BridgeJSSkeleton | 1 - .../Sources/BridgeJSTool/BridgeJSTool.swift | 10 + .../BridgeJS/Sources/BridgeJSTool/TS2Skeleton | 1 - .../Sources/TS2Skeleton/TS2Skeleton.swift | 9 +- .../Tests/BridgeJSToolTests/BridgeJSCore | 1 - .../Tests/BridgeJSToolTests/BridgeJSLink | 1 - .../BridgeJSToolTests/BridgeJSLinkTests.swift | 2 + .../Tests/BridgeJSToolTests/BridgeJSSkeleton | 1 - .../BridgeJSToolTests/ExportSwiftTests.swift | 2 + .../BridgeJSToolTests/ImportTSTests.swift | 2 + .../Tests/BridgeJSToolTests/TS2Skeleton | 1 - Sources/BridgeJSTool/BridgeJSCore | 1 + Sources/BridgeJSTool/BridgeJSSkeleton | 1 + Sources/BridgeJSTool/BridgeJSTool | 1 + Sources/BridgeJSTool/README.md | 9 + Sources/BridgeJSTool/TS2Skeleton | 1 + 25 files changed, 210 insertions(+), 93 deletions(-) delete mode 120000 Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSCore delete mode 120000 Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSSkeleton delete mode 120000 Plugins/BridgeJS/Sources/BridgeJSTool/TS2Skeleton delete mode 120000 Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCore delete mode 120000 Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLink delete mode 120000 Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSSkeleton delete mode 120000 Plugins/BridgeJS/Tests/BridgeJSToolTests/TS2Skeleton create mode 120000 Sources/BridgeJSTool/BridgeJSCore create mode 120000 Sources/BridgeJSTool/BridgeJSSkeleton create mode 120000 Sources/BridgeJSTool/BridgeJSTool create mode 100644 Sources/BridgeJSTool/README.md create mode 120000 Sources/BridgeJSTool/TS2Skeleton diff --git a/Package.swift b/Package.swift index 435ae1a1..4bd0ca45 100644 --- a/Package.swift +++ b/Package.swift @@ -151,7 +151,6 @@ let package = Package( .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ], - path: "Plugins/BridgeJS/Sources/BridgeJSTool", exclude: ["TS2Skeleton/JavaScript"] ), .testTarget( diff --git a/Plugins/BridgeJS/Package.swift b/Plugins/BridgeJS/Package.swift index f7241d86..b9cd907c 100644 --- a/Plugins/BridgeJS/Package.swift +++ b/Plugins/BridgeJS/Package.swift @@ -2,13 +2,6 @@ import PackageDescription -let coreDependencies: [Target.Dependency] = [ - .product(name: "SwiftParser", package: "swift-syntax"), - .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftBasicFormat", package: "swift-syntax"), - .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), -] - let package = Package( name: "BridgeJS", platforms: [.macOS(.v13)], @@ -19,11 +12,42 @@ let package = Package( .target(name: "BridgeJSBuildPlugin"), .executableTarget( name: "BridgeJSTool", - dependencies: coreDependencies + dependencies: [ + "BridgeJSCore", + "TS2Skeleton", + ] + ), + .target( + name: "TS2Skeleton", + dependencies: [ + "BridgeJSCore", + "BridgeJSSkeleton", + ] + ), + .target( + name: "BridgeJSCore", + dependencies: [ + "BridgeJSSkeleton", + .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftBasicFormat", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + ] ), + .target(name: "BridgeJSSkeleton"), + + .target( + name: "BridgeJSLink", + dependencies: ["BridgeJSSkeleton"] + ), + .testTarget( name: "BridgeJSToolTests", - dependencies: coreDependencies, + dependencies: [ + "BridgeJSCore", + "BridgeJSLink", + "TS2Skeleton", + ], exclude: ["__Snapshots__", "Inputs"] ), ] diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift index 6e313754..9cbec438 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSCoreError.swift @@ -1,7 +1,7 @@ -struct BridgeJSCoreError: Swift.Error, CustomStringConvertible { - let description: String +public struct BridgeJSCoreError: Swift.Error, CustomStringConvertible { + public let description: String - init(_ message: String) { + public init(_ message: String) { self.description = message } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index dfe161e9..b8b7d603 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -1,6 +1,9 @@ import SwiftBasicFormat import SwiftSyntax import SwiftSyntaxBuilder +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif /// Exports Swift functions and classes to JavaScript /// @@ -11,14 +14,14 @@ import SwiftSyntaxBuilder /// /// The generated skeletons will be used by ``BridgeJSLink`` to generate /// JavaScript glue code and TypeScript definitions. -class ExportSwift { +public class ExportSwift { let progress: ProgressReporting private var exportedFunctions: [ExportedFunction] = [] private var exportedClasses: [ExportedClass] = [] private var typeDeclResolver: TypeDeclResolver = TypeDeclResolver() - init(progress: ProgressReporting) { + public init(progress: ProgressReporting) { self.progress = progress } @@ -27,7 +30,7 @@ class ExportSwift { /// - Parameters: /// - sourceFile: The parsed Swift source file to process /// - inputFilePath: The file path for error reporting - func addSourceFile(_ sourceFile: SourceFileSyntax, _ inputFilePath: String) throws { + public func addSourceFile(_ sourceFile: SourceFileSyntax, _ inputFilePath: String) throws { progress.print("Processing \(inputFilePath)") typeDeclResolver.addSourceFile(sourceFile) @@ -44,7 +47,7 @@ class ExportSwift { /// /// - Returns: A tuple containing the generated Swift code and a skeleton /// describing the exported APIs - func finalize() throws -> (outputSwift: String, outputSkeleton: ExportedSkeleton)? { + public func finalize() throws -> (outputSwift: String, outputSkeleton: ExportedSkeleton)? { guard let outputSwift = renderSwiftGlue() else { return nil } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 37181114..c7966a84 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -1,6 +1,9 @@ import SwiftBasicFormat import SwiftSyntax import SwiftSyntaxBuilder +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif /// Imports TypeScript declarations and generates Swift bridge code /// @@ -10,25 +13,25 @@ import SwiftSyntaxBuilder /// /// The generated skeletons will be used by ``BridgeJSLink`` to generate /// JavaScript glue code and TypeScript definitions. -struct ImportTS { - let progress: ProgressReporting - private(set) var skeleton: ImportedModuleSkeleton +public struct ImportTS { + public let progress: ProgressReporting + public private(set) var skeleton: ImportedModuleSkeleton private var moduleName: String { skeleton.moduleName } - init(progress: ProgressReporting, moduleName: String) { + public init(progress: ProgressReporting, moduleName: String) { self.progress = progress self.skeleton = ImportedModuleSkeleton(moduleName: moduleName, children: []) } /// Adds a skeleton to the importer's state - mutating func addSkeleton(_ skeleton: ImportedFileSkeleton) { + public mutating func addSkeleton(_ skeleton: ImportedFileSkeleton) { self.skeleton.children.append(skeleton) } /// Finalizes the import process and generates Swift code - func finalize() throws -> String? { + public func finalize() throws -> String? { var decls: [DeclSyntax] = [] for skeleton in self.skeleton.children { for function in skeleton.functions { diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ProgressReporting.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ProgressReporting.swift index 4e92a198..d1a2aa6d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ProgressReporting.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ProgressReporting.swift @@ -1,7 +1,7 @@ -struct ProgressReporting { +public struct ProgressReporting { let print: (String) -> Void - init(verbose: Bool) { + public init(verbose: Bool) { self.init(print: verbose ? { Swift.print($0) } : { _ in }) } @@ -9,11 +9,11 @@ struct ProgressReporting { self.print = print } - static var silent: ProgressReporting { + public static var silent: ProgressReporting { return ProgressReporting(print: { _ in }) } - func print(_ message: String) { + public func print(_ message: String) { self.print(message) } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 022c5cbb..6693c815 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -1,5 +1,8 @@ import class Foundation.JSONDecoder import struct Foundation.Data +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif struct BridgeJSLink { /// The exported skeletons diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 56e88f92..a0a86003 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -2,111 +2,167 @@ // MARK: - Types -enum BridgeType: Codable, Equatable { +public enum BridgeType: Codable, Equatable { case int, float, double, string, bool, jsObject(String?), swiftHeapObject(String), void } -enum WasmCoreType: String, Codable { +public enum WasmCoreType: String, Codable { case i32, i64, f32, f64, pointer } -struct Parameter: Codable { - let label: String? - let name: String - let type: BridgeType +public struct Parameter: Codable { + public let label: String? + public let name: String + public let type: BridgeType + + public init(label: String?, name: String, type: BridgeType) { + self.label = label + self.name = name + self.type = type + } } -struct Effects: Codable { - var isAsync: Bool - var isThrows: Bool +public struct Effects: Codable { + public var isAsync: Bool + public var isThrows: Bool + + public init(isAsync: Bool, isThrows: Bool) { + self.isAsync = isAsync + self.isThrows = isThrows + } } // MARK: - Exported Skeleton -struct ExportedFunction: Codable { - var name: String - var abiName: String - var parameters: [Parameter] - var returnType: BridgeType - var effects: Effects - var namespace: [String]? +public struct ExportedFunction: Codable { + public var name: String + public var abiName: String + public var parameters: [Parameter] + public var returnType: BridgeType + public var effects: Effects + public var namespace: [String]? + + public init( + name: String, + abiName: String, + parameters: [Parameter], + returnType: BridgeType, + effects: Effects, + namespace: [String]? = nil + ) { + self.name = name + self.abiName = abiName + self.parameters = parameters + self.returnType = returnType + self.effects = effects + self.namespace = namespace + } } -struct ExportedClass: Codable { - var name: String - var constructor: ExportedConstructor? - var methods: [ExportedFunction] - var namespace: [String]? +public struct ExportedClass: Codable { + public var name: String + public var constructor: ExportedConstructor? + public var methods: [ExportedFunction] + public var namespace: [String]? + + public init( + name: String, + constructor: ExportedConstructor? = nil, + methods: [ExportedFunction], + namespace: [String]? = nil + ) { + self.name = name + self.constructor = constructor + self.methods = methods + self.namespace = namespace + } } -struct ExportedConstructor: Codable { - var abiName: String - var parameters: [Parameter] - var effects: Effects - var namespace: [String]? +public struct ExportedConstructor: Codable { + public var abiName: String + public var parameters: [Parameter] + public var effects: Effects + public var namespace: [String]? + + public init(abiName: String, parameters: [Parameter], effects: Effects, namespace: [String]? = nil) { + self.abiName = abiName + self.parameters = parameters + self.effects = effects + self.namespace = namespace + } } -struct ExportedSkeleton: Codable { - let functions: [ExportedFunction] - let classes: [ExportedClass] +public struct ExportedSkeleton: Codable { + public let functions: [ExportedFunction] + public let classes: [ExportedClass] + + public init(functions: [ExportedFunction], classes: [ExportedClass]) { + self.functions = functions + self.classes = classes + } } // MARK: - Imported Skeleton -struct ImportedFunctionSkeleton: Codable { - let name: String - let parameters: [Parameter] - let returnType: BridgeType - let documentation: String? +public struct ImportedFunctionSkeleton: Codable { + public let name: String + public let parameters: [Parameter] + public let returnType: BridgeType + public let documentation: String? - func abiName(context: ImportedTypeSkeleton?) -> String { + public func abiName(context: ImportedTypeSkeleton?) -> String { return context.map { "bjs_\($0.name)_\(name)" } ?? "bjs_\(name)" } } -struct ImportedConstructorSkeleton: Codable { - let parameters: [Parameter] +public struct ImportedConstructorSkeleton: Codable { + public let parameters: [Parameter] - func abiName(context: ImportedTypeSkeleton) -> String { + public func abiName(context: ImportedTypeSkeleton) -> String { return "bjs_\(context.name)_init" } } -struct ImportedPropertySkeleton: Codable { - let name: String - let isReadonly: Bool - let type: BridgeType - let documentation: String? +public struct ImportedPropertySkeleton: Codable { + public let name: String + public let isReadonly: Bool + public let type: BridgeType + public let documentation: String? - func getterAbiName(context: ImportedTypeSkeleton) -> String { + public func getterAbiName(context: ImportedTypeSkeleton) -> String { return "bjs_\(context.name)_\(name)_get" } - func setterAbiName(context: ImportedTypeSkeleton) -> String { + public func setterAbiName(context: ImportedTypeSkeleton) -> String { return "bjs_\(context.name)_\(name)_set" } } -struct ImportedTypeSkeleton: Codable { - let name: String - let constructor: ImportedConstructorSkeleton? - let methods: [ImportedFunctionSkeleton] - let properties: [ImportedPropertySkeleton] - let documentation: String? +public struct ImportedTypeSkeleton: Codable { + public let name: String + public let constructor: ImportedConstructorSkeleton? + public let methods: [ImportedFunctionSkeleton] + public let properties: [ImportedPropertySkeleton] + public let documentation: String? } -struct ImportedFileSkeleton: Codable { - let functions: [ImportedFunctionSkeleton] - let types: [ImportedTypeSkeleton] +public struct ImportedFileSkeleton: Codable { + public let functions: [ImportedFunctionSkeleton] + public let types: [ImportedTypeSkeleton] } -struct ImportedModuleSkeleton: Codable { - let moduleName: String - var children: [ImportedFileSkeleton] +public struct ImportedModuleSkeleton: Codable { + public let moduleName: String + public var children: [ImportedFileSkeleton] + + public init(moduleName: String, children: [ImportedFileSkeleton]) { + self.moduleName = moduleName + self.children = children + } } extension BridgeType { - var abiReturnType: WasmCoreType? { + public var abiReturnType: WasmCoreType? { switch self { case .void: return nil case .bool: return .i32 diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSCore b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSCore deleted file mode 120000 index c869f69c..00000000 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSCore +++ /dev/null @@ -1 +0,0 @@ -../BridgeJSCore \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSSkeleton b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSSkeleton deleted file mode 120000 index a2c26678..00000000 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSSkeleton +++ /dev/null @@ -1 +0,0 @@ -../BridgeJSSkeleton \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift index 6096e2b3..bdeae3c3 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift @@ -8,6 +8,16 @@ @preconcurrency import class Foundation.JSONDecoder import SwiftParser +#if canImport(BridgeJSCore) +import BridgeJSCore +#endif +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif +#if canImport(TS2Skeleton) +import TS2Skeleton +#endif + /// BridgeJS Tool /// /// A command-line tool to generate Swift-JavaScript bridge code for WebAssembly applications. diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/TS2Skeleton b/Plugins/BridgeJS/Sources/BridgeJSTool/TS2Skeleton deleted file mode 120000 index f9ba2f57..00000000 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/TS2Skeleton +++ /dev/null @@ -1 +0,0 @@ -../TS2Skeleton \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift b/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift index 262393c4..051f2f4a 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift @@ -12,6 +12,13 @@ import protocol Dispatch.DispatchSourceSignal import class Dispatch.DispatchSource +#if canImport(BridgeJSCore) +import BridgeJSCore +#endif +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif + internal func which(_ executable: String) throws -> URL { func checkCandidate(_ candidate: URL) -> Bool { var isDirectory: ObjCBool = false @@ -46,7 +53,7 @@ internal func which(_ executable: String) throws -> URL { extension ImportTS { /// Processes a TypeScript definition file and extracts its API information - mutating func addSourceFile(_ sourceFile: String, tsconfigPath: String) throws { + public mutating func addSourceFile(_ sourceFile: String, tsconfigPath: String) throws { let nodePath = try which("node") let ts2skeletonPath = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCore b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCore deleted file mode 120000 index 852d5b95..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCore +++ /dev/null @@ -1 +0,0 @@ -../../Sources/BridgeJSCore \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLink b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLink deleted file mode 120000 index 94a1e954..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLink +++ /dev/null @@ -1 +0,0 @@ -../../Sources/BridgeJSLink \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift index 3432551b..925a9757 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift @@ -2,6 +2,8 @@ import Foundation import SwiftSyntax import SwiftParser import Testing +@testable import BridgeJSLink +@testable import BridgeJSCore @Suite struct BridgeJSLinkTests { private func snapshot( diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSSkeleton b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSSkeleton deleted file mode 120000 index c2cf2864..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSSkeleton +++ /dev/null @@ -1 +0,0 @@ -../../Sources/BridgeJSSkeleton \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift index 626248a7..8351d105 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift @@ -3,6 +3,8 @@ import SwiftSyntax import SwiftParser import Testing +@testable import BridgeJSCore + @Suite struct ExportSwiftTests { private func snapshot( swiftAPI: ExportSwift, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift index 071c3d1d..9db37669 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift @@ -1,5 +1,7 @@ import Testing import Foundation +@testable import BridgeJSCore +@testable import TS2Skeleton @Suite struct ImportTSTests { static let inputsDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent().appendingPathComponent( diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/TS2Skeleton b/Plugins/BridgeJS/Tests/BridgeJSToolTests/TS2Skeleton deleted file mode 120000 index feba8470..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/TS2Skeleton +++ /dev/null @@ -1 +0,0 @@ -../../Sources/TS2Skeleton \ No newline at end of file diff --git a/Sources/BridgeJSTool/BridgeJSCore b/Sources/BridgeJSTool/BridgeJSCore new file mode 120000 index 00000000..9934baee --- /dev/null +++ b/Sources/BridgeJSTool/BridgeJSCore @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/BridgeJSCore \ No newline at end of file diff --git a/Sources/BridgeJSTool/BridgeJSSkeleton b/Sources/BridgeJSTool/BridgeJSSkeleton new file mode 120000 index 00000000..794c5b08 --- /dev/null +++ b/Sources/BridgeJSTool/BridgeJSSkeleton @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/BridgeJSSkeleton \ No newline at end of file diff --git a/Sources/BridgeJSTool/BridgeJSTool b/Sources/BridgeJSTool/BridgeJSTool new file mode 120000 index 00000000..e92c6fbb --- /dev/null +++ b/Sources/BridgeJSTool/BridgeJSTool @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/BridgeJSTool \ No newline at end of file diff --git a/Sources/BridgeJSTool/README.md b/Sources/BridgeJSTool/README.md new file mode 100644 index 00000000..c3ec3cf3 --- /dev/null +++ b/Sources/BridgeJSTool/README.md @@ -0,0 +1,9 @@ +# BridgeJSTool (Merged Sources) + +This directory contains symlinked sources from `Plugins/BridgeJS` to provide a merged version of the BridgeJSTool for the root Package.swift. + +## Source Merging via Symlinks + +This module uses symlinks to merge multiple modules into a single compilation unit. Compiling multiple modules separately is much slower than compiling them as one merged module. + +Since BridgeJSTool runs during Swift package builds via the BridgeJS plugin, fast compilation is critical for developer experience. The source modules use `#if canImport` directives to work both standalone and when merged here. \ No newline at end of file diff --git a/Sources/BridgeJSTool/TS2Skeleton b/Sources/BridgeJSTool/TS2Skeleton new file mode 120000 index 00000000..c41c1280 --- /dev/null +++ b/Sources/BridgeJSTool/TS2Skeleton @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/TS2Skeleton \ No newline at end of file From 42ddc65eb04373086663f555450eb9f62fdc3535 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 15 Aug 2025 22:44:49 +0900 Subject: [PATCH 03/61] Suppress warning about the unused README.md file in BridgeJSTool --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 4bd0ca45..fe7f0229 100644 --- a/Package.swift +++ b/Package.swift @@ -151,7 +151,7 @@ let package = Package( .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ], - exclude: ["TS2Skeleton/JavaScript"] + exclude: ["TS2Skeleton/JavaScript", "README.md"] ), .testTarget( name: "BridgeJSRuntimeTests", From 7814be9c048eef02ea458fbf1ac208e8d40515b8 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 15 Aug 2025 22:54:14 +0900 Subject: [PATCH 04/61] Add documentation for BridgeJS generation script to CONTRIBUTING.md Document the Utilities/bridge-js-generate.sh script and when to use it for updating AoT-generated BridgeJS files. --- CONTRIBUTING.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f71ca83a..f3cdb46c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,5 +81,24 @@ To make changes to the runtime, you need to edit the TypeScript files and regene make regenerate_swiftpm_resources ``` +### Working with BridgeJS + +BridgeJS is a Swift Package Manager plugin that automatically generates Swift bindings from TypeScript definitions. This repository contains pre-generated files created by BridgeJS in AoT (Ahead of Time) mode that are checked into version control. + +To update these pre-generated files, use the utility script: + +```bash +./Utilities/bridge-js-generate.sh +``` + +This script runs the BridgeJS plugin in AoT mode (`swift package bridge-js`) on several SwiftPM packages in this repository. + +Run this script when you've made changes to: +- TypeScript definitions +- BridgeJS configuration +- BridgeJS code generator itself + +These changes require updating the pre-generated Swift bindings committed to the repository. + ## Support If you have any questions or need assistance, feel free to reach out via [GitHub Issues](https://github.com/swiftwasm/JavaScriptKit/issues) or [Discord](https://discord.gg/ashJW8T8yp). From 223673e89cecd8e9c0dc1c2e65852043213be9cd Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 15 Aug 2025 22:58:19 +0900 Subject: [PATCH 05/61] Add CI job to check BridgeJS generated files are up-to-date Adds check-bridgejs-generated job that runs bridge-js-generate.sh and fails if generated files are not current. --- .github/workflows/test.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2766d6ef..e6da32aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -88,6 +88,24 @@ jobs: echo "::error::The formatting changed some files. Please run \`./Utilities/format.swift\` and commit the changes." exit 1 } + + check-bridgejs-generated: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v5 + - uses: ./.github/actions/install-swift + with: + download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a/swift-DEVELOPMENT-SNAPSHOT-2025-06-12-a-ubuntu22.04.tar.gz + - run: make bootstrap + - run: ./Utilities/bridge-js-generate.sh + - name: Check if BridgeJS generated files are up-to-date + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git diff --exit-code || { + echo "::error::BridgeJS generated files are out of date. Please run \`./Utilities/bridge-js-generate.sh\` and commit the changes." + exit 1 + } + build-examples: runs-on: ubuntu-latest steps: From 1279f9ba8e580c11c550b20fc3a82fbe009e1725 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 00:28:49 +0900 Subject: [PATCH 06/61] Test: Kill the wrong SwiftHeapObject returning issue --- Tests/prelude.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index c6ac428a..c4de426f 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -116,14 +116,16 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { } const g = new exports.Greeter("John"); - const g2 = exports.roundTripSwiftHeapObject(g) - g2.release(); - assert.equal(g.greet(), "Hello, John!"); g.changeName("Jane"); assert.equal(g.greet(), "Hello, Jane!"); exports.takeGreeter(g, "Jay"); assert.equal(g.greet(), "Hello, Jay!"); + + const g2 = exports.roundTripSwiftHeapObject(g) + assert.equal(g2.greet(), "Hello, Jay!"); + g2.release(); + g.release(); const anyObject = {}; From 8ebf07ee8d627b04f033a4c571c58586538146a4 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 00:42:12 +0900 Subject: [PATCH 07/61] Add BridgeJS testing documentation - Add BridgeJS plugin test commands to CONTRIBUTING.md - Document UPDATE_SNAPSHOTS=1 environment variable usage for snapshot tests --- CONTRIBUTING.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f3cdb46c..666b62d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,18 @@ Tests for `PackageToJS` plugin: swift test --package-path ./Plugins/PackageToJS ``` +Tests for `BridgeJS` plugin: + +```bash +swift test --package-path ./Plugins/BridgeJS +``` + +To update snapshot test files when expected output changes: + +```bash +UPDATE_SNAPSHOTS=1 swift test --package-path ./Plugins/BridgeJS +``` + ### Editing `./Runtime` directory The `./Runtime` directory contains the JavaScript runtime that interacts with the JavaScript environment and Swift code. From fb7d54f8bbe70bae62acc9f37536379bb085b0a1 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 00:42:22 +0900 Subject: [PATCH 08/61] BridgeJS: Change @JS init to generate static init() methods instead of constructors --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 6693c815..3a71446d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -43,6 +43,10 @@ struct BridgeJSLink { let swiftHeapObjectClassJs = """ /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { + static __construct(ptr, deinit) { + return new SwiftHeapObject(ptr, deinit); + } + constructor(pointer, deinit) { this.pointer = pointer; this.hasReleased = false; @@ -401,7 +405,7 @@ struct BridgeJSLink { bodyLines.append("swift.memory.release(retId);") returnExpr = "ret" case .swiftHeapObject(let name): - bodyLines.append("const ret = new \(name)(\(call));") + bodyLines.append("const ret = \(name).__construct(\(call));") returnExpr = "ret" } return returnExpr @@ -490,17 +494,25 @@ struct BridgeJSLink { thunkBuilder.lowerParameter(param: param) } var funcLines: [String] = [] - funcLines.append("constructor(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {") + funcLines.append("static __construct(ptr) {") + funcLines.append("return new \(klass.name)(ptr, instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4)) + funcLines.append("}") + funcLines.append("") + funcLines.append("constructor(pointer, deinit) {") + funcLines.append("super(pointer, deinit);".indent(count: 4)) + funcLines.append("}") + funcLines.append("") + funcLines.append("static init(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {") let returnExpr = thunkBuilder.callConstructor(abiName: constructor.abiName) funcLines.append(contentsOf: thunkBuilder.bodyLines.map { $0.indent(count: 4) }) funcLines.append(contentsOf: thunkBuilder.cleanupLines.map { $0.indent(count: 4) }) funcLines.append(contentsOf: thunkBuilder.checkExceptionLines().map { $0.indent(count: 4) }) - funcLines.append("super(\(returnExpr), instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4)) + funcLines.append("return \(klass.name).__construct(\(returnExpr));".indent(count: 4)) funcLines.append("}") jsLines.append(contentsOf: funcLines.map { $0.indent(count: 4) }) dtsExportEntryLines.append( - "new\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));" + "init\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));" .indent(count: 4) ) } From 326e5937c41b113d816f4ab23d861142e4c3faba Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 00:42:32 +0900 Subject: [PATCH 09/61] BridgeJS: Update tests to use new static init() API --- Tests/prelude.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index c4de426f..4b3b11da 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -115,7 +115,7 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.equal(exports.roundTripString(v), v); } - const g = new exports.Greeter("John"); + const g = exports.Greeter.init("John"); assert.equal(g.greet(), "Hello, John!"); g.changeName("Jane"); assert.equal(g.greet(), "Hello, Jane!"); From ea03d75766035e4a44b536b61478ecb1e6febfe6 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 00:42:43 +0900 Subject: [PATCH 10/61] BridgeJS: Update BridgeJS test snapshots --- .../BridgeJSLinkTests/Namespaces.Export.d.ts | 4 +-- .../BridgeJSLinkTests/Namespaces.Export.js | 28 ++++++++++++++++--- .../BridgeJSLinkTests/SwiftClass.Export.d.ts | 2 +- .../BridgeJSLinkTests/SwiftClass.Export.js | 16 +++++++++-- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts index b2ccecc4..65b9360e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts @@ -51,10 +51,10 @@ export interface UUID extends SwiftHeapObject { } export type Exports = { Greeter: { - new(name: string): Greeter; + init(name: string): Greeter; } Converter: { - new(): Converter; + init(): Converter; } UUID: { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index dce99393..c2755059 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -60,6 +60,10 @@ export async function createInstantiator(options, swift) { const js = swift.memory.heap; /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { + static __construct(ptr, deinit) { + return new SwiftHeapObject(ptr, deinit); + } + constructor(pointer, deinit) { this.pointer = pointer; this.hasReleased = false; @@ -76,12 +80,20 @@ export async function createInstantiator(options, swift) { } } class Greeter extends SwiftHeapObject { - constructor(name) { + static __construct(ptr) { + return new Greeter(ptr, instance.exports.bjs_Greeter_deinit); + } + + constructor(pointer, deinit) { + super(pointer, deinit); + } + + static init(name) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); swift.memory.release(nameId); - super(ret, instance.exports.bjs_Greeter_deinit); + return Greeter.__construct(ret); } greet() { instance.exports.bjs_Greeter_greet(this.pointer); @@ -91,9 +103,17 @@ export async function createInstantiator(options, swift) { } } class Converter extends SwiftHeapObject { - constructor() { + static __construct(ptr) { + return new Converter(ptr, instance.exports.bjs_Converter_deinit); + } + + constructor(pointer, deinit) { + super(pointer, deinit); + } + + static init() { const ret = instance.exports.bjs_Converter_init(); - super(ret, instance.exports.bjs_Converter_deinit); + return Converter.__construct(ret); } toString(value) { instance.exports.bjs_Converter_toString(this.pointer, value); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts index fd376d57..65391433 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts @@ -17,7 +17,7 @@ export interface Greeter extends SwiftHeapObject { } export type Exports = { Greeter: { - new(name: string): Greeter; + init(name: string): Greeter; } takeGreeter(greeter: Greeter): void; } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index 7a5938a1..9f79f20f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -60,6 +60,10 @@ export async function createInstantiator(options, swift) { const js = swift.memory.heap; /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { + static __construct(ptr, deinit) { + return new SwiftHeapObject(ptr, deinit); + } + constructor(pointer, deinit) { this.pointer = pointer; this.hasReleased = false; @@ -76,12 +80,20 @@ export async function createInstantiator(options, swift) { } } class Greeter extends SwiftHeapObject { - constructor(name) { + static __construct(ptr) { + return new Greeter(ptr, instance.exports.bjs_Greeter_deinit); + } + + constructor(pointer, deinit) { + super(pointer, deinit); + } + + static init(name) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); swift.memory.release(nameId); - super(ret, instance.exports.bjs_Greeter_deinit); + return Greeter.__construct(ret); } greet() { instance.exports.bjs_Greeter_greet(this.pointer); From 94ec79026d33691647bdb842f04decd731f78a0f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 00:46:21 +0900 Subject: [PATCH 11/61] BridgeJS: Fix classes without @JS init constructors --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 3a71446d..3330ce52 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -488,19 +488,23 @@ struct BridgeJSLink { dtsExportEntryLines.append("\(klass.name): {") jsLines.append("class \(klass.name) extends SwiftHeapObject {") + // Always add __construct and constructor methods for all classes + var constructorLines: [String] = [] + constructorLines.append("static __construct(ptr) {") + constructorLines.append("return new \(klass.name)(ptr, instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4)) + constructorLines.append("}") + constructorLines.append("") + constructorLines.append("constructor(pointer, deinit) {") + constructorLines.append("super(pointer, deinit);".indent(count: 4)) + constructorLines.append("}") + jsLines.append(contentsOf: constructorLines.map { $0.indent(count: 4) }) + if let constructor: ExportedConstructor = klass.constructor { let thunkBuilder = ExportedThunkBuilder(effects: constructor.effects) for param in constructor.parameters { thunkBuilder.lowerParameter(param: param) } var funcLines: [String] = [] - funcLines.append("static __construct(ptr) {") - funcLines.append("return new \(klass.name)(ptr, instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4)) - funcLines.append("}") - funcLines.append("") - funcLines.append("constructor(pointer, deinit) {") - funcLines.append("super(pointer, deinit);".indent(count: 4)) - funcLines.append("}") funcLines.append("") funcLines.append("static init(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {") let returnExpr = thunkBuilder.callConstructor(abiName: constructor.abiName) From dff6231985ba9a2d4d5845d74128abcafd1abb7e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 00:46:31 +0900 Subject: [PATCH 12/61] BridgeJS: Update snapshots for consistent constructor generation --- .../__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index c2755059..a9678497 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -123,6 +123,13 @@ export async function createInstantiator(options, swift) { } } class UUID extends SwiftHeapObject { + static __construct(ptr) { + return new UUID(ptr, instance.exports.bjs_UUID_deinit); + } + + constructor(pointer, deinit) { + super(pointer, deinit); + } uuidString() { instance.exports.bjs_UUID_uuidString(this.pointer); const ret = tmpRetString; From 9acbb8c181f0fdef4ed69ebc847af0fa58e1c783 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 00:50:11 +0900 Subject: [PATCH 13/61] Test: Cover Swift class without @JS init --- .../BridgeJSRuntimeTests/ExportAPITests.swift | 37 +++++- .../Generated/BridgeJS.ExportSwift.swift | 50 ++++++++ .../JavaScript/BridgeJS.ExportSwift.json | 120 ++++++++++++++++++ Tests/prelude.mjs | 8 ++ 4 files changed, 214 insertions(+), 1 deletion(-) diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 2b78b96b..3ebef279 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -74,13 +74,48 @@ struct TestError: Error { g.changeName(name: name) } +// Test class without @JS init constructor +@JS class Calculator { + nonisolated(unsafe) static var onDeinit: () -> Void = {} + + @JS func square(value: Int) -> Int { + return value * value + } + + @JS func add(a: Int, b: Int) -> Int { + return a + b + } + + deinit { + Self.onDeinit() + } +} + +@JS func createCalculator() -> Calculator { + return Calculator() +} + +@JS func useCalculator(calc: Calculator, x: Int, y: Int) -> Int { + return calc.add(a: calc.square(value: x), b: y) +} + + class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false + var hasDeinitCalculator = false + Greeter.onDeinit = { hasDeinitGreeter = true } + + Calculator.onDeinit = { + hasDeinitCalculator = true + } + runJsWorks() - XCTAssertTrue(hasDeinitGreeter) + + XCTAssertTrue(hasDeinitGreeter, "Greeter (with @JS init) should have been deinitialized") + XCTAssertTrue(hasDeinitCalculator, "Calculator (without @JS init) should have been deinitialized") } } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 2a91da9f..41f0d882 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -314,6 +314,28 @@ public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameL #endif } +@_expose(wasm, "bjs_createCalculator") +@_cdecl("bjs_createCalculator") +public func _bjs_createCalculator() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = createCalculator() + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_useCalculator") +@_cdecl("bjs_useCalculator") +public func _bjs_useCalculator(calc: UnsafeMutableRawPointer, x: Int32, y: Int32) -> Int32 { + #if arch(wasm32) + let ret = useCalculator(calc: Unmanaged.fromOpaque(calc).takeUnretainedValue(), x: Int(x), y: Int(y)) + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { @@ -360,4 +382,32 @@ public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: I @_cdecl("bjs_Greeter_deinit") public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() +} + +@_expose(wasm, "bjs_Calculator_square") +@_cdecl("bjs_Calculator_square") +public func _bjs_Calculator_square(_self: UnsafeMutableRawPointer, value: Int32) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().square(value: Int(value)) + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Calculator_add") +@_cdecl("bjs_Calculator_add") +public func _bjs_Calculator_add(_self: UnsafeMutableRawPointer, a: Int32, b: Int32) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().add(a: Int(a), b: Int(b)) + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Calculator_deinit") +@_cdecl("bjs_Calculator_deinit") +public func _bjs_Calculator_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 7a467cc3..ad759cec 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -62,6 +62,68 @@ } ], "name" : "Greeter" + }, + { + "methods" : [ + { + "abiName" : "bjs_Calculator_square", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "square", + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_Calculator_add", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "add", + "parameters" : [ + { + "label" : "a", + "name" : "a", + "type" : { + "int" : { + + } + } + }, + { + "label" : "b", + "name" : "b", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + } + ], + "name" : "Calculator" } ], "functions" : [ @@ -415,6 +477,64 @@ "returnType" : { "void" : { + } + } + }, + { + "abiName" : "bjs_createCalculator", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "createCalculator", + "parameters" : [ + + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Calculator" + } + } + }, + { + "abiName" : "bjs_useCalculator", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "useCalculator", + "parameters" : [ + { + "label" : "calc", + "name" : "calc", + "type" : { + "swiftHeapObject" : { + "_0" : "Calculator" + } + } + }, + { + "label" : "x", + "name" : "x", + "type" : { + "int" : { + + } + } + }, + { + "label" : "y", + "name" : "y", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + } } } diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 4b3b11da..ddb232d2 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -128,6 +128,14 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { g.release(); + // Test class without @JS init constructor + const calc = exports.createCalculator(); + assert.equal(calc.square(5), 25); + assert.equal(calc.add(3, 4), 7); + assert.equal(exports.useCalculator(calc, 3, 10), 19); // 3^2 + 10 = 19 + + calc.release(); + const anyObject = {}; assert.equal(exports.roundTripJSObject(anyObject), anyObject); From 7df801c140799dbe1b11e8dc0f82e642774dae9a Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 00:58:32 +0900 Subject: [PATCH 14/61] ./Utilities/format.swift --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 4 +++- Tests/BridgeJSRuntimeTests/ExportAPITests.swift | 17 ++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 3330ce52..7dbe3cbb 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -491,7 +491,9 @@ struct BridgeJSLink { // Always add __construct and constructor methods for all classes var constructorLines: [String] = [] constructorLines.append("static __construct(ptr) {") - constructorLines.append("return new \(klass.name)(ptr, instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4)) + constructorLines.append( + "return new \(klass.name)(ptr, instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4) + ) constructorLines.append("}") constructorLines.append("") constructorLines.append("constructor(pointer, deinit) {") diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 3ebef279..7b561f25 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -74,18 +74,18 @@ struct TestError: Error { g.changeName(name: name) } -// Test class without @JS init constructor +// Test class without @JS init constructor @JS class Calculator { nonisolated(unsafe) static var onDeinit: () -> Void = {} - + @JS func square(value: Int) -> Int { return value * value } - + @JS func add(a: Int, b: Int) -> Int { return a + b } - + deinit { Self.onDeinit() } @@ -99,22 +99,21 @@ struct TestError: Error { return calc.add(a: calc.square(value: x), b: y) } - class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false var hasDeinitCalculator = false - + Greeter.onDeinit = { hasDeinitGreeter = true } - + Calculator.onDeinit = { hasDeinitCalculator = true } - + runJsWorks() - + XCTAssertTrue(hasDeinitGreeter, "Greeter (with @JS init) should have been deinitialized") XCTAssertTrue(hasDeinitCalculator, "Calculator (without @JS init) should have been deinitialized") } From ed482fe4630e4733b3f654fda6eb3095bddb449d Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 01:10:39 +0900 Subject: [PATCH 15/61] BridgeJS: Update examples and documentation for `@JS init` --- Examples/ExportSwift/index.js | 2 +- Examples/PlayBridgeJS/Sources/JavaScript/app.js | 4 ++-- .../Articles/Exporting-Swift-to-JavaScript.md | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/ExportSwift/index.js b/Examples/ExportSwift/index.js index 4c5576b2..fcf7c983 100644 --- a/Examples/ExportSwift/index.js +++ b/Examples/ExportSwift/index.js @@ -2,7 +2,7 @@ import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; const { exports } = await init({}); const Greeter = exports.Greeter; -const greeter = new Greeter("World"); +const greeter = Greeter.init("World"); const circle = exports.renderCircleSVG(100); // Display the results diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index b14db79b..30179791 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -52,7 +52,7 @@ export class BridgeJSPlayground { createTS2Skeleton: this.createTS2Skeleton } }); - this.playBridgeJS = new exports.PlayBridgeJS(); + this.playBridgeJS = exports.PlayBridgeJS.init(); console.log('BridgeJS initialized successfully'); } catch (error) { console.error('Failed to initialize BridgeJS:', error); @@ -162,4 +162,4 @@ export class BridgeJSPlayground { hideError() { this.errorDisplay.classList.remove('show'); } -} \ No newline at end of file +} diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md index 6ce30772..6de7db0b 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md @@ -133,7 +133,7 @@ In JavaScript: import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; const { exports } = await init({}); -const cart = new exports.ShoppingCart(); +const cart = exports.ShoppingCart.init(); cart.addItem("Laptop", 999.99, 1); cart.addItem("Mouse", 24.99, 2); console.log(`Items in cart: ${cart.getItemCount()}`); @@ -158,7 +158,7 @@ export interface ShoppingCart extends SwiftHeapObject { export type Exports = { ShoppingCart: { - new(): ShoppingCart; + init(): ShoppingCart; } } ``` @@ -175,8 +175,8 @@ You can export functions to specific namespaces by providing a namespace paramet import JavaScriptKit // Export a function to a custom namespace -@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { - return "namespaced" +@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { + return "namespaced" } ``` From bb114666c9ed0ea36466efccddb4c4366bf1c407 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 01:38:45 +0900 Subject: [PATCH 16/61] BridgeJS: Fix missing TypeScript interface definitions for imported types Previously, BridgeJS generated imports referencing types (e.g., createTS2Skeleton(): TS2Skeleton) without generating the corresponding TypeScript interface definitions, causing reference errors. This fix adds generateImportedTypeDefinitions() function to generate export interface declarations for imported types with their methods and properties, ensuring TypeScript definitions are complete. Added test cases to prevent regression with multiple imported types scenarios. --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 33 +++ .../Inputs/MultipleImportedTypes.d.ts | 23 ++ .../Inputs/TS2SkeletonLike.d.ts | 14 ++ .../BridgeJSLinkTests/Interface.Import.d.ts | 4 + .../MultipleImportedTypes.Import.d.ts | 36 ++++ .../MultipleImportedTypes.Import.js | 198 ++++++++++++++++++ .../TS2SkeletonLike.Import.d.ts | 28 +++ .../TS2SkeletonLike.Import.js | 136 ++++++++++++ .../TypeScriptClass.Import.d.ts | 6 + 9 files changed, 478 insertions(+) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MultipleImportedTypes.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TS2SkeletonLike.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 6693c815..35fea6fd 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -221,6 +221,7 @@ struct BridgeJSLink { var dtsLines: [String] = [] dtsLines.append(contentsOf: namespaceDeclarations()) dtsLines.append(contentsOf: dtsClassLines) + dtsLines.append(contentsOf: generateImportedTypeDefinitions()) dtsLines.append("export type Exports = {") dtsLines.append(contentsOf: dtsExportLines.map { $0.indent(count: 4) }) dtsLines.append("}") @@ -246,6 +247,38 @@ struct BridgeJSLink { return (outputJs, outputDts) } + private func generateImportedTypeDefinitions() -> [String] { + var typeDefinitions: [String] = [] + + for skeletonSet in importedSkeletons { + for fileSkeleton in skeletonSet.children { + for type in fileSkeleton.types { + typeDefinitions.append("export interface \(type.name) {") + + // Add methods + for method in type.methods { + let methodSignature = + "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType));" + typeDefinitions.append(methodSignature.indent(count: 4)) + } + + // Add properties + for property in type.properties { + let propertySignature = + property.isReadonly + ? "readonly \(property.name): \(property.type.tsType);" + : "\(property.name): \(property.type.tsType);" + typeDefinitions.append(propertySignature.indent(count: 4)) + } + + typeDefinitions.append("}") + } + } + } + + return typeDefinitions + } + private func namespaceDeclarations() -> [String] { var dtsLines: [String] = [] var namespaceFunctions: [String: [ExportedFunction]] = [:] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MultipleImportedTypes.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MultipleImportedTypes.d.ts new file mode 100644 index 00000000..70dc2b72 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MultipleImportedTypes.d.ts @@ -0,0 +1,23 @@ +// Test case for multiple imported types with methods and properties +export interface DatabaseConnection { + connect(url: string): void; + execute(query: string): any; + readonly isConnected: boolean; + connectionTimeout: number; +} + +export interface Logger { + log(message: string): void; + error(message: string, error: any): void; + readonly level: string; +} + +export interface ConfigManager { + get(key: string): any; + set(key: string, value: any): void; + readonly configPath: string; +} + +export function createDatabaseConnection(config: any): DatabaseConnection; +export function createLogger(level: string): Logger; +export function getConfigManager(): ConfigManager; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TS2SkeletonLike.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TS2SkeletonLike.d.ts new file mode 100644 index 00000000..d3f07d7a --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/TS2SkeletonLike.d.ts @@ -0,0 +1,14 @@ +// Test case similar to the TS2Skeleton use case that caused the original bug +export interface TypeScriptProcessor { + convert(ts: string): string; + validate(ts: string): boolean; + readonly version: string; +} + +export interface CodeGenerator { + generate(input: any): string; + readonly outputFormat: string; +} + +export function createTS2Skeleton(): TypeScriptProcessor; +export function createCodeGenerator(format: string): CodeGenerator; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts index ffcbcd14..ccd371b7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.d.ts @@ -4,6 +4,10 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export interface Animatable { + animate(keyframes: any, options: any): any; + getAnimations(options: any): any; +} export type Exports = { } export type Imports = { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.d.ts new file mode 100644 index 00000000..83fe3c14 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.d.ts @@ -0,0 +1,36 @@ +// 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 DatabaseConnection { + connect(url: string): void; + execute(query: string): any; + readonly isConnected: boolean; + connectionTimeout: number; +} +export interface Logger { + log(message: string): void; + error(message: string, error: any): void; + readonly level: string; +} +export interface ConfigManager { + get(key: string): any; + set(key: string, value: any): void; + readonly configPath: string; +} +export type Exports = { +} +export type Imports = { + createDatabaseConnection(config: any): DatabaseConnection; + createLogger(level: string): Logger; + getConfigManager(): ConfigManager; +} +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/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js new file mode 100644 index 00000000..f65188f0 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -0,0 +1,198 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** @param {WebAssembly.Imports} importObject */ + addImports: (importObject) => { + const bjs = {}; + importObject["bjs"] = bjs; + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + const TestModule = importObject["TestModule"] = {}; + TestModule["bjs_createDatabaseConnection"] = function bjs_createDatabaseConnection(config) { + try { + let ret = options.imports.createDatabaseConnection(swift.memory.getObject(config)); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_createLogger"] = function bjs_createLogger(level) { + try { + const levelObject = swift.memory.getObject(level); + swift.memory.release(level); + let ret = options.imports.createLogger(levelObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_getConfigManager"] = function bjs_getConfigManager() { + try { + let ret = options.imports.getConfigManager(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_DatabaseConnection_isConnected_get"] = function bjs_DatabaseConnection_isConnected_get(self) { + try { + let ret = swift.memory.getObject(self).isConnected; + return ret !== 0; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_DatabaseConnection_connectionTimeout_get"] = function bjs_DatabaseConnection_connectionTimeout_get(self) { + try { + let ret = swift.memory.getObject(self).connectionTimeout; + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_DatabaseConnection_connectionTimeout_set"] = function bjs_DatabaseConnection_connectionTimeout_set(self, newValue) { + try { + swift.memory.getObject(self).connectionTimeout = newValue; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_DatabaseConnection_connect"] = function bjs_DatabaseConnection_connect(self, url) { + try { + const urlObject = swift.memory.getObject(url); + swift.memory.release(url); + swift.memory.getObject(self).connect(urlObject); + } catch (error) { + setException(error); + } + } + TestModule["bjs_DatabaseConnection_execute"] = function bjs_DatabaseConnection_execute(self, query) { + try { + const queryObject = swift.memory.getObject(query); + swift.memory.release(query); + let ret = swift.memory.getObject(self).execute(queryObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_Logger_level_get"] = function bjs_Logger_level_get(self) { + try { + let ret = swift.memory.getObject(self).level; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_Logger_log"] = function bjs_Logger_log(self, message) { + try { + const messageObject = swift.memory.getObject(message); + swift.memory.release(message); + swift.memory.getObject(self).log(messageObject); + } catch (error) { + setException(error); + } + } + TestModule["bjs_Logger_error"] = function bjs_Logger_error(self, message, error) { + try { + const messageObject = swift.memory.getObject(message); + swift.memory.release(message); + swift.memory.getObject(self).error(messageObject, swift.memory.getObject(error)); + } catch (error) { + setException(error); + } + } + TestModule["bjs_ConfigManager_configPath_get"] = function bjs_ConfigManager_configPath_get(self) { + try { + let ret = swift.memory.getObject(self).configPath; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_ConfigManager_get"] = function bjs_ConfigManager_get(self, key) { + try { + const keyObject = swift.memory.getObject(key); + swift.memory.release(key); + let ret = swift.memory.getObject(self).get(keyObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_ConfigManager_set"] = function bjs_ConfigManager_set(self, key, value) { + try { + const keyObject = swift.memory.getObject(key); + swift.memory.release(key); + swift.memory.getObject(self).set(keyObject, swift.memory.getObject(value)); + } catch (error) { + setException(error); + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.d.ts new file mode 100644 index 00000000..26d56fb6 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.d.ts @@ -0,0 +1,28 @@ +// 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 TypeScriptProcessor { + convert(ts: string): string; + validate(ts: string): boolean; + readonly version: string; +} +export interface CodeGenerator { + generate(input: any): string; + readonly outputFormat: string; +} +export type Exports = { +} +export type Imports = { + createTS2Skeleton(): TypeScriptProcessor; + createCodeGenerator(format: string): CodeGenerator; +} +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/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js new file mode 100644 index 00000000..d86f66dc --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -0,0 +1,136 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** @param {WebAssembly.Imports} importObject */ + addImports: (importObject) => { + const bjs = {}; + importObject["bjs"] = bjs; + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + const TestModule = importObject["TestModule"] = {}; + TestModule["bjs_createTS2Skeleton"] = function bjs_createTS2Skeleton() { + try { + let ret = options.imports.createTS2Skeleton(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_createCodeGenerator"] = function bjs_createCodeGenerator(format) { + try { + const formatObject = swift.memory.getObject(format); + swift.memory.release(format); + let ret = options.imports.createCodeGenerator(formatObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_TypeScriptProcessor_version_get"] = function bjs_TypeScriptProcessor_version_get(self) { + try { + let ret = swift.memory.getObject(self).version; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_TypeScriptProcessor_convert"] = function bjs_TypeScriptProcessor_convert(self, ts) { + try { + const tsObject = swift.memory.getObject(ts); + swift.memory.release(ts); + let ret = swift.memory.getObject(self).convert(tsObject); + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_TypeScriptProcessor_validate"] = function bjs_TypeScriptProcessor_validate(self, ts) { + try { + const tsObject = swift.memory.getObject(ts); + swift.memory.release(ts); + let ret = swift.memory.getObject(self).validate(tsObject); + return ret !== 0; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_CodeGenerator_outputFormat_get"] = function bjs_CodeGenerator_outputFormat_get(self) { + try { + let ret = swift.memory.getObject(self).outputFormat; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_CodeGenerator_generate"] = function bjs_CodeGenerator_generate(self, input) { + try { + let ret = swift.memory.getObject(self).generate(swift.memory.getObject(input)); + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts index bcbcf06f..24d3d8fa 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.d.ts @@ -4,6 +4,12 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export interface Greeter { + greet(): string; + changeName(name: string): void; + name: string; + readonly age: number; +} export type Exports = { } export type Imports = { From 462205bb800fb46b9942d68f4c0edbf9a94052f2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 01:59:16 +0900 Subject: [PATCH 17/61] Add missing ImportTSTests snapshots for new test inputs --- .../ImportTSTests/MultipleImportedTypes.swift | 307 ++++++++++++++++++ .../ImportTSTests/TS2SkeletonLike.swift | 173 ++++++++++ 2 files changed, 480 insertions(+) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift new file mode 100644 index 00000000..d3e06e81 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift @@ -0,0 +1,307 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +func createDatabaseConnection(_ config: JSObject) throws(JSException) -> DatabaseConnection { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createDatabaseConnection") + func bjs_createDatabaseConnection(_ config: Int32) -> Int32 + #else + func bjs_createDatabaseConnection(_ config: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createDatabaseConnection(Int32(bitPattern: config.id)) + if let error = _swift_js_take_exception() { + throw error + } + return DatabaseConnection(takingThis: ret) +} + +func createLogger(_ level: String) throws(JSException) -> Logger { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createLogger") + func bjs_createLogger(_ level: Int32) -> Int32 + #else + func bjs_createLogger(_ level: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + var level = level + let levelId = level.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_createLogger(levelId) + if let error = _swift_js_take_exception() { + throw error + } + return Logger(takingThis: ret) +} + +func getConfigManager() throws(JSException) -> ConfigManager { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_getConfigManager") + func bjs_getConfigManager() -> Int32 + #else + func bjs_getConfigManager() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_getConfigManager() + if let error = _swift_js_take_exception() { + throw error + } + return ConfigManager(takingThis: ret) +} + +struct DatabaseConnection { + let this: JSObject + + init(this: JSObject) { + self.this = this + } + + init(takingThis this: Int32) { + self.this = JSObject(id: UInt32(bitPattern: this)) + } + + var isConnected: Bool { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_isConnected_get") + func bjs_DatabaseConnection_isConnected_get(_ self: Int32) -> Int32 + #else + func bjs_DatabaseConnection_isConnected_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_DatabaseConnection_isConnected_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } + return ret == 1 + } + } + + var connectionTimeout: Double { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_connectionTimeout_get") + func bjs_DatabaseConnection_connectionTimeout_get(_ self: Int32) -> Float64 + #else + func bjs_DatabaseConnection_connectionTimeout_get(_ self: Int32) -> Float64 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_DatabaseConnection_connectionTimeout_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } + return Double(ret) + } + } + + func setConnectionTimeout(_ newValue: Double) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_connectionTimeout_set") + func bjs_DatabaseConnection_connectionTimeout_set(_ self: Int32, _ newValue: Float64) -> Void + #else + func bjs_DatabaseConnection_connectionTimeout_set(_ self: Int32, _ newValue: Float64) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_DatabaseConnection_connectionTimeout_set(Int32(bitPattern: self.this.id), newValue) + if let error = _swift_js_take_exception() { + throw error + } + } + + func connect(_ url: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_connect") + func bjs_DatabaseConnection_connect(_ self: Int32, _ url: Int32) -> Void + #else + func bjs_DatabaseConnection_connect(_ self: Int32, _ url: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + var url = url + let urlId = url.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + bjs_DatabaseConnection_connect(Int32(bitPattern: self.this.id), urlId) + if let error = _swift_js_take_exception() { + throw error + } + } + + func execute(_ query: String) throws(JSException) -> JSObject { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_DatabaseConnection_execute") + func bjs_DatabaseConnection_execute(_ self: Int32, _ query: Int32) -> Int32 + #else + func bjs_DatabaseConnection_execute(_ self: Int32, _ query: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + var query = query + let queryId = query.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_DatabaseConnection_execute(Int32(bitPattern: self.this.id), queryId) + if let error = _swift_js_take_exception() { + throw error + } + return JSObject(id: UInt32(bitPattern: ret)) + } + +} + +struct Logger { + let this: JSObject + + init(this: JSObject) { + self.this = this + } + + init(takingThis this: Int32) { + self.this = JSObject(id: UInt32(bitPattern: this)) + } + + var level: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_Logger_level_get") + func bjs_Logger_level_get(_ self: Int32) -> Int32 + #else + func bjs_Logger_level_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_Logger_level_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + } + + func log(_ message: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_Logger_log") + func bjs_Logger_log(_ self: Int32, _ message: Int32) -> Void + #else + func bjs_Logger_log(_ self: Int32, _ message: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + var message = message + let messageId = message.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + bjs_Logger_log(Int32(bitPattern: self.this.id), messageId) + if let error = _swift_js_take_exception() { + throw error + } + } + + func error(_ message: String, _ error: JSObject) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_Logger_error") + func bjs_Logger_error(_ self: Int32, _ message: Int32, _ error: Int32) -> Void + #else + func bjs_Logger_error(_ self: Int32, _ message: Int32, _ error: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + var message = message + let messageId = message.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + bjs_Logger_error(Int32(bitPattern: self.this.id), messageId, Int32(bitPattern: error.id)) + if let error = _swift_js_take_exception() { + throw error + } + } + +} + +struct ConfigManager { + let this: JSObject + + init(this: JSObject) { + self.this = this + } + + init(takingThis this: Int32) { + self.this = JSObject(id: UInt32(bitPattern: this)) + } + + var configPath: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ConfigManager_configPath_get") + func bjs_ConfigManager_configPath_get(_ self: Int32) -> Int32 + #else + func bjs_ConfigManager_configPath_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_ConfigManager_configPath_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + } + + func get(_ key: String) throws(JSException) -> JSObject { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ConfigManager_get") + func bjs_ConfigManager_get(_ self: Int32, _ key: Int32) -> Int32 + #else + func bjs_ConfigManager_get(_ self: Int32, _ key: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + var key = key + let keyId = key.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_ConfigManager_get(Int32(bitPattern: self.this.id), keyId) + if let error = _swift_js_take_exception() { + throw error + } + return JSObject(id: UInt32(bitPattern: ret)) + } + + func set(_ key: String, _ value: JSObject) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ConfigManager_set") + func bjs_ConfigManager_set(_ self: Int32, _ key: Int32, _ value: Int32) -> Void + #else + func bjs_ConfigManager_set(_ self: Int32, _ key: Int32, _ value: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + var key = key + let keyId = key.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + bjs_ConfigManager_set(Int32(bitPattern: self.this.id), keyId, Int32(bitPattern: value.id)) + if let error = _swift_js_take_exception() { + throw error + } + } + +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift new file mode 100644 index 00000000..95e9da8c --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift @@ -0,0 +1,173 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +func createTS2Skeleton() throws(JSException) -> TypeScriptProcessor { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createTS2Skeleton") + func bjs_createTS2Skeleton() -> Int32 + #else + func bjs_createTS2Skeleton() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createTS2Skeleton() + if let error = _swift_js_take_exception() { + throw error + } + return TypeScriptProcessor(takingThis: ret) +} + +func createCodeGenerator(_ format: String) throws(JSException) -> CodeGenerator { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createCodeGenerator") + func bjs_createCodeGenerator(_ format: Int32) -> Int32 + #else + func bjs_createCodeGenerator(_ format: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + var format = format + let formatId = format.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_createCodeGenerator(formatId) + if let error = _swift_js_take_exception() { + throw error + } + return CodeGenerator(takingThis: ret) +} + +struct TypeScriptProcessor { + let this: JSObject + + init(this: JSObject) { + self.this = this + } + + init(takingThis this: Int32) { + self.this = JSObject(id: UInt32(bitPattern: this)) + } + + var version: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_TypeScriptProcessor_version_get") + func bjs_TypeScriptProcessor_version_get(_ self: Int32) -> Int32 + #else + func bjs_TypeScriptProcessor_version_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_TypeScriptProcessor_version_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + } + + func convert(_ ts: String) throws(JSException) -> String { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_TypeScriptProcessor_convert") + func bjs_TypeScriptProcessor_convert(_ self: Int32, _ ts: Int32) -> Int32 + #else + func bjs_TypeScriptProcessor_convert(_ self: Int32, _ ts: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + var ts = ts + let tsId = ts.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_TypeScriptProcessor_convert(Int32(bitPattern: self.this.id), tsId) + if let error = _swift_js_take_exception() { + throw error + } + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + + func validate(_ ts: String) throws(JSException) -> Bool { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_TypeScriptProcessor_validate") + func bjs_TypeScriptProcessor_validate(_ self: Int32, _ ts: Int32) -> Int32 + #else + func bjs_TypeScriptProcessor_validate(_ self: Int32, _ ts: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + var ts = ts + let tsId = ts.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_TypeScriptProcessor_validate(Int32(bitPattern: self.this.id), tsId) + if let error = _swift_js_take_exception() { + throw error + } + return ret == 1 + } + +} + +struct CodeGenerator { + let this: JSObject + + init(this: JSObject) { + self.this = this + } + + init(takingThis this: Int32) { + self.this = JSObject(id: UInt32(bitPattern: this)) + } + + var outputFormat: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_CodeGenerator_outputFormat_get") + func bjs_CodeGenerator_outputFormat_get(_ self: Int32) -> Int32 + #else + func bjs_CodeGenerator_outputFormat_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_CodeGenerator_outputFormat_get(Int32(bitPattern: self.this.id)) + if let error = _swift_js_take_exception() { + throw error + } + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + } + + func generate(_ input: JSObject) throws(JSException) -> String { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_CodeGenerator_generate") + func bjs_CodeGenerator_generate(_ self: Int32, _ input: Int32) -> Int32 + #else + func bjs_CodeGenerator_generate(_ self: Int32, _ input: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_CodeGenerator_generate(Int32(bitPattern: self.this.id), Int32(bitPattern: input.id)) + if let error = _swift_js_take_exception() { + throw error + } + return String(unsafeUninitializedCapacity: Int(ret)) { b in + _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) + return Int(ret) + } + } + +} \ No newline at end of file From 5c96de5befa5cf872e5d518b0303e3a7820086b3 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 14:52:49 +0900 Subject: [PATCH 18/61] BridgeJS: Restore `new SwiftClass`-style constructor by using `Object.create` --- .../Sources/BridgeJSLink/BridgeJSLink.swift | 28 ++++++-------- .../BridgeJSLinkTests/Namespaces.Export.d.ts | 4 +- .../BridgeJSLinkTests/Namespaces.Export.js | 37 +++++++------------ .../BridgeJSLinkTests/SwiftClass.Export.d.ts | 2 +- .../BridgeJSLinkTests/SwiftClass.Export.js | 25 +++++-------- 5 files changed, 38 insertions(+), 58 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 73c19b69..49cabf41 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -43,18 +43,16 @@ struct BridgeJSLink { let swiftHeapObjectClassJs = """ /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __construct(ptr, deinit) { - return new SwiftHeapObject(ptr, deinit); - } - - constructor(pointer, deinit) { - this.pointer = pointer; - this.hasReleased = false; - this.deinit = deinit; - this.registry = new FinalizationRegistry((pointer) => { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { deinit(pointer); }); - this.registry.register(this, this.pointer); + obj.registry.register(this, obj.pointer); + return obj; } release() { @@ -525,13 +523,11 @@ struct BridgeJSLink { var constructorLines: [String] = [] constructorLines.append("static __construct(ptr) {") constructorLines.append( - "return new \(klass.name)(ptr, instance.exports.bjs_\(klass.name)_deinit);".indent(count: 4) + "return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_\(klass.name)_deinit, \(klass.name).prototype);" + .indent(count: 4) ) constructorLines.append("}") constructorLines.append("") - constructorLines.append("constructor(pointer, deinit) {") - constructorLines.append("super(pointer, deinit);".indent(count: 4)) - constructorLines.append("}") jsLines.append(contentsOf: constructorLines.map { $0.indent(count: 4) }) if let constructor: ExportedConstructor = klass.constructor { @@ -541,7 +537,7 @@ struct BridgeJSLink { } var funcLines: [String] = [] funcLines.append("") - funcLines.append("static init(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {") + funcLines.append("constructor(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {") let returnExpr = thunkBuilder.callConstructor(abiName: constructor.abiName) funcLines.append(contentsOf: thunkBuilder.bodyLines.map { $0.indent(count: 4) }) funcLines.append(contentsOf: thunkBuilder.cleanupLines.map { $0.indent(count: 4) }) @@ -551,7 +547,7 @@ struct BridgeJSLink { jsLines.append(contentsOf: funcLines.map { $0.indent(count: 4) }) dtsExportEntryLines.append( - "init\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));" + "constructor\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));" .indent(count: 4) ) } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts index 65b9360e..d5b901c2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts @@ -51,10 +51,10 @@ export interface UUID extends SwiftHeapObject { } export type Exports = { Greeter: { - init(name: string): Greeter; + constructor(name: string): Greeter; } Converter: { - init(): Converter; + constructor(): Converter; } UUID: { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index a9678497..df3f30de 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -60,18 +60,16 @@ export async function createInstantiator(options, swift) { const js = swift.memory.heap; /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __construct(ptr, deinit) { - return new SwiftHeapObject(ptr, deinit); - } - - constructor(pointer, deinit) { - this.pointer = pointer; - this.hasReleased = false; - this.deinit = deinit; - this.registry = new FinalizationRegistry((pointer) => { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { deinit(pointer); }); - this.registry.register(this, this.pointer); + obj.registry.register(this, obj.pointer); + return obj; } release() { @@ -81,14 +79,11 @@ export async function createInstantiator(options, swift) { } class Greeter extends SwiftHeapObject { static __construct(ptr) { - return new Greeter(ptr, instance.exports.bjs_Greeter_deinit); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); } - constructor(pointer, deinit) { - super(pointer, deinit); - } - static init(name) { + constructor(name) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); @@ -104,14 +99,11 @@ export async function createInstantiator(options, swift) { } class Converter extends SwiftHeapObject { static __construct(ptr) { - return new Converter(ptr, instance.exports.bjs_Converter_deinit); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Converter_deinit, Converter.prototype); } - constructor(pointer, deinit) { - super(pointer, deinit); - } - static init() { + constructor() { const ret = instance.exports.bjs_Converter_init(); return Converter.__construct(ret); } @@ -124,12 +116,9 @@ export async function createInstantiator(options, swift) { } class UUID extends SwiftHeapObject { static __construct(ptr) { - return new UUID(ptr, instance.exports.bjs_UUID_deinit); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_UUID_deinit, UUID.prototype); } - constructor(pointer, deinit) { - super(pointer, deinit); - } uuidString() { instance.exports.bjs_UUID_uuidString(this.pointer); const ret = tmpRetString; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts index 65391433..8c680fbc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts @@ -17,7 +17,7 @@ export interface Greeter extends SwiftHeapObject { } export type Exports = { Greeter: { - init(name: string): Greeter; + constructor(name: string): Greeter; } takeGreeter(greeter: Greeter): void; } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index 9f79f20f..379e76a4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -60,18 +60,16 @@ export async function createInstantiator(options, swift) { const js = swift.memory.heap; /// Represents a Swift heap object like a class instance or an actor instance. class SwiftHeapObject { - static __construct(ptr, deinit) { - return new SwiftHeapObject(ptr, deinit); - } - - constructor(pointer, deinit) { - this.pointer = pointer; - this.hasReleased = false; - this.deinit = deinit; - this.registry = new FinalizationRegistry((pointer) => { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { deinit(pointer); }); - this.registry.register(this, this.pointer); + obj.registry.register(this, obj.pointer); + return obj; } release() { @@ -81,14 +79,11 @@ export async function createInstantiator(options, swift) { } class Greeter extends SwiftHeapObject { static __construct(ptr) { - return new Greeter(ptr, instance.exports.bjs_Greeter_deinit); + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Greeter_deinit, Greeter.prototype); } - constructor(pointer, deinit) { - super(pointer, deinit); - } - static init(name) { + constructor(name) { const nameBytes = textEncoder.encode(name); const nameId = swift.memory.retain(nameBytes); const ret = instance.exports.bjs_Greeter_init(nameId, nameBytes.length); From 0deecb2c7092d0201c6618f30493287a448c4408 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 14:53:38 +0900 Subject: [PATCH 19/61] Revert "BridgeJS: Update tests to use new static init() API" This reverts commit 326e5937c41b113d816f4ab23d861142e4c3faba. --- Tests/prelude.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index ddb232d2..6a26dc8a 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -115,7 +115,7 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.equal(exports.roundTripString(v), v); } - const g = exports.Greeter.init("John"); + const g = new exports.Greeter("John"); assert.equal(g.greet(), "Hello, John!"); g.changeName("Jane"); assert.equal(g.greet(), "Hello, Jane!"); From 87f0a4de2ffc06a18ad3ee516123cd86c7aaa610 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 14:57:30 +0900 Subject: [PATCH 20/61] Revert "BridgeJS: Update examples and documentation for `@JS init`" This reverts commit ed482fe4630e4733b3f654fda6eb3095bddb449d. --- Examples/ExportSwift/index.js | 2 +- Examples/PlayBridgeJS/Sources/JavaScript/app.js | 4 ++-- .../Articles/Exporting-Swift-to-JavaScript.md | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/ExportSwift/index.js b/Examples/ExportSwift/index.js index fcf7c983..4c5576b2 100644 --- a/Examples/ExportSwift/index.js +++ b/Examples/ExportSwift/index.js @@ -2,7 +2,7 @@ import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; const { exports } = await init({}); const Greeter = exports.Greeter; -const greeter = Greeter.init("World"); +const greeter = new Greeter("World"); const circle = exports.renderCircleSVG(100); // Display the results diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index 30179791..b14db79b 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -52,7 +52,7 @@ export class BridgeJSPlayground { createTS2Skeleton: this.createTS2Skeleton } }); - this.playBridgeJS = exports.PlayBridgeJS.init(); + this.playBridgeJS = new exports.PlayBridgeJS(); console.log('BridgeJS initialized successfully'); } catch (error) { console.error('Failed to initialize BridgeJS:', error); @@ -162,4 +162,4 @@ export class BridgeJSPlayground { hideError() { this.errorDisplay.classList.remove('show'); } -} +} \ No newline at end of file diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md index 6de7db0b..6ce30772 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md @@ -133,7 +133,7 @@ In JavaScript: import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; const { exports } = await init({}); -const cart = exports.ShoppingCart.init(); +const cart = new exports.ShoppingCart(); cart.addItem("Laptop", 999.99, 1); cart.addItem("Mouse", 24.99, 2); console.log(`Items in cart: ${cart.getItemCount()}`); @@ -158,7 +158,7 @@ export interface ShoppingCart extends SwiftHeapObject { export type Exports = { ShoppingCart: { - init(): ShoppingCart; + new(): ShoppingCart; } } ``` @@ -175,8 +175,8 @@ You can export functions to specific namespaces by providing a namespace paramet import JavaScriptKit // Export a function to a custom namespace -@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { - return "namespaced" +@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { + return "namespaced" } ``` From 72f26a6e37f9337ce32dc733c81b820750e18775 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 16:23:05 +0900 Subject: [PATCH 21/61] PackageToJS: Fix example tests on macOS --- Plugins/PackageToJS/Tests/ExampleTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift index e635adfc..605f5908 100644 --- a/Plugins/PackageToJS/Tests/ExampleTests.swift +++ b/Plugins/PackageToJS/Tests/ExampleTests.swift @@ -244,7 +244,7 @@ extension Trait where Self == ConditionTrait { try runProcess(which("npm"), ["install"], [:]) try runProcess(which("npx"), ["playwright", "install", "chromium-headless-shell"], [:]) - try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test"], [:]) + try runSwift(["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test"], [:]) try withTemporaryDirectory(body: { tempDir, _ in let scriptContent = """ const fs = require('fs'); @@ -255,7 +255,7 @@ extension Trait where Self == ConditionTrait { try scriptContent.write(to: tempDir.appending(path: "script.js"), atomically: true, encoding: .utf8) let scriptPath = tempDir.appending(path: "script.js") try runSwift( - ["package", "--swift-sdk", swiftSDKID, "js", "test", "-Xnode=--require=\(scriptPath.path)"], + ["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", "-Xnode=--require=\(scriptPath.path)"], [:] ) let testPath = tempDir.appending(path: "test.txt") @@ -265,7 +265,7 @@ extension Trait where Self == ConditionTrait { "test.txt should be created by the script" ) }) - try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test", "--environment", "browser"], [:]) + try runSwift(["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", "--environment", "browser"], [:]) } } From f633ef2b7562de693440c58e2526dccd89d45ad2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 16:46:25 +0900 Subject: [PATCH 22/61] BridgeJS: Generate ConvertibleToJSValue extensions for exported Swift classes @JS classes now automatically support conversion to JSValue, enabling seamless interoperability between Swift and JavaScript. --- .../JavaScript/BridgeJS.ExportSwift.json | 3 +- .../Generated/BridgeJS.ExportSwift.swift | 16 ++ .../JavaScript/BridgeJS.ExportSwift.json | 3 +- .../BridgeJSBuildPlugin.swift | 2 + .../BridgeJSCommandPlugin.swift | 2 + .../Sources/BridgeJSCore/ExportSwift.swift | 39 ++++- .../Sources/BridgeJSLink/BridgeJSLink.swift | 36 ++++- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 4 +- .../Sources/BridgeJSTool/BridgeJSTool.swift | 6 +- .../BridgeJSToolTests/BridgeJSLinkTests.swift | 2 +- .../BridgeJSToolTests/ExportSwiftTests.swift | 2 +- .../ArrayParameter.Import.js | 3 +- .../BridgeJSLinkTests/Interface.Import.js | 3 +- .../MultipleImportedTypes.Import.js | 3 +- .../BridgeJSLinkTests/Namespaces.Export.js | 16 ++ .../PrimitiveParameters.Export.js | 1 + .../PrimitiveParameters.Import.js | 3 +- .../PrimitiveReturn.Export.js | 1 + .../PrimitiveReturn.Import.js | 3 +- .../StringParameter.Export.js | 1 + .../StringParameter.Import.js | 3 +- .../BridgeJSLinkTests/StringReturn.Export.js | 1 + .../BridgeJSLinkTests/StringReturn.Import.js | 3 +- .../BridgeJSLinkTests/SwiftClass.Export.js | 8 + .../TS2SkeletonLike.Import.js | 3 +- .../BridgeJSLinkTests/Throws.Export.js | 1 + .../BridgeJSLinkTests/TypeAlias.Import.js | 3 +- .../TypeScriptClass.Import.js | 3 +- .../VoidParameterVoidReturn.Export.js | 1 + .../VoidParameterVoidReturn.Import.js | 3 +- .../ExportSwiftTests/Namespaces.json | 3 +- .../ExportSwiftTests/Namespaces.swift | 24 +++ .../ExportSwiftTests/Namespaces.swift.actual | 140 ++++++++++++++++++ .../ExportSwiftTests/PrimitiveParameters.json | 3 +- .../ExportSwiftTests/PrimitiveReturn.json | 3 +- .../ExportSwiftTests/StringParameter.json | 3 +- .../ExportSwiftTests/StringReturn.json | 3 +- .../ExportSwiftTests/SwiftClass.json | 3 +- .../ExportSwiftTests/SwiftClass.swift | 8 + .../ExportSwiftTests/SwiftClass.swift.actual | 73 +++++++++ .../ExportSwiftTests/Throws.json | 3 +- .../VoidParameterVoidReturn.json | 3 +- Plugins/PackageToJS/Tests/ExampleTests.swift | 10 +- .../BridgeJSRuntimeTests/ExportAPITests.swift | 14 ++ .../Generated/BridgeJS.ExportSwift.swift | 49 ++++++ .../JavaScript/BridgeJS.ExportSwift.json | 59 +++++++- 46 files changed, 548 insertions(+), 31 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift.actual create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift.actual diff --git a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json index f0fd49e5..2e94644d 100644 --- a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -19,5 +19,6 @@ } } } - ] + ], + "moduleName" : "Benchmarks" } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift index b0656df9..3936b254 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift @@ -56,6 +56,14 @@ public func _bjs_PlayBridgeJS_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } +extension PlayBridgeJS: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJS_wrap") + func _bjs_PlayBridgeJS_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJS_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + @_expose(wasm, "bjs_PlayBridgeJSOutput_outputJs") @_cdecl("bjs_PlayBridgeJSOutput_outputJs") public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> Void { @@ -112,4 +120,12 @@ public func _bjs_PlayBridgeJSOutput_exportSwiftGlue(_self: UnsafeMutableRawPoint @_cdecl("bjs_PlayBridgeJSOutput_deinit") public func _bjs_PlayBridgeJSOutput_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() +} + +extension PlayBridgeJSOutput: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJSOutput_wrap") + func _bjs_PlayBridgeJSOutput_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJSOutput_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json index c4d55d27..e83af9fe 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -120,5 +120,6 @@ ], "functions" : [ - ] + ], + "moduleName" : "PlayBridgeJS" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift b/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift index 8353b5c4..422393d8 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift @@ -42,6 +42,8 @@ struct BridgeJSBuildPlugin: BuildToolPlugin { executable: try context.tool(named: "BridgeJSTool").url, arguments: [ "export", + "--module-name", + target.name, "--output-skeleton", outputSkeletonPath.path, "--output-swift", diff --git a/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift b/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift index 88222a0e..d3a5a6c1 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift @@ -105,6 +105,8 @@ extension BridgeJSCommandPlugin.Context { try runBridgeJSTool( arguments: [ "export", + "--module-name", + target.name, "--output-skeleton", generatedJavaScriptDirectory.appending(path: "BridgeJS.ExportSwift.json").path, "--output-swift", diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index b8b7d603..d550975e 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -16,13 +16,15 @@ import BridgeJSSkeleton /// JavaScript glue code and TypeScript definitions. public class ExportSwift { let progress: ProgressReporting + let moduleName: String private var exportedFunctions: [ExportedFunction] = [] private var exportedClasses: [ExportedClass] = [] private var typeDeclResolver: TypeDeclResolver = TypeDeclResolver() - public init(progress: ProgressReporting) { + public init(progress: ProgressReporting, moduleName: String) { self.progress = progress + self.moduleName = moduleName } /// Processes a Swift source file to find declarations marked with @JS @@ -53,7 +55,7 @@ public class ExportSwift { } return ( outputSwift: outputSwift, - outputSkeleton: ExportedSkeleton(functions: exportedFunctions, classes: exportedClasses) + outputSkeleton: ExportedSkeleton(moduleName: moduleName, functions: exportedFunctions, classes: exportedClasses) ) } @@ -676,8 +678,41 @@ public class ExportSwift { ) } + // Generate ConvertibleToJSValue extension + decls.append(renderConvertibleToJSValueExtension(klass: klass)) + return decls } + + /// Generates a ConvertibleToJSValue extension for the exported class + /// + /// # Example + /// + /// For a class named `Greeter`, this generates: + /// + /// ```swift + /// extension Greeter: ConvertibleToJSValue { + /// var jsValue: JSValue { + /// @_extern(wasm, module: "MyModule", name: "bjs_Greeter_wrap") + /// func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + /// return JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque()))) + /// } + /// } + /// ``` + func renderConvertibleToJSValueExtension(klass: ExportedClass) -> DeclSyntax { + let wrapFunctionName = "_bjs_\(klass.name)_wrap" + let externFunctionName = "bjs_\(klass.name)_wrap" + + return """ + extension \(raw: klass.name): ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "\(raw: moduleName)", name: "\(raw: externFunctionName)") + func \(raw: wrapFunctionName)(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: \(raw: wrapFunctionName)(Unmanaged.passRetained(self).toOpaque())))) + } + } + """ + } } extension AttributeListSyntax { diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 49cabf41..fbf7b285 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -203,6 +203,7 @@ struct BridgeJSLink { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } + \(renderSwiftClassWrappers().map { $0.indent(count: 12) }.joined(separator: "\n")) \(importObjectBuilders.flatMap { $0.importedLines }.map { $0.indent(count: 12) }.joined(separator: "\n")) }, setInstance: (i) => { @@ -249,6 +250,39 @@ struct BridgeJSLink { return (outputJs, outputDts) } + private func renderSwiftClassWrappers() -> [String] { + var wrapperLines: [String] = [] + var modulesByName: [String: [ExportedClass]] = [:] + + // Group classes by their module name + for skeleton in exportedSkeletons { + if skeleton.classes.isEmpty { continue } + + if modulesByName[skeleton.moduleName] == nil { + modulesByName[skeleton.moduleName] = [] + } + modulesByName[skeleton.moduleName]?.append(contentsOf: skeleton.classes) + } + + // Generate wrapper functions for each module + for (moduleName, classes) in modulesByName { + wrapperLines.append("// Wrapper functions for module: \(moduleName)") + wrapperLines.append("if (!importObject[\"\(moduleName)\"]) {") + wrapperLines.append(" importObject[\"\(moduleName)\"] = {};") + wrapperLines.append("}") + + for klass in classes { + let wrapperFunctionName = "bjs_\(klass.name)_wrap" + wrapperLines.append("importObject[\"\(moduleName)\"][\"\(wrapperFunctionName)\"] = function(pointer) {") + wrapperLines.append(" const obj = \(klass.name).__construct(pointer);") + wrapperLines.append(" return swift.memory.retain(obj);") + wrapperLines.append("};") + } + } + + return wrapperLines + } + private func generateImportedTypeDefinitions() -> [String] { var typeDefinitions: [String] = [] @@ -736,7 +770,7 @@ struct BridgeJSLink { init(moduleName: String) { self.moduleName = moduleName - importedLines.append("const \(moduleName) = importObject[\"\(moduleName)\"] = {};") + importedLines.append("const \(moduleName) = importObject[\"\(moduleName)\"] = importObject[\"\(moduleName)\"] || {};") } func assignToImportObject(name: String, function: [String]) { diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index a0a86003..a3c5b401 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -93,10 +93,12 @@ public struct ExportedConstructor: Codable { } public struct ExportedSkeleton: Codable { + public let moduleName: String public let functions: [ExportedFunction] public let classes: [ExportedClass] - public init(functions: [ExportedFunction], classes: [ExportedClass]) { + public init(moduleName: String, functions: [ExportedFunction], classes: [ExportedClass]) { + self.moduleName = moduleName self.functions = functions self.classes = classes } diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift index bdeae3c3..fe48a3f8 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift @@ -149,6 +149,10 @@ import TS2Skeleton let parser = ArgumentParser( singleDashOptions: [:], doubleDashOptions: [ + "module-name": OptionRule( + help: "The name of the module for external function references", + required: true + ), "output-skeleton": OptionRule( help: "The output file path for the skeleton of the exported Swift APIs", required: true @@ -168,7 +172,7 @@ import TS2Skeleton arguments: Array(arguments.dropFirst()) ) let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true") - let exporter = ExportSwift(progress: progress) + let exporter = ExportSwift(progress: progress, moduleName: doubleDashOptions["module-name"]!) for inputFile in positionalArguments.sorted() { let sourceURL = URL(fileURLWithPath: inputFile) guard sourceURL.pathExtension == "swift" else { continue } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift index 925a9757..51131989 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift @@ -46,7 +46,7 @@ import Testing func snapshotExport(input: String) throws { let url = Self.inputsDirectory.appendingPathComponent(input) let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) - let swiftAPI = ExportSwift(progress: .silent) + let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule") try swiftAPI.addSourceFile(sourceFile, input) let name = url.deletingPathExtension().lastPathComponent diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift index 8351d105..e184116f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ExportSwiftTests.swift @@ -47,7 +47,7 @@ import Testing @Test(arguments: collectInputs()) func snapshot(input: String) throws { - let swiftAPI = ExportSwift(progress: .silent) + let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule") let url = Self.inputsDirectory.appendingPathComponent(input) let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8)) try swiftAPI.addSourceFile(sourceFile, input) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js index c90b3919..bd506d33 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkArray"] = function bjs_checkArray(a) { try { options.imports.checkArray(swift.memory.getObject(a)); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js index 4d88bcdb..9c961feb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_returnAnimatable"] = function bjs_returnAnimatable() { try { let ret = options.imports.returnAnimatable(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js index f65188f0..3f80c21a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_createDatabaseConnection"] = function bjs_createDatabaseConnection(config) { try { let ret = options.imports.createDatabaseConnection(swift.memory.getObject(config)); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index df3f30de..56cbcb09 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -46,6 +46,22 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_Greeter_wrap"] = function(pointer) { + const obj = Greeter.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_Converter_wrap"] = function(pointer) { + const obj = Converter.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_UUID_wrap"] = function(pointer) { + const obj = UUID.__construct(pointer); + return swift.memory.retain(obj); + }; }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js index 1f4d6cbc..d245f73b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index c6413b6b..ab897a1e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_check"] = function bjs_check(a, b) { try { options.imports.check(a, b); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index 01dbcb74..4bb1e573 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index 2a3292bc..81679496 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkNumber"] = function bjs_checkNumber() { try { let ret = options.imports.checkNumber(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js index 7c2e883d..d3923891 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js index c12e6c1e..d713168e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkString"] = function bjs_checkString(a) { try { const aObject = swift.memory.getObject(a); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js index 0362f3c4..945d552f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js index bb78c255..fee2ae58 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkString"] = function bjs_checkString() { try { let ret = options.imports.checkString(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index 379e76a4..fea990fe 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -46,6 +46,14 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_Greeter_wrap"] = function(pointer) { + const obj = Greeter.__construct(pointer); + return swift.memory.retain(obj); + }; }, setInstance: (i) => { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js index d86f66dc..7adbaf59 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_createTS2Skeleton"] = function bjs_createTS2Skeleton() { try { let ret = options.imports.createTS2Skeleton(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js index d0f9b623..940c24f7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js index 30639b4a..aa9859e5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkSimple"] = function bjs_checkSimple(a) { try { options.imports.checkSimple(a); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index f5cdc9ef..a99436ab 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_Greeter_init"] = function bjs_Greeter_init(name) { try { const nameObject = swift.memory.getObject(name); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js index c7086eda..f02f8648 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(id); } + }, setInstance: (i) => { instance = i; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js index 2482082c..7983b25f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - const TestModule = importObject["TestModule"] = {}; + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_check"] = function bjs_check() { try { options.imports.check(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json index 2a6440f1..1d1b0fbe 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json @@ -149,5 +149,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift index fba15b29..21937c6c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift @@ -66,6 +66,14 @@ public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } +extension Greeter: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "TestModule", name: "bjs_Greeter_wrap") + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + @_expose(wasm, "bjs_Converter_init") @_cdecl("bjs_Converter_init") public func _bjs_Converter_init() -> UnsafeMutableRawPointer { @@ -96,6 +104,14 @@ public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } +extension Converter: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "TestModule", name: "bjs_Converter_wrap") + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + @_expose(wasm, "bjs_UUID_uuidString") @_cdecl("bjs_UUID_uuidString") public func _bjs_UUID_uuidString(_self: UnsafeMutableRawPointer) -> Void { @@ -113,4 +129,12 @@ public func _bjs_UUID_uuidString(_self: UnsafeMutableRawPointer) -> Void { @_cdecl("bjs_UUID_deinit") public func _bjs_UUID_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() +} + +extension UUID: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "TestModule", name: "bjs_UUID_wrap") + func _bjs_UUID_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_UUID_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift.actual b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift.actual new file mode 100644 index 00000000..1721e579 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift.actual @@ -0,0 +1,140 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_plainFunction") +@_cdecl("bjs_plainFunction") +public func _bjs_plainFunction() -> Void { + #if arch(wasm32) + var ret = plainFunction() + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_namespacedFunction") +@_cdecl("bjs_namespacedFunction") +public func _bjs_namespacedFunction() -> Void { + #if arch(wasm32) + var ret = namespacedFunction() + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_init") +@_cdecl("bjs_Greeter_init") +public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in + _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) + return Int(nameLen) + } + let ret = Greeter(name: name) + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_greet") +@_cdecl("bjs_Greeter_greet") +public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_deinit") +@_cdecl("bjs_Greeter_deinit") +public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Greeter: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "JavaScriptKit", name: "bjs_Greeter_wrap") + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + return JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque()))) + } +} + +@_expose(wasm, "bjs_Converter_init") +@_cdecl("bjs_Converter_init") +public func _bjs_Converter_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Converter() + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_toString") +@_cdecl("bjs_Converter_toString") +public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int(value)) + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_deinit") +@_cdecl("bjs_Converter_deinit") +public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Converter: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "JavaScriptKit", name: "bjs_Converter_wrap") + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + return JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque()))) + } +} + +@_expose(wasm, "bjs_UUID_uuidString") +@_cdecl("bjs_UUID_uuidString") +public func _bjs_UUID_uuidString(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().uuidString() + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_UUID_deinit") +@_cdecl("bjs_UUID_deinit") +public func _bjs_UUID_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension UUID: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "JavaScriptKit", name: "bjs_UUID_wrap") + func _bjs_UUID_wrap(_: UnsafeMutableRawPointer) -> Int32 + return JSObject(id: UInt32(bitPattern: _bjs_UUID_wrap(Unmanaged.passRetained(self).toOpaque()))) + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json index 23fdeab8..7ba4d9dc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json @@ -54,5 +54,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json index f517c68a..54e00ea5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json @@ -67,5 +67,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json index a86fb67e..c2286d12 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json @@ -27,5 +27,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json index b5536572..23331875 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json @@ -19,5 +19,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json index d37a9254..489f1cd5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json @@ -89,5 +89,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift index d8ca05f2..09589de3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift @@ -62,4 +62,12 @@ public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: I @_cdecl("bjs_Greeter_deinit") public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() +} + +extension Greeter: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "TestModule", name: "bjs_Greeter_wrap") + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift.actual b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift.actual new file mode 100644 index 00000000..5342a7dc --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift.actual @@ -0,0 +1,73 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_takeGreeter") +@_cdecl("bjs_takeGreeter") +public func _bjs_takeGreeter(greeter: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + takeGreeter(greeter: Unmanaged.fromOpaque(greeter).takeUnretainedValue()) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_init") +@_cdecl("bjs_Greeter_init") +public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in + _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) + return Int(nameLen) + } + let ret = Greeter(name: name) + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_greet") +@_cdecl("bjs_Greeter_greet") +public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_changeName") +@_cdecl("bjs_Greeter_changeName") +public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { + #if arch(wasm32) + let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in + _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) + return Int(nameLen) + } + Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: name) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_deinit") +@_cdecl("bjs_Greeter_deinit") +public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Greeter: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "JavaScriptKit", name: "bjs_Greeter_wrap") + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + return JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque()))) + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json index 05363283..9acf5b20 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json @@ -19,5 +19,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json index 96f875ab..12c73531 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json @@ -19,5 +19,6 @@ } } } - ] + ], + "moduleName" : "TestModule" } \ No newline at end of file diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift index 605f5908..4de602f1 100644 --- a/Plugins/PackageToJS/Tests/ExampleTests.swift +++ b/Plugins/PackageToJS/Tests/ExampleTests.swift @@ -255,7 +255,10 @@ extension Trait where Self == ConditionTrait { try scriptContent.write(to: tempDir.appending(path: "script.js"), atomically: true, encoding: .utf8) let scriptPath = tempDir.appending(path: "script.js") try runSwift( - ["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", "-Xnode=--require=\(scriptPath.path)"], + [ + "package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", + "-Xnode=--require=\(scriptPath.path)", + ], [:] ) let testPath = tempDir.appending(path: "test.txt") @@ -265,7 +268,10 @@ extension Trait where Self == ConditionTrait { "test.txt should be created by the script" ) }) - try runSwift(["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", "--environment", "browser"], [:]) + try runSwift( + ["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", "--environment", "browser"], + [:] + ) } } diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 7b561f25..591e5c93 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -99,6 +99,20 @@ struct TestError: Error { return calc.add(a: calc.square(value: x), b: y) } +@JS func testGreeterToJSValue() -> JSObject { + let greeter = Greeter(name: "Test") + return greeter.jsValue.object! +} + +@JS func testCalculatorToJSValue() -> JSObject { + let calc = Calculator() + return calc.jsValue.object! +} + +@JS func testSwiftClassAsJSValue(greeter: Greeter) -> JSObject { + return greeter.jsValue.object! +} + class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 41f0d882..ba040de5 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -336,6 +336,39 @@ public func _bjs_useCalculator(calc: UnsafeMutableRawPointer, x: Int32, y: Int32 #endif } +@_expose(wasm, "bjs_testGreeterToJSValue") +@_cdecl("bjs_testGreeterToJSValue") +public func _bjs_testGreeterToJSValue() -> Int32 { + #if arch(wasm32) + let ret = testGreeterToJSValue() + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testCalculatorToJSValue") +@_cdecl("bjs_testCalculatorToJSValue") +public func _bjs_testCalculatorToJSValue() -> Int32 { + #if arch(wasm32) + let ret = testCalculatorToJSValue() + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testSwiftClassAsJSValue") +@_cdecl("bjs_testSwiftClassAsJSValue") +public func _bjs_testSwiftClassAsJSValue(greeter: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = testSwiftClassAsJSValue(greeter: Unmanaged.fromOpaque(greeter).takeUnretainedValue()) + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { @@ -384,6 +417,14 @@ public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } +extension Greeter: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Greeter_wrap") + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + @_expose(wasm, "bjs_Calculator_square") @_cdecl("bjs_Calculator_square") public func _bjs_Calculator_square(_self: UnsafeMutableRawPointer, value: Int32) -> Int32 { @@ -410,4 +451,12 @@ public func _bjs_Calculator_add(_self: UnsafeMutableRawPointer, a: Int32, b: Int @_cdecl("bjs_Calculator_deinit") public func _bjs_Calculator_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() +} + +extension Calculator: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Calculator_wrap") + func _bjs_Calculator_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_Calculator_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index ad759cec..c23b0b2e 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -535,8 +535,65 @@ "returnType" : { "int" : { + } + } + }, + { + "abiName" : "bjs_testGreeterToJSValue", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "testGreeterToJSValue", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + + } + } + }, + { + "abiName" : "bjs_testCalculatorToJSValue", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "testCalculatorToJSValue", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + + } + } + }, + { + "abiName" : "bjs_testSwiftClassAsJSValue", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "testSwiftClassAsJSValue", + "parameters" : [ + { + "label" : "greeter", + "name" : "greeter", + "type" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + ], + "returnType" : { + "jsObject" : { + } } } - ] + ], + "moduleName" : "BridgeJSRuntimeTests" } \ No newline at end of file From d3b1f400f0f82d4bd326c682451136a865a37301 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 16:52:02 +0900 Subject: [PATCH 23/61] Format Swift code --- .../Sources/BridgeJSCore/ExportSwift.swift | 6 +++++- .../Sources/BridgeJSLink/BridgeJSLink.swift | 14 ++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index d550975e..b72561fa 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -55,7 +55,11 @@ public class ExportSwift { } return ( outputSwift: outputSwift, - outputSkeleton: ExportedSkeleton(moduleName: moduleName, functions: exportedFunctions, classes: exportedClasses) + outputSkeleton: ExportedSkeleton( + moduleName: moduleName, + functions: exportedFunctions, + classes: exportedClasses + ) ) } diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index fbf7b285..87b08d17 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -253,24 +253,24 @@ struct BridgeJSLink { private func renderSwiftClassWrappers() -> [String] { var wrapperLines: [String] = [] var modulesByName: [String: [ExportedClass]] = [:] - + // Group classes by their module name for skeleton in exportedSkeletons { if skeleton.classes.isEmpty { continue } - + if modulesByName[skeleton.moduleName] == nil { modulesByName[skeleton.moduleName] = [] } modulesByName[skeleton.moduleName]?.append(contentsOf: skeleton.classes) } - + // Generate wrapper functions for each module for (moduleName, classes) in modulesByName { wrapperLines.append("// Wrapper functions for module: \(moduleName)") wrapperLines.append("if (!importObject[\"\(moduleName)\"]) {") wrapperLines.append(" importObject[\"\(moduleName)\"] = {};") wrapperLines.append("}") - + for klass in classes { let wrapperFunctionName = "bjs_\(klass.name)_wrap" wrapperLines.append("importObject[\"\(moduleName)\"][\"\(wrapperFunctionName)\"] = function(pointer) {") @@ -279,7 +279,7 @@ struct BridgeJSLink { wrapperLines.append("};") } } - + return wrapperLines } @@ -770,7 +770,9 @@ struct BridgeJSLink { init(moduleName: String) { self.moduleName = moduleName - importedLines.append("const \(moduleName) = importObject[\"\(moduleName)\"] = importObject[\"\(moduleName)\"] || {};") + importedLines.append( + "const \(moduleName) = importObject[\"\(moduleName)\"] = importObject[\"\(moduleName)\"] || {};" + ) } func assignToImportObject(name: String, function: [String]) { From 078f456261f47226872f01649d59da97e6fc0359 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 16:59:54 +0900 Subject: [PATCH 24/61] Fix Package.swift warning by excluding JavaScript files Exclude JavaScript directory from TS2Skeleton target to resolve Swift package warning about unhandled files. --- Plugins/BridgeJS/Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Plugins/BridgeJS/Package.swift b/Plugins/BridgeJS/Package.swift index b9cd907c..9ac96d95 100644 --- a/Plugins/BridgeJS/Package.swift +++ b/Plugins/BridgeJS/Package.swift @@ -22,7 +22,8 @@ let package = Package( dependencies: [ "BridgeJSCore", "BridgeJSSkeleton", - ] + ], + exclude: ["JavaScript"] ), .target( name: "BridgeJSCore", From 218d2dffe7a69ff1e6cf3faa8de81c519a7a717d Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 17:06:40 +0900 Subject: [PATCH 25/61] Format Swift code --- Plugins/PackageToJS/Tests/ExampleTests.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift index 605f5908..4de602f1 100644 --- a/Plugins/PackageToJS/Tests/ExampleTests.swift +++ b/Plugins/PackageToJS/Tests/ExampleTests.swift @@ -255,7 +255,10 @@ extension Trait where Self == ConditionTrait { try scriptContent.write(to: tempDir.appending(path: "script.js"), atomically: true, encoding: .utf8) let scriptPath = tempDir.appending(path: "script.js") try runSwift( - ["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", "-Xnode=--require=\(scriptPath.path)"], + [ + "package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", + "-Xnode=--require=\(scriptPath.path)", + ], [:] ) let testPath = tempDir.appending(path: "test.txt") @@ -265,7 +268,10 @@ extension Trait where Self == ConditionTrait { "test.txt should be created by the script" ) }) - try runSwift(["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", "--environment", "browser"], [:]) + try runSwift( + ["package", "--disable-sandbox", "--swift-sdk", swiftSDKID, "js", "test", "--environment", "browser"], + [:] + ) } } From 76cb92d56d790ca47ac58e9954bffa66ebc21c48 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 17:52:35 +0900 Subject: [PATCH 26/61] PlayBridgeJS: Fix ExportSwift constructor call Update PlayBridgeJS to pass required moduleName parameter to ExportSwift constructor --- Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift index 8f224994..35d2340d 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift @@ -17,7 +17,7 @@ import class Foundation.JSONDecoder } func _update(swiftSource: String, dtsSource: String) throws -> PlayBridgeJSOutput { - let exportSwift = ExportSwift(progress: .silent) + let exportSwift = ExportSwift(progress: .silent, moduleName: "Playground") let sourceFile = Parser.parse(source: swiftSource) try exportSwift.addSourceFile(sourceFile, "Playground.swift") let exportResult = try exportSwift.finalize() From 48720bb0aa42be79a6f01fd3585a84a8b3364f1c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 18:17:10 +0900 Subject: [PATCH 27/61] Remove accidentally committed .actual snapshot files These test artifact files should not be committed to the repository. --- .../ExportSwiftTests/Namespaces.swift.actual | 140 ------------------ .../ExportSwiftTests/SwiftClass.swift.actual | 73 --------- 2 files changed, 213 deletions(-) delete mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift.actual delete mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift.actual diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift.actual b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift.actual deleted file mode 100644 index 1721e579..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift.actual +++ /dev/null @@ -1,140 +0,0 @@ -// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, -// DO NOT EDIT. -// -// To update this file, just rebuild your project or run -// `swift package bridge-js`. - -@_spi(BridgeJS) import JavaScriptKit - -@_expose(wasm, "bjs_plainFunction") -@_cdecl("bjs_plainFunction") -public func _bjs_plainFunction() -> Void { - #if arch(wasm32) - var ret = plainFunction() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_namespacedFunction") -@_cdecl("bjs_namespacedFunction") -public func _bjs_namespacedFunction() -> Void { - #if arch(wasm32) - var ret = namespacedFunction() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_Greeter_init") -@_cdecl("bjs_Greeter_init") -public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { - #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - let ret = Greeter(name: name) - return Unmanaged.passRetained(ret).toOpaque() - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_Greeter_greet") -@_cdecl("bjs_Greeter_greet") -public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_Greeter_deinit") -@_cdecl("bjs_Greeter_deinit") -public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { - Unmanaged.fromOpaque(pointer).release() -} - -extension Greeter: ConvertibleToJSValue { - var jsValue: JSValue { - @_extern(wasm, module: "JavaScriptKit", name: "bjs_Greeter_wrap") - func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 - return JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque()))) - } -} - -@_expose(wasm, "bjs_Converter_init") -@_cdecl("bjs_Converter_init") -public func _bjs_Converter_init() -> UnsafeMutableRawPointer { - #if arch(wasm32) - let ret = Converter() - return Unmanaged.passRetained(ret).toOpaque() - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_Converter_toString") -@_cdecl("bjs_Converter_toString") -public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { - #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int(value)) - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_Converter_deinit") -@_cdecl("bjs_Converter_deinit") -public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { - Unmanaged.fromOpaque(pointer).release() -} - -extension Converter: ConvertibleToJSValue { - var jsValue: JSValue { - @_extern(wasm, module: "JavaScriptKit", name: "bjs_Converter_wrap") - func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 - return JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque()))) - } -} - -@_expose(wasm, "bjs_UUID_uuidString") -@_cdecl("bjs_UUID_uuidString") -public func _bjs_UUID_uuidString(_self: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().uuidString() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_UUID_deinit") -@_cdecl("bjs_UUID_deinit") -public func _bjs_UUID_deinit(pointer: UnsafeMutableRawPointer) { - Unmanaged.fromOpaque(pointer).release() -} - -extension UUID: ConvertibleToJSValue { - var jsValue: JSValue { - @_extern(wasm, module: "JavaScriptKit", name: "bjs_UUID_wrap") - func _bjs_UUID_wrap(_: UnsafeMutableRawPointer) -> Int32 - return JSObject(id: UInt32(bitPattern: _bjs_UUID_wrap(Unmanaged.passRetained(self).toOpaque()))) - } -} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift.actual b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift.actual deleted file mode 100644 index 5342a7dc..00000000 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift.actual +++ /dev/null @@ -1,73 +0,0 @@ -// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, -// DO NOT EDIT. -// -// To update this file, just rebuild your project or run -// `swift package bridge-js`. - -@_spi(BridgeJS) import JavaScriptKit - -@_expose(wasm, "bjs_takeGreeter") -@_cdecl("bjs_takeGreeter") -public func _bjs_takeGreeter(greeter: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - takeGreeter(greeter: Unmanaged.fromOpaque(greeter).takeUnretainedValue()) - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_Greeter_init") -@_cdecl("bjs_Greeter_init") -public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { - #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - let ret = Greeter(name: name) - return Unmanaged.passRetained(ret).toOpaque() - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_Greeter_greet") -@_cdecl("bjs_Greeter_greet") -public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { - #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_Greeter_changeName") -@_cdecl("bjs_Greeter_changeName") -public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { - #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: name) - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_Greeter_deinit") -@_cdecl("bjs_Greeter_deinit") -public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { - Unmanaged.fromOpaque(pointer).release() -} - -extension Greeter: ConvertibleToJSValue { - var jsValue: JSValue { - @_extern(wasm, module: "JavaScriptKit", name: "bjs_Greeter_wrap") - func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 - return JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque()))) - } -} \ No newline at end of file From 6d3fb9208a670eed73b084cfa09a4a88d407b380 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 16 Aug 2025 18:11:15 +0900 Subject: [PATCH 28/61] BridgeJS: Add async function support with Promise-based interop Implements comprehensive async Swift-to-JavaScript interoperability using JSPromise.async wrapper. Includes TypeScript Promise type generation and full integration with module name architecture. --- Examples/ImportTS/index.js | 16 +- .../PlayBridgeJS/Sources/JavaScript/app.js | 6 +- Package.swift | 2 +- .../Sources/BridgeJSCore/ExportSwift.swift | 53 ++++- .../Sources/BridgeJSLink/BridgeJSLink.swift | 48 +++-- .../TS2Skeleton/JavaScript/src/index.d.ts | 5 + .../TS2Skeleton/JavaScript/src/processor.js | 35 +++- .../Tests/BridgeJSToolTests/Inputs/Async.d.ts | 7 + .../BridgeJSToolTests/Inputs/Async.swift | 19 ++ .../ArrayParameter.Import.js | 13 +- .../BridgeJSLinkTests/Async.Export.d.ts | 24 +++ .../BridgeJSLinkTests/Async.Export.js | 115 +++++++++++ .../BridgeJSLinkTests/Async.Import.d.ts | 24 +++ .../BridgeJSLinkTests/Async.Import.js | 136 +++++++++++++ .../BridgeJSLinkTests/Interface.Import.js | 9 +- .../MultipleImportedTypes.Import.js | 13 +- .../BridgeJSLinkTests/Namespaces.Export.js | 7 +- .../PrimitiveParameters.Export.js | 7 +- .../PrimitiveParameters.Import.js | 9 +- .../PrimitiveReturn.Export.js | 7 +- .../PrimitiveReturn.Import.js | 11 +- .../StringParameter.Export.js | 7 +- .../StringParameter.Import.js | 11 +- .../BridgeJSLinkTests/StringReturn.Export.js | 7 +- .../BridgeJSLinkTests/StringReturn.Import.js | 9 +- .../BridgeJSLinkTests/SwiftClass.Export.js | 7 +- .../TS2SkeletonLike.Import.js | 11 +- .../BridgeJSLinkTests/Throws.Export.js | 7 +- .../BridgeJSLinkTests/TypeAlias.Import.js | 9 +- .../TypeScriptClass.Import.js | 9 +- .../VoidParameterVoidReturn.Export.js | 7 +- .../VoidParameterVoidReturn.Import.js | 9 +- .../__Snapshots__/ExportSwiftTests/Async.json | 168 ++++++++++++++++ .../ExportSwiftTests/Async.swift | 102 ++++++++++ .../__Snapshots__/ImportTSTests/Async.swift | 123 ++++++++++++ Plugins/PackageToJS/Templates/index.d.ts | 2 +- Plugins/PackageToJS/Templates/index.js | 4 +- .../PackageToJS/Templates/instantiate.d.ts | 8 +- Plugins/PackageToJS/Templates/instantiate.js | 14 +- .../Templates/platforms/browser.d.ts | 2 +- .../Templates/platforms/browser.js | 2 +- .../Templates/platforms/browser.worker.js | 2 +- .../PackageToJS/Templates/platforms/node.js | 4 +- .../BasicObjects/JSPromise.swift | 42 ++++ .../Importing-TypeScript-into-Swift.md | 16 +- .../BridgeJSRuntimeTests/ExportAPITests.swift | 14 ++ .../Generated/BridgeJS.ExportSwift.swift | 108 ++++++++++ .../Generated/BridgeJS.ImportTS.swift | 16 ++ .../JavaScript/BridgeJS.ExportSwift.json | 184 ++++++++++++++++++ .../JavaScript/BridgeJS.ImportTS.json | 11 ++ Tests/BridgeJSRuntimeTests/bridge-js.d.ts | 2 + Tests/prelude.mjs | 127 +++++++----- 52 files changed, 1449 insertions(+), 161 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift diff --git a/Examples/ImportTS/index.js b/Examples/ImportTS/index.js index 9452b7ec..f0b81e3b 100644 --- a/Examples/ImportTS/index.js +++ b/Examples/ImportTS/index.js @@ -1,12 +1,14 @@ import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; const { exports } = await init({ - imports: { - consoleLog: (message) => { - console.log(message); - }, - getDocument: () => { - return document; - }, + getImports() { + return { + consoleLog: (message) => { + console.log(message); + }, + getDocument: () => { + return document; + }, + } } }); diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index b14db79b..89280f7b 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -48,8 +48,10 @@ export class BridgeJSPlayground { // Import the BridgeJS module const { init } = await import("../../.build/plugins/PackageToJS/outputs/Package/index.js"); const { exports } = await init({ - imports: { - createTS2Skeleton: this.createTS2Skeleton + getImports() { + return { + createTS2Skeleton: this.createTS2Skeleton + } } }); this.playBridgeJS = new exports.PlayBridgeJS(); diff --git a/Package.swift b/Package.swift index fe7f0229..44d37166 100644 --- a/Package.swift +++ b/Package.swift @@ -155,7 +155,7 @@ let package = Package( ), .testTarget( name: "BridgeJSRuntimeTests", - dependencies: ["JavaScriptKit"], + dependencies: ["JavaScriptKit", "JavaScriptEventLoop"], exclude: [ "bridge-js.config.json", "bridge-js.d.ts", diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index b72561fa..e928011a 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -453,7 +453,9 @@ public class ExportSwift { var callExpr: ExprSyntax = "\(raw: callee)(\(raw: abiParameterForwardings.map { $0.description }.joined(separator: ", ")))" if effects.isAsync { - callExpr = ExprSyntax(AwaitExprSyntax(awaitKeyword: .keyword(.await), expression: callExpr)) + callExpr = ExprSyntax( + AwaitExprSyntax(awaitKeyword: .keyword(.await).with(\.trailingTrivia, .space), expression: callExpr) + ) } if effects.isThrows { callExpr = ExprSyntax( @@ -463,6 +465,11 @@ public class ExportSwift { ) ) } + + if effects.isAsync, returnType != .void { + return CodeBlockItemSyntax(item: .init(StmtSyntax("return \(raw: callExpr).jsValue"))) + } + let retMutability = returnType == .string ? "var" : "let" if returnType == .void { return CodeBlockItemSyntax(item: .init(ExpressionStmtSyntax(expression: callExpr))) @@ -486,7 +493,40 @@ public class ExportSwift { } func lowerReturnValue(returnType: BridgeType) { - abiReturnType = returnType.abiReturnType + if effects.isAsync { + // Async functions always return a Promise, which is a JSObject + _lowerReturnValue(returnType: .jsObject(nil)) + } else { + _lowerReturnValue(returnType: returnType) + } + } + + private func _lowerReturnValue(returnType: BridgeType) { + switch returnType { + case .void: + abiReturnType = nil + case .bool: + abiReturnType = .i32 + case .int: + abiReturnType = .i32 + case .float: + abiReturnType = .f32 + case .double: + abiReturnType = .f64 + case .string: + abiReturnType = nil + case .jsObject: + abiReturnType = .i32 + case .swiftHeapObject: + // UnsafeMutableRawPointer is returned as an i32 pointer + abiReturnType = .pointer + } + + if effects.isAsync { + // The return value of async function (T of `(...) async -> T`) is + // handled by the JSPromise.async, so we don't need to do anything here. + return + } switch returnType { case .void: break @@ -527,7 +567,14 @@ public class ExportSwift { func render(abiName: String) -> DeclSyntax { let body: CodeBlockItemListSyntax - if effects.isThrows { + if effects.isAsync { + body = """ + let ret = JSPromise.async { + \(CodeBlockItemListSyntax(self.body)) + }.jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + """ + } else if effects.isThrows { body = """ do { \(CodeBlockItemListSyntax(self.body)) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 87b08d17..85b0e658 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -172,10 +172,13 @@ struct BridgeJSLink { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); tmpRetString = textDecoder.decode(bytes); @@ -294,7 +297,7 @@ struct BridgeJSLink { // Add methods for method in type.methods { let methodSignature = - "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType));" + "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: Effects(isAsync: false, isThrows: false)));" typeDefinitions.append(methodSignature.indent(count: 4)) } @@ -368,7 +371,7 @@ struct BridgeJSLink { for method in klass.methods { let methodSignature = - "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType));" + "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" dtsLines.append("\(methodSignature)".indent(count: identBaseSize * (parts.count + 2))) } @@ -394,7 +397,7 @@ struct BridgeJSLink { for function in functions { let signature = - "function \(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" + "function \(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" dtsLines.append("\(signature)".indent(count: identBaseSize * (parts.count + 1))) } @@ -446,6 +449,14 @@ struct BridgeJSLink { } func call(abiName: String, returnType: BridgeType) -> String? { + if effects.isAsync { + return _call(abiName: abiName, returnType: .jsObject(nil)) + } else { + return _call(abiName: abiName, returnType: returnType) + } + } + + private func _call(abiName: String, returnType: BridgeType) -> String? { let call = "instance.exports.\(abiName)(\(parameterForwardings.joined(separator: ", ")))" var returnExpr: String? @@ -519,8 +530,15 @@ struct BridgeJSLink { } } - private func renderTSSignature(parameters: [Parameter], returnType: BridgeType) -> String { - return "(\(parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", "))): \(returnType.tsType)" + private func renderTSSignature(parameters: [Parameter], returnType: BridgeType, effects: Effects) -> String { + let returnTypeWithEffect: String + if effects.isAsync { + returnTypeWithEffect = "Promise<\(returnType.tsType)>" + } else { + returnTypeWithEffect = returnType.tsType + } + return + "(\(parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", "))): \(returnTypeWithEffect)" } func renderExportedFunction(function: ExportedFunction) -> (js: [String], dts: [String]) { @@ -538,7 +556,7 @@ struct BridgeJSLink { ) var dtsLines: [String] = [] dtsLines.append( - "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" + "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: function.effects));" ) return (funcLines, dtsLines) @@ -581,7 +599,7 @@ struct BridgeJSLink { jsLines.append(contentsOf: funcLines.map { $0.indent(count: 4) }) dtsExportEntryLines.append( - "constructor\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name)));" + "constructor\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name), effects: constructor.effects));" .indent(count: 4) ) } @@ -603,7 +621,7 @@ struct BridgeJSLink { ).map { $0.indent(count: 4) } ) dtsTypeLines.append( - "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType));" + "\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));" .indent(count: 4) ) } @@ -712,7 +730,7 @@ struct BridgeJSLink { } func call(name: String, returnType: BridgeType) { - let call = "options.imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" + let call = "imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" if returnType == .void { bodyLines.append("\(call);") } else { @@ -721,7 +739,7 @@ struct BridgeJSLink { } func callConstructor(name: String) { - let call = "new options.imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" + let call = "new imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" bodyLines.append("let ret = \(call);") } @@ -801,9 +819,10 @@ struct BridgeJSLink { returnExpr: returnExpr, returnType: function.returnType ) + let effects = Effects(isAsync: false, isThrows: false) importObjectBuilder.appendDts( [ - "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType));" + "\(function.name)\(renderTSSignature(parameters: function.parameters, returnType: function.returnType, effects: effects));" ] ) importObjectBuilder.assignToImportObject(name: function.abiName(context: nil), function: funcLines) @@ -878,7 +897,8 @@ struct BridgeJSLink { importObjectBuilder.assignToImportObject(name: abiName, function: funcLines) importObjectBuilder.appendDts([ "\(type.name): {", - "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType));".indent(count: 4), + "new\(renderTSSignature(parameters: constructor.parameters, returnType: returnType, effects: Effects(isAsync: false, isThrows: false)));" + .indent(count: 4), "}", ]) } diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts index e1daa4af..b53e2420 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/index.d.ts @@ -12,10 +12,15 @@ export type Parameter = { type: BridgeType; } +export type Effects = { + isAsync: boolean; +} + export type ImportFunctionSkeleton = { name: string; parameters: Parameter[]; returnType: BridgeType; + effects: Effects; documentation: string | undefined; } diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js index 0f97ea14..aeaf6a2d 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js @@ -162,6 +162,7 @@ export class TypeProcessor { parameters, returnType: bridgeReturnType, documentation, + effects: { isAsync: false }, }; } @@ -341,6 +342,10 @@ export class TypeProcessor { * @private */ visitType(type, node) { + // Treat A and A as the same type + if (isTypeReference(type)) { + type = type.target; + } const maybeProcessed = this.processedTypes.get(type); if (maybeProcessed) { return maybeProcessed; @@ -364,8 +369,13 @@ export class TypeProcessor { "object": { "jsObject": {} }, "symbol": { "jsObject": {} }, "never": { "void": {} }, + "Promise": { + "jsObject": { + "_0": "JSPromise" + } + }, }; - const typeString = this.checker.typeToString(type); + const typeString = type.getSymbol()?.name ?? this.checker.typeToString(type); if (typeMap[typeString]) { return typeMap[typeString]; } @@ -377,7 +387,7 @@ export class TypeProcessor { if (this.checker.isTypeAssignableTo(type, this.checker.getStringType())) { return { "string": {} }; } - if (type.getFlags() & ts.TypeFlags.TypeParameter) { + if (type.isTypeParameter()) { return { "jsObject": {} }; } @@ -412,3 +422,24 @@ export class TypeProcessor { return undefined; } } + +/** + * @param {ts.Type} type + * @returns {type is ts.ObjectType} + */ +function isObjectType(type) { + // @ts-ignore + return typeof type.objectFlags === "number"; +} + +/** + * + * @param {ts.Type} type + * @returns {type is ts.TypeReference} + */ +function isTypeReference(type) { + return ( + isObjectType(type) && + (type.objectFlags & ts.ObjectFlags.Reference) !== 0 + ); +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts new file mode 100644 index 00000000..cb0d0cef --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.d.ts @@ -0,0 +1,7 @@ +export function asyncReturnVoid(): Promise; +export function asyncRoundTripInt(v: number): Promise; +export function asyncRoundTripString(v: string): Promise; +export function asyncRoundTripBool(v: boolean): Promise; +export function asyncRoundTripFloat(v: number): Promise; +export function asyncRoundTripDouble(v: number): Promise; +export function asyncRoundTripJSObject(v: any): Promise; \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift new file mode 100644 index 00000000..214331b3 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Async.swift @@ -0,0 +1,19 @@ +@JS func asyncReturnVoid() async {} +@JS func asyncRoundTripInt(_ v: Int) async -> Int { + return v +} +@JS func asyncRoundTripString(_ v: String) async -> String { + return v +} +@JS func asyncRoundTripBool(_ v: Bool) async -> Bool { + return v +} +@JS func asyncRoundTripFloat(_ v: Float) async -> Float { + return v +} +@JS func asyncRoundTripDouble(_ v: Double) async -> Double { + return v +} +@JS func asyncRoundTripJSObject(_ v: JSObject) async -> JSObject { + return v +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js index bd506d33..c122f179 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -50,21 +53,21 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkArray"] = function bjs_checkArray(a) { try { - options.imports.checkArray(swift.memory.getObject(a)); + imports.checkArray(swift.memory.getObject(a)); } catch (error) { setException(error); } } TestModule["bjs_checkArrayWithLength"] = function bjs_checkArrayWithLength(a, b) { try { - options.imports.checkArrayWithLength(swift.memory.getObject(a), b); + imports.checkArrayWithLength(swift.memory.getObject(a), b); } catch (error) { setException(error); } } TestModule["bjs_checkArray"] = function bjs_checkArray(a) { try { - options.imports.checkArray(swift.memory.getObject(a)); + imports.checkArray(swift.memory.getObject(a)); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts new file mode 100644 index 00000000..aecab090 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.d.ts @@ -0,0 +1,24 @@ +// 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 type Exports = { + asyncReturnVoid(): Promise; + asyncRoundTripInt(v: number): Promise; + asyncRoundTripString(v: string): Promise; + asyncRoundTripBool(v: boolean): Promise; + asyncRoundTripFloat(v: number): Promise; + asyncRoundTripDouble(v: number): Promise; + asyncRoundTripJSObject(v: any): Promise; +} +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/Async.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js new file mode 100644 index 00000000..1da2f58e --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js @@ -0,0 +1,115 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + asyncReturnVoid: function bjs_asyncReturnVoid() { + const retId = instance.exports.bjs_asyncReturnVoid(); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripInt: function bjs_asyncRoundTripInt(v) { + const retId = instance.exports.bjs_asyncRoundTripInt(v); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripString: function bjs_asyncRoundTripString(v) { + const vBytes = textEncoder.encode(v); + const vId = swift.memory.retain(vBytes); + const retId = instance.exports.bjs_asyncRoundTripString(vId, vBytes.length); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + swift.memory.release(vId); + return ret; + }, + asyncRoundTripBool: function bjs_asyncRoundTripBool(v) { + const retId = instance.exports.bjs_asyncRoundTripBool(v); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripFloat: function bjs_asyncRoundTripFloat(v) { + const retId = instance.exports.bjs_asyncRoundTripFloat(v); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripDouble: function bjs_asyncRoundTripDouble(v) { + const retId = instance.exports.bjs_asyncRoundTripDouble(v); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + asyncRoundTripJSObject: function bjs_asyncRoundTripJSObject(v) { + const retId = instance.exports.bjs_asyncRoundTripJSObject(swift.memory.retain(v)); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + }, + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts new file mode 100644 index 00000000..dea0bd18 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.d.ts @@ -0,0 +1,24 @@ +// 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 type Exports = { +} +export type Imports = { + asyncReturnVoid(): JSPromise; + asyncRoundTripInt(v: number): JSPromise; + asyncRoundTripString(v: string): JSPromise; + asyncRoundTripBool(v: boolean): JSPromise; + asyncRoundTripFloat(v: number): JSPromise; + asyncRoundTripDouble(v: number): JSPromise; + asyncRoundTripJSObject(v: any): JSPromise; +} +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/Async.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js new file mode 100644 index 00000000..21d11fa4 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js @@ -0,0 +1,136 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; + TestModule["bjs_asyncReturnVoid"] = function bjs_asyncReturnVoid() { + try { + let ret = imports.asyncReturnVoid(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripInt"] = function bjs_asyncRoundTripInt(v) { + try { + let ret = imports.asyncRoundTripInt(v); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripString"] = function bjs_asyncRoundTripString(v) { + try { + const vObject = swift.memory.getObject(v); + swift.memory.release(v); + let ret = imports.asyncRoundTripString(vObject); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripBool"] = function bjs_asyncRoundTripBool(v) { + try { + let ret = imports.asyncRoundTripBool(v); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripFloat"] = function bjs_asyncRoundTripFloat(v) { + try { + let ret = imports.asyncRoundTripFloat(v); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripDouble"] = function bjs_asyncRoundTripDouble(v) { + try { + let ret = imports.asyncRoundTripDouble(v); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_asyncRoundTripJSObject"] = function bjs_asyncRoundTripJSObject(v) { + try { + let ret = imports.asyncRoundTripJSObject(swift.memory.getObject(v)); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js index 9c961feb..f81c7e47 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -50,7 +53,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_returnAnimatable"] = function bjs_returnAnimatable() { try { - let ret = options.imports.returnAnimatable(); + let ret = imports.returnAnimatable(); return swift.memory.retain(ret); } catch (error) { setException(error); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js index 3f80c21a..394d996b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -50,7 +53,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_createDatabaseConnection"] = function bjs_createDatabaseConnection(config) { try { - let ret = options.imports.createDatabaseConnection(swift.memory.getObject(config)); + let ret = imports.createDatabaseConnection(swift.memory.getObject(config)); return swift.memory.retain(ret); } catch (error) { setException(error); @@ -61,7 +64,7 @@ export async function createInstantiator(options, swift) { try { const levelObject = swift.memory.getObject(level); swift.memory.release(level); - let ret = options.imports.createLogger(levelObject); + let ret = imports.createLogger(levelObject); return swift.memory.retain(ret); } catch (error) { setException(error); @@ -70,7 +73,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_getConfigManager"] = function bjs_getConfigManager() { try { - let ret = options.imports.getConfigManager(); + let ret = imports.getConfigManager(); return swift.memory.retain(ret); } catch (error) { setException(error); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index 56cbcb09..6915a61a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js index d245f73b..4873fc33 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index ab897a1e..3b93b2dd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -50,7 +53,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_check"] = function bjs_check(a, b) { try { - options.imports.check(a, b); + imports.check(a, b); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index 4bb1e573..53332b97 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index 81679496..1892eb46 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -50,7 +53,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkNumber"] = function bjs_checkNumber() { try { - let ret = options.imports.checkNumber(); + let ret = imports.checkNumber(); return ret; } catch (error) { setException(error); @@ -59,7 +62,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_checkBoolean"] = function bjs_checkBoolean() { try { - let ret = options.imports.checkBoolean(); + let ret = imports.checkBoolean(); return ret !== 0; } catch (error) { setException(error); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js index d3923891..ea47fb55 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js index d713168e..16ed1081 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -52,7 +55,7 @@ export async function createInstantiator(options, swift) { try { const aObject = swift.memory.getObject(a); swift.memory.release(a); - options.imports.checkString(aObject); + imports.checkString(aObject); } catch (error) { setException(error); } @@ -61,7 +64,7 @@ export async function createInstantiator(options, swift) { try { const aObject = swift.memory.getObject(a); swift.memory.release(a); - options.imports.checkStringWithLength(aObject, b); + imports.checkStringWithLength(aObject, b); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js index 945d552f..f98cea55 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js index fee2ae58..3220ae7b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -50,7 +53,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkString"] = function bjs_checkString() { try { - let ret = options.imports.checkString(); + let ret = imports.checkString(); tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; } catch (error) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index fea990fe..ab4caba3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js index 7adbaf59..705c6a37 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -50,7 +53,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_createTS2Skeleton"] = function bjs_createTS2Skeleton() { try { - let ret = options.imports.createTS2Skeleton(); + let ret = imports.createTS2Skeleton(); return swift.memory.retain(ret); } catch (error) { setException(error); @@ -61,7 +64,7 @@ export async function createInstantiator(options, swift) { try { const formatObject = swift.memory.getObject(format); swift.memory.release(format); - let ret = options.imports.createCodeGenerator(formatObject); + let ret = imports.createCodeGenerator(formatObject); return swift.memory.retain(ret); } catch (error) { setException(error); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js index 940c24f7..b2089962 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js index aa9859e5..2eb9dee5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -50,7 +53,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_checkSimple"] = function bjs_checkSimple(a) { try { - options.imports.checkSimple(a); + imports.checkSimple(a); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index a99436ab..c7d622ea 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -52,7 +55,7 @@ export async function createInstantiator(options, swift) { try { const nameObject = swift.memory.getObject(name); swift.memory.release(name); - let ret = new options.imports.Greeter(nameObject); + let ret = new imports.Greeter(nameObject); return swift.memory.retain(ret); } catch (error) { setException(error); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js index f02f8648..c200c077 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js index 7983b25f..ca497688 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js @@ -15,10 +15,13 @@ export async function createInstantiator(options, swift) { let tmpRetBytes; let tmpRetException; return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { const bjs = {}; importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { const bytes = new Uint8Array(memory.buffer, ptr, len); tmpRetString = textDecoder.decode(bytes); @@ -50,7 +53,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_check"] = function bjs_check() { try { - options.imports.check(); + imports.check(); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json new file mode 100644 index 00000000..8e715451 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json @@ -0,0 +1,168 @@ +{ + "classes" : [ + + ], + "functions" : [ + { + "abiName" : "bjs_asyncReturnVoid", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncReturnVoid", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripInt", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripInt", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripString", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripString", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripBool", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripBool", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripFloat", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripFloat", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "float" : { + + } + } + } + ], + "returnType" : { + "float" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripDouble", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripDouble", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripJSObject", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripJSObject", + "parameters" : [ + { + "label" : "_", + "name" : "v", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + + } + } + } + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift new file mode 100644 index 00000000..10a3a24d --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift @@ -0,0 +1,102 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_asyncReturnVoid") +@_cdecl("bjs_asyncReturnVoid") +public func _bjs_asyncReturnVoid() -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + await asyncReturnVoid() + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripInt") +@_cdecl("bjs_asyncRoundTripInt") +public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripInt(_: Int(v)).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripString") +@_cdecl("bjs_asyncRoundTripString") +public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + let v = String(unsafeUninitializedCapacity: Int(vLen)) { b in + _swift_js_init_memory(vBytes, b.baseAddress.unsafelyUnwrapped) + return Int(vLen) + } + return await asyncRoundTripString(_: v).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripBool") +@_cdecl("bjs_asyncRoundTripBool") +public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripBool(_: v == 1).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripFloat") +@_cdecl("bjs_asyncRoundTripFloat") +public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripFloat(_: v).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripDouble") +@_cdecl("bjs_asyncRoundTripDouble") +public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripDouble(_: v).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripJSObject") +@_cdecl("bjs_asyncRoundTripJSObject") +public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripJSObject(_: JSObject(id: UInt32(bitPattern: v))).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift new file mode 100644 index 00000000..aa11cd0c --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift @@ -0,0 +1,123 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +func asyncReturnVoid() throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncReturnVoid") + func bjs_asyncReturnVoid() -> Int32 + #else + func bjs_asyncReturnVoid() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncReturnVoid() + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise(takingThis: ret) +} + +func asyncRoundTripInt(_ v: Double) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripInt") + func bjs_asyncRoundTripInt(_ v: Float64) -> Int32 + #else + func bjs_asyncRoundTripInt(_ v: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripInt(v) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise(takingThis: ret) +} + +func asyncRoundTripString(_ v: String) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripString") + func bjs_asyncRoundTripString(_ v: Int32) -> Int32 + #else + func bjs_asyncRoundTripString(_ v: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + var v = v + let vId = v.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + let ret = bjs_asyncRoundTripString(vId) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise(takingThis: ret) +} + +func asyncRoundTripBool(_ v: Bool) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripBool") + func bjs_asyncRoundTripBool(_ v: Int32) -> Int32 + #else + func bjs_asyncRoundTripBool(_ v: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripBool(Int32(v ? 1 : 0)) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise(takingThis: ret) +} + +func asyncRoundTripFloat(_ v: Double) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripFloat") + func bjs_asyncRoundTripFloat(_ v: Float64) -> Int32 + #else + func bjs_asyncRoundTripFloat(_ v: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripFloat(v) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise(takingThis: ret) +} + +func asyncRoundTripDouble(_ v: Double) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripDouble") + func bjs_asyncRoundTripDouble(_ v: Float64) -> Int32 + #else + func bjs_asyncRoundTripDouble(_ v: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripDouble(v) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise(takingThis: ret) +} + +func asyncRoundTripJSObject(_ v: JSObject) throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_asyncRoundTripJSObject") + func bjs_asyncRoundTripJSObject(_ v: Int32) -> Int32 + #else + func bjs_asyncRoundTripJSObject(_ v: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_asyncRoundTripJSObject(Int32(bitPattern: v.id)) + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise(takingThis: ret) +} \ No newline at end of file diff --git a/Plugins/PackageToJS/Templates/index.d.ts b/Plugins/PackageToJS/Templates/index.d.ts index 77d68efd..757a8828 100644 --- a/Plugins/PackageToJS/Templates/index.d.ts +++ b/Plugins/PackageToJS/Templates/index.d.ts @@ -11,7 +11,7 @@ export type Options = { /** * The imports to use for the module */ - imports: Imports + getImports: () => Imports /* #endif */ } diff --git a/Plugins/PackageToJS/Templates/index.js b/Plugins/PackageToJS/Templates/index.js index 76721511..f44dce48 100644 --- a/Plugins/PackageToJS/Templates/index.js +++ b/Plugins/PackageToJS/Templates/index.js @@ -8,7 +8,7 @@ export async function init(_options) { const options = _options || { /* #if HAS_IMPORTS */ /** @returns {import('./instantiate.d').Imports} */ - get imports() { (() => { throw new Error("No imports provided") })() } + getImports() { (() => { throw new Error("No imports provided") })() } /* #endif */ }; let module = options.module; @@ -18,7 +18,7 @@ export async function init(_options) { const instantiateOptions = await defaultBrowserSetup({ module, /* #if HAS_IMPORTS */ - imports: options.imports, + getImports: () => options.getImports(), /* #endif */ /* #if USE_SHARED_MEMORY */ spawnWorker: createDefaultWorkerFactory() diff --git a/Plugins/PackageToJS/Templates/instantiate.d.ts b/Plugins/PackageToJS/Templates/instantiate.d.ts index 86ea6e56..9074d8d2 100644 --- a/Plugins/PackageToJS/Templates/instantiate.d.ts +++ b/Plugins/PackageToJS/Templates/instantiate.d.ts @@ -75,9 +75,13 @@ export type InstantiateOptions = { module: ModuleSource, /* #if HAS_IMPORTS */ /** - * The imports provided by the embedder + * The function to get the imports provided by the embedder */ - imports: Imports, + getImports: (importsContext: { + getInstance: () => WebAssembly.Instance | null, + getExports: () => Exports | null, + _swift: SwiftRuntime, + }) => Imports, /* #endif */ /* #if IS_WASI */ /** diff --git a/Plugins/PackageToJS/Templates/instantiate.js b/Plugins/PackageToJS/Templates/instantiate.js index 65996d86..5d6fe6b9 100644 --- a/Plugins/PackageToJS/Templates/instantiate.js +++ b/Plugins/PackageToJS/Templates/instantiate.js @@ -23,8 +23,11 @@ import { createInstantiator } from "./bridge-js.js" */ async function createInstantiator(options, swift) { return { - /** @param {WebAssembly.Imports} importObject */ - addImports: (importObject) => {}, + /** + * @param {WebAssembly.Imports} importObject + * @param {unknown} importsContext + */ + addImports: (importObject, importsContext) => {}, /** @param {WebAssembly.Instance} instance */ setInstance: (instance) => {}, /** @param {WebAssembly.Instance} instance */ @@ -93,12 +96,13 @@ async function _instantiate( /* #endif */ /* #endif */ }; - instantiator.addImports(importObject); - options.addToCoreImports?.(importObject, { + const importsContext = { getInstance: () => instance, getExports: () => exports, _swift: swift, - }); + }; + instantiator.addImports(importObject, importsContext); + options.addToCoreImports?.(importObject, importsContext); let module; let instance; diff --git a/Plugins/PackageToJS/Templates/platforms/browser.d.ts b/Plugins/PackageToJS/Templates/platforms/browser.d.ts index b851c228..babe3f48 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.d.ts +++ b/Plugins/PackageToJS/Templates/platforms/browser.d.ts @@ -8,7 +8,7 @@ export function defaultBrowserSetup(options: { onStderrLine?: (line: string) => void, /* #endif */ /* #if HAS_IMPORTS */ - imports: Imports, + getImports: () => Imports, /* #endif */ /* #if USE_SHARED_MEMORY */ spawnWorker: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, diff --git a/Plugins/PackageToJS/Templates/platforms/browser.js b/Plugins/PackageToJS/Templates/platforms/browser.js index 9afd5c94..3fce7c55 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.js +++ b/Plugins/PackageToJS/Templates/platforms/browser.js @@ -124,7 +124,7 @@ export async function defaultBrowserSetup(options) { return { module: options.module, /* #if HAS_IMPORTS */ - imports: options.imports, + getImports() { return options.getImports() }, /* #endif */ /* #if IS_WASI */ wasi: Object.assign(wasi, { diff --git a/Plugins/PackageToJS/Templates/platforms/browser.worker.js b/Plugins/PackageToJS/Templates/platforms/browser.worker.js index 42fe6a2f..a1ce626d 100644 --- a/Plugins/PackageToJS/Templates/platforms/browser.worker.js +++ b/Plugins/PackageToJS/Templates/platforms/browser.worker.js @@ -13,6 +13,6 @@ self.onmessage = async (event) => { await instantiateForThread(tid, startArg, { ...options, module, memory, - imports: {}, + getImports() { return {} }, }) } diff --git a/Plugins/PackageToJS/Templates/platforms/node.js b/Plugins/PackageToJS/Templates/platforms/node.js index aff708be..4d29fc33 100644 --- a/Plugins/PackageToJS/Templates/platforms/node.js +++ b/Plugins/PackageToJS/Templates/platforms/node.js @@ -65,7 +65,7 @@ export function createDefaultWorkerFactory(preludeScript) { await instantiateForThread(tid, startArg, { ...options, module, memory, - imports: {}, + getImports() { return {} }, }) }) `, @@ -139,7 +139,7 @@ export async function defaultNodeSetup(options) { return { module, - imports: {}, + getImports() { return {} }, /* #if IS_WASI */ wasi: Object.assign(wasi, { setInstance(instance) { diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index 24a9ae48..ec2a7724 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -23,6 +23,10 @@ public final class JSPromise: JSBridgedClass { self.init(from: jsObject) } + @_spi(BridgeJS) public convenience init(takingThis: Int32) { + self.init(unsafelyWrapping: JSObject(id: UInt32(bitPattern: takingThis))) + } + /// Creates a new `JSPromise` instance from a given JavaScript `Promise` object. If `value` /// is not an object and is not an instance of JavaScript `Promise`, this function will /// return `nil`. @@ -66,6 +70,44 @@ public final class JSPromise: JSBridgedClass { self.init(unsafelyWrapping: Self.constructor!.new(closure)) } + #if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI)) + /// Creates a new `JSPromise` instance from a given async closure. + /// + /// - Parameter body: The async closure to execute. + /// - Returns: A new `JSPromise` instance. + public static func async(body: @escaping @isolated(any) () async throws(JSException) -> Void) -> JSPromise { + self.async { () throws(JSException) -> JSValue in + try await body() + return .undefined + } + } + + /// Creates a new `JSPromise` instance from a given async closure. + /// + /// - Parameter body: The async closure to execute. + /// - Returns: A new `JSPromise` instance. + public static func async(body: @escaping @isolated(any) () async throws(JSException) -> JSValue) -> JSPromise { + JSPromise { resolver in + // NOTE: The context is fully transferred to the unstructured task + // isolation but the compiler can't prove it yet, so we need to + // use `@unchecked Sendable` to make it compile with the Swift 6 mode. + struct Context: @unchecked Sendable { + let resolver: (JSPromise.Result) -> Void + let body: () async throws(JSException) -> JSValue + } + let context = Context(resolver: resolver, body: body) + Task { + do throws(JSException) { + let result = try await context.body() + context.resolver(.success(result)) + } catch { + context.resolver(.failure(error.thrownValue)) + } + } + } + } + #endif + #if !hasFeature(Embedded) public static func resolve(_ value: ConvertibleToJSValue) -> JSPromise { self.init(unsafelyWrapping: Self.constructor!.resolve!(value).object!) diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md b/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md index 98a9c80c..853c8800 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md @@ -62,7 +62,7 @@ interface Document { // Properties title: string; readonly body: HTMLElement; - + // Methods getElementById(id: string): HTMLElement; createElement(tagName: string): HTMLElement; @@ -96,7 +96,7 @@ struct Document { struct HTMLElement { var innerText: String { get set } var className: String { get set } - + func appendChild(_ child: HTMLElement) } @@ -161,11 +161,13 @@ import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; // Initialize the WebAssembly module with JavaScript implementations const { exports } = await init({ - imports: { - consoleLog: (message) => { - console.log(message); - }, - getDocument: () => document, + getImports() { + return { + consoleLog: (message) => { + console.log(message); + }, + getDocument: () => document, + } } }); diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 591e5c93..307fa21e 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -1,5 +1,6 @@ import XCTest import JavaScriptKit +import JavaScriptEventLoop @_extern(wasm, module: "BridgeJSRuntimeTests", name: "runJsWorks") @_extern(c) @@ -49,6 +50,15 @@ struct TestError: Error { @JS func throwsWithSwiftHeapObjectResult() throws(JSException) -> Greeter { return Greeter(name: "Test") } @JS func throwsWithJSObjectResult() throws(JSException) -> JSObject { return JSObject() } +@JS func asyncRoundTripVoid() async -> Void { return } +@JS func asyncRoundTripInt(v: Int) async -> Int { return v } +@JS func asyncRoundTripFloat(v: Float) async -> Float { return v } +@JS func asyncRoundTripDouble(v: Double) async -> Double { return v } +@JS func asyncRoundTripBool(v: Bool) async -> Bool { return v } +@JS func asyncRoundTripString(v: String) async -> String { return v } +@JS func asyncRoundTripSwiftHeapObject(v: Greeter) async -> Greeter { return v } +@JS func asyncRoundTripJSObject(v: JSObject) async -> JSObject { return v } + @JS class Greeter { var name: String @@ -131,4 +141,8 @@ class ExportAPITests: XCTestCase { XCTAssertTrue(hasDeinitGreeter, "Greeter (with @JS init) should have been deinitialized") XCTAssertTrue(hasDeinitCalculator, "Calculator (without @JS init) should have been deinitialized") } + + func testAllAsync() async throws { + _ = try await runAsyncWorks().value() + } } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index ba040de5..579dd36b 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -300,6 +300,114 @@ public func _bjs_throwsWithJSObjectResult() -> Int32 { #endif } +@_expose(wasm, "bjs_asyncRoundTripVoid") +@_cdecl("bjs_asyncRoundTripVoid") +public func _bjs_asyncRoundTripVoid() -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + await asyncRoundTripVoid() + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripInt") +@_cdecl("bjs_asyncRoundTripInt") +public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripInt(v: Int(v)).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripFloat") +@_cdecl("bjs_asyncRoundTripFloat") +public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripFloat(v: v).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripDouble") +@_cdecl("bjs_asyncRoundTripDouble") +public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripDouble(v: v).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripBool") +@_cdecl("bjs_asyncRoundTripBool") +public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripBool(v: v == 1).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripString") +@_cdecl("bjs_asyncRoundTripString") +public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + let v = String(unsafeUninitializedCapacity: Int(vLen)) { b in + _swift_js_init_memory(vBytes, b.baseAddress.unsafelyUnwrapped) + return Int(vLen) + } + return await asyncRoundTripString(v: v).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripSwiftHeapObject") +@_cdecl("bjs_asyncRoundTripSwiftHeapObject") +public func _bjs_asyncRoundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripSwiftHeapObject(v: Unmanaged.fromOpaque(v).takeUnretainedValue()).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_asyncRoundTripJSObject") +@_cdecl("bjs_asyncRoundTripJSObject") +public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { + #if arch(wasm32) + let ret = JSPromise.async { + return await asyncRoundTripJSObject(v: JSObject(id: UInt32(bitPattern: v))).jsValue + } .jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_takeGreeter") @_cdecl("bjs_takeGreeter") public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift index fd558ab8..255853fa 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift @@ -142,6 +142,22 @@ func jsThrowOrString(_ shouldThrow: Bool) throws(JSException) -> String { } } +func runAsyncWorks() throws(JSException) -> JSPromise { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_runAsyncWorks") + func bjs_runAsyncWorks() -> Int32 + #else + func bjs_runAsyncWorks() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_runAsyncWorks() + if let error = _swift_js_take_exception() { + throw error + } + return JSPromise(takingThis: ret) +} + struct JsGreeter { let this: JSObject diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index c23b0b2e..97b86cec 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -447,6 +447,190 @@ } } }, + { + "abiName" : "bjs_asyncRoundTripVoid", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripVoid", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripInt", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripInt", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "int" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripFloat", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripFloat", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "float" : { + + } + } + } + ], + "returnType" : { + "float" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripDouble", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripDouble", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "double" : { + + } + } + } + ], + "returnType" : { + "double" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripBool", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripBool", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "bool" : { + + } + } + } + ], + "returnType" : { + "bool" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripString", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripString", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_asyncRoundTripSwiftHeapObject", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripSwiftHeapObject", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "Greeter" + } + } + }, + { + "abiName" : "bjs_asyncRoundTripJSObject", + "effects" : { + "isAsync" : true, + "isThrows" : false + }, + "name" : "asyncRoundTripJSObject", + "parameters" : [ + { + "label" : "v", + "name" : "v", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "jsObject" : { + + } + } + }, { "abiName" : "bjs_takeGreeter", "effects" : { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json index bf3190b8..82515fec 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ImportTS.json @@ -138,6 +138,17 @@ } } + }, + { + "name" : "runAsyncWorks", + "parameters" : [ + + ], + "returnType" : { + "jsObject" : { + "_0" : "JSPromise" + } + } } ], "types" : [ diff --git a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts index b03ef570..0f0d0155 100644 --- a/Tests/BridgeJSRuntimeTests/bridge-js.d.ts +++ b/Tests/BridgeJSRuntimeTests/bridge-js.d.ts @@ -14,3 +14,5 @@ export class JsGreeter { greet(): string; changeName(name: string): void; } + +export function runAsyncWorks(): Promise; diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 6a26dc8a..88de303a 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -6,59 +6,69 @@ export async function setupOptions(options, context) { setupTestGlobals(globalThis); return { ...options, - imports: { - "jsRoundTripVoid": () => { - return; - }, - "jsRoundTripNumber": (v) => { - return v; - }, - "jsRoundTripBool": (v) => { - return v; - }, - "jsRoundTripString": (v) => { - return v; - }, - "jsThrowOrVoid": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - }, - "jsThrowOrNumber": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - return 1; - }, - "jsThrowOrBool": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - return true; - }, - "jsThrowOrString": (shouldThrow) => { - if (shouldThrow) { - throw new Error("TestError"); - } - return "Hello, world!"; - }, - JsGreeter: class { - /** - * @param {string} name - * @param {string} prefix - */ - constructor(name, prefix) { - this.name = name; - this.prefix = prefix; - } - greet() { - return `${this.prefix}, ${this.name}!`; - } - /** @param {string} name */ - changeName(name) { - this.name = name; + getImports: (importsContext) => { + return { + "jsRoundTripVoid": () => { + return; + }, + "jsRoundTripNumber": (v) => { + return v; + }, + "jsRoundTripBool": (v) => { + return v; + }, + "jsRoundTripString": (v) => { + return v; + }, + "jsThrowOrVoid": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + }, + "jsThrowOrNumber": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return 1; + }, + "jsThrowOrBool": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return true; + }, + "jsThrowOrString": (shouldThrow) => { + if (shouldThrow) { + throw new Error("TestError"); + } + return "Hello, world!"; + }, + JsGreeter: class { + /** + * @param {string} name + * @param {string} prefix + */ + constructor(name, prefix) { + this.name = name; + this.prefix = prefix; + } + greet() { + return `${this.prefix}, ${this.name}!`; + } + /** @param {string} name */ + changeName(name) { + this.name = name; + } + }, + runAsyncWorks: async () => { + const exports = importsContext.getExports(); + if (!exports) { + throw new Error("No exports!?"); + } + BridgeJSRuntimeTests_runAsyncWorks(exports); + return; } - } + }; }, addToCoreImports(importObject, importsContext) { const { getInstance, getExports } = importsContext; @@ -68,7 +78,11 @@ export async function setupOptions(options, context) { } const bridgeJSRuntimeTests = importObject["BridgeJSRuntimeTests"] || {}; bridgeJSRuntimeTests["runJsWorks"] = () => { - return BridgeJSRuntimeTests_runJsWorks(getInstance(), getExports()); + const exports = getExports(); + if (!exports) { + throw new Error("No exports!?"); + } + return BridgeJSRuntimeTests_runJsWorks(getInstance(), exports); } importObject["BridgeJSRuntimeTests"] = bridgeJSRuntimeTests; } @@ -133,7 +147,7 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.equal(calc.square(5), 25); assert.equal(calc.add(3, 4), 7); assert.equal(exports.useCalculator(calc, 3, 10), 19); // 3^2 + 10 = 19 - + calc.release(); const anyObject = {}; @@ -153,6 +167,11 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { } } +/** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ +async function BridgeJSRuntimeTests_runAsyncWorks(exports) { + await exports.asyncRoundTripVoid(); +} + function setupTestGlobals(global) { global.globalObject1 = { prop_1: { From bb147b19457a82046352f647dc3505c5a0e73bd8 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 17 Aug 2025 14:32:39 +0900 Subject: [PATCH 29/61] Ignore .actual files generated by snapshot tests --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5aac0048..e66d976c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ Examples/*/Bundle Examples/*/package-lock.json Package.resolved Plugins/BridgeJS/Sources/JavaScript/package-lock.json +Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/**/*.actual From 99db921c20c9214f0b14b1ba181e3524d8f91a01 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 17 Aug 2025 13:13:51 +0900 Subject: [PATCH 30/61] BridgeJS: Fix TypeScript constructor signature generation Change constructor signature from 'constructor(...)' to 'new(...)' in generated TypeScript declaration files, which is the correct syntax for constructor call signatures in TypeScript. --- Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift | 2 +- .../__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts | 4 ++-- .../__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 85b0e658..1483692f 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -599,7 +599,7 @@ struct BridgeJSLink { jsLines.append(contentsOf: funcLines.map { $0.indent(count: 4) }) dtsExportEntryLines.append( - "constructor\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name), effects: constructor.effects));" + "new\(renderTSSignature(parameters: constructor.parameters, returnType: .swiftHeapObject(klass.name), effects: constructor.effects));" .indent(count: 4) ) } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts index d5b901c2..b2ccecc4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts @@ -51,10 +51,10 @@ export interface UUID extends SwiftHeapObject { } export type Exports = { Greeter: { - constructor(name: string): Greeter; + new(name: string): Greeter; } Converter: { - constructor(): Converter; + new(): Converter; } UUID: { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts index 8c680fbc..fd376d57 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts @@ -17,7 +17,7 @@ export interface Greeter extends SwiftHeapObject { } export type Exports = { Greeter: { - constructor(name: string): Greeter; + new(name: string): Greeter; } takeGreeter(greeter: Greeter): void; } From a57eafc62b761f1573ddf9f95b9e9aad409ae686 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 17 Aug 2025 15:07:01 +0900 Subject: [PATCH 31/61] Update arrow function syntax to fix PlayBridgeJS example --- Examples/PlayBridgeJS/Sources/JavaScript/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index 89280f7b..4d2e5ff3 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -48,10 +48,10 @@ export class BridgeJSPlayground { // Import the BridgeJS module const { init } = await import("../../.build/plugins/PackageToJS/outputs/Package/index.js"); const { exports } = await init({ - getImports() { + getImports: () => { return { createTS2Skeleton: this.createTS2Skeleton - } + }; } }); this.playBridgeJS = new exports.PlayBridgeJS(); From d2a2ca2b2e5f8f77b263ba7bc5b381bdb52b0310 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 17 Aug 2025 15:45:41 +0900 Subject: [PATCH 32/61] PlayBridgeJS: Update sample code to use `log(message: string)` instead of `log: (message: string) => void` --- Examples/PlayBridgeJS/README.md | 2 +- .../PlayBridgeJS/Sources/JavaScript/app.js | 53 +++++++++++++++---- .../PlayBridgeJS/Sources/JavaScript/editor.js | 53 ++++++++----------- .../PlayBridgeJS/Sources/JavaScript/index.js | 30 ++++++++++- 4 files changed, 95 insertions(+), 43 deletions(-) diff --git a/Examples/PlayBridgeJS/README.md b/Examples/PlayBridgeJS/README.md index 930f07c9..26a55943 100644 --- a/Examples/PlayBridgeJS/README.md +++ b/Examples/PlayBridgeJS/README.md @@ -5,5 +5,5 @@ Install Development Snapshot toolchain `DEVELOPMENT-SNAPSHOT-2024-07-08-a` from ```sh $ swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi.artifactbundle.zip $ ./build.sh -$ npx serve +$ npx serve --symlinks ``` diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index 4d2e5ff3..58beeefa 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -1,22 +1,48 @@ -// BridgeJS Playground Main Application +// @ts-check import { EditorSystem } from './editor.js'; import ts from 'typescript'; import { TypeProcessor } from './processor.js'; +/** + * @typedef {import('../../.build/plugins/PackageToJS/outputs/Package/bridge-js.js').PlayBridgeJS} PlayBridgeJS + */ + +/** + * The main controller for the BridgeJS Playground. + */ export class BridgeJSPlayground { + /** + * Creates a new instance of the BridgeJSPlayground. + */ constructor() { this.editorSystem = new EditorSystem(); + /** @type {PlayBridgeJS | null} */ this.playBridgeJS = null; + /** @type {ReturnType | null} */ this.generateTimeout = null; + /** @type {boolean} */ this.isInitialized = false; - // DOM Elements - this.errorDisplay = document.getElementById('errorDisplay'); - this.errorMessage = document.getElementById('errorMessage'); + const errorDisplay = document.getElementById('errorDisplay'); + if (!errorDisplay) { + throw new Error('Error display element not found'); + } + /** @type {HTMLElement} */ + this.errorDisplay = errorDisplay; + + const errorMessage = document.getElementById('errorMessage'); + if (!errorMessage) { + throw new Error('Error message element not found'); + } + /** @type {HTMLElement} */ + this.errorMessage = errorMessage; } - // Initialize the application - async initialize() { + /** + * Initializes the application. + * @param {{swift: string, dts: string}} sampleCode - The sample code to initialize the application with. + */ + async initialize(sampleCode) { if (this.isInitialized) { return; } @@ -32,7 +58,7 @@ export class BridgeJSPlayground { this.setupEventListeners(); // Load sample code - this.editorSystem.loadSampleCode(); + this.editorSystem.setInputs(sampleCode) this.isInitialized = true; console.log('BridgeJS Playground initialized successfully'); @@ -126,7 +152,9 @@ export class BridgeJSPlayground { } } - // Generate code through BridgeJS + /** + * Generates code through BridgeJS. + */ async generateCode() { if (!this.playBridgeJS) { this.showError('BridgeJS is not initialized'); @@ -154,13 +182,18 @@ export class BridgeJSPlayground { } } - // Show error message + /** + * Shows an error message. + * @param {string} message - The message to show. + */ showError(message) { this.errorMessage.textContent = message; this.errorDisplay.classList.add('show'); } - // Hide error message + /** + * Hides the error message. + */ hideError() { this.errorDisplay.classList.remove('show'); } diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/editor.js b/Examples/PlayBridgeJS/Sources/JavaScript/editor.js index 88a07c43..dabe7dc5 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/editor.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/editor.js @@ -1,4 +1,12 @@ +// @ts-check + +/** + * The editor system for the BridgeJS Playground. + */ export class EditorSystem { + /** + * Creates a new instance of the EditorSystem. + */ constructor() { this.editors = new Map(); this.config = { @@ -70,7 +78,9 @@ export class EditorSystem { async loadMonaco() { return new Promise((resolve) => { + // @ts-ignore require.config({ paths: { vs: 'https://unpkg.com/monaco-editor@0.45.0/min/vs' } }); + // @ts-ignore require(['vs/editor/editor.main'], resolve); }); } @@ -98,12 +108,15 @@ export class EditorSystem { return; } + // @ts-ignore const model = monaco.editor.createModel( config.placeholder, config.language, + // @ts-ignore monaco.Uri.parse(config.modelUri) ); + // @ts-ignore const editor = monaco.editor.create(element, { ...commonOptions, value: config.placeholder, @@ -140,7 +153,6 @@ export class EditorSystem { } this.updateTabStates(); - this.updateLayout(); } updateTabStates() { @@ -183,6 +195,15 @@ export class EditorSystem { }; } + /** + * Sets the inputs for the editor system. + * @param {{swift: string, dts: string}} sampleCode - The sample code to set the inputs to. + */ + setInputs({ swift, dts }) { + this.editors.get('swift')?.setValue(swift); + this.editors.get('dts')?.setValue(dts); + } + updateOutputs(result) { const outputMap = { 'import-glue': () => result.importSwiftGlue(), @@ -200,36 +221,6 @@ export class EditorSystem { }); } - loadSampleCode() { - const sampleSwift = `import JavaScriptKit - -@JS public func calculateTotal(price: Double, quantity: Int) -> Double { - return price * Double(quantity) -} - -@JS class ShoppingCart { - private var items: [(name: String, price: Double, quantity: Int)] = [] - - @JS init() {} - - @JS public func addItem(name: String, price: Double, quantity: Int) { - items.append((name, price, quantity)) - } - - @JS public func getTotal() -> Double { - return items.reduce(0) { $0 + $1.price * Double($1.quantity) } - } -}`; - - const sampleDts = `export type Console = { - log: (message: string) => void; -} -export function console(): Console;`; - - this.editors.get('swift')?.setValue(sampleSwift); - this.editors.get('dts')?.setValue(sampleDts); - } - addChangeListeners(callback) { this.config.input.forEach(config => { const editor = this.editors.get(config.key); diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/index.js b/Examples/PlayBridgeJS/Sources/JavaScript/index.js index 8983c5eb..356f9b4f 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/index.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/index.js @@ -3,11 +3,39 @@ import { BridgeJSPlayground } from './app.js'; Error.stackTraceLimit = Infinity; +const SWIFT_INPUT = `import JavaScriptKit + +@JS public func calculateTotal(price: Double, quantity: Int) -> Double { + return price * Double(quantity) +} + +@JS class ShoppingCart { + private var items: [(name: String, price: Double, quantity: Int)] = [] + + @JS init() {} + + @JS public func addItem(name: String, price: Double, quantity: Int) { + items.append((name, price, quantity)) + } + + @JS public func getTotal() -> Double { + return items.reduce(0) { $0 + $1.price * Double($1.quantity) } + } +}` + +const DTS_INPUT = `export type Console = { + log(message: string): void; +} +export function console(): Console;` + // Initialize the playground when the page loads document.addEventListener('DOMContentLoaded', async () => { try { const playground = new BridgeJSPlayground(); - await playground.initialize(); + await playground.initialize({ + swift: SWIFT_INPUT, + dts: DTS_INPUT + }); } catch (error) { console.error('Failed to initialize playground:', error); } From 64fe6740ea9d7a173e81b1d8316b502b7cddfa7e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 17 Aug 2025 16:18:38 +0900 Subject: [PATCH 33/61] PlayBridgeJS: Add code sharing feature with URL parameter compression - Add compression-based code sharing functionality using CompressionStream API - Implement encoding type versioning (enc parameter) for future extensibility - Add share button with modal dialog and copy-to-clipboard functionality - Support automatic URL parameter detection on page load - Use gzip compression with base64 encoding for URL-safe sharing - Responsive design with mobile support --- Examples/PlayBridgeJS/README.md | 2 +- .../PlayBridgeJS/Sources/JavaScript/app.js | 101 ++++++++-- .../Sources/JavaScript/code-share.js | 189 ++++++++++++++++++ .../Sources/JavaScript/styles.css | 130 ++++++++++++ Examples/PlayBridgeJS/index.html | 15 ++ 5 files changed, 421 insertions(+), 16 deletions(-) create mode 100644 Examples/PlayBridgeJS/Sources/JavaScript/code-share.js diff --git a/Examples/PlayBridgeJS/README.md b/Examples/PlayBridgeJS/README.md index 26a55943..85c5a6f9 100644 --- a/Examples/PlayBridgeJS/README.md +++ b/Examples/PlayBridgeJS/README.md @@ -4,6 +4,6 @@ Install Development Snapshot toolchain `DEVELOPMENT-SNAPSHOT-2024-07-08-a` from ```sh $ swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi.artifactbundle.zip -$ ./build.sh +$ ./build.sh release $ npx serve --symlinks ``` diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index 58beeefa..9e1d39e2 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -2,6 +2,7 @@ import { EditorSystem } from './editor.js'; import ts from 'typescript'; import { TypeProcessor } from './processor.js'; +import { CodeShareManager } from './code-share.js'; /** * @typedef {import('../../.build/plugins/PackageToJS/outputs/Package/bridge-js.js').PlayBridgeJS} PlayBridgeJS @@ -23,19 +24,20 @@ export class BridgeJSPlayground { /** @type {boolean} */ this.isInitialized = false; - const errorDisplay = document.getElementById('errorDisplay'); - if (!errorDisplay) { - throw new Error('Error display element not found'); - } - /** @type {HTMLElement} */ - this.errorDisplay = errorDisplay; - - const errorMessage = document.getElementById('errorMessage'); - if (!errorMessage) { - throw new Error('Error message element not found'); - } - /** @type {HTMLElement} */ - this.errorMessage = errorMessage; + /** @type {HTMLDivElement} */ + this.errorDisplay = /** @type {HTMLDivElement} */ (document.getElementById('errorDisplay')); + /** @type {HTMLDivElement} */ + this.errorMessage = /** @type {HTMLDivElement} */ (document.getElementById('errorMessage')); + /** @type {HTMLButtonElement} */ + this.shareButton = /** @type {HTMLButtonElement} */ (document.getElementById('shareButton')); + /** @type {HTMLDialogElement} */ + this.shareDialog = /** @type {HTMLDialogElement} */ (document.getElementById('shareDialog')); + /** @type {HTMLInputElement} */ + this.shareUrlInput = /** @type {HTMLInputElement} */ (document.getElementById('shareUrl')); + /** @type {HTMLButtonElement} */ + this.copyButton = /** @type {HTMLButtonElement} */ (document.getElementById('copyButton')); + /** @type {HTMLButtonElement} */ + this.closeShareDialogButton = /** @type {HTMLButtonElement} */ (document.getElementById('closeShareDialog')); } /** @@ -57,8 +59,14 @@ export class BridgeJSPlayground { // Set up event listeners this.setupEventListeners(); - // Load sample code - this.editorSystem.setInputs(sampleCode) + // Check for shared code in URL + const sharedCode = await CodeShareManager.extractCodeFromUrl(); + if (sharedCode) { + this.editorSystem.setInputs(sharedCode); + } else { + // Load sample code + this.editorSystem.setInputs(sampleCode); + } this.isInitialized = true; console.log('BridgeJS Playground initialized successfully'); @@ -98,6 +106,69 @@ export class BridgeJSPlayground { } this.generateTimeout = setTimeout(() => this.generateCode(), 300); }); + + // Set up share functionality + this.setupShareListeners(); + } + + // Set up share-related event listeners + setupShareListeners() { + // Show share dialog + this.shareButton.addEventListener('click', async () => { + try { + const inputs = this.editorSystem.getInputs(); + const shareUrl = await CodeShareManager.generateShareUrl(inputs); + this.shareUrlInput.value = shareUrl; + this.shareDialog.classList.remove('hidden'); + this.shareUrlInput.select(); + } catch (error) { + console.error('Failed to generate share URL:', error); + this.showError('Failed to generate share URL: ' + error.message); + } + }); + + // Copy share URL + this.copyButton.addEventListener('click', async () => { + try { + await navigator.clipboard.writeText(this.shareUrlInput.value); + + const originalText = this.copyButton.textContent; + this.copyButton.textContent = 'Copied!'; + this.copyButton.classList.add('copied'); + + setTimeout(() => { + this.copyButton.textContent = originalText; + this.copyButton.classList.remove('copied'); + }, 2000); + } catch (error) { + console.error('Failed to copy URL:', error); + this.shareUrlInput.select(); + } + }); + + // Close share dialog + this.closeShareDialogButton.addEventListener('click', () => { + this.shareDialog.classList.add('hidden'); + }); + + // Close dialog when clicking outside + document.addEventListener('click', (event) => { + if (!this.shareDialog.classList.contains('hidden')) { + const dialogContent = this.shareDialog.querySelector('.share-dialog-content'); + const target = event.target; + if (dialogContent && target instanceof Node && !dialogContent.contains(target) && + this.shareButton && !this.shareButton.contains(target)) { + this.shareDialog.classList.add('hidden'); + } + } + }); + + // Close dialog with Escape key + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && !this.shareDialog.classList.contains('hidden')) { + this.shareDialog.classList.add('hidden'); + } + }); } createTS2Skeleton() { diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/code-share.js b/Examples/PlayBridgeJS/Sources/JavaScript/code-share.js new file mode 100644 index 00000000..61d6ee95 --- /dev/null +++ b/Examples/PlayBridgeJS/Sources/JavaScript/code-share.js @@ -0,0 +1,189 @@ +// @ts-check + +export class CodeCompression { + /** + * Compresses a string using gzip compression and returns base64-encoded result. + * @param {string} text - The text to compress + * @returns {Promise} Base64-encoded compressed string + */ + static async compress(text) { + const textEncoder = new TextEncoder(); + const stream = new CompressionStream('gzip'); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + + // Start compression + const writePromise = writer.write(textEncoder.encode(text)).then(() => writer.close()); + + // Read compressed chunks + const chunks = []; + let readResult; + do { + readResult = await reader.read(); + if (readResult.value) { + chunks.push(readResult.value); + } + } while (!readResult.done); + + await writePromise; + + // Combine all chunks into single Uint8Array + const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const compressed = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + compressed.set(chunk, offset); + offset += chunk.length; + } + + // Convert to base64 for URL safety + return this.uint8ArrayToBase64(compressed); + } + + /** + * Decompresses a base64-encoded gzip string back to original text. + * @param {string} compressedBase64 - Base64-encoded compressed string + * @returns {Promise} Original decompressed text + */ + static async decompress(compressedBase64) { + const compressed = this.base64ToUint8Array(compressedBase64); + const stream = new DecompressionStream('gzip'); + const writer = stream.writable.getWriter(); + const reader = stream.readable.getReader(); + + // Start decompression + const writePromise = writer.write(compressed).then(() => writer.close()); + + // Read decompressed chunks + const chunks = []; + let readResult; + do { + readResult = await reader.read(); + if (readResult.value) { + chunks.push(readResult.value); + } + } while (!readResult.done); + + await writePromise; + + // Combine chunks and decode + const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const decompressed = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + decompressed.set(chunk, offset); + offset += chunk.length; + } + + const textDecoder = new TextDecoder(); + return textDecoder.decode(decompressed); + } + + /** + * Converts Uint8Array to base64 string. + * @param {Uint8Array} uint8Array - Array to convert + * @returns {string} Base64 string + */ + static uint8ArrayToBase64(uint8Array) { + let binary = ''; + for (let i = 0; i < uint8Array.byteLength; i++) { + binary += String.fromCharCode(uint8Array[i]); + } + return btoa(binary); + } + + /** + * Converts base64 string to Uint8Array. + * @param {string} base64 - Base64 string to convert + * @returns {Uint8Array} Converted array + */ + static base64ToUint8Array(base64) { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes; + } +} + +/** + * URL parameter manager for sharing code. + * Handles compression, URL generation, and parameter extraction with encoding type versioning. + */ +export class CodeShareManager { + /** @type {string} */ + static CURRENT_ENCODING = 'gzip-b64'; + + /** + * Available encoding types for future extensibility. + * @type {Object} + */ + static ENCODERS = { + 'gzip-b64': { + compress: CodeCompression.compress.bind(CodeCompression), + decompress: CodeCompression.decompress.bind(CodeCompression) + } + }; + + /** + * Generates a shareable URL with compressed code and encoding type. + * @param {Object} code - Code object containing swift and dts properties + * @param {string} code.swift - Swift code + * @param {string} code.dts - TypeScript definition code + * @returns {Promise} Shareable URL + */ + static async generateShareUrl(code) { + const codeData = JSON.stringify(code); + const encoder = this.ENCODERS[this.CURRENT_ENCODING]; + + if (!encoder) { + throw new Error(`Unsupported encoding type: ${this.CURRENT_ENCODING}`); + } + + const compressed = await encoder.compress(codeData); + + const url = new URL(window.location.href); + url.searchParams.set('code', compressed); + url.searchParams.set('enc', this.CURRENT_ENCODING); + + return url.toString(); + } + + /** + * Extracts code from URL parameters with encoding type detection. + * @param {string} [url] - URL to extract from (defaults to current URL) + * @returns {Promise} Code object or null if no code found + */ + static async extractCodeFromUrl(url) { + const urlObj = new URL(url || window.location.href); + const compressedCode = urlObj.searchParams.get('code'); + const encodingType = urlObj.searchParams.get('enc') || this.CURRENT_ENCODING; + + if (!compressedCode) { + return null; + } + + const encoder = this.ENCODERS[encodingType]; + if (!encoder) { + console.error(`Unsupported encoding type: ${encodingType}`); + throw new Error(`Unsupported encoding type: ${encodingType}. Supported types: ${Object.keys(this.ENCODERS).join(', ')}`); + } + + try { + const decompressed = await encoder.decompress(compressedCode); + return JSON.parse(decompressed); + } catch (error) { + console.error('Failed to extract code from URL:', error); + throw new Error(`Failed to decode shared code (encoding: ${encodingType}): ${error.message}`); + } + } + + /** + * Checks if current URL contains shared code. + * @returns {boolean} True if URL contains code parameter + */ + static hasSharedCode() { + return new URL(window.location.href).searchParams.has('code'); + } +} \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/styles.css b/Examples/PlayBridgeJS/Sources/JavaScript/styles.css index a41258c2..1a8414e2 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/styles.css +++ b/Examples/PlayBridgeJS/Sources/JavaScript/styles.css @@ -83,6 +83,116 @@ body { font-weight: 400; } +.share-controls { + margin-top: 16px; + display: flex; + justify-content: center; + position: relative; +} + +.share-button { + padding: 10px 20px; + border: none; + background-color: var(--color-button-background); + color: var(--color-button-text); + border-radius: 8px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s ease; +} + +.share-button:hover { + background-color: var(--color-button-background-hover); +} + +.share-dialog { + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + margin-top: 8px; + min-width: 400px; + max-width: 90vw; +} + +.share-dialog.hidden { + display: none; +} + +.share-dialog-content { + background-color: var(--color-fill); + border: 1px solid var(--color-border); + border-radius: 12px; + padding: 20px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} + +.share-dialog-content h3 { + margin: 0 0 16px 0; + font-size: 18px; + font-weight: 600; + text-align: center; +} + +.share-url-container { + display: flex; + gap: 8px; + margin-bottom: 16px; +} + +.share-url-input { + flex: 1; + padding: 10px 12px; + border: 1px solid var(--color-border); + border-radius: 6px; + font-size: 14px; + font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace; + background-color: var(--color-fill-secondary); + color: var(--color-text); +} + +.copy-button { + padding: 10px 16px; + border: none; + background-color: var(--color-figure-green); + color: var(--color-fill); + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: opacity 0.2s ease; +} + +.copy-button:hover { + opacity: 0.8; +} + +.copy-button.copied { + background-color: var(--color-figure-blue); +} + +.share-dialog-actions { + text-align: center; +} + +.close-button { + padding: 8px 16px; + border: 1px solid var(--color-border); + background-color: var(--color-fill); + color: var(--color-text); + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background-color 0.2s ease; +} + +.close-button:hover { + background-color: var(--color-fill-secondary); +} + .error-display { margin-bottom: 24px; padding: 16px; @@ -266,4 +376,24 @@ body { .section-header h2 { font-size: 18px; } + + .share-dialog { + min-width: 320px; + max-width: calc(100vw - 24px); + left: 50%; + transform: translateX(-50%); + } + + .share-dialog-content { + padding: 16px; + } + + .share-url-container { + flex-direction: column; + gap: 12px; + } + + .share-url-input { + font-size: 12px; + } } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/index.html b/Examples/PlayBridgeJS/index.html index 21566ee0..2a584132 100644 --- a/Examples/PlayBridgeJS/index.html +++ b/Examples/PlayBridgeJS/index.html @@ -26,6 +26,21 @@

BridgeJS Playground

Interactive playground to preview bridged code generated by BridgeJS

+
From f0624bbaab8fabc6c2ea17cabb8f418be2473c7a Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 17 Aug 2025 22:52:39 +0900 Subject: [PATCH 34/61] BridgeJS: Rename `which` override env-var format to `JAVASCRIPTKIT__EXEC` --- .../Sources/TS2Skeleton/TS2Skeleton.swift | 13 +- .../Tests/BridgeJSToolTests/WhichTests.swift | 190 ++++++++++++++++++ Plugins/PackageToJS/Sources/PackageToJS.swift | 11 +- .../Sources/PackageToJSPlugin.swift | 2 +- 4 files changed, 205 insertions(+), 11 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift b/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift index 051f2f4a..dca486d2 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift @@ -19,7 +19,10 @@ import BridgeJSCore import BridgeJSSkeleton #endif -internal func which(_ executable: String) throws -> URL { +internal func which( + _ executable: String, + environment: [String: String] = ProcessInfo.processInfo.environment +) throws -> URL { func checkCandidate(_ candidate: URL) -> Bool { var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists(atPath: candidate.path, isDirectory: &isDirectory) @@ -27,9 +30,9 @@ internal func which(_ executable: String) throws -> URL { } do { // Check overriding environment variable - let envVariable = executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_PATH" - if let path = ProcessInfo.processInfo.environment[envVariable] { - let url = URL(fileURLWithPath: path).appendingPathComponent(executable) + let envVariable = "JAVASCRIPTKIT_" + executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_EXEC" + if let executablePath = environment[envVariable] { + let url = URL(fileURLWithPath: executablePath) if checkCandidate(url) { return url } @@ -41,7 +44,7 @@ internal func which(_ executable: String) throws -> URL { #else pathSeparator = ":" #endif - let paths = ProcessInfo.processInfo.environment["PATH"]!.split(separator: pathSeparator) + let paths = environment["PATH"]?.split(separator: pathSeparator) ?? [] for path in paths { let url = URL(fileURLWithPath: String(path)).appendingPathComponent(executable) if checkCandidate(url) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift new file mode 100644 index 00000000..3771772c --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift @@ -0,0 +1,190 @@ +import Testing +import Foundation +@testable import TS2Skeleton +@testable import BridgeJSCore + +@Suite struct WhichTests { + + // MARK: - Helper Functions + + private static var pathSeparator: String { + #if os(Windows) + return ";" + #else + return ":" + #endif + } + + // MARK: - Successful Path Resolution Tests + + @Test func whichFindsExecutableInPath() throws { + try withTemporaryDirectory { tempDir, _ in + let execFile = tempDir.appendingPathComponent("testexec") + try "#!/bin/sh\necho 'test'".write(to: execFile, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: execFile.path) + + let environment = ["PATH": tempDir.path] + + let result = try which("testexec", environment: environment) + + #expect(result.path == execFile.path) + } + } + + @Test func whichReturnsFirstMatchInPath() throws { + try withTemporaryDirectory { tempDir1, _ in + try withTemporaryDirectory { tempDir2, _ in + let exec1 = tempDir1.appendingPathComponent("testexec") + let exec2 = tempDir2.appendingPathComponent("testexec") + + // Create executable files in both directories + try "#!/bin/sh\necho 'first'".write(to: exec1, atomically: true, encoding: .utf8) + try "#!/bin/sh\necho 'second'".write(to: exec2, atomically: true, encoding: .utf8) + + // Make files executable + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: exec1.path) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: exec2.path) + + let pathEnv = "\(tempDir1.path)\(Self.pathSeparator)\(tempDir2.path)" + let environment = ["PATH": pathEnv] + + let result = try which("testexec", environment: environment) + + // Should return the first one found + #expect(result.path == exec1.path) + } + } + } + + // MARK: - Environment Variable Override Tests + + @Test func whichUsesEnvironmentVariableOverride() throws { + try withTemporaryDirectory { tempDir, _ in + let customExec = tempDir.appendingPathComponent("mynode") + try "#!/bin/sh\necho 'custom node'".write(to: customExec, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: customExec.path) + + let environment = [ + "PATH": "/nonexistent/path", + "JAVASCRIPTKIT_NODE_EXEC": customExec.path, + ] + + let result = try which("node", environment: environment) + + #expect(result.path == customExec.path) + } + } + + @Test func whichHandlesHyphenatedExecutableNames() throws { + try withTemporaryDirectory { tempDir, _ in + let customExec = tempDir.appendingPathComponent("my-exec") + try "#!/bin/sh\necho 'hyphenated'".write(to: customExec, atomically: true, encoding: .utf8) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: customExec.path) + + let environment = [ + "PATH": "/nonexistent/path", + "JAVASCRIPTKIT_MY_EXEC_EXEC": customExec.path, + ] + + let result = try which("my-exec", environment: environment) + + #expect(result.path == customExec.path) + } + } + + @Test func whichPrefersEnvironmentOverridePath() throws { + try withTemporaryDirectory { tempDir1, _ in + try withTemporaryDirectory { tempDir2, _ in + let pathExec = tempDir1.appendingPathComponent("testexec") + let envExec = tempDir2.appendingPathComponent("testexec") + + try "#!/bin/sh\necho 'from path'".write(to: pathExec, atomically: true, encoding: .utf8) + try "#!/bin/sh\necho 'from env'".write(to: envExec, atomically: true, encoding: .utf8) + + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: pathExec.path) + try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: envExec.path) + + let environment = [ + "PATH": tempDir1.path, + "JAVASCRIPTKIT_TESTEXEC_EXEC": envExec.path, + ] + + let result = try which("testexec", environment: environment) + + // Should prefer environment variable over PATH + #expect(result.path == envExec.path) + } + } + } + + // MARK: - Error Handling Tests + + @Test func whichThrowsWhenExecutableNotFound() throws { + let environment = ["PATH": "/nonexistent\(Self.pathSeparator)/also/nonexistent"] + + #expect(throws: BridgeJSCoreError.self) { + _ = try which("nonexistent_executable_12345", environment: environment) + } + } + + @Test func whichThrowsWhenEnvironmentPathIsInvalid() throws { + try withTemporaryDirectory { tempDir, _ in + let nonExecFile = tempDir.appendingPathComponent("notexecutable") + try "not executable".write(to: nonExecFile, atomically: true, encoding: .utf8) + + let environment = [ + "PATH": tempDir.path, + "JAVASCRIPTKIT_NOTEXECUTABLE_EXEC": nonExecFile.path, + ] + + #expect(throws: BridgeJSCoreError.self) { + _ = try which("notexecutable", environment: environment) + } + } + } + + @Test func whichThrowsWhenPathPointsToDirectory() throws { + try withTemporaryDirectory { tempDir, _ in + let environment = [ + "PATH": "/nonexistent/path", + "JAVASCRIPTKIT_TESTEXEC_EXEC": tempDir.path, + ] + + #expect(throws: BridgeJSCoreError.self) { + _ = try which("testexec", environment: environment) + } + } + } + + // MARK: - Edge Case Tests + + @Test func whichHandlesEmptyPath() throws { + let environment = ["PATH": ""] + + #expect(throws: BridgeJSCoreError.self) { + _ = try which("anyexec", environment: environment) + } + } + + @Test func whichHandlesMissingPathEnvironment() throws { + let environment: [String: String] = [:] + + #expect(throws: BridgeJSCoreError.self) { + _ = try which("anyexec", environment: environment) + } + } + + @Test func whichIgnoresNonExecutableFiles() throws { + try withTemporaryDirectory { tempDir, _ in + let nonExecFile = tempDir.appendingPathComponent("testfile") + try "content".write(to: nonExecFile, atomically: true, encoding: .utf8) + // Don't set executable permissions + + let environment = ["PATH": tempDir.path] + + #expect(throws: BridgeJSCoreError.self) { + _ = try which("testfile", environment: environment) + } + } + } +} diff --git a/Plugins/PackageToJS/Sources/PackageToJS.swift b/Plugins/PackageToJS/Sources/PackageToJS.swift index 9a3f4c54..c486c327 100644 --- a/Plugins/PackageToJS/Sources/PackageToJS.swift +++ b/Plugins/PackageToJS/Sources/PackageToJS.swift @@ -295,7 +295,7 @@ final class DefaultPackagingSystem: PackagingSystem { private let printWarning: (String) -> Void private let which: (String) throws -> URL - init(printWarning: @escaping (String) -> Void, which: @escaping (String) throws -> URL = which(_:)) { + init(printWarning: @escaping (String) -> Void, which: @escaping (String) throws -> URL) { self.printWarning = printWarning self.which = which } @@ -323,6 +323,7 @@ final class DefaultPackagingSystem: PackagingSystem { } internal func which(_ executable: String) throws -> URL { + let environment = ProcessInfo.processInfo.environment func checkCandidate(_ candidate: URL) -> Bool { var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists(atPath: candidate.path, isDirectory: &isDirectory) @@ -330,9 +331,9 @@ internal func which(_ executable: String) throws -> URL { } do { // Check overriding environment variable - let envVariable = executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_PATH" - if let path = ProcessInfo.processInfo.environment[envVariable] { - let url = URL(fileURLWithPath: path).appendingPathComponent(executable) + let envVariable = "JAVASCRIPTKIT_" + executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_EXEC" + if let executablePath = environment[envVariable] { + let url = URL(fileURLWithPath: executablePath) if checkCandidate(url) { return url } @@ -344,7 +345,7 @@ internal func which(_ executable: String) throws -> URL { #else pathSeparator = ":" #endif - let paths = ProcessInfo.processInfo.environment["PATH"]!.split(separator: pathSeparator) + let paths = environment["PATH"]?.split(separator: pathSeparator) ?? [] for path in paths { let url = URL(fileURLWithPath: String(path)).appendingPathComponent(executable) if checkCandidate(url) { diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 1f15f267..75c73675 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -762,7 +762,7 @@ extension PackagingPlanner { ) { let outputBaseName = outputDir.lastPathComponent let (configuration, triple) = PackageToJS.deriveBuildConfiguration(wasmProductArtifact: wasmProductArtifact) - let system = DefaultPackagingSystem(printWarning: printStderr) + let system = DefaultPackagingSystem(printWarning: printStderr, which: which(_:)) self.init( options: options, packageId: context.package.id, From 019ab51dcbb046f2f942de1f6e389f9748ed3abf Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 18 Aug 2025 03:11:23 +0900 Subject: [PATCH 35/61] BridgeJS: Add configuration support with bridge-js.config.json files --- .gitignore | 1 + Examples/ImportTS/Package.swift | 2 +- Examples/ImportTS/Sources/main.swift | 16 ++--- .../BridgeJSBuildPlugin.swift | 2 + .../BridgeJSCommandPlugin.swift | 2 + .../Sources/BridgeJSCore/BridgeJSConfig.swift | 55 +++++++++++++++++ .../Sources/BridgeJSTool/BridgeJSTool.swift | 13 +++- .../Sources/TS2Skeleton/TS2Skeleton.swift | 33 ++++++++-- .../BridgeJSToolTests/BridgeJSLinkTests.swift | 4 +- .../BridgeJSToolTests/ImportTSTests.swift | 3 +- .../Tests/BridgeJSToolTests/WhichTests.swift | 34 ++++------- .../Articles/BridgeJS-Configuration.md | 60 +++++++++++++++++++ .../Documentation.docc/Documentation.md | 1 + 13 files changed, 186 insertions(+), 40 deletions(-) create mode 100644 Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSConfig.swift create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS-Configuration.md diff --git a/.gitignore b/.gitignore index e66d976c..a62100fd 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ Examples/*/package-lock.json Package.resolved Plugins/BridgeJS/Sources/JavaScript/package-lock.json Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/**/*.actual +bridge-js.config.local.json diff --git a/Examples/ImportTS/Package.swift b/Examples/ImportTS/Package.swift index 4809ec00..fdcf09b7 100644 --- a/Examples/ImportTS/Package.swift +++ b/Examples/ImportTS/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "MyApp", platforms: [ - .macOS(.v10_15), + .macOS(.v11), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), diff --git a/Examples/ImportTS/Sources/main.swift b/Examples/ImportTS/Sources/main.swift index 4853a966..79654032 100644 --- a/Examples/ImportTS/Sources/main.swift +++ b/Examples/ImportTS/Sources/main.swift @@ -2,25 +2,25 @@ import JavaScriptKit // This function is automatically generated by the @JS plugin // It demonstrates how to use TypeScript functions and types imported from bridge-js.d.ts -@JS public func run() { +@JS public func run() throws(JSException) { // Call the imported consoleLog function defined in bridge-js.d.ts - consoleLog("Hello, World!") + try consoleLog("Hello, World!") // Get the document object - this comes from the imported getDocument() function - let document = getDocument() + let document = try getDocument() // Access and modify properties - the title property is read/write - document.title = "Hello, World!" + try document.setTitle("Hello, World!") // Access read-only properties - body is defined as readonly in TypeScript - let body = document.body + let body = try document.body // Create a new element using the document.createElement method - let h1 = document.createElement("h1") + let h1 = try document.createElement("h1") // Set properties on the created element - h1.innerText = "Hello, World!" + try h1.setInnerText("Hello, World!") // Call methods on objects - appendChild is defined in the HTMLElement interface - body.appendChild(h1) + try body.appendChild(h1) } diff --git a/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift b/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift index 422393d8..9ea09520 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSBuildPlugin/BridgeJSBuildPlugin.swift @@ -79,6 +79,8 @@ struct BridgeJSBuildPlugin: BuildToolPlugin { executable: try context.tool(named: "BridgeJSTool").url, arguments: [ "import", + "--target-dir", + target.directoryURL.path, "--output-skeleton", outputSkeletonPath.path, "--output-swift", diff --git a/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift b/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift index d3a5a6c1..a4a2fcf1 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCommandPlugin/BridgeJSCommandPlugin.swift @@ -127,6 +127,8 @@ extension BridgeJSCommandPlugin.Context { try runBridgeJSTool( arguments: [ "import", + "--target-dir", + target.directoryURL.path, "--output-skeleton", generatedJavaScriptDirectory.appending(path: "BridgeJS.ImportTS.json").path, "--output-swift", diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSConfig.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSConfig.swift new file mode 100644 index 00000000..cf6f881e --- /dev/null +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/BridgeJSConfig.swift @@ -0,0 +1,55 @@ +import struct Foundation.URL +import struct Foundation.Data +import class Foundation.FileManager +import class Foundation.JSONDecoder + +/// Configuration file representation for BridgeJS. +public struct BridgeJSConfig: Codable { + /// A mapping of tool names to their override paths. + /// + /// If not present, the tool will be searched for in the system PATH. + public var tools: [String: String]? + + /// Load the configuration file from the SwiftPM package target directory. + /// + /// Files are loaded **in this order** and merged (later files override earlier ones): + /// 1. `bridge-js.config.json` + /// 2. `bridge-js.config.local.json` + public static func load(targetDirectory: URL) throws -> BridgeJSConfig { + // Define file paths in priority order: base first, then local overrides + let files = [ + targetDirectory.appendingPathComponent("bridge-js.config.json"), + targetDirectory.appendingPathComponent("bridge-js.config.local.json"), + ] + + var config = BridgeJSConfig() + + for file in files { + do { + if let loaded = try loadConfig(from: file) { + config = config.merging(overrides: loaded) + } + } catch { + throw BridgeJSCoreError("Failed to parse \(file.path): \(error)") + } + } + + return config + } + + /// Load a config file from the given URL if it exists, otherwise return nil + private static func loadConfig(from url: URL) throws -> BridgeJSConfig? { + guard FileManager.default.fileExists(atPath: url.path) else { + return nil + } + let data = try Data(contentsOf: url) + return try JSONDecoder().decode(BridgeJSConfig.self, from: data) + } + + /// Merge the current configuration with the overrides. + func merging(overrides: BridgeJSConfig) -> BridgeJSConfig { + return BridgeJSConfig( + tools: (tools ?? [:]).merging(overrides.tools ?? [:], uniquingKeysWith: { $1 }) + ) + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift index fe48a3f8..dba6fe47 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift @@ -3,9 +3,11 @@ @preconcurrency import var Foundation.stderr @preconcurrency import struct Foundation.URL @preconcurrency import struct Foundation.Data +@preconcurrency import struct Foundation.ObjCBool @preconcurrency import class Foundation.JSONEncoder @preconcurrency import class Foundation.FileManager @preconcurrency import class Foundation.JSONDecoder +@preconcurrency import class Foundation.ProcessInfo import SwiftParser #if canImport(BridgeJSCore) @@ -50,7 +52,7 @@ import TS2Skeleton do { try run() } catch { - printStderr("Error: \(error)") + printStderr("error: \(error)") exit(1) } } @@ -83,6 +85,10 @@ import TS2Skeleton help: "Print verbose output", required: false ), + "target-dir": OptionRule( + help: "The SwiftPM package target directory", + required: true + ), "output-swift": OptionRule(help: "The output file path for the Swift source code", required: true), "output-skeleton": OptionRule( help: "The output file path for the skeleton of the imported TypeScript APIs", @@ -99,6 +105,9 @@ import TS2Skeleton ) let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true") var importer = ImportTS(progress: progress, moduleName: doubleDashOptions["module-name"]!) + let targetDirectory = URL(fileURLWithPath: doubleDashOptions["target-dir"]!) + let config = try BridgeJSConfig.load(targetDirectory: targetDirectory) + let nodePath: URL = try config.findTool("node", targetDirectory: targetDirectory) for inputFile in positionalArguments { if inputFile.hasSuffix(".json") { let sourceURL = URL(fileURLWithPath: inputFile) @@ -109,7 +118,7 @@ import TS2Skeleton importer.addSkeleton(skeleton) } else if inputFile.hasSuffix(".d.ts") { let tsconfigPath = URL(fileURLWithPath: doubleDashOptions["project"]!) - try importer.addSourceFile(inputFile, tsconfigPath: tsconfigPath.path) + try importer.addSourceFile(inputFile, tsconfigPath: tsconfigPath.path, nodePath: nodePath) } } diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift b/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift index dca486d2..c7725faf 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/TS2Skeleton.swift @@ -22,7 +22,7 @@ import BridgeJSSkeleton internal func which( _ executable: String, environment: [String: String] = ProcessInfo.processInfo.environment -) throws -> URL { +) -> URL? { func checkCandidate(_ candidate: URL) -> Bool { var isDirectory: ObjCBool = false let fileExists = FileManager.default.fileExists(atPath: candidate.path, isDirectory: &isDirectory) @@ -51,13 +51,38 @@ internal func which( return url } } - throw BridgeJSCoreError("Executable \(executable) not found in PATH") + return nil +} + +extension BridgeJSConfig { + /// Find a tool from the system PATH, using environment variable override, or bridge-js.config.json + public func findTool(_ name: String, targetDirectory: URL) throws -> URL { + if let tool = tools?[name] { + return URL(fileURLWithPath: tool) + } + if let url = which(name) { + return url + } + + // Emit a helpful error message with a suggestion to create a local config override. + throw BridgeJSCoreError( + """ + Executable "\(name)" not found in PATH. \ + Hint: Try setting the JAVASCRIPTKIT_\(name.uppercased().replacingOccurrences(of: "-", with: "_"))_EXEC environment variable, \ + or create a local config override with: + echo '{ "tools": { "\(name)": "'$(which \(name))'" } }' > \(targetDirectory.appendingPathComponent("bridge-js.config.local.json").path) + """ + ) + } } extension ImportTS { /// Processes a TypeScript definition file and extracts its API information - public mutating func addSourceFile(_ sourceFile: String, tsconfigPath: String) throws { - let nodePath = try which("node") + public mutating func addSourceFile( + _ sourceFile: String, + tsconfigPath: String, + nodePath: URL + ) throws { let ts2skeletonPath = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() .appendingPathComponent("JavaScript") diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift index 51131989..37edf830 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift @@ -4,6 +4,7 @@ import SwiftParser import Testing @testable import BridgeJSLink @testable import BridgeJSCore +@testable import TS2Skeleton @Suite struct BridgeJSLinkTests { private func snapshot( @@ -65,7 +66,8 @@ import Testing let tsconfigPath = url.deletingLastPathComponent().appendingPathComponent("tsconfig.json") var importTS = ImportTS(progress: .silent, moduleName: "TestModule") - try importTS.addSourceFile(url.path, tsconfigPath: tsconfigPath.path) + let nodePath = try #require(which("node")) + try importTS.addSourceFile(url.path, tsconfigPath: tsconfigPath.path, nodePath: nodePath) let name = url.deletingPathExtension().deletingPathExtension().lastPathComponent let encoder = JSONEncoder() diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift index 9db37669..ef642ed3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/ImportTSTests.swift @@ -18,8 +18,9 @@ import Foundation func snapshot(input: String) throws { var api = ImportTS(progress: .silent, moduleName: "Check") let url = Self.inputsDirectory.appendingPathComponent(input) + let nodePath = try #require(which("node")) let tsconfigPath = url.deletingLastPathComponent().appendingPathComponent("tsconfig.json") - try api.addSourceFile(url.path, tsconfigPath: tsconfigPath.path) + try api.addSourceFile(url.path, tsconfigPath: tsconfigPath.path, nodePath: nodePath) let outputSwift = try #require(try api.finalize()) let name = url.deletingPathExtension().deletingPathExtension().deletingPathExtension().lastPathComponent try assertSnapshot( diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift index 3771772c..958d4d64 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/WhichTests.swift @@ -25,7 +25,7 @@ import Foundation let environment = ["PATH": tempDir.path] - let result = try which("testexec", environment: environment) + let result = try #require(which("testexec", environment: environment)) #expect(result.path == execFile.path) } @@ -48,7 +48,7 @@ import Foundation let pathEnv = "\(tempDir1.path)\(Self.pathSeparator)\(tempDir2.path)" let environment = ["PATH": pathEnv] - let result = try which("testexec", environment: environment) + let result = try #require(which("testexec", environment: environment)) // Should return the first one found #expect(result.path == exec1.path) @@ -69,7 +69,7 @@ import Foundation "JAVASCRIPTKIT_NODE_EXEC": customExec.path, ] - let result = try which("node", environment: environment) + let result = try #require(which("node", environment: environment)) #expect(result.path == customExec.path) } @@ -86,7 +86,7 @@ import Foundation "JAVASCRIPTKIT_MY_EXEC_EXEC": customExec.path, ] - let result = try which("my-exec", environment: environment) + let result = try #require(which("my-exec", environment: environment)) #expect(result.path == customExec.path) } @@ -109,7 +109,7 @@ import Foundation "JAVASCRIPTKIT_TESTEXEC_EXEC": envExec.path, ] - let result = try which("testexec", environment: environment) + let result = try #require(which("testexec", environment: environment)) // Should prefer environment variable over PATH #expect(result.path == envExec.path) @@ -122,9 +122,7 @@ import Foundation @Test func whichThrowsWhenExecutableNotFound() throws { let environment = ["PATH": "/nonexistent\(Self.pathSeparator)/also/nonexistent"] - #expect(throws: BridgeJSCoreError.self) { - _ = try which("nonexistent_executable_12345", environment: environment) - } + #expect(which("nonexistent_executable_12345", environment: environment) == nil) } @Test func whichThrowsWhenEnvironmentPathIsInvalid() throws { @@ -137,9 +135,7 @@ import Foundation "JAVASCRIPTKIT_NOTEXECUTABLE_EXEC": nonExecFile.path, ] - #expect(throws: BridgeJSCoreError.self) { - _ = try which("notexecutable", environment: environment) - } + #expect(which("notexecutable", environment: environment) == nil) } } @@ -150,9 +146,7 @@ import Foundation "JAVASCRIPTKIT_TESTEXEC_EXEC": tempDir.path, ] - #expect(throws: BridgeJSCoreError.self) { - _ = try which("testexec", environment: environment) - } + #expect(which("testexec", environment: environment) == nil) } } @@ -161,17 +155,13 @@ import Foundation @Test func whichHandlesEmptyPath() throws { let environment = ["PATH": ""] - #expect(throws: BridgeJSCoreError.self) { - _ = try which("anyexec", environment: environment) - } + #expect(which("anyexec", environment: environment) == nil) } @Test func whichHandlesMissingPathEnvironment() throws { let environment: [String: String] = [:] - #expect(throws: BridgeJSCoreError.self) { - _ = try which("anyexec", environment: environment) - } + #expect(which("anyexec", environment: environment) == nil) } @Test func whichIgnoresNonExecutableFiles() throws { @@ -182,9 +172,7 @@ import Foundation let environment = ["PATH": tempDir.path] - #expect(throws: BridgeJSCoreError.self) { - _ = try which("testfile", environment: environment) - } + #expect(which("testfile", environment: environment) == nil) } } } diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS-Configuration.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS-Configuration.md new file mode 100644 index 00000000..5b982e18 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS-Configuration.md @@ -0,0 +1,60 @@ +# BridgeJS Configuration + +Configure BridgeJS behavior using bridge-js.config.json and bridge-js.config.local.json files. + +## Overview + +BridgeJS supports configuration through JSON configuration files that allow you to customize various aspects of the build process and tool behavior. + +The configuration system supports two complementary files: +- `bridge-js.config.json` - Base configuration (checked into version control) +- `bridge-js.config.local.json` - Local overrides (intended to be ignored by git, for developer-specific settings) + +## Configuration Loading + +### File Locations + +Configuration files should be placed in your Swift package target directory, typically alongside your `bridge-js.d.ts` file: + +``` +Sources/ + YourTarget/ + bridge-js.d.ts + bridge-js.config.json # Base config (commit to git) + bridge-js.config.local.json # Local config (add to .gitignore) + main.swift +``` + +### Loading Order + +BridgeJS loads and merges configuration files in the following order: + +1. **`bridge-js.config.json`** - Base configuration +2. **`bridge-js.config.local.json`** - Local overrides + +Later files override settings from earlier files. This allows teams to commit a base configuration while allowing individual developers to customize their local environment. + +## Configuration Options + +### `tools` + +Specify custom paths for external executables. This is particularly useful when working in environments like Xcode where the system PATH may not be inherited, or when you need to use a specific version of tools for your project. + +Currently supported tools: +- `node` - Node.js runtime (required for TypeScript processing) + +Example: +```json +{ + "tools": { + "node": "/usr/local/bin/node" + } +} +``` + +BridgeJS resolves tool paths in the following priority order: + +1. **Configuration files** (`bridge-js.config.local.json` > `bridge-js.config.json`) +2. **Environment variables** (`JAVASCRIPTKIT_NODE_EXEC`) +3. **System PATH lookup** + diff --git a/Sources/JavaScriptKit/Documentation.docc/Documentation.md b/Sources/JavaScriptKit/Documentation.docc/Documentation.md index ffc16843..48bf995b 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Documentation.md +++ b/Sources/JavaScriptKit/Documentation.docc/Documentation.md @@ -53,6 +53,7 @@ Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Ex - - +- - - - From 3328a179e138fbe3b68e72b2b8b0606a6ccd5c3c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 18 Aug 2025 03:16:35 +0900 Subject: [PATCH 36/61] DocC: Add dedicated BridgeJS section for better organization --- .../{ => BridgeJS}/Ahead-of-Time-Code-Generation.md | 0 .../Articles/{ => BridgeJS}/BridgeJS-Configuration.md | 2 ++ .../{ => BridgeJS}/Exporting-Swift-to-JavaScript.md | 0 .../{ => BridgeJS}/Importing-TypeScript-into-Swift.md | 0 .../JavaScriptKit/Documentation.docc/Documentation.md | 11 +++++++---- 5 files changed, 9 insertions(+), 4 deletions(-) rename Sources/JavaScriptKit/Documentation.docc/Articles/{ => BridgeJS}/Ahead-of-Time-Code-Generation.md (100%) rename Sources/JavaScriptKit/Documentation.docc/Articles/{ => BridgeJS}/BridgeJS-Configuration.md (93%) rename Sources/JavaScriptKit/Documentation.docc/Articles/{ => BridgeJS}/Exporting-Swift-to-JavaScript.md (100%) rename Sources/JavaScriptKit/Documentation.docc/Articles/{ => BridgeJS}/Importing-TypeScript-into-Swift.md (100%) diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Ahead-of-Time-Code-Generation.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Ahead-of-Time-Code-Generation.md similarity index 100% rename from Sources/JavaScriptKit/Documentation.docc/Articles/Ahead-of-Time-Code-Generation.md rename to Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Ahead-of-Time-Code-Generation.md diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS-Configuration.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md similarity index 93% rename from Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS-Configuration.md rename to Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md index 5b982e18..835cf6be 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS-Configuration.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/BridgeJS-Configuration.md @@ -4,6 +4,8 @@ Configure BridgeJS behavior using bridge-js.config.json and bridge-js.config.loc ## Overview +> Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. + BridgeJS supports configuration through JSON configuration files that allow you to customize various aspects of the build process and tool behavior. The configuration system supports two complementary files: diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md similarity index 100% rename from Sources/JavaScriptKit/Documentation.docc/Articles/Exporting-Swift-to-JavaScript.md rename to Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript-into-Swift.md similarity index 100% rename from Sources/JavaScriptKit/Documentation.docc/Articles/Importing-TypeScript-into-Swift.md rename to Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript-into-Swift.md diff --git a/Sources/JavaScriptKit/Documentation.docc/Documentation.md b/Sources/JavaScriptKit/Documentation.docc/Documentation.md index 48bf995b..0a410916 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Documentation.md +++ b/Sources/JavaScriptKit/Documentation.docc/Documentation.md @@ -51,15 +51,18 @@ Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Ex ### Articles -- +- +- + +### BridgeJS + - +- - -- - -- ### Core APIs - ``JSValue`` - ``JSObject`` -- ``JS()`` +- ``JS(namespace:)`` From c6d1aaf69a571a3320eb65a362a9dfb06c8e1b58 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 18 Aug 2025 09:43:40 +0900 Subject: [PATCH 37/61] BridgeJS: Always use a valid JSON file for the config --- Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json index e69de29b..0967ef42 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/bridge-js.config.json @@ -0,0 +1 @@ +{} From d03e74caed1c6d9070b17a06d4eefafe28e60b94 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Tue, 5 Aug 2025 22:21:27 -0600 Subject: [PATCH 38/61] BridgeJS: Support for case / raw type Swift -> TS code generation --- .../Sources/BridgeJSCore/ExportSwift.swift | 304 +++++- .../Sources/BridgeJSCore/ImportTS.swift | 4 + .../Sources/BridgeJSLink/BridgeJSLink.swift | 188 ++++ .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 132 ++- .../TS2Skeleton/JavaScript/package-lock.json | 28 + .../TS2Skeleton/JavaScript/package.json | 2 +- .../BridgeJSToolTests/Inputs/EnumCase.swift | 16 + .../Inputs/EnumNamespace.swift | 36 + .../Inputs/EnumRawType.swift | 104 ++ .../BridgeJSToolTests/Inputs/tsconfig.json | 11 + .../ArrayParameter.Import.js | 1 + .../BridgeJSLinkTests/Async.Export.js | 1 + .../BridgeJSLinkTests/Async.Import.js | 1 + .../BridgeJSLinkTests/EnumCase.Export.d.ts | 35 + .../BridgeJSLinkTests/EnumCase.Export.js | 96 ++ .../EnumNamespace.Export.d.ts | 20 + .../BridgeJSLinkTests/EnumNamespace.Export.js | 76 ++ .../BridgeJSLinkTests/EnumRawType.Export.d.ts | 121 +++ .../BridgeJSLinkTests/EnumRawType.Export.js | 248 +++++ .../BridgeJSLinkTests/Interface.Import.js | 1 + .../MultipleImportedTypes.Import.js | 1 + .../BridgeJSLinkTests/Namespaces.Export.js | 1 + .../PrimitiveParameters.Export.js | 1 + .../PrimitiveParameters.Import.js | 1 + .../PrimitiveReturn.Export.js | 1 + .../PrimitiveReturn.Import.js | 1 + .../StringParameter.Export.js | 1 + .../StringParameter.Import.js | 1 + .../BridgeJSLinkTests/StringReturn.Export.js | 1 + .../BridgeJSLinkTests/StringReturn.Import.js | 1 + .../BridgeJSLinkTests/SwiftClass.Export.js | 1 + .../TS2SkeletonLike.Import.js | 1 + .../BridgeJSLinkTests/Throws.Export.js | 1 + .../BridgeJSLinkTests/TypeAlias.Import.js | 1 + .../TypeScriptClass.Import.js | 1 + .../VoidParameterVoidReturn.Export.js | 1 + .../VoidParameterVoidReturn.Import.js | 1 + .../__Snapshots__/ExportSwiftTests/Async.json | 3 + .../ExportSwiftTests/EnumCase.json | 132 +++ .../ExportSwiftTests/EnumCase.swift | 39 + .../ExportSwiftTests/EnumNamespace.json | 39 + .../ExportSwiftTests/EnumNamespace.swift | 7 + .../ExportSwiftTests/EnumRawType.json | 894 ++++++++++++++++++ .../ExportSwiftTests/EnumRawType.swift | 283 ++++++ .../ExportSwiftTests/Namespaces.json | 3 + .../ExportSwiftTests/PrimitiveParameters.json | 3 + .../ExportSwiftTests/PrimitiveReturn.json | 3 + .../ExportSwiftTests/StringParameter.json | 3 + .../ExportSwiftTests/StringReturn.json | 3 + .../ExportSwiftTests/SwiftClass.json | 3 + .../ExportSwiftTests/Throws.json | 3 + .../VoidParameterVoidReturn.json | 3 + 52 files changed, 2859 insertions(+), 4 deletions(-) create mode 100644 Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package-lock.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/tsconfig.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index e928011a..fba27aa1 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -20,6 +20,7 @@ public class ExportSwift { private var exportedFunctions: [ExportedFunction] = [] private var exportedClasses: [ExportedClass] = [] + private var exportedEnums: [ExportedEnum] = [] private var typeDeclResolver: TypeDeclResolver = TypeDeclResolver() public init(progress: ProgressReporting, moduleName: String) { @@ -58,7 +59,8 @@ public class ExportSwift { outputSkeleton: ExportedSkeleton( moduleName: moduleName, functions: exportedFunctions, - classes: exportedClasses + classes: exportedClasses, + enums: exportedEnums ) ) } @@ -68,6 +70,9 @@ public class ExportSwift { /// The names of the exported classes, in the order they were written in the source file var exportedClassNames: [String] = [] var exportedClassByName: [String: ExportedClass] = [:] + /// The names of the exported enums, in the order they were written in the source file + var exportedEnumNames: [String] = [] + var exportedEnumByName: [String: ExportedEnum] = [:] var errors: [DiagnosticError] = [] enum State { @@ -292,6 +297,94 @@ public class ExportSwift { override func visitPost(_ node: ClassDeclSyntax) { stateStack.pop() } + + override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + guard let jsAttribute = node.attributes.firstJSAttribute else { + return .skipChildren + } + + let name = node.name.text + let namespace = extractNamespace(from: jsAttribute) + + if let exportedEnum = parseEnum(node: node, namespace: namespace) { + exportedEnumByName[name] = exportedEnum + exportedEnumNames.append(name) + } + + return .skipChildren + } + + private func parseEnum(node: EnumDeclSyntax, namespace: [String]?) -> ExportedEnum? { + let name = node.name.text + + let rawType: String? = node.inheritanceClause?.inheritedTypes.first { inheritedType in + let typeName = inheritedType.type.trimmedDescription + return Constants.supportedRawTypes.contains(typeName) + }?.type.trimmedDescription + + var cases: [EnumCase] = [] + var nestedTypes: [String] = [] + + for member in node.memberBlock.members { + if let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) { + for element in caseDecl.elements { + let caseName = element.name.text + let rawValue: String? + if let stringLiteral = element.rawValue?.value.as(StringLiteralExprSyntax.self) { + rawValue = stringLiteral.segments.first?.as(StringSegmentSyntax.self)?.content.text + } else if let boolLiteral = element.rawValue?.value.as(BooleanLiteralExprSyntax.self) { + rawValue = boolLiteral.literal.text + } else if let intLiteral = element.rawValue?.value.as(IntegerLiteralExprSyntax.self) { + rawValue = intLiteral.literal.text + } else if let floatLiteral = element.rawValue?.value.as(FloatLiteralExprSyntax.self) { + rawValue = floatLiteral.literal.text + } else { + // Other unsupported type or no raw value + rawValue = nil + } + + var associatedValues: [AssociatedValue] = [] + if let parameterClause = element.parameterClause { + for param in parameterClause.parameters { + guard let bridgeType = parent.lookupType(for: param.type) else { + diagnose( + node: param.type, + message: "Unsupported associated value type: \(param.type.trimmedDescription)", + hint: "Only primitive types and types defined in the same module are allowed" + ) + continue + } + + let label = param.firstName?.text + associatedValues.append(AssociatedValue(label: label, type: bridgeType)) + } + } + + cases.append( + EnumCase( + name: caseName, + rawValue: rawValue, + associatedValues: associatedValues + ) + ) + } + } else if let nestedEnum = member.decl.as(EnumDeclSyntax.self) { + // Track nested enums for namespace enums + nestedTypes.append(nestedEnum.name.text) + } else if let nestedClass = member.decl.as(ClassDeclSyntax.self) { + // Track nested classes for namespace enums + nestedTypes.append(nestedClass.name.text) + } + } + + return ExportedEnum( + name: name, + cases: cases, + rawType: rawType, + namespace: namespace, + nestedTypes: nestedTypes + ) + } } func parseSingleFile(_ sourceFile: SourceFileSyntax) throws -> [DiagnosticError] { @@ -303,6 +396,11 @@ public class ExportSwift { collector.exportedClassByName[$0]! } ) + exportedEnums.append( + contentsOf: collector.exportedEnumNames.map { + collector.exportedEnumByName[$0]! + } + ) return collector.errors } @@ -310,12 +408,20 @@ public class ExportSwift { if let primitive = BridgeType(swiftType: type.trimmedDescription) { return primitive } + guard let identifier = type.as(IdentifierTypeSyntax.self) else { return nil } + guard let typeDecl = typeDeclResolver.lookupType(for: identifier) else { return nil } + + // Check if it's an enum + if let enumDecl = typeDecl.as(EnumDeclSyntax.self) { + return classifyEnumType(enumDecl) + } + guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else { return nil } @@ -334,7 +440,7 @@ public class ExportSwift { func renderSwiftGlue() -> String? { var decls: [DeclSyntax] = [] - guard exportedFunctions.count > 0 || exportedClasses.count > 0 else { + guard exportedFunctions.count > 0 || exportedClasses.count > 0 || exportedEnums.count > 0 else { return nil } decls.append(Self.prelude) @@ -344,10 +450,53 @@ public class ExportSwift { for klass in exportedClasses { decls.append(contentsOf: renderSingleExportedClass(klass: klass)) } + // Note: Enums don't need Swift glue code generation - they're used directly let format = BasicFormat() return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n") } + /// Classifies an enum declaration into the appropriate BridgeType + private func classifyEnumType(_ enumDecl: EnumDeclSyntax) -> BridgeType? { + let enumName = enumDecl.name.text + + // Check for raw value type + let rawType = enumDecl.inheritanceClause?.inheritedTypes.first { inheritedType in + let typeName = inheritedType.type.trimmedDescription + return Constants.supportedRawTypes.contains(typeName) + }?.type.trimmedDescription + + // Count cases and check for associated values + var hasCases = false + var hasAssociatedValues = false + + for member in enumDecl.memberBlock.members { + if let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) { + hasCases = true + for element in caseDecl.elements { + if element.parameterClause != nil { + hasAssociatedValues = true + break + } + } + if hasAssociatedValues { break } + } + } + + // Classify based on structure + if !hasCases { + // Empty enum - used as namespace + return .namespaceEnum(enumName) + } else if hasAssociatedValues { + // Has associated values + return .associatedValueEnum(enumName) + } else if let rawType = rawType { + // Has raw values + return .rawValueEnum(enumName, rawType) + } else { + return .caseEnum(enumName) + } + } + class ExportedThunkBuilder { var body: [CodeBlockItemSyntax] = [] var abiParameterForwardings: [LabeledExprSyntax] = [] @@ -420,6 +569,92 @@ public class ExportSwift { ) abiParameterSignatures.append((bytesLabel, .i32)) abiParameterSignatures.append((lengthLabel, .i32)) + case .caseEnum(let enumName): + abiParameterForwardings.append( + LabeledExprSyntax( + label: param.label, + expression: ExprSyntax("\(raw: enumName)(rawValue: Int(\(raw: param.name)))!") + ) + ) + abiParameterSignatures.append((param.name, .i32)) + case .rawValueEnum(let enumName, let rawType): + if rawType == "String" { + let bytesLabel = "\(param.name)Bytes" + let lengthLabel = "\(param.name)Len" + let prepare: CodeBlockItemSyntax = """ + let \(raw: param.name) = String(unsafeUninitializedCapacity: Int(\(raw: lengthLabel))) { b in + _swift_js_init_memory(\(raw: bytesLabel), b.baseAddress.unsafelyUnwrapped) + return Int(\(raw: lengthLabel)) + } + """ + append(prepare) + abiParameterForwardings.append( + LabeledExprSyntax( + label: param.label, + expression: ExprSyntax("\(raw: enumName)(rawValue: \(raw: param.name))!") + ) + ) + abiParameterSignatures.append((bytesLabel, .i32)) + abiParameterSignatures.append((lengthLabel, .i32)) + } else { + // Numeric raw types - use correct WASM ABI type + let conversionExpr: String + switch rawType { + case "Bool": + // Bool requires special conversion from Int32 (0/1) to Bool (false/true) + conversionExpr = "\(enumName)(rawValue: \(param.name) != 0)!" + case "UInt", "UInt32", "UInt64": + // Unsigned types: use bitPattern to handle potential negative Int32 values safely + if rawType == "UInt64" { + conversionExpr = "\(enumName)(rawValue: \(rawType)(bitPattern: Int64(\(param.name))))!" + } else { + conversionExpr = "\(enumName)(rawValue: \(rawType)(bitPattern: \(param.name)))!" + } + default: + // Signed integer and float types: direct conversion + conversionExpr = "\(enumName)(rawValue: \(rawType)(\(param.name)))!" + } + + abiParameterForwardings.append( + LabeledExprSyntax( + label: param.label, + expression: ExprSyntax(stringLiteral: conversionExpr) + ) + ) + switch rawType { + case "Bool", "Int", "Int32", "UInt", "UInt32": + abiParameterSignatures.append((param.name, .i32)) + case "Int64", "UInt64": + abiParameterSignatures.append((param.name, .i64)) + case "Float": + abiParameterSignatures.append((param.name, .f32)) + case "Double": + abiParameterSignatures.append((param.name, .f64)) + default: + abiParameterSignatures.append((param.name, .i32)) // Fallback + } + } + case .associatedValueEnum(let enumName): + let bytesLabel = "\(param.name)Bytes" + let lengthLabel = "\(param.name)Len" + let prepare: CodeBlockItemSyntax = """ + let \(raw: param.name)JsonString = String(unsafeUninitializedCapacity: Int(\(raw: lengthLabel))) { b in + _swift_js_init_memory(\(raw: bytesLabel), b.baseAddress.unsafelyUnwrapped) + return Int(\(raw: lengthLabel)) + } + let \(raw: param.name) = try! JSONDecoder().decode(\(raw: enumName).self, from: \(raw: param.name)JsonString.data(using: .utf8)!) + """ + append(prepare) + abiParameterForwardings.append( + LabeledExprSyntax( + label: param.label, + expression: ExprSyntax("\(raw: param.name)") + ) + ) + abiParameterSignatures.append((bytesLabel, .i32)) + abiParameterSignatures.append((lengthLabel, .i32)) + case .namespaceEnum: + break case .jsObject(nil): abiParameterForwardings.append( LabeledExprSyntax( @@ -520,6 +755,27 @@ public class ExportSwift { case .swiftHeapObject: // UnsafeMutableRawPointer is returned as an i32 pointer abiReturnType = .pointer + case .caseEnum: + abiReturnType = .i32 + case .rawValueEnum(_, let rawType): + switch rawType { + case "String": + abiReturnType = nil + case "Bool", "Int", "Int32", "UInt", "UInt32": + abiReturnType = .i32 + case "Int64", "UInt64": + abiReturnType = .i64 + case "Float": + abiReturnType = .f32 + case "Double": + abiReturnType = .f64 + default: + abiReturnType = nil + } + case .associatedValueEnum: + abiReturnType = nil + case .namespaceEnum: + abiReturnType = nil } if effects.isAsync { @@ -542,6 +798,46 @@ public class ExportSwift { } """ ) + case .caseEnum: + abiReturnType = .i32 + append("return Int32(ret.rawValue)") + case .rawValueEnum(_, let rawType): + if rawType == "String" { + append( + """ + return ret.rawValue.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + """ + ) + } else { + switch rawType { + case "Bool": + append("return Int32(ret.rawValue ? 1 : 0)") + case "Int", "Int32", "UInt", "UInt32": + append("return Int32(ret.rawValue)") + case "Int64", "UInt64": + append("return Int64(ret.rawValue)") + case "Float": + append("return Float32(ret.rawValue)") + case "Double": + append("return Float64(ret.rawValue)") + default: + append("return Int32(ret.rawValue)") // Fallback + } + } + case .associatedValueEnum: + append( + """ + let jsonData = try! JSONEncoder().encode(ret) + return String(data: jsonData, encoding: .utf8)!.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + """ + ) + case .namespaceEnum: + abiReturnType = .i32 + append("return 0") case .jsObject(nil): append( """ @@ -825,6 +1121,10 @@ extension BridgeType { case .jsObject(let name?): return name case .swiftHeapObject(let name): return name case .void: return "Void" + case .caseEnum(let name): return name + case .rawValueEnum(let name, _): return name + case .associatedValueEnum(let name): return name + case .namespaceEnum(let name): return name } } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index c7966a84..9c4679e9 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -123,6 +123,8 @@ public struct ImportTS { ) ) abiParameterSignatures.append((param.name, .i32)) + case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum: + throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") case .jsObject(_?): abiParameterSignatures.append((param.name, .i32)) abiParameterForwardings.append( @@ -181,6 +183,8 @@ public struct ImportTS { } """ ) + case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum: + throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") case .jsObject(let name): abiReturnType = .i32 if let name = name { diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 1483692f..07712a83 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -69,6 +69,8 @@ struct BridgeJSLink { var dtsClassLines: [String] = [] var namespacedFunctions: [ExportedFunction] = [] var namespacedClasses: [ExportedClass] = [] + var enumConstantLines: [String] = [] + var dtsEnumLines: [String] = [] if exportedSkeletons.contains(where: { $0.classes.count > 0 }) { classLines.append( @@ -96,6 +98,15 @@ struct BridgeJSLink { } } + if !skeleton.enums.isEmpty { + for enumDef in skeleton.enums { + let (jsEnum, dtsEnum) = try renderExportedEnum(enumDef) + enumConstantLines.append(contentsOf: jsEnum) + exportsLines.append("\(enumDef.name),") + dtsEnumLines.append(contentsOf: dtsEnum) + } + } + for function in skeleton.functions { var (js, dts) = renderExportedFunction(function: function) @@ -135,6 +146,7 @@ struct BridgeJSLink { .map { $0.indent(count: 12) }.joined(separator: "\n") exportsSection = """ \(classLines.map { $0.indent(count: 12) }.joined(separator: "\n")) + \(enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n")) const exports = { \(exportsLines.map { $0.indent(count: 16) }.joined(separator: "\n")) }; @@ -147,6 +159,7 @@ struct BridgeJSLink { } else { exportsSection = """ \(classLines.map { $0.indent(count: 12) }.joined(separator: "\n")) + \(enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n")) return { \(exportsLines.map { $0.indent(count: 16) }.joined(separator: "\n")) }; @@ -227,6 +240,7 @@ struct BridgeJSLink { var dtsLines: [String] = [] dtsLines.append(contentsOf: namespaceDeclarations()) dtsLines.append(contentsOf: dtsClassLines) + dtsLines.append(contentsOf: dtsEnumLines) dtsLines.append(contentsOf: generateImportedTypeDefinitions()) dtsLines.append("export type Exports = {") dtsLines.append(contentsOf: dtsExportLines.map { $0.indent(count: 4) }) @@ -437,6 +451,29 @@ struct BridgeJSLink { cleanupLines.append("swift.memory.release(\(bytesIdLabel));") parameterForwardings.append(bytesIdLabel) parameterForwardings.append("\(bytesLabel).length") + case .caseEnum(_): + // Case enum: JavaScript receives a number (0,1,2...), pass directly to WASM + parameterForwardings.append("\(param.name) | 0") + case .rawValueEnum(_, let rawType): + switch rawType { + case "String": + let bytesLabel = "\(param.name)Bytes" + let bytesIdLabel = "\(param.name)Id" + bodyLines.append("const \(bytesLabel) = textEncoder.encode(\(param.name));") + bodyLines.append("const \(bytesIdLabel) = swift.memory.retain(\(bytesLabel));") + cleanupLines.append("swift.memory.release(\(bytesIdLabel));") + parameterForwardings.append(bytesIdLabel) + parameterForwardings.append("\(bytesLabel).length") + case "Bool": + parameterForwardings.append("\(param.name) ? 1 : 0") + default: + parameterForwardings.append("\(param.name)") + } + case .associatedValueEnum: + parameterForwardings.append("0") + parameterForwardings.append("0") + case .namespaceEnum: + break case .jsObject: parameterForwardings.append("swift.memory.retain(\(param.name))") case .swiftHeapObject: @@ -468,6 +505,30 @@ struct BridgeJSLink { bodyLines.append("const ret = tmpRetString;") bodyLines.append("tmpRetString = undefined;") returnExpr = "ret" + case .caseEnum(_): + // Case enum: WASM returns Int32, use directly as JavaScript number + bodyLines.append("const ret = \(call);") + returnExpr = "ret" + case .rawValueEnum(_, let rawType): + switch rawType { + case "String": + bodyLines.append("\(call);") + bodyLines.append("const ret = tmpRetString;") + bodyLines.append("tmpRetString = undefined;") + returnExpr = "ret" + case "Bool": + bodyLines.append("const ret = \(call);") + returnExpr = "ret !== 0" + default: + bodyLines.append("const ret = \(call);") + returnExpr = "ret" + } + case .associatedValueEnum: + bodyLines.append("\(call);") + returnExpr = "\"\"" + case .namespaceEnum: + bodyLines.append("\(call);") + returnExpr = "undefined" case .int, .float, .double: bodyLines.append("const ret = \(call);") returnExpr = "ret" @@ -541,6 +602,86 @@ struct BridgeJSLink { "(\(parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", "))): \(returnTypeWithEffect)" } + func renderExportedEnum(_ enumDef: ExportedEnum) throws -> (js: [String], dts: [String]) { + var jsLines: [String] = [] + var dtsLines: [String] = [] + + switch enumDef.enumType { + case .simple: + jsLines.append("const \(enumDef.name) = {") + for (index, enumCase) in enumDef.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + jsLines.append(" \(caseName): \(index),".indent(count: 0)) + } + jsLines.append("};") + jsLines.append("") + + dtsLines.append("export const \(enumDef.name): {") + for (index, enumCase) in enumDef.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append(" readonly \(caseName): \(index);") + } + dtsLines.append("};") + dtsLines.append("export type \(enumDef.name) = typeof \(enumDef.name)[keyof typeof \(enumDef.name)];") + dtsLines.append("") + case .rawValue: + guard let rawType = enumDef.rawType else { + throw BridgeJSLinkError(message: "Raw value enum \(enumDef.name) is missing rawType") + } + + jsLines.append("const \(enumDef.name) = {") + for enumCase in enumDef.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + + switch rawType { + case "String": + formattedValue = "\"\(rawValue)\"" + case "Bool": + formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": + formattedValue = rawValue + default: + formattedValue = rawValue + } + + jsLines.append("\(caseName): \(formattedValue),".indent(count: 4)) + } + jsLines.append("};") + jsLines.append("") + + dtsLines.append("export const \(enumDef.name): {") + for enumCase in enumDef.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + + switch rawType { + case "String": + formattedValue = "\"\(rawValue)\"" + case "Bool": + formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": + formattedValue = rawValue + default: + formattedValue = rawValue + } + + dtsLines.append(" readonly \(caseName): \(formattedValue);") + } + dtsLines.append("};") + dtsLines.append("export type \(enumDef.name) = typeof \(enumDef.name)[keyof typeof \(enumDef.name)];") + dtsLines.append("") + + case .associatedValue, .namespace: + jsLines.append("// TODO: Implement \(enumDef.enumType) enum: \(enumDef.name)") + dtsLines.append("// TODO: Implement \(enumDef.enumType) enum: \(enumDef.name)") + } + + return (jsLines, dtsLines) + } + func renderExportedFunction(function: ExportedFunction) -> (js: [String], dts: [String]) { let thunkBuilder = ExportedThunkBuilder(effects: function.effects) for param in function.parameters { @@ -698,6 +839,24 @@ struct BridgeJSLink { bodyLines.append("const \(stringObjectName) = swift.memory.getObject(\(param.name));") bodyLines.append("swift.memory.release(\(param.name));") parameterForwardings.append(stringObjectName) + case .caseEnum(_): + parameterForwardings.append(param.name) + case .rawValueEnum(_, let rawType): + switch rawType { + case "String": + let stringObjectName = "\(param.name)Object" + bodyLines.append("const \(stringObjectName) = swift.memory.getObject(\(param.name));") + bodyLines.append("swift.memory.release(\(param.name));") + parameterForwardings.append(stringObjectName) + case "Bool": + parameterForwardings.append("\(param.name) !== 0") + default: + parameterForwardings.append(param.name) + } + case .associatedValueEnum: + parameterForwardings.append("\"\"") + case .namespaceEnum: + break case .jsObject: parameterForwardings.append("swift.memory.getObject(\(param.name))") default: @@ -769,6 +928,22 @@ struct BridgeJSLink { case .string: bodyLines.append("tmpRetBytes = textEncoder.encode(ret);") return "tmpRetBytes.length" + case .caseEnum(_): + return "ret" + case .rawValueEnum(_, let rawType): + switch rawType { + case "String": + bodyLines.append("tmpRetBytes = textEncoder.encode(ret);") + return "tmpRetBytes.length" + case "Bool": + return "ret ? 1 : 0" + default: + return "ret" + } + case .associatedValueEnum: + return "0" + case .namespaceEnum: + return "0" case .int, .float, .double: return "ret" case .bool: @@ -947,6 +1122,11 @@ extension String { func indent(count: Int) -> String { return String(repeating: " ", count: count) + self } + + var capitalizedFirstLetter: String { + guard !isEmpty else { return self } + return prefix(1).uppercased() + dropFirst() + } } extension BridgeType { @@ -968,6 +1148,14 @@ extension BridgeType { return name ?? "any" case .swiftHeapObject(let name): return name + case .caseEnum(let name): + return name + case .rawValueEnum(let name, _): + return name + case .associatedValueEnum(let name): + return name + case .namespaceEnum(let name): + return name } } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index a3c5b401..e1f1b3ef 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -2,8 +2,18 @@ // MARK: - Types +public enum Constants { + public static let supportedRawTypes = [ + "String", "Bool", "Int", "Int32", "Int64", "UInt", "UInt32", "UInt64", "Float", "Double", + ] +} + public enum BridgeType: Codable, Equatable { case int, float, double, string, bool, jsObject(String?), swiftHeapObject(String), void + case caseEnum(String) + case rawValueEnum(String, String) + case associatedValueEnum(String) + case namespaceEnum(String) } public enum WasmCoreType: String, Codable { @@ -32,6 +42,67 @@ public struct Effects: Codable { } } +// MARK: - Enum Skeleton + +public struct AssociatedValue: Codable, Equatable { + public let label: String? + public let type: BridgeType + + public init(label: String?, type: BridgeType) { + self.label = label + self.type = type + } +} + +public struct EnumCase: Codable, Equatable { + public let name: String + public let rawValue: String? + public let associatedValues: [AssociatedValue] + + public var isSimple: Bool { + associatedValues.isEmpty + } + + public init(name: String, rawValue: String?, associatedValues: [AssociatedValue]) { + self.name = name + self.rawValue = rawValue + self.associatedValues = associatedValues + } +} + +public struct ExportedEnum: Codable, Equatable { + public let name: String + public let cases: [EnumCase] + public let rawType: String? + public let namespace: [String]? + public let nestedTypes: [String] + + public var enumType: EnumType { + if cases.isEmpty { + return .namespace + } else if cases.allSatisfy(\.isSimple) { + return rawType != nil ? .rawValue : .simple + } else { + return .associatedValue + } + } + + public init(name: String, cases: [EnumCase], rawType: String?, namespace: [String]?, nestedTypes: [String]) { + self.name = name + self.cases = cases + self.rawType = rawType + self.namespace = namespace + self.nestedTypes = nestedTypes + } +} + +public enum EnumType: String, Codable { + case simple // enum Direction { case north, south, east } + case rawValue // enum Mode: String { case light = "light" } + case associatedValue // enum Result { case success(String), failure(Int) } + case namespace // enum Utils { } (empty, used as namespace) +} + // MARK: - Exported Skeleton public struct ExportedFunction: Codable { @@ -96,11 +167,13 @@ public struct ExportedSkeleton: Codable { public let moduleName: String public let functions: [ExportedFunction] public let classes: [ExportedClass] + public let enums: [ExportedEnum] - public init(moduleName: String, functions: [ExportedFunction], classes: [ExportedClass]) { + public init(moduleName: String, functions: [ExportedFunction], classes: [ExportedClass], enums: [ExportedEnum]) { self.moduleName = moduleName self.functions = functions self.classes = classes + self.enums = enums } } @@ -163,6 +236,8 @@ public struct ImportedModuleSkeleton: Codable { } } +// MARK: - BridgeType extension + extension BridgeType { public var abiReturnType: WasmCoreType? { switch self { @@ -176,6 +251,61 @@ extension BridgeType { case .swiftHeapObject: // UnsafeMutableRawPointer is returned as an i32 pointer return .pointer + case .caseEnum: + return .i32 + case .rawValueEnum(_, let rawType): + switch rawType { + case "String": + return nil // String uses special handling + case "Bool", "Int", "Int32", "UInt", "UInt32": + return .i32 + case "Int64", "UInt64": + return .i64 + case "Float": + return .f32 + case "Double": + return .f64 + default: + return nil + } + case .associatedValueEnum: + return nil + case .namespaceEnum: + return nil + } + } + + /// Returns the Swift type name for this bridge type + var swiftTypeName: String { + switch self { + case .void: return "Void" + case .bool: return "Bool" + case .int: return "Int" + case .float: return "Float" + case .double: return "Double" + case .string: return "String" + case .jsObject(let name): return name ?? "JSObject" + case .swiftHeapObject(let name): return name + case .caseEnum(let name): return name + case .rawValueEnum(let name, _): return name + case .associatedValueEnum(let name): return name + case .namespaceEnum(let name): return name + } + } + + /// Returns the TypeScript type name for this bridge type + var tsTypeName: String { + switch self { + case .void: return "void" + case .bool: return "boolean" + case .int, .float, .double: return "number" + case .string: return "string" + case .jsObject(let name): return name ?? "any" + case .swiftHeapObject(let name): return name + case .caseEnum(let name): return name + case .rawValueEnum(let name, _): return name + case .associatedValueEnum(let name): return name + case .namespaceEnum(let name): return name } } } diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package-lock.json b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package-lock.json new file mode 100644 index 00000000..7ddef637 --- /dev/null +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "JavaScript", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "typescript": "^5.8.2" + }, + "bin": { + "ts2skeleton": "bin/ts2skeleton.js" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package.json b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package.json index 48fb77cf..6638cb8d 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package.json +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package.json @@ -1,7 +1,7 @@ { "type": "module", "dependencies": { - "typescript": "5.8.2" + "typescript": "^5.8.2" }, "bin": { "ts2skeleton": "./bin/ts2skeleton.js" diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift new file mode 100644 index 00000000..b82ef33f --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift @@ -0,0 +1,16 @@ +@JS enum Direction { + case north + case south + case east + case west +} + +@JS enum Status { + case loading + case success + case error +} + +@JS func setDirection(_ direction: Direction) +@JS func getDirection() -> Direction +@JS func processDirection(_ input: Direction) -> Status diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift new file mode 100644 index 00000000..15ea3584 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift @@ -0,0 +1,36 @@ +// Empty enum to act as namespace wrapper for classes +@JS enum Utils { + @JS class Converter { + @JS init() {} + + @JS func toString(value: Int) -> String { + return String(value) + } + } +} + +// TODO: Add namespace enum with static functions when supported + +@JS enum Networking { + @JS enum Method { + case get + case post + case put + case delete + } +} + +@JS enum Configuration { + @JS enum LogLevel: String { + case debug = "debug" + case info = "info" + case warning = "warning" + case error = "error" + } + + @JS enum Port: Int { + case http = 80 + case https = 443 + case development = 3000 + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift new file mode 100644 index 00000000..3948470f --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift @@ -0,0 +1,104 @@ +@JS enum Theme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +@JS enum FeatureFlag: Bool { + case enabled = true + case disabled = false +} + +@JS enum HttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +@JS enum Priority: Int32 { + case lowest = 1 + case low = 2 + case medium = 3 + case high = 4 + case highest = 5 +} + +@JS enum FileSize: Int64 { + case tiny = 1024 + case small = 10240 + case medium = 102400 + case large = 1048576 +} + +@JS enum UserId: UInt { + case guest = 0 + case user = 1000 + case admin = 9999 +} + +@JS enum TokenId: UInt32 { + case invalid = 0 + case session = 12345 + case refresh = 67890 +} + +@JS enum SessionId: UInt64 { + case none = 0 + case active = 9876543210 + case expired = 1234567890 +} + +@JS enum Precision: Float { + case rough = 0.1 + case normal = 0.01 + case fine = 0.001 +} + +@JS enum Ratio: Double { + case quarter = 0.25 + case half = 0.5 + case golden = 1.618 + case pi = 3.14159 +} + +@JS enum FeatureFlag: Bool { + case enabled = true + case disabled = false +} + +@JS func setTheme(_ theme: Theme) +@JS func getTheme() -> Theme + +@JS func setFeatureFlag(_ flag: FeatureFlag) +@JS func getFeatureFlag() -> FeatureFlag + +@JS func setHttpStatus(_ status: HttpStatus) +@JS func getHttpStatus() -> HttpStatus + +@JS func setPriority(_ priority: Priority) +@JS func getPriority() -> Priority + +@JS func setFileSize(_ size: FileSize) +@JS func getFileSize() -> FileSize + +@JS func setUserId(_ id: UserId) +@JS func getUserId() -> UserId + +@JS func setTokenId(_ token: TokenId) +@JS func getTokenId() -> TokenId + +@JS func setSessionId(_ session: SessionId) +@JS func getSessionId() -> SessionId + +@JS func setPrecision(_ precision: Precision) +@JS func getPrecision() -> Precision + +@JS func setRatio(_ ratio: Ratio) +@JS func getRatio() -> Ratio + +@JS func setFeatureFlag(_ featureFlag: FeatureFlag) +@JS func getFeatureFlag() -> FeatureFlag + +@JS func processTheme(_ theme: Theme) -> HttpStatus +@JS func convertPriority(_ status: HttpStatus) -> Priority +@JS func validateSession(_ session: SessionId) -> Theme diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/tsconfig.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/tsconfig.json new file mode 100644 index 00000000..86e2ba17 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "lib": ["es2017"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js index c122f179..16995227 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js @@ -84,6 +84,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js index 1da2f58e..6ced9f91 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js @@ -63,6 +63,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { asyncReturnVoid: function bjs_asyncReturnVoid() { const retId = instance.exports.bjs_asyncReturnVoid(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js index 21d11fa4..0e39bd38 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js @@ -128,6 +128,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts new file mode 100644 index 00000000..f4d2c90b --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts @@ -0,0 +1,35 @@ +// 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 Direction: { + readonly North: 0; + readonly South: 1; + readonly East: 2; + readonly West: 3; +}; +export type Direction = typeof Direction[keyof typeof Direction]; + +export const Status: { + readonly Loading: 0; + readonly Success: 1; + readonly Error: 2; +}; +export type Status = typeof Status[keyof typeof Status]; + +export type Exports = { + setDirection(direction: Direction): void; + getDirection(): Direction; + processDirection(input: Direction): Status; +} +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/EnumCase.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js new file mode 100644 index 00000000..ed355d06 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js @@ -0,0 +1,96 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + const Direction = { + North: 0, + South: 1, + East: 2, + West: 3, + }; + + const Status = { + Loading: 0, + Success: 1, + Error: 2, + }; + + return { + Direction, + Status, + setDirection: function bjs_setDirection(direction) { + instance.exports.bjs_setDirection(direction | 0); + }, + getDirection: function bjs_getDirection() { + const ret = instance.exports.bjs_getDirection(); + return ret; + }, + processDirection: function bjs_processDirection(input) { + const ret = instance.exports.bjs_processDirection(input | 0); + return ret; + }, + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts new file mode 100644 index 00000000..5a1fb674 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts @@ -0,0 +1,20 @@ +// 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`. + +// TODO: Implement namespace enum: Utils +// TODO: Implement namespace enum: Networking +// TODO: Implement namespace enum: Configuration +export type Exports = { +} +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/EnumNamespace.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js new file mode 100644 index 00000000..0136a85f --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js @@ -0,0 +1,76 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + // TODO: Implement namespace enum: Utils + // TODO: Implement namespace enum: Networking + // TODO: Implement namespace enum: Configuration + return { + Utils, + Networking, + Configuration, + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts new file mode 100644 index 00000000..7f1ededc --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts @@ -0,0 +1,121 @@ +// 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 Theme: { + readonly Light: "light"; + readonly Dark: "dark"; + readonly Auto: "auto"; +}; +export type Theme = typeof Theme[keyof typeof Theme]; + +export const FeatureFlag: { + readonly Enabled: true; + readonly Disabled: false; +}; +export type FeatureFlag = typeof FeatureFlag[keyof typeof FeatureFlag]; + +export const HttpStatus: { + readonly Ok: 200; + readonly NotFound: 404; + readonly ServerError: 500; +}; +export type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus]; + +export const Priority: { + readonly Lowest: 1; + readonly Low: 2; + readonly Medium: 3; + readonly High: 4; + readonly Highest: 5; +}; +export type Priority = typeof Priority[keyof typeof Priority]; + +export const FileSize: { + readonly Tiny: 1024; + readonly Small: 10240; + readonly Medium: 102400; + readonly Large: 1048576; +}; +export type FileSize = typeof FileSize[keyof typeof FileSize]; + +export const UserId: { + readonly Guest: 0; + readonly User: 1000; + readonly Admin: 9999; +}; +export type UserId = typeof UserId[keyof typeof UserId]; + +export const TokenId: { + readonly Invalid: 0; + readonly Session: 12345; + readonly Refresh: 67890; +}; +export type TokenId = typeof TokenId[keyof typeof TokenId]; + +export const SessionId: { + readonly None: 0; + readonly Active: 9876543210; + readonly Expired: 1234567890; +}; +export type SessionId = typeof SessionId[keyof typeof SessionId]; + +export const Precision: { + readonly Rough: 0.1; + readonly Normal: 0.01; + readonly Fine: 0.001; +}; +export type Precision = typeof Precision[keyof typeof Precision]; + +export const Ratio: { + readonly Quarter: 0.25; + readonly Half: 0.5; + readonly Golden: 1.618; + readonly Pi: 3.14159; +}; +export type Ratio = typeof Ratio[keyof typeof Ratio]; + +export const FeatureFlag: { + readonly Enabled: true; + readonly Disabled: false; +}; +export type FeatureFlag = typeof FeatureFlag[keyof typeof FeatureFlag]; + +export type Exports = { + setTheme(theme: Theme): void; + getTheme(): Theme; + setFeatureFlag(flag: FeatureFlag): void; + getFeatureFlag(): FeatureFlag; + setHttpStatus(status: HttpStatus): void; + getHttpStatus(): HttpStatus; + setPriority(priority: Priority): void; + getPriority(): Priority; + setFileSize(size: FileSize): void; + getFileSize(): FileSize; + setUserId(id: UserId): void; + getUserId(): UserId; + setTokenId(token: TokenId): void; + getTokenId(): TokenId; + setSessionId(session: SessionId): void; + getSessionId(): SessionId; + setPrecision(precision: Precision): void; + getPrecision(): Precision; + setRatio(ratio: Ratio): void; + getRatio(): Ratio; + setFeatureFlag(featureFlag: FeatureFlag): void; + getFeatureFlag(): FeatureFlag; + processTheme(theme: Theme): HttpStatus; + convertPriority(status: HttpStatus): Priority; + validateSession(session: SessionId): Theme; +} +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/EnumRawType.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js new file mode 100644 index 00000000..e63be465 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js @@ -0,0 +1,248 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + const Theme = { + Light: "light", + Dark: "dark", + Auto: "auto", + }; + + const FeatureFlag = { + Enabled: true, + Disabled: false, + }; + + const HttpStatus = { + Ok: 200, + NotFound: 404, + ServerError: 500, + }; + + const Priority = { + Lowest: 1, + Low: 2, + Medium: 3, + High: 4, + Highest: 5, + }; + + const FileSize = { + Tiny: 1024, + Small: 10240, + Medium: 102400, + Large: 1048576, + }; + + const UserId = { + Guest: 0, + User: 1000, + Admin: 9999, + }; + + const TokenId = { + Invalid: 0, + Session: 12345, + Refresh: 67890, + }; + + const SessionId = { + None: 0, + Active: 9876543210, + Expired: 1234567890, + }; + + const Precision = { + Rough: 0.1, + Normal: 0.01, + Fine: 0.001, + }; + + const Ratio = { + Quarter: 0.25, + Half: 0.5, + Golden: 1.618, + Pi: 3.14159, + }; + + const FeatureFlag = { + Enabled: true, + Disabled: false, + }; + + return { + Theme, + FeatureFlag, + HttpStatus, + Priority, + FileSize, + UserId, + TokenId, + SessionId, + Precision, + Ratio, + FeatureFlag, + setTheme: function bjs_setTheme(theme) { + const themeBytes = textEncoder.encode(theme); + const themeId = swift.memory.retain(themeBytes); + instance.exports.bjs_setTheme(themeId, themeBytes.length); + swift.memory.release(themeId); + }, + getTheme: function bjs_getTheme() { + instance.exports.bjs_getTheme(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + setFeatureFlag: function bjs_setFeatureFlag(flag) { + instance.exports.bjs_setFeatureFlag(flag ? 1 : 0); + }, + getFeatureFlag: function bjs_getFeatureFlag() { + const ret = instance.exports.bjs_getFeatureFlag(); + return ret !== 0; + }, + setHttpStatus: function bjs_setHttpStatus(status) { + instance.exports.bjs_setHttpStatus(status); + }, + getHttpStatus: function bjs_getHttpStatus() { + const ret = instance.exports.bjs_getHttpStatus(); + return ret; + }, + setPriority: function bjs_setPriority(priority) { + instance.exports.bjs_setPriority(priority); + }, + getPriority: function bjs_getPriority() { + const ret = instance.exports.bjs_getPriority(); + return ret; + }, + setFileSize: function bjs_setFileSize(size) { + instance.exports.bjs_setFileSize(size); + }, + getFileSize: function bjs_getFileSize() { + const ret = instance.exports.bjs_getFileSize(); + return ret; + }, + setUserId: function bjs_setUserId(id) { + instance.exports.bjs_setUserId(id); + }, + getUserId: function bjs_getUserId() { + const ret = instance.exports.bjs_getUserId(); + return ret; + }, + setTokenId: function bjs_setTokenId(token) { + instance.exports.bjs_setTokenId(token); + }, + getTokenId: function bjs_getTokenId() { + const ret = instance.exports.bjs_getTokenId(); + return ret; + }, + setSessionId: function bjs_setSessionId(session) { + instance.exports.bjs_setSessionId(session); + }, + getSessionId: function bjs_getSessionId() { + const ret = instance.exports.bjs_getSessionId(); + return ret; + }, + setPrecision: function bjs_setPrecision(precision) { + instance.exports.bjs_setPrecision(precision); + }, + getPrecision: function bjs_getPrecision() { + const ret = instance.exports.bjs_getPrecision(); + return ret; + }, + setRatio: function bjs_setRatio(ratio) { + instance.exports.bjs_setRatio(ratio); + }, + getRatio: function bjs_getRatio() { + const ret = instance.exports.bjs_getRatio(); + return ret; + }, + setFeatureFlag: function bjs_setFeatureFlag(featureFlag) { + instance.exports.bjs_setFeatureFlag(featureFlag ? 1 : 0); + }, + getFeatureFlag: function bjs_getFeatureFlag() { + const ret = instance.exports.bjs_getFeatureFlag(); + return ret !== 0; + }, + processTheme: function bjs_processTheme(theme) { + const themeBytes = textEncoder.encode(theme); + const themeId = swift.memory.retain(themeBytes); + const ret = instance.exports.bjs_processTheme(themeId, themeBytes.length); + swift.memory.release(themeId); + return ret; + }, + convertPriority: function bjs_convertPriority(status) { + const ret = instance.exports.bjs_convertPriority(status); + return ret; + }, + validateSession: function bjs_validateSession(session) { + instance.exports.bjs_validateSession(session); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js index f81c7e47..874bebd3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js @@ -90,6 +90,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js index 394d996b..7e955b1a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -194,6 +194,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index 6915a61a..cdf5aa25 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -145,6 +145,7 @@ export async function createInstantiator(options, swift) { return ret; } } + const exports = { Greeter, Converter, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js index 4873fc33..40ac2cda 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js @@ -63,6 +63,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { check: function bjs_check(a, b, c, d) { instance.exports.bjs_check(a, b, c, d); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index 3b93b2dd..1cee664b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -70,6 +70,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index 53332b97..7daa5bbb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -63,6 +63,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { checkInt: function bjs_checkInt() { const ret = instance.exports.bjs_checkInt(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index 1892eb46..19af42bd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -81,6 +81,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js index ea47fb55..bb5cabd8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js @@ -63,6 +63,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { checkString: function bjs_checkString(a) { const aBytes = textEncoder.encode(a); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js index 16ed1081..524a0466 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js @@ -81,6 +81,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js index f98cea55..67c63565 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js @@ -63,6 +63,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { checkString: function bjs_checkString() { instance.exports.bjs_checkString(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js index 3220ae7b..5830cd3d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js @@ -72,6 +72,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index ab4caba3..0a1da2c5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -114,6 +114,7 @@ export async function createInstantiator(options, swift) { swift.memory.release(nameId); } } + return { Greeter, takeGreeter: function bjs_takeGreeter(greeter) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js index 705c6a37..15e5d4d8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -132,6 +132,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js index b2089962..95b558a1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js @@ -63,6 +63,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { throwsSomething: function bjs_throwsSomething() { instance.exports.bjs_throwsSomething(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js index 2eb9dee5..dccc193e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js @@ -70,6 +70,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index c7d622ea..cc060599 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -119,6 +119,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js index c200c077..91017e56 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js @@ -63,6 +63,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { check: function bjs_check() { instance.exports.bjs_check(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js index ca497688..77087eba 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js @@ -70,6 +70,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; + return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json index 8e715451..c0d5347d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json new file mode 100644 index 00000000..3a29a1a1 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json @@ -0,0 +1,132 @@ +{ + "classes" : [ + + ], + "enums" : [ + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "north" + }, + { + "associatedValues" : [ + + ], + "name" : "south" + }, + { + "associatedValues" : [ + + ], + "name" : "east" + }, + { + "associatedValues" : [ + + ], + "name" : "west" + } + ], + "name" : "Direction", + "nestedTypes" : [ + + ] + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "loading" + }, + { + "associatedValues" : [ + + ], + "name" : "success" + }, + { + "associatedValues" : [ + + ], + "name" : "error" + } + ], + "name" : "Status", + "nestedTypes" : [ + + ] + } + ], + "functions" : [ + { + "abiName" : "bjs_setDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setDirection", + "parameters" : [ + { + "label" : "_", + "name" : "direction", + "type" : { + "caseEnum" : { + "_0" : "Direction" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getDirection", + "parameters" : [ + + ], + "returnType" : { + "caseEnum" : { + "_0" : "Direction" + } + } + }, + { + "abiName" : "bjs_processDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "processDirection", + "parameters" : [ + { + "label" : "_", + "name" : "input", + "type" : { + "caseEnum" : { + "_0" : "Direction" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "Status" + } + } + } + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift new file mode 100644 index 00000000..affe3d3d --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift @@ -0,0 +1,39 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_setDirection") +@_cdecl("bjs_setDirection") +public func _bjs_setDirection(direction: Int32) -> Void { + #if arch(wasm32) + setDirection(_: Direction(rawValue: Int(direction))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getDirection") +@_cdecl("bjs_getDirection") +public func _bjs_getDirection() -> Int32 { + #if arch(wasm32) + let ret = getDirection() + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processDirection") +@_cdecl("bjs_processDirection") +public func _bjs_processDirection(input: Int32) -> Int32 { + #if arch(wasm32) + let ret = processDirection(_: Direction(rawValue: Int(input))!) + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json new file mode 100644 index 00000000..fc427dec --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json @@ -0,0 +1,39 @@ +{ + "classes" : [ + + ], + "enums" : [ + { + "cases" : [ + + ], + "name" : "Utils", + "nestedTypes" : [ + "Converter" + ] + }, + { + "cases" : [ + + ], + "name" : "Networking", + "nestedTypes" : [ + "Method" + ] + }, + { + "cases" : [ + + ], + "name" : "Configuration", + "nestedTypes" : [ + "LogLevel", + "Port" + ] + } + ], + "functions" : [ + + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift new file mode 100644 index 00000000..e241d180 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift @@ -0,0 +1,7 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json new file mode 100644 index 00000000..5b88d275 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json @@ -0,0 +1,894 @@ +{ + "classes" : [ + + ], + "enums" : [ + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "light", + "rawValue" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark", + "rawValue" : "dark" + }, + { + "associatedValues" : [ + + ], + "name" : "auto", + "rawValue" : "auto" + } + ], + "name" : "Theme", + "nestedTypes" : [ + + ], + "rawType" : "String" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "enabled", + "rawValue" : "true" + }, + { + "associatedValues" : [ + + ], + "name" : "disabled", + "rawValue" : "false" + } + ], + "name" : "FeatureFlag", + "nestedTypes" : [ + + ], + "rawType" : "Bool" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "ok", + "rawValue" : "200" + }, + { + "associatedValues" : [ + + ], + "name" : "notFound", + "rawValue" : "404" + }, + { + "associatedValues" : [ + + ], + "name" : "serverError", + "rawValue" : "500" + } + ], + "name" : "HttpStatus", + "nestedTypes" : [ + + ], + "rawType" : "Int" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "lowest", + "rawValue" : "1" + }, + { + "associatedValues" : [ + + ], + "name" : "low", + "rawValue" : "2" + }, + { + "associatedValues" : [ + + ], + "name" : "medium", + "rawValue" : "3" + }, + { + "associatedValues" : [ + + ], + "name" : "high", + "rawValue" : "4" + }, + { + "associatedValues" : [ + + ], + "name" : "highest", + "rawValue" : "5" + } + ], + "name" : "Priority", + "nestedTypes" : [ + + ], + "rawType" : "Int32" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "tiny", + "rawValue" : "1024" + }, + { + "associatedValues" : [ + + ], + "name" : "small", + "rawValue" : "10240" + }, + { + "associatedValues" : [ + + ], + "name" : "medium", + "rawValue" : "102400" + }, + { + "associatedValues" : [ + + ], + "name" : "large", + "rawValue" : "1048576" + } + ], + "name" : "FileSize", + "nestedTypes" : [ + + ], + "rawType" : "Int64" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "guest", + "rawValue" : "0" + }, + { + "associatedValues" : [ + + ], + "name" : "user", + "rawValue" : "1000" + }, + { + "associatedValues" : [ + + ], + "name" : "admin", + "rawValue" : "9999" + } + ], + "name" : "UserId", + "nestedTypes" : [ + + ], + "rawType" : "UInt" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "invalid", + "rawValue" : "0" + }, + { + "associatedValues" : [ + + ], + "name" : "session", + "rawValue" : "12345" + }, + { + "associatedValues" : [ + + ], + "name" : "refresh", + "rawValue" : "67890" + } + ], + "name" : "TokenId", + "nestedTypes" : [ + + ], + "rawType" : "UInt32" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "none", + "rawValue" : "0" + }, + { + "associatedValues" : [ + + ], + "name" : "active", + "rawValue" : "9876543210" + }, + { + "associatedValues" : [ + + ], + "name" : "expired", + "rawValue" : "1234567890" + } + ], + "name" : "SessionId", + "nestedTypes" : [ + + ], + "rawType" : "UInt64" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "rough", + "rawValue" : "0.1" + }, + { + "associatedValues" : [ + + ], + "name" : "normal", + "rawValue" : "0.01" + }, + { + "associatedValues" : [ + + ], + "name" : "fine", + "rawValue" : "0.001" + } + ], + "name" : "Precision", + "nestedTypes" : [ + + ], + "rawType" : "Float" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "quarter", + "rawValue" : "0.25" + }, + { + "associatedValues" : [ + + ], + "name" : "half", + "rawValue" : "0.5" + }, + { + "associatedValues" : [ + + ], + "name" : "golden", + "rawValue" : "1.618" + }, + { + "associatedValues" : [ + + ], + "name" : "pi", + "rawValue" : "3.14159" + } + ], + "name" : "Ratio", + "nestedTypes" : [ + + ], + "rawType" : "Double" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "enabled", + "rawValue" : "true" + }, + { + "associatedValues" : [ + + ], + "name" : "disabled", + "rawValue" : "false" + } + ], + "name" : "FeatureFlag", + "nestedTypes" : [ + + ], + "rawType" : "Bool" + } + ], + "functions" : [ + { + "abiName" : "bjs_setTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTheme", + "parameters" : [ + { + "label" : "_", + "name" : "theme", + "type" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTheme", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + }, + { + "abiName" : "bjs_setFeatureFlag", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setFeatureFlag", + "parameters" : [ + { + "label" : "_", + "name" : "flag", + "type" : { + "rawValueEnum" : { + "_0" : "FeatureFlag", + "_1" : "Bool" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getFeatureFlag", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getFeatureFlag", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "FeatureFlag", + "_1" : "Bool" + } + } + }, + { + "abiName" : "bjs_setHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setHttpStatus", + "parameters" : [ + { + "label" : "_", + "name" : "status", + "type" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getHttpStatus", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_setPriority", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setPriority", + "parameters" : [ + { + "label" : "_", + "name" : "priority", + "type" : { + "rawValueEnum" : { + "_0" : "Priority", + "_1" : "Int32" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getPriority", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getPriority", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Priority", + "_1" : "Int32" + } + } + }, + { + "abiName" : "bjs_setFileSize", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setFileSize", + "parameters" : [ + { + "label" : "_", + "name" : "size", + "type" : { + "rawValueEnum" : { + "_0" : "FileSize", + "_1" : "Int64" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getFileSize", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getFileSize", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "FileSize", + "_1" : "Int64" + } + } + }, + { + "abiName" : "bjs_setUserId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setUserId", + "parameters" : [ + { + "label" : "_", + "name" : "id", + "type" : { + "rawValueEnum" : { + "_0" : "UserId", + "_1" : "UInt" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getUserId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getUserId", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "UserId", + "_1" : "UInt" + } + } + }, + { + "abiName" : "bjs_setTokenId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTokenId", + "parameters" : [ + { + "label" : "_", + "name" : "token", + "type" : { + "rawValueEnum" : { + "_0" : "TokenId", + "_1" : "UInt32" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTokenId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTokenId", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "TokenId", + "_1" : "UInt32" + } + } + }, + { + "abiName" : "bjs_setSessionId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setSessionId", + "parameters" : [ + { + "label" : "_", + "name" : "session", + "type" : { + "rawValueEnum" : { + "_0" : "SessionId", + "_1" : "UInt64" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getSessionId", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getSessionId", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "SessionId", + "_1" : "UInt64" + } + } + }, + { + "abiName" : "bjs_setPrecision", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setPrecision", + "parameters" : [ + { + "label" : "_", + "name" : "precision", + "type" : { + "rawValueEnum" : { + "_0" : "Precision", + "_1" : "Float" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getPrecision", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getPrecision", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Precision", + "_1" : "Float" + } + } + }, + { + "abiName" : "bjs_setRatio", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setRatio", + "parameters" : [ + { + "label" : "_", + "name" : "ratio", + "type" : { + "rawValueEnum" : { + "_0" : "Ratio", + "_1" : "Double" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getRatio", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getRatio", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Ratio", + "_1" : "Double" + } + } + }, + { + "abiName" : "bjs_setFeatureFlag", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setFeatureFlag", + "parameters" : [ + { + "label" : "_", + "name" : "featureFlag", + "type" : { + "rawValueEnum" : { + "_0" : "FeatureFlag", + "_1" : "Bool" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getFeatureFlag", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getFeatureFlag", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "FeatureFlag", + "_1" : "Bool" + } + } + }, + { + "abiName" : "bjs_processTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "processTheme", + "parameters" : [ + { + "label" : "_", + "name" : "theme", + "type" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_convertPriority", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "convertPriority", + "parameters" : [ + { + "label" : "_", + "name" : "status", + "type" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Priority", + "_1" : "Int32" + } + } + }, + { + "abiName" : "bjs_validateSession", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "validateSession", + "parameters" : [ + { + "label" : "_", + "name" : "session", + "type" : { + "rawValueEnum" : { + "_0" : "SessionId", + "_1" : "UInt64" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift new file mode 100644 index 00000000..185c4ac8 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift @@ -0,0 +1,283 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_setTheme") +@_cdecl("bjs_setTheme") +public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { + #if arch(wasm32) + let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in + _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) + return Int(themeLen) + } + setTheme(_: Theme(rawValue: theme)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTheme") +@_cdecl("bjs_getTheme") +public func _bjs_getTheme() -> Void { + #if arch(wasm32) + let ret = getTheme() + return ret.rawValue.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setFeatureFlag") +@_cdecl("bjs_setFeatureFlag") +public func _bjs_setFeatureFlag(flag: Int32) -> Void { + #if arch(wasm32) + setFeatureFlag(_: FeatureFlag(rawValue: flag != 0)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getFeatureFlag") +@_cdecl("bjs_getFeatureFlag") +public func _bjs_getFeatureFlag() -> Int32 { + #if arch(wasm32) + let ret = getFeatureFlag() + return Int32(ret.rawValue ? 1 : 0) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setHttpStatus") +@_cdecl("bjs_setHttpStatus") +public func _bjs_setHttpStatus(status: Int32) -> Void { + #if arch(wasm32) + setHttpStatus(_: HttpStatus(rawValue: Int(status))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getHttpStatus") +@_cdecl("bjs_getHttpStatus") +public func _bjs_getHttpStatus() -> Int32 { + #if arch(wasm32) + let ret = getHttpStatus() + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setPriority") +@_cdecl("bjs_setPriority") +public func _bjs_setPriority(priority: Int32) -> Void { + #if arch(wasm32) + setPriority(_: Priority(rawValue: Int32(priority))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getPriority") +@_cdecl("bjs_getPriority") +public func _bjs_getPriority() -> Int32 { + #if arch(wasm32) + let ret = getPriority() + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setFileSize") +@_cdecl("bjs_setFileSize") +public func _bjs_setFileSize(size: Int64) -> Void { + #if arch(wasm32) + setFileSize(_: FileSize(rawValue: Int64(size))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getFileSize") +@_cdecl("bjs_getFileSize") +public func _bjs_getFileSize() -> Int64 { + #if arch(wasm32) + let ret = getFileSize() + return Int64(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setUserId") +@_cdecl("bjs_setUserId") +public func _bjs_setUserId(id: Int32) -> Void { + #if arch(wasm32) + setUserId(_: UserId(rawValue: UInt(bitPattern: id))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getUserId") +@_cdecl("bjs_getUserId") +public func _bjs_getUserId() -> Int32 { + #if arch(wasm32) + let ret = getUserId() + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTokenId") +@_cdecl("bjs_setTokenId") +public func _bjs_setTokenId(token: Int32) -> Void { + #if arch(wasm32) + setTokenId(_: TokenId(rawValue: UInt32(bitPattern: token))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTokenId") +@_cdecl("bjs_getTokenId") +public func _bjs_getTokenId() -> Int32 { + #if arch(wasm32) + let ret = getTokenId() + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setSessionId") +@_cdecl("bjs_setSessionId") +public func _bjs_setSessionId(session: Int64) -> Void { + #if arch(wasm32) + setSessionId(_: SessionId(rawValue: UInt64(bitPattern: Int64(session)))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getSessionId") +@_cdecl("bjs_getSessionId") +public func _bjs_getSessionId() -> Int64 { + #if arch(wasm32) + let ret = getSessionId() + return Int64(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setPrecision") +@_cdecl("bjs_setPrecision") +public func _bjs_setPrecision(precision: Float32) -> Void { + #if arch(wasm32) + setPrecision(_: Precision(rawValue: Float(precision))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getPrecision") +@_cdecl("bjs_getPrecision") +public func _bjs_getPrecision() -> Float32 { + #if arch(wasm32) + let ret = getPrecision() + return Float32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setRatio") +@_cdecl("bjs_setRatio") +public func _bjs_setRatio(ratio: Float64) -> Void { + #if arch(wasm32) + setRatio(_: Ratio(rawValue: Double(ratio))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getRatio") +@_cdecl("bjs_getRatio") +public func _bjs_getRatio() -> Float64 { + #if arch(wasm32) + let ret = getRatio() + return Float64(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setFeatureFlag") +@_cdecl("bjs_setFeatureFlag") +public func _bjs_setFeatureFlag(featureFlag: Int32) -> Void { + #if arch(wasm32) + setFeatureFlag(_: FeatureFlag(rawValue: featureFlag != 0)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getFeatureFlag") +@_cdecl("bjs_getFeatureFlag") +public func _bjs_getFeatureFlag() -> Int32 { + #if arch(wasm32) + let ret = getFeatureFlag() + return Int32(ret.rawValue ? 1 : 0) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processTheme") +@_cdecl("bjs_processTheme") +public func _bjs_processTheme(themeBytes: Int32, themeLen: Int32) -> Int32 { + #if arch(wasm32) + let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in + _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) + return Int(themeLen) + } + let ret = processTheme(_: Theme(rawValue: theme)!) + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_convertPriority") +@_cdecl("bjs_convertPriority") +public func _bjs_convertPriority(status: Int32) -> Int32 { + #if arch(wasm32) + let ret = convertPriority(_: HttpStatus(rawValue: Int(status))!) + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_validateSession") +@_cdecl("bjs_validateSession") +public func _bjs_validateSession(session: Int64) -> Void { + #if arch(wasm32) + let ret = validateSession(_: SessionId(rawValue: UInt64(bitPattern: Int64(session)))!) + return ret.rawValue.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json index 1d1b0fbe..79ff94d8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json @@ -111,6 +111,9 @@ "Foundation" ] } + ], + "enums" : [ + ], "functions" : [ { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json index 7ba4d9dc..c58f3c8e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json index 54e00ea5..ee29313b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json index c2286d12..22df1dc5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json index 23331875..75439e36 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json index 489f1cd5..3f621eb6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json @@ -63,6 +63,9 @@ ], "name" : "Greeter" } + ], + "enums" : [ + ], "functions" : [ { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json index 9acf5b20..cc3184fb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Throws.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json index 12c73531..413fe084 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/VoidParameterVoidReturn.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { From 46c64dbbfc3c550106b42711c08cc16adacfbc0f Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Tue, 19 Aug 2025 12:46:32 +0200 Subject: [PATCH 39/61] BridgeJS: Namespace enum implementation, refactor to parse enums using default SwiftSyntax methods --- .../Sources/BridgeJSCore/ExportSwift.swift | 498 +++++++++------- .../Sources/BridgeJSLink/BridgeJSLink.swift | 541 ++++++++++++------ .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 106 ++-- .../Inputs/EnumNamespace.swift | 36 +- .../Inputs/EnumRawType.swift | 5 - .../EnumNamespace.Export.d.ts | 82 ++- .../BridgeJSLinkTests/EnumNamespace.Export.js | 150 ++++- .../BridgeJSLinkTests/EnumRawType.Export.d.ts | 6 - .../BridgeJSLinkTests/EnumRawType.Export.js | 6 - .../BridgeJSLinkTests/Namespaces.Export.d.ts | 10 +- .../ExportSwiftTests/EnumCase.json | 8 +- .../ExportSwiftTests/EnumNamespace.json | 260 ++++++++- .../ExportSwiftTests/EnumNamespace.swift | 110 +++- .../ExportSwiftTests/EnumRawType.json | 83 +-- .../ExportSwiftTests/Namespaces.json | 9 +- .../ExportSwiftTests/SwiftClass.json | 3 +- 16 files changed, 1353 insertions(+), 560 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index fba27aa1..211f7139 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -75,9 +75,27 @@ public class ExportSwift { var exportedEnumByName: [String: ExportedEnum] = [:] var errors: [DiagnosticError] = [] + /// Creates a unique key for a class by combining name and namespace + private func classKey(name: String, namespace: [String]?) -> String { + if let namespace = namespace, !namespace.isEmpty { + return "\(namespace.joined(separator: ".")).\(name)" + } else { + return name + } + } + + /// Temporary storage for enum data during visitor traversal since EnumCaseDeclSyntax lacks parent context + struct CurrentEnum { + var name: String? + var cases: [EnumCase] = [] + var rawType: String? + } + var currentEnum = CurrentEnum() + enum State { case topLevel - case classBody(name: String) + case classBody(name: String, key: String) + case enumBody(name: String) } struct StateStack { @@ -124,15 +142,22 @@ public class ExportSwift { override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { switch state { case .topLevel: - if let exportedFunction = visitFunction(node: node) { + if let exportedFunction = visitFunction( + node: node + ) { exportedFunctions.append(exportedFunction) } return .skipChildren - case .classBody(let name): - if let exportedFunction = visitFunction(node: node) { - exportedClassByName[name]?.methods.append(exportedFunction) + case .classBody(_, let classKey): + if let exportedFunction = visitFunction( + node: node + ) { + exportedClassByName[classKey]?.methods.append(exportedFunction) } return .skipChildren + case .enumBody: + diagnose(node: node, message: "Functions are not supported inside enums") + return .skipChildren } } @@ -177,8 +202,14 @@ public class ExportSwift { switch state { case .topLevel: abiName = "bjs_\(name)" - case .classBody(let className): + case .classBody(let className, _): abiName = "bjs_\(className)_\(name)" + case .enumBody: + abiName = "" + diagnose( + node: node, + message: "Functions are not supported inside enums" + ) } guard let effects = collectEffects(signature: node.signature) else { @@ -238,8 +269,12 @@ public class ExportSwift { override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard node.attributes.hasJSAttribute() else { return .skipChildren } - guard case .classBody(let name) = state else { - diagnose(node: node, message: "@JS init must be inside a @JS class") + guard case .classBody(let className, _) = state else { + if case .enumBody(_) = state { + diagnose(node: node, message: "Initializers are not supported inside enums") + } else { + diagnose(node: node, message: "@JS init must be inside a @JS class") + } return .skipChildren } @@ -269,52 +304,72 @@ public class ExportSwift { } let constructor = ExportedConstructor( - abiName: "bjs_\(name)_init", + abiName: "bjs_\(className)_init", parameters: parameters, effects: effects ) - exportedClassByName[name]?.constructor = constructor + if case .classBody(_, let classKey) = state { + exportedClassByName[classKey]?.constructor = constructor + } return .skipChildren } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let name = node.name.text - stateStack.push(state: .classBody(name: name)) + guard let jsAttribute = node.attributes.firstJSAttribute else { + if case .enumBody(_) = state { + return .skipChildren + } + return .skipChildren + } - guard let jsAttribute = node.attributes.firstJSAttribute else { return .skipChildren } + let attributeNamespace = extractNamespace(from: jsAttribute) + let computedNamespace = computeNamespace(for: node) - let namespace = extractNamespace(from: jsAttribute) - exportedClassByName[name] = ExportedClass( + if computedNamespace != nil && attributeNamespace != nil { + diagnose( + node: jsAttribute, + message: "Nested classes cannot specify their own namespace", + hint: "Remove the namespace from @JS attribute - nested classes inherit namespace from parent" + ) + return .skipChildren + } + + let effectiveNamespace = computedNamespace ?? attributeNamespace + + let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: name) + let exportedClass = ExportedClass( name: name, + swiftCallName: swiftCallName, constructor: nil, methods: [], - namespace: namespace + namespace: effectiveNamespace ) - exportedClassNames.append(name) + let uniqueKey = classKey(name: name, namespace: effectiveNamespace) + + stateStack.push(state: .classBody(name: name, key: uniqueKey)) + exportedClassByName[uniqueKey] = exportedClass + exportedClassNames.append(uniqueKey) return .visitChildren } + override func visitPost(_ node: ClassDeclSyntax) { stateStack.pop() } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - guard let jsAttribute = node.attributes.firstJSAttribute else { + guard node.attributes.hasJSAttribute() else { + if case .enumBody(_) = state { + return .skipChildren + } return .skipChildren } - let name = node.name.text - let namespace = extractNamespace(from: jsAttribute) - - if let exportedEnum = parseEnum(node: node, namespace: namespace) { - exportedEnumByName[name] = exportedEnum - exportedEnumNames.append(name) + guard let jsAttribute = node.attributes.firstJSAttribute else { + return .skipChildren } - return .skipChildren - } - - private func parseEnum(node: EnumDeclSyntax, namespace: [String]?) -> ExportedEnum? { let name = node.name.text let rawType: String? = node.inheritanceClause?.inheritedTypes.first { inheritedType in @@ -322,68 +377,138 @@ public class ExportSwift { return Constants.supportedRawTypes.contains(typeName) }?.type.trimmedDescription - var cases: [EnumCase] = [] - var nestedTypes: [String] = [] - - for member in node.memberBlock.members { - if let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) { - for element in caseDecl.elements { - let caseName = element.name.text - let rawValue: String? - if let stringLiteral = element.rawValue?.value.as(StringLiteralExprSyntax.self) { - rawValue = stringLiteral.segments.first?.as(StringSegmentSyntax.self)?.content.text - } else if let boolLiteral = element.rawValue?.value.as(BooleanLiteralExprSyntax.self) { - rawValue = boolLiteral.literal.text - } else if let intLiteral = element.rawValue?.value.as(IntegerLiteralExprSyntax.self) { - rawValue = intLiteral.literal.text - } else if let floatLiteral = element.rawValue?.value.as(FloatLiteralExprSyntax.self) { - rawValue = floatLiteral.literal.text - } else { - // Other unsupported type or no raw value - rawValue = nil - } + let attributeNamespace = extractNamespace(from: jsAttribute) + let computedNamespace = computeNamespace(for: node) - var associatedValues: [AssociatedValue] = [] - if let parameterClause = element.parameterClause { - for param in parameterClause.parameters { - guard let bridgeType = parent.lookupType(for: param.type) else { - diagnose( - node: param.type, - message: "Unsupported associated value type: \(param.type.trimmedDescription)", - hint: "Only primitive types and types defined in the same module are allowed" - ) - continue - } - - let label = param.firstName?.text - associatedValues.append(AssociatedValue(label: label, type: bridgeType)) - } - } + if computedNamespace != nil && attributeNamespace != nil { + diagnose( + node: jsAttribute, + message: "Nested enums cannot specify their own namespace", + hint: "Remove the namespace from @JS attribute - nested enums inherit namespace from parent" + ) + return .skipChildren + } + + currentEnum.name = name + currentEnum.cases = [] + currentEnum.rawType = rawType + + stateStack.push(state: .enumBody(name: name)) - cases.append( - EnumCase( - name: caseName, - rawValue: rawValue, - associatedValues: associatedValues + return .visitChildren + } + + override func visitPost(_ node: EnumDeclSyntax) { + guard let jsAttribute = node.attributes.firstJSAttribute, + let enumName = currentEnum.name + else { + return + } + + let attributeNamespace = extractNamespace(from: jsAttribute) + let computedNamespace = computeNamespace(for: node) + + let effectiveNamespace: [String]? + if computedNamespace == nil && attributeNamespace != nil { + effectiveNamespace = attributeNamespace + } else { + effectiveNamespace = computedNamespace + } + + let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: enumName) + let exportedEnum = ExportedEnum( + name: enumName, + swiftCallName: swiftCallName, + cases: currentEnum.cases, + rawType: currentEnum.rawType, + namespace: effectiveNamespace + ) + exportedEnumByName[enumName] = exportedEnum + exportedEnumNames.append(enumName) + + currentEnum = CurrentEnum() + stateStack.pop() + } + + override func visit(_ node: EnumCaseDeclSyntax) -> SyntaxVisitorContinueKind { + for element in node.elements { + let caseName = element.name.text + let rawValue: String? + var associatedValues: [AssociatedValue] = [] + + if currentEnum.rawType != nil { + if let stringLiteral = element.rawValue?.value.as(StringLiteralExprSyntax.self) { + rawValue = stringLiteral.segments.first?.as(StringSegmentSyntax.self)?.content.text + } else if let boolLiteral = element.rawValue?.value.as(BooleanLiteralExprSyntax.self) { + rawValue = boolLiteral.literal.text + } else if let intLiteral = element.rawValue?.value.as(IntegerLiteralExprSyntax.self) { + rawValue = intLiteral.literal.text + } else if let floatLiteral = element.rawValue?.value.as(FloatLiteralExprSyntax.self) { + rawValue = floatLiteral.literal.text + } else { + rawValue = nil + } + } else { + rawValue = nil + } + if let parameterClause = element.parameterClause { + for param in parameterClause.parameters { + guard let bridgeType = parent.lookupType(for: param.type) else { + diagnose( + node: param.type, + message: "Unsupported associated value type: \(param.type.trimmedDescription)", + hint: "Only primitive types and types defined in the same module are allowed" ) - ) + continue + } + + let label = param.firstName?.text + associatedValues.append(AssociatedValue(label: label, type: bridgeType)) } - } else if let nestedEnum = member.decl.as(EnumDeclSyntax.self) { - // Track nested enums for namespace enums - nestedTypes.append(nestedEnum.name.text) - } else if let nestedClass = member.decl.as(ClassDeclSyntax.self) { - // Track nested classes for namespace enums - nestedTypes.append(nestedClass.name.text) } + let enumCase = EnumCase( + name: caseName, + rawValue: rawValue, + associatedValues: associatedValues + ) + + currentEnum.cases.append(enumCase) } - return ExportedEnum( - name: name, - cases: cases, - rawType: rawType, - namespace: namespace, - nestedTypes: nestedTypes - ) + return .visitChildren + } + + /// Computes namespace by walking up the AST hierarchy to find parent namespace enums + /// If parent enum is a namespace enum (no cases) then it will be used as part of namespace for given node + /// + /// + /// Method allows for explicit namespace for top level enum, it will be used as base namespace and will concat enum name + private func computeNamespace(for node: some SyntaxProtocol) -> [String]? { + var namespace: [String] = [] + var currentNode: Syntax? = node.parent + + while let parent = currentNode { + if let enumDecl = parent.as(EnumDeclSyntax.self), + enumDecl.attributes.hasJSAttribute() + { + let isNamespaceEnum = !enumDecl.memberBlock.members.contains { member in + member.decl.is(EnumCaseDeclSyntax.self) + } + if isNamespaceEnum { + namespace.insert(enumDecl.name.text, at: 0) + + if let jsAttribute = enumDecl.attributes.firstJSAttribute, + let explicitNamespace = extractNamespace(from: jsAttribute) + { + namespace = explicitNamespace + namespace + break + } + } + } + currentNode = parent.parent + } + + return namespace.isEmpty ? nil : namespace } } @@ -404,11 +529,32 @@ public class ExportSwift { return collector.errors } + /// Computes the full Swift call name by walking up the AST hierarchy to find all parent enums + /// This generates the qualified name needed for Swift code generation (e.g., "Networking.API.HTTPServer") + private static func computeSwiftCallName(for node: some SyntaxProtocol, itemName: String) -> String { + var swiftPath: [String] = [] + var currentNode: Syntax? = node.parent + + while let parent = currentNode { + if let enumDecl = parent.as(EnumDeclSyntax.self), + enumDecl.attributes.hasJSAttribute() + { + swiftPath.insert(enumDecl.name.text, at: 0) + } + currentNode = parent.parent + } + + if swiftPath.isEmpty { + return itemName + } else { + return swiftPath.joined(separator: ".") + "." + itemName + } + } + func lookupType(for type: TypeSyntax) -> BridgeType? { if let primitive = BridgeType(swiftType: type.trimmedDescription) { return primitive } - guard let identifier = type.as(IdentifierTypeSyntax.self) else { return nil } @@ -416,10 +562,34 @@ public class ExportSwift { guard let typeDecl = typeDeclResolver.lookupType(for: identifier) else { return nil } - - // Check if it's an enum if let enumDecl = typeDecl.as(EnumDeclSyntax.self) { - return classifyEnumType(enumDecl) + let enumName = enumDecl.name.text + if let existingEnum = exportedEnums.first(where: { $0.name == enumName }) { + switch existingEnum.enumType { + case .simple: + return .caseEnum(existingEnum.swiftCallName) + case .rawValue: + let rawType = SwiftEnumRawType.from(existingEnum.rawType!)! + return .rawValueEnum(existingEnum.swiftCallName, rawType) + case .associatedValue: + return .associatedValueEnum(existingEnum.swiftCallName) + case .namespace: + return .namespaceEnum(existingEnum.swiftCallName) + } + } + let swiftCallName = ExportSwift.computeSwiftCallName(for: enumDecl, itemName: enumDecl.name.text) + let rawTypeString = enumDecl.inheritanceClause?.inheritedTypes.first { inheritedType in + let typeName = inheritedType.type.trimmedDescription + return Constants.supportedRawTypes.contains(typeName) + }?.type.trimmedDescription + + if let rawTypeString = rawTypeString, + let rawType = SwiftEnumRawType.from(rawTypeString) + { + return .rawValueEnum(swiftCallName, rawType) + } else { + return .caseEnum(swiftCallName) + } } guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else { @@ -450,53 +620,10 @@ public class ExportSwift { for klass in exportedClasses { decls.append(contentsOf: renderSingleExportedClass(klass: klass)) } - // Note: Enums don't need Swift glue code generation - they're used directly let format = BasicFormat() return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n") } - /// Classifies an enum declaration into the appropriate BridgeType - private func classifyEnumType(_ enumDecl: EnumDeclSyntax) -> BridgeType? { - let enumName = enumDecl.name.text - - // Check for raw value type - let rawType = enumDecl.inheritanceClause?.inheritedTypes.first { inheritedType in - let typeName = inheritedType.type.trimmedDescription - return Constants.supportedRawTypes.contains(typeName) - }?.type.trimmedDescription - - // Count cases and check for associated values - var hasCases = false - var hasAssociatedValues = false - - for member in enumDecl.memberBlock.members { - if let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) { - hasCases = true - for element in caseDecl.elements { - if element.parameterClause != nil { - hasAssociatedValues = true - break - } - } - if hasAssociatedValues { break } - } - } - - // Classify based on structure - if !hasCases { - // Empty enum - used as namespace - return .namespaceEnum(enumName) - } else if hasAssociatedValues { - // Has associated values - return .associatedValueEnum(enumName) - } else if let rawType = rawType { - // Has raw values - return .rawValueEnum(enumName, rawType) - } else { - return .caseEnum(enumName) - } - } - class ExportedThunkBuilder { var body: [CodeBlockItemSyntax] = [] var abiParameterForwardings: [LabeledExprSyntax] = [] @@ -578,7 +705,7 @@ public class ExportSwift { ) abiParameterSignatures.append((param.name, .i32)) case .rawValueEnum(let enumName, let rawType): - if rawType == "String" { + if rawType == .string { let bytesLabel = "\(param.name)Bytes" let lengthLabel = "\(param.name)Len" let prepare: CodeBlockItemSyntax = """ @@ -597,22 +724,19 @@ public class ExportSwift { abiParameterSignatures.append((bytesLabel, .i32)) abiParameterSignatures.append((lengthLabel, .i32)) } else { - // Numeric raw types - use correct WASM ABI type let conversionExpr: String switch rawType { - case "Bool": - // Bool requires special conversion from Int32 (0/1) to Bool (false/true) + case .bool: conversionExpr = "\(enumName)(rawValue: \(param.name) != 0)!" - case "UInt", "UInt32", "UInt64": - // Unsigned types: use bitPattern to handle potential negative Int32 values safely - if rawType == "UInt64" { - conversionExpr = "\(enumName)(rawValue: \(rawType)(bitPattern: Int64(\(param.name))))!" + case .uint, .uint32, .uint64: + if rawType == .uint64 { + conversionExpr = + "\(enumName)(rawValue: \(rawType.rawValue)(bitPattern: Int64(\(param.name))))!" } else { - conversionExpr = "\(enumName)(rawValue: \(rawType)(bitPattern: \(param.name)))!" + conversionExpr = "\(enumName)(rawValue: \(rawType.rawValue)(bitPattern: \(param.name)))!" } default: - // Signed integer and float types: direct conversion - conversionExpr = "\(enumName)(rawValue: \(rawType)(\(param.name)))!" + conversionExpr = "\(enumName)(rawValue: \(rawType.rawValue)(\(param.name)))!" } abiParameterForwardings.append( @@ -621,38 +745,12 @@ public class ExportSwift { expression: ExprSyntax(stringLiteral: conversionExpr) ) ) - switch rawType { - case "Bool", "Int", "Int32", "UInt", "UInt32": - abiParameterSignatures.append((param.name, .i32)) - case "Int64", "UInt64": - abiParameterSignatures.append((param.name, .i64)) - case "Float": - abiParameterSignatures.append((param.name, .f32)) - case "Double": - abiParameterSignatures.append((param.name, .f64)) - default: - abiParameterSignatures.append((param.name, .i32)) // Fallback + if let wasmType = rawType.wasmCoreType { + abiParameterSignatures.append((param.name, wasmType)) } } - case .associatedValueEnum(let enumName): - let bytesLabel = "\(param.name)Bytes" - let lengthLabel = "\(param.name)Len" - let prepare: CodeBlockItemSyntax = """ - let \(raw: param.name)JsonString = String(unsafeUninitializedCapacity: Int(\(raw: lengthLabel))) { b in - _swift_js_init_memory(\(raw: bytesLabel), b.baseAddress.unsafelyUnwrapped) - return Int(\(raw: lengthLabel)) - } - let \(raw: param.name) = try! JSONDecoder().decode(\(raw: enumName).self, from: \(raw: param.name)JsonString.data(using: .utf8)!) - """ - append(prepare) - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((bytesLabel, .i32)) - abiParameterSignatures.append((lengthLabel, .i32)) + case .associatedValueEnum(_): + break case .namespaceEnum: break case .jsObject(nil): @@ -758,20 +856,7 @@ public class ExportSwift { case .caseEnum: abiReturnType = .i32 case .rawValueEnum(_, let rawType): - switch rawType { - case "String": - abiReturnType = nil - case "Bool", "Int", "Int32", "UInt", "UInt32": - abiReturnType = .i32 - case "Int64", "UInt64": - abiReturnType = .i64 - case "Float": - abiReturnType = .f32 - case "Double": - abiReturnType = .f64 - default: - abiReturnType = nil - } + abiReturnType = rawType == .string ? nil : rawType.wasmCoreType case .associatedValueEnum: abiReturnType = nil case .namespaceEnum: @@ -802,7 +887,7 @@ public class ExportSwift { abiReturnType = .i32 append("return Int32(ret.rawValue)") case .rawValueEnum(_, let rawType): - if rawType == "String" { + if rawType == .string { append( """ return ret.rawValue.withUTF8 { ptr in @@ -812,32 +897,22 @@ public class ExportSwift { ) } else { switch rawType { - case "Bool": + case .bool: append("return Int32(ret.rawValue ? 1 : 0)") - case "Int", "Int32", "UInt", "UInt32": + case .int, .int32, .uint, .uint32: append("return Int32(ret.rawValue)") - case "Int64", "UInt64": + case .int64, .uint64: append("return Int64(ret.rawValue)") - case "Float": + case .float: append("return Float32(ret.rawValue)") - case "Double": + case .double: append("return Float64(ret.rawValue)") default: - append("return Int32(ret.rawValue)") // Fallback + append("return Int32(ret.rawValue)") } } - case .associatedValueEnum: - append( - """ - let jsonData = try! JSONEncoder().encode(ret) - return String(data: jsonData, encoding: .utf8)!.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } - """ - ) - case .namespaceEnum: - abiReturnType = .i32 - append("return 0") + case .associatedValueEnum: break; + case .namespaceEnum: break; case .jsObject(nil): append( """ @@ -987,25 +1062,26 @@ public class ExportSwift { /// ``` func renderSingleExportedClass(klass: ExportedClass) -> [DeclSyntax] { var decls: [DeclSyntax] = [] + if let constructor = klass.constructor { let builder = ExportedThunkBuilder(effects: constructor.effects) for param in constructor.parameters { builder.liftParameter(param: param) } - builder.call(name: klass.name, returnType: .swiftHeapObject(klass.name)) - builder.lowerReturnValue(returnType: .swiftHeapObject(klass.name)) + builder.call(name: klass.swiftCallName, returnType: BridgeType.swiftHeapObject(klass.name)) + builder.lowerReturnValue(returnType: BridgeType.swiftHeapObject(klass.name)) decls.append(builder.render(abiName: constructor.abiName)) } for method in klass.methods { let builder = ExportedThunkBuilder(effects: method.effects) builder.liftParameter( - param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(klass.name)) + param: Parameter(label: nil, name: "_self", type: BridgeType.swiftHeapObject(klass.swiftCallName)) ) for param in method.parameters { builder.liftParameter(param: param) } builder.callMethod( - klassName: klass.name, + klassName: klass.swiftCallName, methodName: method.name, returnType: method.returnType ) @@ -1019,7 +1095,7 @@ public class ExportSwift { @_expose(wasm, "bjs_\(raw: klass.name)_deinit") @_cdecl("bjs_\(raw: klass.name)_deinit") public func _bjs_\(raw: klass.name)_deinit(pointer: UnsafeMutableRawPointer) { - Unmanaged<\(raw: klass.name)>.fromOpaque(pointer).release() + Unmanaged<\(raw: klass.swiftCallName)>.fromOpaque(pointer).release() } """ ) @@ -1051,7 +1127,7 @@ public class ExportSwift { let externFunctionName = "bjs_\(klass.name)_wrap" return """ - extension \(raw: klass.name): ConvertibleToJSValue { + extension \(raw: klass.swiftCallName): ConvertibleToJSValue { var jsValue: JSValue { @_extern(wasm, module: "\(raw: moduleName)", name: "\(raw: externFunctionName)") func \(raw: wrapFunctionName)(_: UnsafeMutableRawPointer) -> Int32 @@ -1062,6 +1138,10 @@ public class ExportSwift { } } +fileprivate enum Constants { + static let supportedRawTypes = SwiftEnumRawType.allCases.map { $0.rawValue } +} + extension AttributeListSyntax { fileprivate func hasJSAttribute() -> Bool { firstJSAttribute != nil diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 07712a83..0f40af96 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -69,6 +69,7 @@ struct BridgeJSLink { var dtsClassLines: [String] = [] var namespacedFunctions: [ExportedFunction] = [] var namespacedClasses: [ExportedClass] = [] + var namespacedEnums: [ExportedEnum] = [] var enumConstantLines: [String] = [] var dtsEnumLines: [String] = [] @@ -99,10 +100,15 @@ struct BridgeJSLink { } if !skeleton.enums.isEmpty { - for enumDef in skeleton.enums { - let (jsEnum, dtsEnum) = try renderExportedEnum(enumDef) + for enumDefinition in skeleton.enums { + let (jsEnum, dtsEnum) = try renderExportedEnum(enumDefinition) enumConstantLines.append(contentsOf: jsEnum) - exportsLines.append("\(enumDef.name),") + if enumDefinition.enumType != .namespace { + exportsLines.append("\(enumDefinition.name),") + if enumDefinition.namespace != nil { + namespacedEnums.append(enumDefinition) + } + } dtsEnumLines.append(contentsOf: dtsEnum) } } @@ -135,13 +141,22 @@ struct BridgeJSLink { importObjectBuilders.append(importObjectBuilder) } - let hasNamespacedItems = !namespacedFunctions.isEmpty || !namespacedClasses.isEmpty + let hasNamespacedItems = !namespacedFunctions.isEmpty || !namespacedClasses.isEmpty || !namespacedEnums.isEmpty + + let namespaceBuilder = NamespaceBuilder() + let namespaceDeclarationsLines = namespaceBuilder.namespaceDeclarations( + exportedSkeletons: exportedSkeletons, + renderTSSignatureCallback: { parameters, returnType, effects in + self.renderTSSignature(parameters: parameters, returnType: returnType, effects: effects) + } + ) let exportsSection: String if hasNamespacedItems { - let namespaceSetupCode = renderGlobalNamespace( + let namespaceSetupCode = namespaceBuilder.renderGlobalNamespace( namespacedFunctions: namespacedFunctions, - namespacedClasses: namespacedClasses + namespacedClasses: namespacedClasses, + namespacedEnums: namespacedEnums ) .map { $0.indent(count: 12) }.joined(separator: "\n") exportsSection = """ @@ -238,7 +253,7 @@ struct BridgeJSLink { """ var dtsLines: [String] = [] - dtsLines.append(contentsOf: namespaceDeclarations()) + dtsLines.append(contentsOf: namespaceDeclarationsLines) dtsLines.append(contentsOf: dtsClassLines) dtsLines.append(contentsOf: dtsEnumLines) dtsLines.append(contentsOf: generateImportedTypeDefinitions()) @@ -332,102 +347,6 @@ struct BridgeJSLink { return typeDefinitions } - private func namespaceDeclarations() -> [String] { - var dtsLines: [String] = [] - var namespaceFunctions: [String: [ExportedFunction]] = [:] - var namespaceClasses: [String: [ExportedClass]] = [:] - - for skeleton in exportedSkeletons { - for function in skeleton.functions { - if let namespace = function.namespace { - let namespaceKey = namespace.joined(separator: ".") - if namespaceFunctions[namespaceKey] == nil { - namespaceFunctions[namespaceKey] = [] - } - namespaceFunctions[namespaceKey]?.append(function) - } - } - - for klass in skeleton.classes { - if let classNamespace = klass.namespace { - let namespaceKey = classNamespace.joined(separator: ".") - if namespaceClasses[namespaceKey] == nil { - namespaceClasses[namespaceKey] = [] - } - namespaceClasses[namespaceKey]?.append(klass) - } - } - } - - guard !namespaceFunctions.isEmpty || !namespaceClasses.isEmpty else { return dtsLines } - - dtsLines.append("export {};") - dtsLines.append("") - dtsLines.append("declare global {") - - let identBaseSize = 4 - - for (namespacePath, classes) in namespaceClasses.sorted(by: { $0.key < $1.key }) { - let parts = namespacePath.split(separator: ".").map(String.init) - - for i in 0.. (js: [String], dts: [String]) { + func renderExportedEnum(_ enumDefinition: ExportedEnum) throws -> (js: [String], dts: [String]) { var jsLines: [String] = [] var dtsLines: [String] = [] - switch enumDef.enumType { + switch enumDefinition.enumType { case .simple: - jsLines.append("const \(enumDef.name) = {") - for (index, enumCase) in enumDef.cases.enumerated() { + jsLines.append("const \(enumDefinition.name) = {") + for (index, enumCase) in enumDefinition.cases.enumerated() { let caseName = enumCase.name.capitalizedFirstLetter jsLines.append(" \(caseName): \(index),".indent(count: 0)) } jsLines.append("};") jsLines.append("") - dtsLines.append("export const \(enumDef.name): {") - for (index, enumCase) in enumDef.cases.enumerated() { - let caseName = enumCase.name.capitalizedFirstLetter - dtsLines.append(" readonly \(caseName): \(index);") + if enumDefinition.namespace == nil { + dtsLines.append("export const \(enumDefinition.name): {") + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append(" readonly \(caseName): \(index);") + } + dtsLines.append("};") + dtsLines.append( + "export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + ) + dtsLines.append("") } - dtsLines.append("};") - dtsLines.append("export type \(enumDef.name) = typeof \(enumDef.name)[keyof typeof \(enumDef.name)];") - dtsLines.append("") case .rawValue: - guard let rawType = enumDef.rawType else { - throw BridgeJSLinkError(message: "Raw value enum \(enumDef.name) is missing rawType") + guard let rawType = enumDefinition.rawType else { + throw BridgeJSLinkError(message: "Raw value enum \(enumDefinition.name) is missing rawType") } - jsLines.append("const \(enumDef.name) = {") - for enumCase in enumDef.cases { + jsLines.append("const \(enumDefinition.name) = {") + for enumCase in enumDefinition.cases { let caseName = enumCase.name.capitalizedFirstLetter let rawValue = enumCase.rawValue ?? enumCase.name let formattedValue: String - switch rawType { - case "String": - formattedValue = "\"\(rawValue)\"" - case "Bool": - formattedValue = rawValue.lowercased() == "true" ? "true" : "false" - case "Float", "Double": - formattedValue = rawValue - default: + if let rawTypeEnum = SwiftEnumRawType.from(rawType) { + switch rawTypeEnum { + case .string: + formattedValue = "\"\(rawValue)\"" + case .bool: + formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case .float, .double: + formattedValue = rawValue + default: + formattedValue = rawValue + } + } else { formattedValue = rawValue } @@ -651,32 +575,38 @@ struct BridgeJSLink { jsLines.append("};") jsLines.append("") - dtsLines.append("export const \(enumDef.name): {") - for enumCase in enumDef.cases { - let caseName = enumCase.name.capitalizedFirstLetter - let rawValue = enumCase.rawValue ?? enumCase.name - let formattedValue: String + if enumDefinition.namespace == nil { + dtsLines.append("export const \(enumDefinition.name): {") + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + + switch rawType { + case "String": + formattedValue = "\"\(rawValue)\"" + case "Bool": + formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": + formattedValue = rawValue + default: + formattedValue = rawValue + } - switch rawType { - case "String": - formattedValue = "\"\(rawValue)\"" - case "Bool": - formattedValue = rawValue.lowercased() == "true" ? "true" : "false" - case "Float", "Double": - formattedValue = rawValue - default: - formattedValue = rawValue + dtsLines.append(" readonly \(caseName): \(formattedValue);") } - - dtsLines.append(" readonly \(caseName): \(formattedValue);") + dtsLines.append("};") + dtsLines.append( + "export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + ) + dtsLines.append("") } - dtsLines.append("};") - dtsLines.append("export type \(enumDef.name) = typeof \(enumDef.name)[keyof typeof \(enumDef.name)];") - dtsLines.append("") - case .associatedValue, .namespace: - jsLines.append("// TODO: Implement \(enumDef.enumType) enum: \(enumDef.name)") - dtsLines.append("// TODO: Implement \(enumDef.enumType) enum: \(enumDef.name)") + case .associatedValue: + jsLines.append("// TODO: Implement \(enumDefinition.enumType) enum: \(enumDefinition.name)") + dtsLines.append("// TODO: Implement \(enumDefinition.enumType) enum: \(enumDefinition.name)") + case .namespace: + break } return (jsLines, dtsLines) @@ -774,8 +704,11 @@ struct BridgeJSLink { return (jsLines, dtsTypeLines, dtsExportEntryLines) } - func renderGlobalNamespace(namespacedFunctions: [ExportedFunction], namespacedClasses: [ExportedClass]) -> [String] - { + func renderGlobalNamespace( + namespacedFunctions: [ExportedFunction], + namespacedClasses: [ExportedClass], + namespacedEnums: [ExportedEnum] + ) -> [String] { var lines: [String] = [] var uniqueNamespaces: [String] = [] var seen = Set() @@ -788,10 +721,15 @@ struct BridgeJSLink { namespacedClasses .compactMap { $0.namespace } ) + let enumNamespacePaths: Set<[String]> = Set( + namespacedEnums + .compactMap { $0.namespace } + ) let allNamespacePaths = functionNamespacePaths .union(classNamespacePaths) + .union(enumNamespacePaths) allNamespacePaths.forEach { namespacePath in namespacePath.makeIterator().enumerated().forEach { (index, _) in @@ -813,6 +751,11 @@ struct BridgeJSLink { lines.append("globalThis.\(namespacePath).\(klass.name) = exports.\(klass.name);") } + namespacedEnums.forEach { enumDefinition in + let namespacePath: String = enumDefinition.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(enumDefinition.name) = exports.\(enumDefinition.name);") + } + namespacedFunctions.forEach { function in let namespacePath: String = function.namespace?.joined(separator: ".") ?? "" lines.append("globalThis.\(namespacePath).\(function.name) = exports.\(function.name);") @@ -843,12 +786,12 @@ struct BridgeJSLink { parameterForwardings.append(param.name) case .rawValueEnum(_, let rawType): switch rawType { - case "String": + case .string: let stringObjectName = "\(param.name)Object" bodyLines.append("const \(stringObjectName) = swift.memory.getObject(\(param.name));") bodyLines.append("swift.memory.release(\(param.name));") parameterForwardings.append(stringObjectName) - case "Bool": + case .bool: parameterForwardings.append("\(param.name) !== 0") default: parameterForwardings.append(param.name) @@ -932,18 +875,18 @@ struct BridgeJSLink { return "ret" case .rawValueEnum(_, let rawType): switch rawType { - case "String": + case .string: bodyLines.append("tmpRetBytes = textEncoder.encode(ret);") return "tmpRetBytes.length" - case "Bool": + case .bool: return "ret ? 1 : 0" default: return "ret" } case .associatedValueEnum: - return "0" + return nil case .namespaceEnum: - return "0" + return nil case .int, .float, .double: return "ret" case .bool: @@ -979,6 +922,276 @@ struct BridgeJSLink { } } + struct NamespaceBuilder { + + /// Generates JavaScript code for setting up global namespace structure + /// + /// This function creates the necessary JavaScript code to properly expose namespaced + /// functions, classes, and enums on the global object (globalThis). It ensures that + /// nested namespace paths are created correctly and that all exported items are + /// accessible through their full namespace paths. + /// + /// For example, if you have @JS("Utils.Math") func add() it will generate code that + /// makes globalThis.Utils.Math.add accessible. + /// + /// - Parameters: + /// - namespacedFunctions: Functions annotated with @JS("namespace.path") + /// - namespacedClasses: Classes annotated with @JS("namespace.path") + /// - namespacedEnums: Enums annotated with @JS("namespace.path") + /// - Returns: Array of JavaScript code lines that set up the global namespace structure + func renderGlobalNamespace( + namespacedFunctions: [ExportedFunction], + namespacedClasses: [ExportedClass], + namespacedEnums: [ExportedEnum] + ) -> [String] { + var lines: [String] = [] + var uniqueNamespaces: [String] = [] + var seen = Set() + + let functionNamespacePaths: Set<[String]> = Set( + namespacedFunctions + .compactMap { $0.namespace } + ) + let classNamespacePaths: Set<[String]> = Set( + namespacedClasses + .compactMap { $0.namespace } + ) + let enumNamespacePaths: Set<[String]> = Set( + namespacedEnums + .compactMap { $0.namespace } + ) + + let allNamespacePaths = + functionNamespacePaths + .union(classNamespacePaths) + .union(enumNamespacePaths) + + allNamespacePaths.forEach { namespacePath in + namespacePath.makeIterator().enumerated().forEach { (index, _) in + let path = namespacePath[0...index].joined(separator: ".") + if seen.insert(path).inserted { + uniqueNamespaces.append(path) + } + } + } + + uniqueNamespaces.sorted().forEach { namespace in + lines.append("if (typeof globalThis.\(namespace) === 'undefined') {") + lines.append(" globalThis.\(namespace) = {};") + lines.append("}") + } + + namespacedClasses.forEach { klass in + let namespacePath: String = klass.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(klass.name) = exports.\(klass.name);") + } + + namespacedEnums.forEach { enumDefinition in + let namespacePath: String = enumDefinition.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(enumDefinition.name) = exports.\(enumDefinition.name);") + } + + namespacedFunctions.forEach { function in + let namespacePath: String = function.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(function.name) = exports.\(function.name);") + } + + return lines + } + + private struct NamespaceContent { + var functions: [ExportedFunction] = [] + var classes: [ExportedClass] = [] + var enums: [ExportedEnum] = [] + } + + private final class NamespaceNode { + let name: String + var children: [String: NamespaceNode] = [:] + var content: NamespaceContent = NamespaceContent() + + init(name: String) { + self.name = name + } + + func addChild(_ childName: String) -> NamespaceNode { + if let existing = children[childName] { + return existing + } + let newChild = NamespaceNode(name: childName) + children[childName] = newChild + return newChild + } + } + + /// Generates TypeScript declarations for all namespaces + /// + /// This function enables properly grouping all Swift code within given namespaces + /// regardless of location in Swift input files. It uses a tree-based structure to + /// properly create unique namespace declarations that avoid namespace duplication in TS and generate + /// predictable declarations in sorted order. + /// + /// The function collects all namespaced items (functions, classes, enums) from the + /// exported skeletons and builds a hierarchical namespace tree. It then traverses + /// this tree to generate TypeScript namespace declarations that mirror the Swift + /// namespace structure. + /// - Parameters: + /// - exportedSkeletons: Exported Swift structures to generate namespaces for + /// - renderTSSignatureCallback: closure to generate TS signature that aligns with rest of codebase + /// - Returns: Array of TypeScript declaration lines defining the global namespace structure + func namespaceDeclarations( + exportedSkeletons: [ExportedSkeleton], + renderTSSignatureCallback: @escaping ([Parameter], BridgeType, Effects) -> String + ) -> [String] { + var dtsLines: [String] = [] + + let rootNode = NamespaceNode(name: "") + + for skeleton in exportedSkeletons { + for function in skeleton.functions { + if let namespace = function.namespace { + var currentNode = rootNode + for part in namespace { + currentNode = currentNode.addChild(part) + } + currentNode.content.functions.append(function) + } + } + + for klass in skeleton.classes { + if let classNamespace = klass.namespace { + var currentNode = rootNode + for part in classNamespace { + currentNode = currentNode.addChild(part) + } + currentNode.content.classes.append(klass) + } + } + + for enumDefinition in skeleton.enums { + if let enumNamespace = enumDefinition.namespace, enumDefinition.enumType != .namespace { + var currentNode = rootNode + for part in enumNamespace { + currentNode = currentNode.addChild(part) + } + currentNode.content.enums.append(enumDefinition) + } + } + } + + guard !rootNode.children.isEmpty else { + return dtsLines + } + + dtsLines.append("export {};") + dtsLines.append("") + dtsLines.append("declare global {") + + let identBaseSize = 4 + + func generateNamespaceDeclarations(node: NamespaceNode, depth: Int) { + let sortedChildren = node.children.sorted { $0.key < $1.key } + + for (childName, childNode) in sortedChildren { + dtsLines.append("namespace \(childName) {".indent(count: identBaseSize * depth)) + + let contentDepth = depth + 1 + + let sortedClasses = childNode.content.classes.sorted { $0.name < $1.name } + for klass in sortedClasses { + dtsLines.append("class \(klass.name) {".indent(count: identBaseSize * contentDepth)) + + if let constructor = klass.constructor { + let constructorSignature = + "constructor(\(constructor.parameters.map { "\($0.name): \($0.type.tsType)" }.joined(separator: ", ")));" + dtsLines.append("\(constructorSignature)".indent(count: identBaseSize * (contentDepth + 1))) + } + + let sortedMethods = klass.methods.sorted { $0.name < $1.name } + for method in sortedMethods { + let methodSignature = + "\(method.name)\(renderTSSignatureCallback(method.parameters, method.returnType, method.effects));" + dtsLines.append("\(methodSignature)".indent(count: identBaseSize * (contentDepth + 1))) + } + + dtsLines.append("}".indent(count: identBaseSize * contentDepth)) + } + + let sortedEnums = childNode.content.enums.sorted { $0.name < $1.name } + for enumDefinition in sortedEnums { + switch enumDefinition.enumType { + case .simple: + dtsLines.append( + "const \(enumDefinition.name): {".indent(count: identBaseSize * contentDepth) + ) + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append( + "readonly \(caseName): \(index);".indent(count: identBaseSize * (contentDepth + 1)) + ) + } + dtsLines.append("};".indent(count: identBaseSize * contentDepth)) + dtsLines.append( + "type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + .indent( + count: identBaseSize * contentDepth + ) + ) + case .rawValue: + guard let rawType = enumDefinition.rawType else { continue } + dtsLines.append( + "const \(enumDefinition.name): {".indent(count: identBaseSize * contentDepth) + ) + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + switch rawType { + case "String": formattedValue = "\"\(rawValue)\"" + case "Bool": formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": formattedValue = rawValue + default: formattedValue = rawValue + } + dtsLines.append( + "readonly \(caseName): \(formattedValue);".indent( + count: identBaseSize * (contentDepth + 1) + ) + ) + } + dtsLines.append("};".indent(count: identBaseSize * contentDepth)) + dtsLines.append( + "type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + .indent( + count: identBaseSize * contentDepth + ) + ) + case .associatedValue, .namespace: + continue + } + } + + let sortedFunctions = childNode.content.functions.sorted { $0.name < $1.name } + for function in sortedFunctions { + let signature = + "\(function.name)\(renderTSSignatureCallback(function.parameters, function.returnType, function.effects));" + dtsLines.append("\(signature)".indent(count: identBaseSize * contentDepth)) + } + + generateNamespaceDeclarations(node: childNode, depth: contentDepth) + + dtsLines.append("}".indent(count: identBaseSize * depth)) + } + } + + generateNamespaceDeclarations(node: rootNode, depth: 1) + + dtsLines.append("}") + dtsLines.append("") + + return dtsLines + } + } + func renderImportedFunction( importObjectBuilder: ImportObjectBuilder, function: ImportedFunctionSkeleton diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index e1f1b3ef..89f2771e 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -2,16 +2,10 @@ // MARK: - Types -public enum Constants { - public static let supportedRawTypes = [ - "String", "Bool", "Int", "Int32", "Int64", "UInt", "UInt32", "UInt64", "Float", "Double", - ] -} - public enum BridgeType: Codable, Equatable { case int, float, double, string, bool, jsObject(String?), swiftHeapObject(String), void case caseEnum(String) - case rawValueEnum(String, String) + case rawValueEnum(String, SwiftEnumRawType) case associatedValueEnum(String) case namespaceEnum(String) } @@ -20,6 +14,39 @@ public enum WasmCoreType: String, Codable { case i32, i64, f32, f64, pointer } +/// Represents supported Swift enum raw types with their WASM ABI properties +public enum SwiftEnumRawType: String, CaseIterable, Codable { + case string = "String" + case bool = "Bool" + case int = "Int" + case int32 = "Int32" + case int64 = "Int64" + case uint = "UInt" + case uint32 = "UInt32" + case uint64 = "UInt64" + case float = "Float" + case double = "Double" + + public var wasmCoreType: WasmCoreType? { + switch self { + case .string: + return nil // String has special handling with UTF-8 bytes and length + case .bool, .int, .int32, .uint, .uint32: + return .i32 + case .int64, .uint64: + return .i64 + case .float: + return .f32 + case .double: + return .f64 + } + } + + public static func from(_ rawTypeString: String) -> SwiftEnumRawType? { + return Self.allCases.first { $0.rawValue == rawTypeString } + } +} + public struct Parameter: Codable { public let label: String? public let name: String @@ -72,11 +99,10 @@ public struct EnumCase: Codable, Equatable { public struct ExportedEnum: Codable, Equatable { public let name: String + public let swiftCallName: String public let cases: [EnumCase] public let rawType: String? public let namespace: [String]? - public let nestedTypes: [String] - public var enumType: EnumType { if cases.isEmpty { return .namespace @@ -87,12 +113,18 @@ public struct ExportedEnum: Codable, Equatable { } } - public init(name: String, cases: [EnumCase], rawType: String?, namespace: [String]?, nestedTypes: [String]) { + public init( + name: String, + swiftCallName: String, + cases: [EnumCase], + rawType: String?, + namespace: [String]? + ) { self.name = name + self.swiftCallName = swiftCallName self.cases = cases self.rawType = rawType self.namespace = namespace - self.nestedTypes = nestedTypes } } @@ -132,17 +164,20 @@ public struct ExportedFunction: Codable { public struct ExportedClass: Codable { public var name: String + public var swiftCallName: String public var constructor: ExportedConstructor? public var methods: [ExportedFunction] public var namespace: [String]? public init( name: String, + swiftCallName: String, constructor: ExportedConstructor? = nil, methods: [ExportedFunction], namespace: [String]? = nil ) { self.name = name + self.swiftCallName = swiftCallName self.constructor = constructor self.methods = methods self.namespace = namespace @@ -254,58 +289,11 @@ extension BridgeType { case .caseEnum: return .i32 case .rawValueEnum(_, let rawType): - switch rawType { - case "String": - return nil // String uses special handling - case "Bool", "Int", "Int32", "UInt", "UInt32": - return .i32 - case "Int64", "UInt64": - return .i64 - case "Float": - return .f32 - case "Double": - return .f64 - default: - return nil - } + return rawType.wasmCoreType case .associatedValueEnum: return nil case .namespaceEnum: return nil } } - - /// Returns the Swift type name for this bridge type - var swiftTypeName: String { - switch self { - case .void: return "Void" - case .bool: return "Bool" - case .int: return "Int" - case .float: return "Float" - case .double: return "Double" - case .string: return "String" - case .jsObject(let name): return name ?? "JSObject" - case .swiftHeapObject(let name): return name - case .caseEnum(let name): return name - case .rawValueEnum(let name, _): return name - case .associatedValueEnum(let name): return name - case .namespaceEnum(let name): return name - } - } - - /// Returns the TypeScript type name for this bridge type - var tsTypeName: String { - switch self { - case .void: return "void" - case .bool: return "boolean" - case .int, .float, .double: return "number" - case .string: return "string" - case .jsObject(let name): return name ?? "any" - case .swiftHeapObject(let name): return name - case .caseEnum(let name): return name - case .rawValueEnum(let name, _): return name - case .associatedValueEnum(let name): return name - case .namespaceEnum(let name): return name - } - } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift index 15ea3584..68c666db 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift @@ -1,4 +1,5 @@ -// Empty enum to act as namespace wrapper for classes +// Empty enums to act as namespace wrappers for nested elements + @JS enum Utils { @JS class Converter { @JS init() {} @@ -9,14 +10,19 @@ } } -// TODO: Add namespace enum with static functions when supported - @JS enum Networking { - @JS enum Method { - case get - case post - case put - case delete + @JS enum API { + @JS enum Method { + case get + case post + case put + case delete + } + // Invalid to declare @JS(namespace) here as it would be conflicting with nesting + @JS class HTTPServer { + @JS init() {} + @JS func call(_ method: Method) + } } } @@ -34,3 +40,17 @@ case development = 3000 } } + +@JS(namespace: "Networking.APIV2") +enum Internal { + @JS enum SupportedMethod { + case get + case post + } + @JS class TestServer { + @JS init() {} + @JS func call(_ method: SupportedMethod) + } +} + +// TODO: Add namespace enum with static functions when supported diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift index 3948470f..bf1a69cc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift @@ -61,11 +61,6 @@ case pi = 3.14159 } -@JS enum FeatureFlag: Bool { - case enabled = true - case disabled = false -} - @JS func setTheme(_ theme: Theme) @JS func getTheme() -> Theme diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts index 5a1fb674..3d37ca6c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.d.ts @@ -4,10 +4,86 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. -// TODO: Implement namespace enum: Utils -// TODO: Implement namespace enum: Networking -// TODO: Implement namespace enum: Configuration +export {}; + +declare global { + namespace Configuration { + const LogLevel: { + readonly Debug: "debug"; + readonly Info: "info"; + readonly Warning: "warning"; + readonly Error: "error"; + }; + type LogLevel = typeof LogLevel[keyof typeof LogLevel]; + const Port: { + readonly Http: 80; + readonly Https: 443; + readonly Development: 3000; + }; + type Port = typeof Port[keyof typeof Port]; + } + namespace Networking { + namespace API { + class HTTPServer { + constructor(); + call(method: Networking.API.Method): void; + } + const Method: { + readonly Get: 0; + readonly Post: 1; + readonly Put: 2; + readonly Delete: 3; + }; + type Method = typeof Method[keyof typeof Method]; + } + namespace APIV2 { + namespace Internal { + class TestServer { + constructor(); + call(method: Internal.SupportedMethod): void; + } + const SupportedMethod: { + readonly Get: 0; + readonly Post: 1; + }; + type SupportedMethod = typeof SupportedMethod[keyof typeof SupportedMethod]; + } + } + } + namespace Utils { + class Converter { + constructor(); + toString(value: number): string; + } + } +} + +/// 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 Converter extends SwiftHeapObject { + toString(value: number): string; +} +export interface HTTPServer extends SwiftHeapObject { + call(method: Networking.API.Method): void; +} +export interface TestServer extends SwiftHeapObject { + call(method: Internal.SupportedMethod): void; +} export type Exports = { + Converter: { + new(): Converter; + } + HTTPServer: { + new(): HTTPServer; + } + TestServer: { + new(): TestServer; + } } export type Imports = { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js index 0136a85f..530822a5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js @@ -49,7 +49,22 @@ export async function createInstantiator(options, swift) { bjs["swift_js_release"] = function(id) { swift.memory.release(id); } - + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_Converter_wrap"] = function(pointer) { + const obj = Converter.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_HTTPServer_wrap"] = function(pointer) { + const obj = HTTPServer.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_TestServer_wrap"] = function(pointer) { + const obj = TestServer.__construct(pointer); + return swift.memory.retain(obj); + }; }, setInstance: (i) => { @@ -62,15 +77,132 @@ export async function createInstantiator(options, swift) { /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { const js = swift.memory.heap; - - // TODO: Implement namespace enum: Utils - // TODO: Implement namespace enum: Networking - // TODO: Implement namespace enum: Configuration - return { - Utils, - Networking, - Configuration, + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { + deinit(pointer); + }); + obj.registry.register(this, obj.pointer); + return obj; + } + + release() { + this.registry.unregister(this); + this.deinit(this.pointer); + } + } + class Converter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_Converter_deinit, Converter.prototype); + } + + + constructor() { + const ret = instance.exports.bjs_Converter_init(); + return Converter.__construct(ret); + } + toString(value) { + instance.exports.bjs_Converter_toString(this.pointer, value); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + } + class HTTPServer extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_HTTPServer_deinit, HTTPServer.prototype); + } + + + constructor() { + const ret = instance.exports.bjs_HTTPServer_init(); + return HTTPServer.__construct(ret); + } + call(method) { + instance.exports.bjs_HTTPServer_call(this.pointer, method | 0); + } + } + class TestServer extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_TestServer_deinit, TestServer.prototype); + } + + + constructor() { + const ret = instance.exports.bjs_TestServer_init(); + return TestServer.__construct(ret); + } + call(method) { + instance.exports.bjs_TestServer_call(this.pointer, method | 0); + } + } + const Method = { + Get: 0, + Post: 1, + Put: 2, + Delete: 3, + }; + + const LogLevel = { + Debug: "debug", + Info: "info", + Warning: "warning", + Error: "error", }; + + const Port = { + Http: 80, + Https: 443, + Development: 3000, + }; + + const SupportedMethod = { + Get: 0, + Post: 1, + }; + + const exports = { + Converter, + HTTPServer, + TestServer, + Method, + LogLevel, + Port, + SupportedMethod, + }; + + if (typeof globalThis.Configuration === 'undefined') { + globalThis.Configuration = {}; + } + if (typeof globalThis.Networking === 'undefined') { + globalThis.Networking = {}; + } + if (typeof globalThis.Networking.API === 'undefined') { + globalThis.Networking.API = {}; + } + if (typeof globalThis.Networking.APIV2 === 'undefined') { + globalThis.Networking.APIV2 = {}; + } + if (typeof globalThis.Networking.APIV2.Internal === 'undefined') { + globalThis.Networking.APIV2.Internal = {}; + } + if (typeof globalThis.Utils === 'undefined') { + globalThis.Utils = {}; + } + globalThis.Utils.Converter = exports.Converter; + globalThis.Networking.API.HTTPServer = exports.HTTPServer; + globalThis.Networking.APIV2.Internal.TestServer = exports.TestServer; + globalThis.Networking.API.Method = exports.Method; + globalThis.Configuration.LogLevel = exports.LogLevel; + globalThis.Configuration.Port = exports.Port; + globalThis.Networking.APIV2.Internal.SupportedMethod = exports.SupportedMethod; + + return exports; }, } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts index 7f1ededc..d67ee1fe 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts @@ -77,12 +77,6 @@ export const Ratio: { }; export type Ratio = typeof Ratio[keyof typeof Ratio]; -export const FeatureFlag: { - readonly Enabled: true; - readonly Disabled: false; -}; -export type FeatureFlag = typeof FeatureFlag[keyof typeof FeatureFlag]; - export type Exports = { setTheme(theme: Theme): void; getTheme(): Theme; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js index e63be465..f4615d99 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js @@ -126,11 +126,6 @@ export async function createInstantiator(options, swift) { Pi: 3.14159, }; - const FeatureFlag = { - Enabled: true, - Disabled: false, - }; - return { Theme, FeatureFlag, @@ -142,7 +137,6 @@ export async function createInstantiator(options, swift) { SessionId, Precision, Ratio, - FeatureFlag, setTheme: function bjs_setTheme(theme) { const themeBytes = textEncoder.encode(theme); const themeId = swift.memory.retain(themeBytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts index b2ccecc4..c6e40399 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.d.ts @@ -7,6 +7,11 @@ export {}; declare global { + namespace MyModule { + namespace Utils { + namespacedFunction(): string; + } + } namespace Utils { namespace Converters { class Converter { @@ -26,11 +31,6 @@ declare global { } } } - namespace MyModule { - namespace Utils { - function namespacedFunction(): string; - } - } } /// Represents a Swift heap object like a class instance or an actor instance. diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json index 3a29a1a1..7527eef9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json @@ -31,9 +31,7 @@ } ], "name" : "Direction", - "nestedTypes" : [ - - ] + "swiftCallName" : "Direction" }, { "cases" : [ @@ -57,9 +55,7 @@ } ], "name" : "Status", - "nestedTypes" : [ - - ] + "swiftCallName" : "Status" } ], "functions" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json index fc427dec..f7dd3872 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json @@ -1,6 +1,137 @@ { "classes" : [ + { + "constructor" : { + "abiName" : "bjs_Converter_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_Converter_toString", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "toString", + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "string" : { + } + } + } + ], + "name" : "Converter", + "namespace" : [ + "Utils" + ], + "swiftCallName" : "Utils.Converter" + }, + { + "constructor" : { + "abiName" : "bjs_HTTPServer_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_HTTPServer_call", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "call", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Networking.API.Method" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "HTTPServer", + "namespace" : [ + "Networking", + "API" + ], + "swiftCallName" : "Networking.API.HTTPServer" + }, + { + "constructor" : { + "abiName" : "bjs_TestServer_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_TestServer_call", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "call", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Internal.SupportedMethod" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "TestServer", + "namespace" : [ + "Networking", + "APIV2", + "Internal" + ], + "swiftCallName" : "Internal.TestServer" + } ], "enums" : [ { @@ -8,28 +139,133 @@ ], "name" : "Utils", - "nestedTypes" : [ - "Converter" - ] + "swiftCallName" : "Utils" }, { "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "get" + }, + { + "associatedValues" : [ + + ], + "name" : "post" + }, + { + "associatedValues" : [ + + ], + "name" : "put" + }, + { + "associatedValues" : [ + ], + "name" : "delete" + } ], - "name" : "Networking", - "nestedTypes" : [ - "Method" - ] + "name" : "Method", + "namespace" : [ + "Networking", + "API" + ], + "swiftCallName" : "Networking.API.Method" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "debug", + "rawValue" : "debug" + }, + { + "associatedValues" : [ + + ], + "name" : "info", + "rawValue" : "info" + }, + { + "associatedValues" : [ + + ], + "name" : "warning", + "rawValue" : "warning" + }, + { + "associatedValues" : [ + + ], + "name" : "error", + "rawValue" : "error" + } + ], + "name" : "LogLevel", + "namespace" : [ + "Configuration" + ], + "rawType" : "String", + "swiftCallName" : "Configuration.LogLevel" }, { "cases" : [ + { + "associatedValues" : [ + ], + "name" : "http", + "rawValue" : "80" + }, + { + "associatedValues" : [ + + ], + "name" : "https", + "rawValue" : "443" + }, + { + "associatedValues" : [ + + ], + "name" : "development", + "rawValue" : "3000" + } + ], + "name" : "Port", + "namespace" : [ + "Configuration" + ], + "rawType" : "Int", + "swiftCallName" : "Configuration.Port" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "get" + }, + { + "associatedValues" : [ + + ], + "name" : "post" + } + ], + "name" : "SupportedMethod", + "namespace" : [ + "Networking", + "APIV2", + "Internal" ], - "name" : "Configuration", - "nestedTypes" : [ - "LogLevel", - "Port" - ] + "swiftCallName" : "Internal.SupportedMethod" } ], "functions" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift index e241d180..fa77613f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift @@ -4,4 +4,112 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. -@_spi(BridgeJS) import JavaScriptKit \ No newline at end of file +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_Converter_init") +@_cdecl("bjs_Converter_init") +public func _bjs_Converter_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Utils.Converter() + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_toString") +@_cdecl("bjs_Converter_toString") +public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int(value)) + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_deinit") +@_cdecl("bjs_Converter_deinit") +public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Utils.Converter: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "TestModule", name: "bjs_Converter_wrap") + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_HTTPServer_init") +@_cdecl("bjs_HTTPServer_init") +public func _bjs_HTTPServer_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Networking.API.HTTPServer() + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_HTTPServer_call") +@_cdecl("bjs_HTTPServer_call") +public func _bjs_HTTPServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Networking.API.Method(rawValue: Int(method))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_HTTPServer_deinit") +@_cdecl("bjs_HTTPServer_deinit") +public func _bjs_HTTPServer_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Networking.API.HTTPServer: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "TestModule", name: "bjs_HTTPServer_wrap") + func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_HTTPServer_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_TestServer_init") +@_cdecl("bjs_TestServer_init") +public func _bjs_TestServer_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Internal.TestServer() + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_TestServer_call") +@_cdecl("bjs_TestServer_call") +public func _bjs_TestServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Internal.SupportedMethod(rawValue: Int(method))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_TestServer_deinit") +@_cdecl("bjs_TestServer_deinit") +public func _bjs_TestServer_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Internal.TestServer: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "TestModule", name: "bjs_TestServer_wrap") + func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_TestServer_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json index 5b88d275..d85bc550 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json @@ -28,10 +28,8 @@ } ], "name" : "Theme", - "nestedTypes" : [ - - ], - "rawType" : "String" + "rawType" : "String", + "swiftCallName" : "Theme" }, { "cases" : [ @@ -51,10 +49,8 @@ } ], "name" : "FeatureFlag", - "nestedTypes" : [ - - ], - "rawType" : "Bool" + "rawType" : "Bool", + "swiftCallName" : "FeatureFlag" }, { "cases" : [ @@ -81,10 +77,8 @@ } ], "name" : "HttpStatus", - "nestedTypes" : [ - - ], - "rawType" : "Int" + "rawType" : "Int", + "swiftCallName" : "HttpStatus" }, { "cases" : [ @@ -125,10 +119,8 @@ } ], "name" : "Priority", - "nestedTypes" : [ - - ], - "rawType" : "Int32" + "rawType" : "Int32", + "swiftCallName" : "Priority" }, { "cases" : [ @@ -162,10 +154,8 @@ } ], "name" : "FileSize", - "nestedTypes" : [ - - ], - "rawType" : "Int64" + "rawType" : "Int64", + "swiftCallName" : "FileSize" }, { "cases" : [ @@ -192,10 +182,8 @@ } ], "name" : "UserId", - "nestedTypes" : [ - - ], - "rawType" : "UInt" + "rawType" : "UInt", + "swiftCallName" : "UserId" }, { "cases" : [ @@ -222,10 +210,8 @@ } ], "name" : "TokenId", - "nestedTypes" : [ - - ], - "rawType" : "UInt32" + "rawType" : "UInt32", + "swiftCallName" : "TokenId" }, { "cases" : [ @@ -252,10 +238,8 @@ } ], "name" : "SessionId", - "nestedTypes" : [ - - ], - "rawType" : "UInt64" + "rawType" : "UInt64", + "swiftCallName" : "SessionId" }, { "cases" : [ @@ -282,10 +266,8 @@ } ], "name" : "Precision", - "nestedTypes" : [ - - ], - "rawType" : "Float" + "rawType" : "Float", + "swiftCallName" : "Precision" }, { "cases" : [ @@ -319,33 +301,8 @@ } ], "name" : "Ratio", - "nestedTypes" : [ - - ], - "rawType" : "Double" - }, - { - "cases" : [ - { - "associatedValues" : [ - - ], - "name" : "enabled", - "rawValue" : "true" - }, - { - "associatedValues" : [ - - ], - "name" : "disabled", - "rawValue" : "false" - } - ], - "name" : "FeatureFlag", - "nestedTypes" : [ - - ], - "rawType" : "Bool" + "rawType" : "Double", + "swiftCallName" : "Ratio" } ], "functions" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json index 79ff94d8..a5e960be 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json @@ -41,7 +41,8 @@ "namespace" : [ "__Swift", "Foundation" - ] + ], + "swiftCallName" : "Greeter" }, { "constructor" : { @@ -84,7 +85,8 @@ "namespace" : [ "Utils", "Converters" - ] + ], + "swiftCallName" : "Converter" }, { "methods" : [ @@ -109,7 +111,8 @@ "namespace" : [ "__Swift", "Foundation" - ] + ], + "swiftCallName" : "UUID" } ], "enums" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json index 3f621eb6..7f8324ac 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json @@ -61,7 +61,8 @@ } } ], - "name" : "Greeter" + "name" : "Greeter", + "swiftCallName" : "Greeter" } ], "enums" : [ From 6333085e25dd8533abb3dd498823dc2d0b6f3274 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Wed, 20 Aug 2025 17:26:54 +0200 Subject: [PATCH 40/61] BridgeJS: Support TS enum style syntax for raw type string and numeric type + docs --- .../JavaScript/BridgeJS.ExportSwift.json | 3 + .../JavaScript/BridgeJS.ExportSwift.json | 9 +- .../Sources/BridgeJSCore/ExportSwift.swift | 84 +++- .../Sources/BridgeJSLink/BridgeJSLink.swift | 217 +++++++--- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 13 +- .../TS2Skeleton/JavaScript/package-lock.json | 28 -- .../TS2Skeleton/JavaScript/package.json | 2 +- .../BridgeJSToolTests/Inputs/EnumCase.swift | 10 + .../Inputs/EnumRawType.swift | 18 + .../ArrayParameter.Import.js | 1 - .../BridgeJSLinkTests/Async.Export.js | 1 - .../BridgeJSLinkTests/Async.Import.js | 1 - .../BridgeJSLinkTests/EnumCase.Export.d.ts | 9 + .../BridgeJSLinkTests/EnumCase.Export.js | 15 + .../BridgeJSLinkTests/EnumRawType.Export.d.ts | 16 + .../BridgeJSLinkTests/EnumRawType.Export.js | 33 ++ .../BridgeJSLinkTests/Interface.Import.js | 1 - .../MultipleImportedTypes.Import.js | 1 - .../BridgeJSLinkTests/Namespaces.Export.js | 1 - .../PrimitiveParameters.Export.js | 1 - .../PrimitiveParameters.Import.js | 1 - .../PrimitiveReturn.Export.js | 1 - .../PrimitiveReturn.Import.js | 1 - .../StringParameter.Export.js | 1 - .../StringParameter.Import.js | 1 - .../BridgeJSLinkTests/StringReturn.Export.js | 1 - .../BridgeJSLinkTests/StringReturn.Import.js | 1 - .../BridgeJSLinkTests/SwiftClass.Export.js | 1 - .../TS2SkeletonLike.Import.js | 1 - .../BridgeJSLinkTests/Throws.Export.js | 1 - .../BridgeJSLinkTests/TypeAlias.Import.js | 1 - .../TypeScriptClass.Import.js | 1 - .../VoidParameterVoidReturn.Export.js | 1 - .../VoidParameterVoidReturn.Import.js | 1 - .../ExportSwiftTests/EnumCase.json | 73 ++++ .../ExportSwiftTests/EnumCase.swift | 115 ++++- .../ExportSwiftTests/EnumNamespace.json | 5 + .../ExportSwiftTests/EnumNamespace.swift | 56 ++- .../ExportSwiftTests/EnumRawType.json | 152 +++++++ .../ExportSwiftTests/EnumRawType.swift | 48 +++ .../BridgeJS/Exporting-Swift-to-JavaScript.md | 407 +++++++++++++++++- Sources/JavaScriptKit/Macros.swift | 15 +- .../JavaScript/BridgeJS.ExportSwift.json | 9 +- 43 files changed, 1211 insertions(+), 147 deletions(-) delete mode 100644 Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package-lock.json diff --git a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json index 2e94644d..b00ec9ab 100644 --- a/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Benchmarks/Sources/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -1,6 +1,9 @@ { "classes" : [ + ], + "enums" : [ + ], "functions" : [ { diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json index e83af9fe..2b5ce07d 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -46,7 +46,8 @@ } } ], - "name" : "PlayBridgeJS" + "name" : "PlayBridgeJS", + "swiftCallName" : "PlayBridgeJS" }, { "methods" : [ @@ -115,8 +116,12 @@ } } ], - "name" : "PlayBridgeJSOutput" + "name" : "PlayBridgeJSOutput", + "swiftCallName" : "PlayBridgeJSOutput" } + ], + "enums" : [ + ], "functions" : [ diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 211f7139..0b5e7f20 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -267,6 +267,24 @@ public class ExportSwift { return namespaceString.split(separator: ".").map(String.init) } + private func extractEnumStyle( + from jsAttribute: AttributeSyntax + ) -> EnumEmitStyle? { + guard let arguments = jsAttribute.arguments?.as(LabeledExprListSyntax.self), + let styleArg = arguments.first(where: { $0.label?.text == "enumStyle" }) + else { + return nil + } + let text = styleArg.expression.trimmedDescription + if text.contains("tsEnum") { + return .tsEnum + } + if text.contains("const") { + return .const + } + return nil + } + override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { guard node.attributes.hasJSAttribute() else { return .skipChildren } guard case .classBody(let className, _) = state else { @@ -318,9 +336,6 @@ public class ExportSwift { let name = node.name.text guard let jsAttribute = node.attributes.firstJSAttribute else { - if case .enumBody(_) = state { - return .skipChildren - } return .skipChildren } @@ -355,14 +370,14 @@ public class ExportSwift { } override func visitPost(_ node: ClassDeclSyntax) { - stateStack.pop() + // Make sure we pop the state stack only if we're in a class body state (meaning we successfully pushed) + if case .classBody(_, _) = stateStack.current { + stateStack.pop() + } } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { guard node.attributes.hasJSAttribute() else { - if case .enumBody(_) = state { - return .skipChildren - } return .skipChildren } @@ -402,6 +417,10 @@ public class ExportSwift { guard let jsAttribute = node.attributes.firstJSAttribute, let enumName = currentEnum.name else { + // Only pop if we have a valid enum that was processed + if case .enumBody(_) = stateStack.current { + stateStack.pop() + } return } @@ -415,13 +434,26 @@ public class ExportSwift { effectiveNamespace = computedNamespace } + let emitStyle = extractEnumStyle(from: jsAttribute) ?? .const + if case .tsEnum = emitStyle, + let raw = currentEnum.rawType, + let rawEnum = SwiftEnumRawType.from(raw), rawEnum == .bool + { + diagnose( + node: jsAttribute, + message: "TypeScript enum style is not supported for Bool raw-value enums", + hint: "Use enumStyle: .const or change the raw type to String or a numeric type" + ) + } + let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: enumName) let exportedEnum = ExportedEnum( name: enumName, swiftCallName: swiftCallName, cases: currentEnum.cases, rawType: currentEnum.rawType, - namespace: effectiveNamespace + namespace: effectiveNamespace, + emitStyle: emitStyle ) exportedEnumByName[enumName] = exportedEnum exportedEnumNames.append(enumName) @@ -614,6 +646,11 @@ public class ExportSwift { return nil } decls.append(Self.prelude) + + for enumDef in exportedEnums where enumDef.enumType == .simple { + decls.append(renderCaseEnumHelpers(enumDef)) + } + for function in exportedFunctions { decls.append(renderSingleExportedFunction(function: function)) } @@ -624,6 +661,32 @@ public class ExportSwift { return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n") } + func renderCaseEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax { + let typeName = enumDef.swiftCallName + var initCases: [String] = [] + var valueCases: [String] = [] + for (index, c) in enumDef.cases.enumerated() { + initCases.append("case \(index): self = .\(c.name)") + valueCases.append("case .\(c.name): return \(index)") + } + let initSwitch = (["switch bridgeJSRawValue {"] + initCases + ["default: return nil", "}"]).joined( + separator: "\n" + ) + let valueSwitch = (["switch self {"] + valueCases + ["}"]).joined(separator: "\n") + + return """ + extension \(raw: typeName) { + init?(bridgeJSRawValue: Int32) { + \(raw: initSwitch) + } + + var bridgeJSRawValue: Int32 { + \(raw: valueSwitch) + } + } + """ + } + class ExportedThunkBuilder { var body: [CodeBlockItemSyntax] = [] var abiParameterForwardings: [LabeledExprSyntax] = [] @@ -700,7 +763,7 @@ public class ExportSwift { abiParameterForwardings.append( LabeledExprSyntax( label: param.label, - expression: ExprSyntax("\(raw: enumName)(rawValue: Int(\(raw: param.name)))!") + expression: ExprSyntax("\(raw: enumName)(bridgeJSRawValue: \(raw: param.name))!") ) ) abiParameterSignatures.append((param.name, .i32)) @@ -770,7 +833,6 @@ public class ExportSwift { ) abiParameterSignatures.append((param.name, .i32)) case .swiftHeapObject: - // UnsafeMutableRawPointer is passed as an i32 pointer let objectExpr: ExprSyntax = "Unmanaged<\(raw: param.type.swiftType)>.fromOpaque(\(raw: param.name)).takeUnretainedValue()" abiParameterForwardings.append( @@ -885,7 +947,7 @@ public class ExportSwift { ) case .caseEnum: abiReturnType = .i32 - append("return Int32(ret.rawValue)") + append("return ret.bridgeJSRawValue") case .rawValueEnum(_, let rawType): if rawType == .string { append( diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 0f40af96..b2fcb00c 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -159,25 +159,32 @@ struct BridgeJSLink { namespacedEnums: namespacedEnums ) .map { $0.indent(count: 12) }.joined(separator: "\n") + + let enumSection = + enumConstantLines.isEmpty + ? "" : enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n") + "\n" + exportsSection = """ \(classLines.map { $0.indent(count: 12) }.joined(separator: "\n")) - \(enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n")) - const exports = { + \(enumSection)\("const exports = {".indent(count: 12)) \(exportsLines.map { $0.indent(count: 16) }.joined(separator: "\n")) - }; + \("};".indent(count: 12)) \(namespaceSetupCode) - return exports; + \("return exports;".indent(count: 12)) }, """ } else { + let enumSection = + enumConstantLines.isEmpty + ? "" : enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n") + "\n" + exportsSection = """ \(classLines.map { $0.indent(count: 12) }.joined(separator: "\n")) - \(enumConstantLines.map { $0.indent(count: 12) }.joined(separator: "\n")) - return { + \(enumSection)\("return {".indent(count: 12)) \(exportsLines.map { $0.indent(count: 16) }.joined(separator: "\n")) - }; + \("};".indent(count: 12)) }, """ } @@ -521,6 +528,7 @@ struct BridgeJSLink { func renderExportedEnum(_ enumDefinition: ExportedEnum) throws -> (js: [String], dts: [String]) { var jsLines: [String] = [] var dtsLines: [String] = [] + let style: EnumEmitStyle = enumDefinition.emitStyle switch enumDefinition.enumType { case .simple: @@ -533,16 +541,27 @@ struct BridgeJSLink { jsLines.append("") if enumDefinition.namespace == nil { - dtsLines.append("export const \(enumDefinition.name): {") - for (index, enumCase) in enumDefinition.cases.enumerated() { - let caseName = enumCase.name.capitalizedFirstLetter - dtsLines.append(" readonly \(caseName): \(index);") + switch style { + case .tsEnum: + dtsLines.append("export enum \(enumDefinition.name) {") + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append(" \(caseName) = \(index),") + } + dtsLines.append("}") + dtsLines.append("") + case .const: + dtsLines.append("export const \(enumDefinition.name): {") + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append(" readonly \(caseName): \(index);") + } + dtsLines.append("};") + dtsLines.append( + "export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + ) + dtsLines.append("") } - dtsLines.append("};") - dtsLines.append( - "export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" - ) - dtsLines.append("") } case .rawValue: guard let rawType = enumDefinition.rawType else { @@ -576,30 +595,49 @@ struct BridgeJSLink { jsLines.append("") if enumDefinition.namespace == nil { - dtsLines.append("export const \(enumDefinition.name): {") - for enumCase in enumDefinition.cases { - let caseName = enumCase.name.capitalizedFirstLetter - let rawValue = enumCase.rawValue ?? enumCase.name - let formattedValue: String - - switch rawType { - case "String": - formattedValue = "\"\(rawValue)\"" - case "Bool": - formattedValue = rawValue.lowercased() == "true" ? "true" : "false" - case "Float", "Double": - formattedValue = rawValue - default: - formattedValue = rawValue + switch style { + case .tsEnum: + dtsLines.append("export enum \(enumDefinition.name) {") + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + switch rawType { + case "String": formattedValue = "\"\(rawValue)\"" + case "Bool": formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": formattedValue = rawValue + default: formattedValue = rawValue + } + dtsLines.append(" \(caseName) = \(formattedValue),") } + dtsLines.append("}") + dtsLines.append("") + case .const: + dtsLines.append("export const \(enumDefinition.name): {") + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + + switch rawType { + case "String": + formattedValue = "\"\(rawValue)\"" + case "Bool": + formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": + formattedValue = rawValue + default: + formattedValue = rawValue + } - dtsLines.append(" readonly \(caseName): \(formattedValue);") + dtsLines.append(" readonly \(caseName): \(formattedValue);") + } + dtsLines.append("};") + dtsLines.append( + "export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + ) + dtsLines.append("") } - dtsLines.append("};") - dtsLines.append( - "export type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" - ) - dtsLines.append("") } case .associatedValue: @@ -1119,52 +1157,89 @@ struct BridgeJSLink { let sortedEnums = childNode.content.enums.sorted { $0.name < $1.name } for enumDefinition in sortedEnums { + let style: EnumEmitStyle = enumDefinition.emitStyle switch enumDefinition.enumType { case .simple: - dtsLines.append( - "const \(enumDefinition.name): {".indent(count: identBaseSize * contentDepth) - ) - for (index, enumCase) in enumDefinition.cases.enumerated() { - let caseName = enumCase.name.capitalizedFirstLetter + switch style { + case .tsEnum: dtsLines.append( - "readonly \(caseName): \(index);".indent(count: identBaseSize * (contentDepth + 1)) + "enum \(enumDefinition.name) {".indent(count: identBaseSize * contentDepth) ) - } - dtsLines.append("};".indent(count: identBaseSize * contentDepth)) - dtsLines.append( - "type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" - .indent( - count: identBaseSize * contentDepth + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append( + "\(caseName) = \(index),".indent(count: identBaseSize * (contentDepth + 1)) ) - ) + } + dtsLines.append("}".indent(count: identBaseSize * contentDepth)) + case .const: + dtsLines.append( + "const \(enumDefinition.name): {".indent(count: identBaseSize * contentDepth) + ) + for (index, enumCase) in enumDefinition.cases.enumerated() { + let caseName = enumCase.name.capitalizedFirstLetter + dtsLines.append( + "readonly \(caseName): \(index);".indent( + count: identBaseSize * (contentDepth + 1) + ) + ) + } + dtsLines.append("};".indent(count: identBaseSize * contentDepth)) + dtsLines.append( + "type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + .indent(count: identBaseSize * contentDepth) + ) + } case .rawValue: guard let rawType = enumDefinition.rawType else { continue } - dtsLines.append( - "const \(enumDefinition.name): {".indent(count: identBaseSize * contentDepth) - ) - for enumCase in enumDefinition.cases { - let caseName = enumCase.name.capitalizedFirstLetter - let rawValue = enumCase.rawValue ?? enumCase.name - let formattedValue: String - switch rawType { - case "String": formattedValue = "\"\(rawValue)\"" - case "Bool": formattedValue = rawValue.lowercased() == "true" ? "true" : "false" - case "Float", "Double": formattedValue = rawValue - default: formattedValue = rawValue + switch style { + case .tsEnum: + dtsLines.append( + "enum \(enumDefinition.name) {".indent(count: identBaseSize * contentDepth) + ) + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + switch rawType { + case "String": formattedValue = "\"\(rawValue)\"" + case "Bool": formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": formattedValue = rawValue + default: formattedValue = rawValue + } + dtsLines.append( + "\(caseName) = \(formattedValue),".indent( + count: identBaseSize * (contentDepth + 1) + ) + ) } + dtsLines.append("}".indent(count: identBaseSize * contentDepth)) + case .const: dtsLines.append( - "readonly \(caseName): \(formattedValue);".indent( - count: identBaseSize * (contentDepth + 1) + "const \(enumDefinition.name): {".indent(count: identBaseSize * contentDepth) + ) + for enumCase in enumDefinition.cases { + let caseName = enumCase.name.capitalizedFirstLetter + let rawValue = enumCase.rawValue ?? enumCase.name + let formattedValue: String + switch rawType { + case "String": formattedValue = "\"\(rawValue)\"" + case "Bool": formattedValue = rawValue.lowercased() == "true" ? "true" : "false" + case "Float", "Double": formattedValue = rawValue + default: formattedValue = rawValue + } + dtsLines.append( + "readonly \(caseName): \(formattedValue);".indent( + count: identBaseSize * (contentDepth + 1) + ) ) + } + dtsLines.append("};".indent(count: identBaseSize * contentDepth)) + dtsLines.append( + "type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" + .indent(count: identBaseSize * contentDepth) ) } - dtsLines.append("};".indent(count: identBaseSize * contentDepth)) - dtsLines.append( - "type \(enumDefinition.name) = typeof \(enumDefinition.name)[keyof typeof \(enumDefinition.name)];" - .indent( - count: identBaseSize * contentDepth - ) - ) case .associatedValue, .namespace: continue } diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 89f2771e..0d872160 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -14,7 +14,6 @@ public enum WasmCoreType: String, Codable { case i32, i64, f32, f64, pointer } -/// Represents supported Swift enum raw types with their WASM ABI properties public enum SwiftEnumRawType: String, CaseIterable, Codable { case string = "String" case bool = "Bool" @@ -30,7 +29,7 @@ public enum SwiftEnumRawType: String, CaseIterable, Codable { public var wasmCoreType: WasmCoreType? { switch self { case .string: - return nil // String has special handling with UTF-8 bytes and length + return nil case .bool, .int, .int32, .uint, .uint32: return .i32 case .int64, .uint64: @@ -97,12 +96,18 @@ public struct EnumCase: Codable, Equatable { } } +public enum EnumEmitStyle: String, Codable { + case const + case tsEnum +} + public struct ExportedEnum: Codable, Equatable { public let name: String public let swiftCallName: String public let cases: [EnumCase] public let rawType: String? public let namespace: [String]? + public let emitStyle: EnumEmitStyle public var enumType: EnumType { if cases.isEmpty { return .namespace @@ -118,13 +123,15 @@ public struct ExportedEnum: Codable, Equatable { swiftCallName: String, cases: [EnumCase], rawType: String?, - namespace: [String]? + namespace: [String]?, + emitStyle: EnumEmitStyle ) { self.name = name self.swiftCallName = swiftCallName self.cases = cases self.rawType = rawType self.namespace = namespace + self.emitStyle = emitStyle } } diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package-lock.json b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package-lock.json deleted file mode 100644 index 7ddef637..00000000 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package-lock.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "JavaScript", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "typescript": "^5.8.2" - }, - "bin": { - "ts2skeleton": "bin/ts2skeleton.js" - } - }, - "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - } - } -} diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package.json b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package.json index 6638cb8d..48fb77cf 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package.json +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/package.json @@ -1,7 +1,7 @@ { "type": "module", "dependencies": { - "typescript": "^5.8.2" + "typescript": "5.8.2" }, "bin": { "ts2skeleton": "./bin/ts2skeleton.js" diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift index b82ef33f..6d5d6b55 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift @@ -14,3 +14,13 @@ @JS func setDirection(_ direction: Direction) @JS func getDirection() -> Direction @JS func processDirection(_ input: Direction) -> Status + +@JS(enumStyle: .tsEnum) enum TSDirection { + case north + case south + case east + case west +} + +@JS func setTSDirection(_ direction: TSDirection) +@JS func getTSDirection() -> TSDirection diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift index bf1a69cc..799df164 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumRawType.swift @@ -4,6 +4,12 @@ case auto = "auto" } +@JS(enumStyle: .tsEnum) enum TSTheme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + @JS enum FeatureFlag: Bool { case enabled = true case disabled = false @@ -15,6 +21,12 @@ case serverError = 500 } +@JS(enumStyle: .tsEnum) enum TSHttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + @JS enum Priority: Int32 { case lowest = 1 case low = 2 @@ -64,12 +76,18 @@ @JS func setTheme(_ theme: Theme) @JS func getTheme() -> Theme +@JS func setTSTheme(_ theme: TSTheme) +@JS func getTSTheme() -> TSTheme + @JS func setFeatureFlag(_ flag: FeatureFlag) @JS func getFeatureFlag() -> FeatureFlag @JS func setHttpStatus(_ status: HttpStatus) @JS func getHttpStatus() -> HttpStatus +@JS func setTSHttpStatus(_ status: TSHttpStatus) +@JS func getTSHttpStatus() -> TSHttpStatus + @JS func setPriority(_ priority: Priority) @JS func getPriority() -> Priority diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js index 16995227..c122f179 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayParameter.Import.js @@ -84,7 +84,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js index 6ced9f91..1da2f58e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js @@ -63,7 +63,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { asyncReturnVoid: function bjs_asyncReturnVoid() { const retId = instance.exports.bjs_asyncReturnVoid(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js index 0e39bd38..21d11fa4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js @@ -128,7 +128,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts index f4d2c90b..2d45e998 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts @@ -19,10 +19,19 @@ export const Status: { }; export type Status = typeof Status[keyof typeof Status]; +export enum TSDirection { + North = 0, + South = 1, + East = 2, + West = 3, +} + export type Exports = { setDirection(direction: Direction): void; getDirection(): Direction; processDirection(input: Direction): Status; + setTSDirection(direction: TSDirection): void; + getTSDirection(): TSDirection; } export type Imports = { } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js index ed355d06..d9f4b87d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js @@ -76,9 +76,17 @@ export async function createInstantiator(options, swift) { Error: 2, }; + const TSDirection = { + North: 0, + South: 1, + East: 2, + West: 3, + }; + return { Direction, Status, + TSDirection, setDirection: function bjs_setDirection(direction) { instance.exports.bjs_setDirection(direction | 0); }, @@ -90,6 +98,13 @@ export async function createInstantiator(options, swift) { const ret = instance.exports.bjs_processDirection(input | 0); return ret; }, + setTSDirection: function bjs_setTSDirection(direction) { + instance.exports.bjs_setTSDirection(direction | 0); + }, + getTSDirection: function bjs_getTSDirection() { + const ret = instance.exports.bjs_getTSDirection(); + return ret; + }, }; }, } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts index d67ee1fe..51b020ad 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.d.ts @@ -11,6 +11,12 @@ export const Theme: { }; export type Theme = typeof Theme[keyof typeof Theme]; +export enum TSTheme { + Light = "light", + Dark = "dark", + Auto = "auto", +} + export const FeatureFlag: { readonly Enabled: true; readonly Disabled: false; @@ -24,6 +30,12 @@ export const HttpStatus: { }; export type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus]; +export enum TSHttpStatus { + Ok = 200, + NotFound = 404, + ServerError = 500, +} + export const Priority: { readonly Lowest: 1; readonly Low: 2; @@ -80,10 +92,14 @@ export type Ratio = typeof Ratio[keyof typeof Ratio]; export type Exports = { setTheme(theme: Theme): void; getTheme(): Theme; + setTSTheme(theme: TSTheme): void; + getTSTheme(): TSTheme; setFeatureFlag(flag: FeatureFlag): void; getFeatureFlag(): FeatureFlag; setHttpStatus(status: HttpStatus): void; getHttpStatus(): HttpStatus; + setTSHttpStatus(status: TSHttpStatus): void; + getTSHttpStatus(): TSHttpStatus; setPriority(priority: Priority): void; getPriority(): Priority; setFileSize(size: FileSize): void; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js index f4615d99..e8b7adcf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js @@ -69,6 +69,12 @@ export async function createInstantiator(options, swift) { Auto: "auto", }; + const TSTheme = { + Light: "light", + Dark: "dark", + Auto: "auto", + }; + const FeatureFlag = { Enabled: true, Disabled: false, @@ -80,6 +86,12 @@ export async function createInstantiator(options, swift) { ServerError: 500, }; + const TSHttpStatus = { + Ok: 200, + NotFound: 404, + ServerError: 500, + }; + const Priority = { Lowest: 1, Low: 2, @@ -128,8 +140,10 @@ export async function createInstantiator(options, swift) { return { Theme, + TSTheme, FeatureFlag, HttpStatus, + TSHttpStatus, Priority, FileSize, UserId, @@ -149,6 +163,18 @@ export async function createInstantiator(options, swift) { tmpRetString = undefined; return ret; }, + setTSTheme: function bjs_setTSTheme(theme) { + const themeBytes = textEncoder.encode(theme); + const themeId = swift.memory.retain(themeBytes); + instance.exports.bjs_setTSTheme(themeId, themeBytes.length); + swift.memory.release(themeId); + }, + getTSTheme: function bjs_getTSTheme() { + instance.exports.bjs_getTSTheme(); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, setFeatureFlag: function bjs_setFeatureFlag(flag) { instance.exports.bjs_setFeatureFlag(flag ? 1 : 0); }, @@ -163,6 +189,13 @@ export async function createInstantiator(options, swift) { const ret = instance.exports.bjs_getHttpStatus(); return ret; }, + setTSHttpStatus: function bjs_setTSHttpStatus(status) { + instance.exports.bjs_setTSHttpStatus(status); + }, + getTSHttpStatus: function bjs_getTSHttpStatus() { + const ret = instance.exports.bjs_getTSHttpStatus(); + return ret; + }, setPriority: function bjs_setPriority(priority) { instance.exports.bjs_setPriority(priority); }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js index 874bebd3..f81c7e47 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Interface.Import.js @@ -90,7 +90,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js index 7e955b1a..394d996b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -194,7 +194,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js index cdf5aa25..6915a61a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Export.js @@ -145,7 +145,6 @@ export async function createInstantiator(options, swift) { return ret; } } - const exports = { Greeter, Converter, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js index 40ac2cda..4873fc33 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Export.js @@ -63,7 +63,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { check: function bjs_check(a, b, c, d) { instance.exports.bjs_check(a, b, c, d); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index 1cee664b..3b93b2dd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -70,7 +70,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index 7daa5bbb..53332b97 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -63,7 +63,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { checkInt: function bjs_checkInt() { const ret = instance.exports.bjs_checkInt(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index 19af42bd..1892eb46 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -81,7 +81,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js index bb5cabd8..ea47fb55 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Export.js @@ -63,7 +63,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { checkString: function bjs_checkString(a) { const aBytes = textEncoder.encode(a); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js index 524a0466..16ed1081 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.Import.js @@ -81,7 +81,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js index 67c63565..f98cea55 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Export.js @@ -63,7 +63,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { checkString: function bjs_checkString() { instance.exports.bjs_checkString(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js index 5830cd3d..3220ae7b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.Import.js @@ -72,7 +72,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index 0a1da2c5..ab4caba3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -114,7 +114,6 @@ export async function createInstantiator(options, swift) { swift.memory.release(nameId); } } - return { Greeter, takeGreeter: function bjs_takeGreeter(greeter) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js index 15e5d4d8..705c6a37 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -132,7 +132,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js index 95b558a1..b2089962 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.Export.js @@ -63,7 +63,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { throwsSomething: function bjs_throwsSomething() { instance.exports.bjs_throwsSomething(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js index dccc193e..2eb9dee5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeAlias.Import.js @@ -70,7 +70,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index cc060599..c7d622ea 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -119,7 +119,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js index 91017e56..c200c077 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Export.js @@ -63,7 +63,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { check: function bjs_check() { instance.exports.bjs_check(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js index 77087eba..ca497688 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.Import.js @@ -70,7 +70,6 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - return { }; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json index 7527eef9..efb6e805 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json @@ -30,6 +30,7 @@ "name" : "west" } ], + "emitStyle" : "const", "name" : "Direction", "swiftCallName" : "Direction" }, @@ -54,8 +55,40 @@ "name" : "error" } ], + "emitStyle" : "const", "name" : "Status", "swiftCallName" : "Status" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "north" + }, + { + "associatedValues" : [ + + ], + "name" : "south" + }, + { + "associatedValues" : [ + + ], + "name" : "east" + }, + { + "associatedValues" : [ + + ], + "name" : "west" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSDirection", + "swiftCallName" : "TSDirection" } ], "functions" : [ @@ -122,6 +155,46 @@ "_0" : "Status" } } + }, + { + "abiName" : "bjs_setTSDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTSDirection", + "parameters" : [ + { + "label" : "_", + "name" : "direction", + "type" : { + "caseEnum" : { + "_0" : "TSDirection" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTSDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTSDirection", + "parameters" : [ + + ], + "returnType" : { + "caseEnum" : { + "_0" : "TSDirection" + } + } } ], "moduleName" : "TestModule" diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift index affe3d3d..363ade82 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift @@ -6,11 +6,97 @@ @_spi(BridgeJS) import JavaScriptKit +extension Direction { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} + +extension Status { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .loading + case 1: + self = .success + case 2: + self = .error + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .loading: + return 0 + case .success: + return 1 + case .error: + return 2 + } + } +} + +extension TSDirection { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} + @_expose(wasm, "bjs_setDirection") @_cdecl("bjs_setDirection") public func _bjs_setDirection(direction: Int32) -> Void { #if arch(wasm32) - setDirection(_: Direction(rawValue: Int(direction))!) + setDirection(_: Direction(bridgeJSRawValue: direction)!) #else fatalError("Only available on WebAssembly") #endif @@ -21,7 +107,7 @@ public func _bjs_setDirection(direction: Int32) -> Void { public func _bjs_getDirection() -> Int32 { #if arch(wasm32) let ret = getDirection() - return Int32(ret.rawValue) + return ret.bridgeJSRawValue #else fatalError("Only available on WebAssembly") #endif @@ -31,8 +117,29 @@ public func _bjs_getDirection() -> Int32 { @_cdecl("bjs_processDirection") public func _bjs_processDirection(input: Int32) -> Int32 { #if arch(wasm32) - let ret = processDirection(_: Direction(rawValue: Int(input))!) - return Int32(ret.rawValue) + let ret = processDirection(_: Direction(bridgeJSRawValue: input)!) + return ret.bridgeJSRawValue + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTSDirection") +@_cdecl("bjs_setTSDirection") +public func _bjs_setTSDirection(direction: Int32) -> Void { + #if arch(wasm32) + setTSDirection(_: TSDirection(bridgeJSRawValue: direction)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSDirection") +@_cdecl("bjs_getTSDirection") +public func _bjs_getTSDirection() -> Int32 { + #if arch(wasm32) + let ret = getTSDirection() + return ret.bridgeJSRawValue #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json index f7dd3872..a9483455 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json @@ -138,6 +138,7 @@ "cases" : [ ], + "emitStyle" : "const", "name" : "Utils", "swiftCallName" : "Utils" }, @@ -168,6 +169,7 @@ "name" : "delete" } ], + "emitStyle" : "const", "name" : "Method", "namespace" : [ "Networking", @@ -206,6 +208,7 @@ "rawValue" : "error" } ], + "emitStyle" : "const", "name" : "LogLevel", "namespace" : [ "Configuration" @@ -237,6 +240,7 @@ "rawValue" : "3000" } ], + "emitStyle" : "const", "name" : "Port", "namespace" : [ "Configuration" @@ -259,6 +263,7 @@ "name" : "post" } ], + "emitStyle" : "const", "name" : "SupportedMethod", "namespace" : [ "Networking", diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift index fa77613f..9517ad80 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift @@ -6,6 +6,58 @@ @_spi(BridgeJS) import JavaScriptKit +extension Networking.API.Method { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .get + case 1: + self = .post + case 2: + self = .put + case 3: + self = .delete + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .get: + return 0 + case .post: + return 1 + case .put: + return 2 + case .delete: + return 3 + } + } +} + +extension Internal.SupportedMethod { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .get + case 1: + self = .post + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .get: + return 0 + case .post: + return 1 + } + } +} + @_expose(wasm, "bjs_Converter_init") @_cdecl("bjs_Converter_init") public func _bjs_Converter_init() -> UnsafeMutableRawPointer { @@ -59,7 +111,7 @@ public func _bjs_HTTPServer_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_HTTPServer_call") public func _bjs_HTTPServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Networking.API.Method(rawValue: Int(method))!) + Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Networking.API.Method(bridgeJSRawValue: method)!) #else fatalError("Only available on WebAssembly") #endif @@ -94,7 +146,7 @@ public func _bjs_TestServer_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_TestServer_call") public func _bjs_TestServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Internal.SupportedMethod(rawValue: Int(method))!) + Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Internal.SupportedMethod(bridgeJSRawValue: method)!) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json index d85bc550..09ce5d6e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.json @@ -27,6 +27,7 @@ "rawValue" : "auto" } ], + "emitStyle" : "const", "name" : "Theme", "rawType" : "String", "swiftCallName" : "Theme" @@ -36,6 +37,35 @@ { "associatedValues" : [ + ], + "name" : "light", + "rawValue" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark", + "rawValue" : "dark" + }, + { + "associatedValues" : [ + + ], + "name" : "auto", + "rawValue" : "auto" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSTheme", + "rawType" : "String", + "swiftCallName" : "TSTheme" + }, + { + "cases" : [ + { + "associatedValues" : [ + ], "name" : "enabled", "rawValue" : "true" @@ -48,6 +78,7 @@ "rawValue" : "false" } ], + "emitStyle" : "const", "name" : "FeatureFlag", "rawType" : "Bool", "swiftCallName" : "FeatureFlag" @@ -76,6 +107,7 @@ "rawValue" : "500" } ], + "emitStyle" : "const", "name" : "HttpStatus", "rawType" : "Int", "swiftCallName" : "HttpStatus" @@ -85,6 +117,35 @@ { "associatedValues" : [ + ], + "name" : "ok", + "rawValue" : "200" + }, + { + "associatedValues" : [ + + ], + "name" : "notFound", + "rawValue" : "404" + }, + { + "associatedValues" : [ + + ], + "name" : "serverError", + "rawValue" : "500" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSHttpStatus", + "rawType" : "Int", + "swiftCallName" : "TSHttpStatus" + }, + { + "cases" : [ + { + "associatedValues" : [ + ], "name" : "lowest", "rawValue" : "1" @@ -118,6 +179,7 @@ "rawValue" : "5" } ], + "emitStyle" : "const", "name" : "Priority", "rawType" : "Int32", "swiftCallName" : "Priority" @@ -153,6 +215,7 @@ "rawValue" : "1048576" } ], + "emitStyle" : "const", "name" : "FileSize", "rawType" : "Int64", "swiftCallName" : "FileSize" @@ -181,6 +244,7 @@ "rawValue" : "9999" } ], + "emitStyle" : "const", "name" : "UserId", "rawType" : "UInt", "swiftCallName" : "UserId" @@ -209,6 +273,7 @@ "rawValue" : "67890" } ], + "emitStyle" : "const", "name" : "TokenId", "rawType" : "UInt32", "swiftCallName" : "TokenId" @@ -237,6 +302,7 @@ "rawValue" : "1234567890" } ], + "emitStyle" : "const", "name" : "SessionId", "rawType" : "UInt64", "swiftCallName" : "SessionId" @@ -265,6 +331,7 @@ "rawValue" : "0.001" } ], + "emitStyle" : "const", "name" : "Precision", "rawType" : "Float", "swiftCallName" : "Precision" @@ -300,6 +367,7 @@ "rawValue" : "3.14159" } ], + "emitStyle" : "const", "name" : "Ratio", "rawType" : "Double", "swiftCallName" : "Ratio" @@ -348,6 +416,48 @@ } } }, + { + "abiName" : "bjs_setTSTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTSTheme", + "parameters" : [ + { + "label" : "_", + "name" : "theme", + "type" : { + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTSTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTSTheme", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" + } + } + }, { "abiName" : "bjs_setFeatureFlag", "effects" : { @@ -432,6 +542,48 @@ } } }, + { + "abiName" : "bjs_setTSHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTSHttpStatus", + "parameters" : [ + { + "label" : "_", + "name" : "status", + "type" : { + "rawValueEnum" : { + "_0" : "TSHttpStatus", + "_1" : "Int" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getTSHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTSHttpStatus", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "TSHttpStatus", + "_1" : "Int" + } + } + }, { "abiName" : "bjs_setPriority", "effects" : { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift index 185c4ac8..84ad9821 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift @@ -33,6 +33,33 @@ public func _bjs_getTheme() -> Void { #endif } +@_expose(wasm, "bjs_setTSTheme") +@_cdecl("bjs_setTSTheme") +public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { + #if arch(wasm32) + let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in + _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) + return Int(themeLen) + } + setTSTheme(_: TSTheme(rawValue: theme)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSTheme") +@_cdecl("bjs_getTSTheme") +public func _bjs_getTSTheme() -> Void { + #if arch(wasm32) + let ret = getTSTheme() + return ret.rawValue.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_setFeatureFlag") @_cdecl("bjs_setFeatureFlag") public func _bjs_setFeatureFlag(flag: Int32) -> Void { @@ -75,6 +102,27 @@ public func _bjs_getHttpStatus() -> Int32 { #endif } +@_expose(wasm, "bjs_setTSHttpStatus") +@_cdecl("bjs_setTSHttpStatus") +public func _bjs_setTSHttpStatus(status: Int32) -> Void { + #if arch(wasm32) + setTSHttpStatus(_: TSHttpStatus(rawValue: Int(status))!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSHttpStatus") +@_cdecl("bjs_getTSHttpStatus") +public func _bjs_getTSHttpStatus() -> Int32 { + #if arch(wasm32) + let ret = getTSHttpStatus() + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_setPriority") @_cdecl("bjs_setPriority") public func _bjs_setPriority(priority: Int32) -> Void { diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md index 6ce30772..ad7932f1 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md @@ -50,9 +50,10 @@ swift package --swift-sdk $SWIFT_SDK_ID js ``` This command will: + 1. Process all Swift files with `@JS` annotations 2. Generate JavaScript bindings and TypeScript type definitions (`.d.ts`) for your exported Swift code -4. Output everything to the `.build/plugins/PackageToJS/outputs/` directory +3. Output everything to the `.build/plugins/PackageToJS/outputs/` directory > Note: For larger projects, you may want to generate the BridgeJS code ahead of time to improve build performance. See for more information. @@ -163,6 +164,410 @@ export type Exports = { } ``` + +### Enum Support + +BridgeJS supports two output styles for enums, controlled by the `enumStyle` parameter: + +- **`.const` (default)**: Generates const objects with union types +- **`.tsEnum`**: Generates native TypeScript enum declarations - **only available for case enums and raw value enums with String or numeric raw types** + +Examples output of both styles can be found below. + +#### Case Enums + +**Swift Definition:** + +```swift +@JS enum Direction { + case north + case south + case east + case west +} + +@JS(enumStyle: .tsEnum) enum TSDirection { + case north + case south + case east + case west +} + +@JS enum Status { + case loading + case success + case error +} +``` + +**Generated TypeScript Declaration:** + +```typescript +// Const object style (default) +const Direction: { + readonly North: 0; + readonly South: 1; + readonly East: 2; + readonly West: 3; +}; +type Direction = typeof Direction[keyof typeof Direction]; + +// Native TypeScript enum style +enum TSDirection { + North = 0, + South = 1, + East = 2, + West = 3, +} + +const Status: { + readonly Loading: 0; + readonly Success: 1; + readonly Error: 2; +}; +type Status = typeof Status[keyof typeof Status]; +``` + +**Usage in TypeScript:** + +```typescript +const direction: Direction = exports.Direction.North; +const tsDirection: TSDirection = exports.TSDirection.North; +const status: Status = exports.Status.Loading; + +exports.setDirection(exports.Direction.South); +exports.setTSDirection(exports.TSDirection.East); +const currentDirection: Direction = exports.getDirection(); +const currentTSDirection: TSDirection = exports.getTSDirection(); + +const result: Status = exports.processDirection(exports.Direction.East); + +function handleDirection(direction: Direction) { + switch (direction) { + case exports.Direction.North: + console.log("Going north"); + break; + case exports.Direction.South: + console.log("Going south"); + break; + // TypeScript will warn about missing cases + } +} +``` + +BridgeJS also generates convenience initializers and computed properties for each case-style enum, allowing the rest of the Swift glue code to remain minimal and consistent. This avoids repetitive switch statements in every function that passes enum values between JavaScript and Swift. + +```swift +extension Direction { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} +... +@_expose(wasm, "bjs_setDirection") +@_cdecl("bjs_setDirection") +public func _bjs_setDirection(direction: Int32) -> Void { + #if arch(wasm32) + setDirection(_: Direction(bridgeJSRawValue: direction)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getDirection") +@_cdecl("bjs_getDirection") +public func _bjs_getDirection() -> Int32 { + #if arch(wasm32) + let ret = getDirection() + return ret.bridgeJSRawValue + #else + fatalError("Only available on WebAssembly") + #endif +} +``` + +#### Raw Value Enums + +##### String Raw Values + +**Swift Definition:** + +```swift +// Default const object style +@JS enum Theme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +// Native TypeScript enum style +@JS(enumStyle: .tsEnum) enum TSTheme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} +``` + +**Generated TypeScript Declaration:** + +```typescript +// Const object style (default) +const Theme: { + readonly Light: "light"; + readonly Dark: "dark"; + readonly Auto: "auto"; +}; +type Theme = typeof Theme[keyof typeof Theme]; + +// Native TypeScript enum style +enum TSTheme { + Light = "light", + Dark = "dark", + Auto = "auto", +} +``` + +**Usage in TypeScript:** + +```typescript +// Both styles work similarly in usage +const theme: Theme = exports.Theme.Dark; +const tsTheme: TSTheme = exports.TSTheme.Dark; + +exports.setTheme(exports.Theme.Light); +const currentTheme: Theme = exports.getTheme(); + +const status: HttpStatus = exports.processTheme(exports.Theme.Auto); +``` + +##### Integer Raw Values + +**Swift Definition:** + +```swift +// Default const object style +@JS enum HttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +// Native TypeScript enum style +@JS(enumStyle: .tsEnum) enum TSHttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +@JS enum Priority: Int32 { + case lowest = 1 + case low = 2 + case medium = 3 + case high = 4 + case highest = 5 +} +``` + +**Generated TypeScript Declaration:** + +```typescript +// Const object style (default) +const HttpStatus: { + readonly Ok: 200; + readonly NotFound: 404; + readonly ServerError: 500; +}; +type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus]; + +// Native TypeScript enum style +enum TSHttpStatus { + Ok = 200, + NotFound = 404, + ServerError = 500, +} + +const Priority: { + readonly Lowest: 1; + readonly Low: 2; + readonly Medium: 3; + readonly High: 4; + readonly Highest: 5; +}; +type Priority = typeof Priority[keyof typeof Priority]; +``` + +**Usage in TypeScript:** + +```typescript +const status: HttpStatus = exports.HttpStatus.Ok; +const tsStatus: TSHttpStatus = exports.TSHttpStatus.Ok; +const priority: Priority = exports.Priority.High; + +exports.setHttpStatus(exports.HttpStatus.NotFound); +exports.setPriority(exports.Priority.Medium); + +const convertedPriority: Priority = exports.convertPriority(exports.HttpStatus.Ok); +``` + +### Namespace Enums + +Namespace enums are empty enums (containing no cases) used for organizing related types and functions into hierarchical namespaces. + +**Swift Definition:** + +```swift +@JS enum Utils { + @JS class Converter { + @JS init() {} + + @JS func toString(value: Int) -> String { + return String(value) + } + } +} + +// Nested namespace enums with no @JS(namespace:) macro used +@JS enum Networking { + @JS enum API { + @JS enum Method { + case get + case post + } + + @JS class HTTPServer { + @JS init() {} + @JS func call(_ method: Method) + } + } +} + +// Top level enum can still use explicit namespace via @JS(namespace:) +@JS(namespace: "Networking.APIV2") +enum Internal { + @JS enum SupportedMethod { + case get + case post + } + + @JS class TestServer { + @JS init() {} + @JS func call(_ method: SupportedMethod) + } +} +``` + +**Generated TypeScript Declaration:** + +```typescript +declare global { + namespace Utils { + class Converter { + constructor(); + toString(value: number): string; + } + } + namespace Networking { + namespace API { + class HTTPServer { + constructor(); + call(method: Networking.API.Method): void; + } + const Method: { + readonly Get: 0; + readonly Post: 1; + }; + type Method = typeof Method[keyof typeof Method]; + } + namespace APIV2 { + namespace Internal { + class TestServer { + constructor(); + call(method: Internal.SupportedMethod): void; + } + const SupportedMethod: { + readonly Get: 0; + readonly Post: 1; + }; + type SupportedMethod = typeof SupportedMethod[keyof typeof SupportedMethod]; + } + } + } +} +``` + +**Usage in TypeScript:** + +```typescript +// Access nested classes through namespaces +const converter = new globalThis.Utils.Converter(); +const result: string = converter.toString(42) + +const server = new globalThis.Networking.API.HTTPServer(); +const method: Networking.API.Method = globalThis.Networking.API.Method.Get; +server.call(method) + +const testServer = new globalThis.Networking.APIV2.Internal.TestServer(); +const supportedMethod: Internal.SupportedMethod = globalThis.Networking.APIV2.Internal.SupportedMethod.Post; +testServer.call(supportedMethod); +``` + +Things to remember when using enums for namespacing: + +1. Only enums with no cases will be used for namespaces +2. Top-level enums can use `@JS(namespace: "Custom.Path")` to place themselves in custom namespaces, which will be used as "base namespace" for all nested elements as well +3. Classes and enums nested within namespace enums **cannot** use `@JS(namespace:)` - this would create conflicting namespace declarations + +**Invalid Usage:** + +```swift +@JS enum Utils { + // Invalid - nested items cannot specify their own namespace + @JS(namespace: "Custom") class Helper { + @JS init() {} + } +} +``` + +**Valid Usage:** + +```swift +// Valid - top-level enum with explicit namespace +@JS(namespace: "Custom.Utils") +enum Helper { + @JS class Converter { + @JS init() {} + } +} +``` + +#### Associated Value Enums + +Associated value enums are not currently supported, but are planned for future releases. + ## Using Namespaces The `@JS` macro supports organizing your exported Swift code into namespaces using dot-separated strings. This allows you to create hierarchical structures in JavaScript that mirror your Swift code organization. diff --git a/Sources/JavaScriptKit/Macros.swift b/Sources/JavaScriptKit/Macros.swift index dac264ff..b8a44a08 100644 --- a/Sources/JavaScriptKit/Macros.swift +++ b/Sources/JavaScriptKit/Macros.swift @@ -1,3 +1,11 @@ +/// Controls how Swift enums annotated with `@JS` are emitted to TypeScript. +/// - `const`: Emit the current BridgeJS style: a `const` object with literal members plus a type alias. +/// - `tsEnum`: Emit a TypeScript `enum` declaration (only valid for simple enums and raw-value enums with String or numeric raw types). +public enum JSEnumStyle: String { + case const + case tsEnum +} + /// A macro that exposes Swift functions, classes, and methods to JavaScript. /// /// Apply this macro to Swift declarations that you want to make callable from JavaScript: @@ -90,7 +98,12 @@ /// /// - Parameter namespace: A dot-separated string that defines the namespace hierarchy in JavaScript. /// Each segment becomes a nested object in the resulting JavaScript structure. +/// - Parameter enumStyle: Controls how enums are emitted to TypeScript for this declaration: +/// use `.const` (default) to emit a const object + type alias, +/// or `.tsEnum` to emit a TypeScript `enum`. +/// `.tsEnum` is supported for case enums and raw-value enums with String or numeric raw types. +/// Bool raw-value enums are not supported with `.tsEnum` and will produce an error. /// /// - Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. @attached(peer) -public macro JS(namespace: String? = nil) = Builtin.ExternalMacro +public macro JS(namespace: String? = nil, enumStyle: JSEnumStyle = .const) = Builtin.ExternalMacro diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 97b86cec..19c77f92 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -61,7 +61,8 @@ } } ], - "name" : "Greeter" + "name" : "Greeter", + "swiftCallName" : "Greeter" }, { "methods" : [ @@ -123,8 +124,12 @@ } } ], - "name" : "Calculator" + "name" : "Calculator", + "swiftCallName" : "Calculator" } + ], + "enums" : [ + ], "functions" : [ { From d2fd19522a4b23fb52893e851cbd64de655e1526 Mon Sep 17 00:00:00 2001 From: Diana Ma Date: Fri, 22 Aug 2025 00:23:49 +0000 Subject: [PATCH 41/61] enable using JavaScriptKit with packages that have macros that use SwiftSyntax 601 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 44d37166..2d162514 100644 --- a/Package.swift +++ b/Package.swift @@ -28,7 +28,7 @@ let package = Package( .plugin(name: "BridgeJSCommandPlugin", targets: ["BridgeJSCommandPlugin"]), ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"601.0.0") + .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0" ..< "602.0.0") ], targets: [ .target( From 67526018d7852c090214a24b9f5c563c660794f9 Mon Sep 17 00:00:00 2001 From: Diana Ma Date: Fri, 22 Aug 2025 00:40:50 +0000 Subject: [PATCH 42/61] format --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 2d162514..cf3055c3 100644 --- a/Package.swift +++ b/Package.swift @@ -28,7 +28,7 @@ let package = Package( .plugin(name: "BridgeJSCommandPlugin", targets: ["BridgeJSCommandPlugin"]), ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0" ..< "602.0.0") + .package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"602.0.0") ], targets: [ .target( From 59353155798f49e793e3e5f75d208443443a1c4c Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Fri, 22 Aug 2025 11:13:37 +0200 Subject: [PATCH 43/61] BridgeJS: Runtime tests, string enum fixes and code review feedback --- .../Sources/BridgeJSCore/ExportSwift.swift | 10 +- .../Sources/BridgeJSCore/ImportTS.swift | 2 +- .../Sources/BridgeJSLink/BridgeJSLink.swift | 101 ++- .../Inputs/EnumNamespace.swift | 4 +- .../BridgeJSLinkTests/EnumCase.Export.js | 44 +- .../BridgeJSLinkTests/EnumNamespace.Export.js | 83 ++- .../BridgeJSLinkTests/EnumRawType.Export.js | 163 ++-- .../ExportSwiftTests/EnumRawType.swift | 9 +- .../BridgeJSRuntimeTests/ExportAPITests.swift | 147 ++++ .../Generated/BridgeJS.ExportSwift.swift | 402 ++++++++++ .../JavaScript/BridgeJS.ExportSwift.json | 702 ++++++++++++++++++ Tests/prelude.mjs | 87 +++ 12 files changed, 1585 insertions(+), 169 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 0b5e7f20..a5f2e108 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -865,7 +865,12 @@ public class ExportSwift { return CodeBlockItemSyntax(item: .init(StmtSyntax("return \(raw: callExpr).jsValue"))) } - let retMutability = returnType == .string ? "var" : "let" + let retMutability: String + if returnType == .string { + retMutability = "var" + } else { + retMutability = "let" + } if returnType == .void { return CodeBlockItemSyntax(item: .init(ExpressionStmtSyntax(expression: callExpr))) } else { @@ -952,7 +957,8 @@ public class ExportSwift { if rawType == .string { append( """ - return ret.rawValue.withUTF8 { ptr in + var rawValue = ret.rawValue + return rawValue.withUTF8 { ptr in _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) } """ diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 9c4679e9..bcbae469 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -505,7 +505,7 @@ public struct ImportTS { } } -extension String { +fileprivate extension String { func capitalizedFirstLetter() -> String { guard !isEmpty else { return self } return prefix(1).uppercased() + dropFirst() diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index b2fcb00c..046cb92d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -72,6 +72,8 @@ struct BridgeJSLink { var namespacedEnums: [ExportedEnum] = [] var enumConstantLines: [String] = [] var dtsEnumLines: [String] = [] + var topLevelEnumLines: [String] = [] + var topLevelDtsEnumLines: [String] = [] if exportedSkeletons.contains(where: { $0.classes.count > 0 }) { classLines.append( @@ -102,14 +104,29 @@ struct BridgeJSLink { if !skeleton.enums.isEmpty { for enumDefinition in skeleton.enums { let (jsEnum, dtsEnum) = try renderExportedEnum(enumDefinition) - enumConstantLines.append(contentsOf: jsEnum) - if enumDefinition.enumType != .namespace { + + switch enumDefinition.enumType { + case .namespace: + break + case .simple, .rawValue: + var exportedJsEnum = jsEnum + if !exportedJsEnum.isEmpty && exportedJsEnum[0].hasPrefix("const ") { + exportedJsEnum[0] = "export " + exportedJsEnum[0] + } + topLevelEnumLines.append(contentsOf: exportedJsEnum) + topLevelDtsEnumLines.append(contentsOf: dtsEnum) + + if enumDefinition.namespace != nil { + namespacedEnums.append(enumDefinition) + } + case .associatedValue: + enumConstantLines.append(contentsOf: jsEnum) exportsLines.append("\(enumDefinition.name),") if enumDefinition.namespace != nil { namespacedEnums.append(enumDefinition) } + dtsEnumLines.append(contentsOf: dtsEnum) } - dtsEnumLines.append(contentsOf: dtsEnum) } } @@ -153,10 +170,11 @@ struct BridgeJSLink { let exportsSection: String if hasNamespacedItems { + let namespacedEnumsForExports = namespacedEnums.filter { $0.enumType == .associatedValue } let namespaceSetupCode = namespaceBuilder.renderGlobalNamespace( namespacedFunctions: namespacedFunctions, namespacedClasses: namespacedClasses, - namespacedEnums: namespacedEnums + namespacedEnums: namespacedEnumsForExports ) .map { $0.indent(count: 12) }.joined(separator: "\n") @@ -189,6 +207,14 @@ struct BridgeJSLink { """ } + let topLevelEnumsSection = topLevelEnumLines.isEmpty ? "" : topLevelEnumLines.joined(separator: "\n") + "\n\n" + + let topLevelNamespaceCode = namespaceBuilder.renderTopLevelEnumNamespaceAssignments( + namespacedEnums: namespacedEnums + ) + let namespaceAssignmentsSection = + topLevelNamespaceCode.isEmpty ? "" : topLevelNamespaceCode.joined(separator: "\n") + "\n\n" + let outputJs = """ // NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, // DO NOT EDIT. @@ -196,7 +222,7 @@ struct BridgeJSLink { // To update this file, just rebuild your project or run // `swift package bridge-js`. - export async function createInstantiator(options, swift) { + \(topLevelEnumsSection)\(namespaceAssignmentsSection)export async function createInstantiator(options, swift) { let instance; let memory; let setException; @@ -270,6 +296,9 @@ struct BridgeJSLink { dtsLines.append("export type Imports = {") dtsLines.append(contentsOf: importObjectBuilders.flatMap { $0.dtsImportLines }.map { $0.indent(count: 4) }) dtsLines.append("}") + let topLevelDtsEnumsSection = + topLevelDtsEnumLines.isEmpty ? "" : topLevelDtsEnumLines.joined(separator: "\n") + "\n" + let outputDts = """ // NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, // DO NOT EDIT. @@ -277,7 +306,7 @@ struct BridgeJSLink { // To update this file, just rebuild your project or run // `swift package bridge-js`. - \(dtsLines.joined(separator: "\n")) + \(topLevelDtsEnumsSection)\(dtsLines.joined(separator: "\n")) export function createInstantiator(options: { imports: Imports; }, swift: any): Promise<{ @@ -535,7 +564,7 @@ struct BridgeJSLink { jsLines.append("const \(enumDefinition.name) = {") for (index, enumCase) in enumDefinition.cases.enumerated() { let caseName = enumCase.name.capitalizedFirstLetter - jsLines.append(" \(caseName): \(index),".indent(count: 0)) + jsLines.append("\(caseName): \(index),".indent(count: 4)) } jsLines.append("};") jsLines.append("") @@ -546,7 +575,7 @@ struct BridgeJSLink { dtsLines.append("export enum \(enumDefinition.name) {") for (index, enumCase) in enumDefinition.cases.enumerated() { let caseName = enumCase.name.capitalizedFirstLetter - dtsLines.append(" \(caseName) = \(index),") + dtsLines.append("\(caseName) = \(index),".indent(count: 4)) } dtsLines.append("}") dtsLines.append("") @@ -554,7 +583,7 @@ struct BridgeJSLink { dtsLines.append("export const \(enumDefinition.name): {") for (index, enumCase) in enumDefinition.cases.enumerated() { let caseName = enumCase.name.capitalizedFirstLetter - dtsLines.append(" readonly \(caseName): \(index);") + dtsLines.append("readonly \(caseName): \(index);".indent(count: 4)) } dtsLines.append("};") dtsLines.append( @@ -608,7 +637,7 @@ struct BridgeJSLink { case "Float", "Double": formattedValue = rawValue default: formattedValue = rawValue } - dtsLines.append(" \(caseName) = \(formattedValue),") + dtsLines.append("\(caseName) = \(formattedValue),".indent(count: 4)) } dtsLines.append("}") dtsLines.append("") @@ -630,7 +659,7 @@ struct BridgeJSLink { formattedValue = rawValue } - dtsLines.append(" readonly \(caseName): \(formattedValue);") + dtsLines.append("readonly \(caseName): \(formattedValue);".indent(count: 4)) } dtsLines.append("};") dtsLines.append( @@ -780,7 +809,7 @@ struct BridgeJSLink { uniqueNamespaces.sorted().forEach { namespace in lines.append("if (typeof globalThis.\(namespace) === 'undefined') {") - lines.append(" globalThis.\(namespace) = {};") + lines.append("globalThis.\(namespace) = {};".indent(count: 4)) lines.append("}") } @@ -790,8 +819,10 @@ struct BridgeJSLink { } namespacedEnums.forEach { enumDefinition in - let namespacePath: String = enumDefinition.namespace?.joined(separator: ".") ?? "" - lines.append("globalThis.\(namespacePath).\(enumDefinition.name) = exports.\(enumDefinition.name);") + if enumDefinition.enumType == .associatedValue { + let namespacePath: String = enumDefinition.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(enumDefinition.name) = exports.\(enumDefinition.name);") + } } namespacedFunctions.forEach { function in @@ -1015,7 +1046,7 @@ struct BridgeJSLink { uniqueNamespaces.sorted().forEach { namespace in lines.append("if (typeof globalThis.\(namespace) === 'undefined') {") - lines.append(" globalThis.\(namespace) = {};") + lines.append("globalThis.\(namespace) = {};".indent(count: 4)) lines.append("}") } @@ -1037,6 +1068,44 @@ struct BridgeJSLink { return lines } + func renderTopLevelEnumNamespaceAssignments(namespacedEnums: [ExportedEnum]) -> [String] { + let topLevelNamespacedEnums = namespacedEnums.filter { $0.enumType == .simple || $0.enumType == .rawValue } + + guard !topLevelNamespacedEnums.isEmpty else { return [] } + + var lines: [String] = [] + var uniqueNamespaces: [String] = [] + var seen = Set() + + for enumDef in topLevelNamespacedEnums { + guard let namespacePath = enumDef.namespace else { continue } + namespacePath.enumerated().forEach { (index, _) in + let path = namespacePath[0...index].joined(separator: ".") + if !seen.contains(path) { + seen.insert(path) + uniqueNamespaces.append(path) + } + } + } + + for namespace in uniqueNamespaces { + lines.append("if (typeof globalThis.\(namespace) === 'undefined') {") + lines.append("globalThis.\(namespace) = {};".indent(count: 4)) + lines.append("}") + } + + if !lines.isEmpty { + lines.append("") + } + + for enumDef in topLevelNamespacedEnums { + let namespacePath = enumDef.namespace?.joined(separator: ".") ?? "" + lines.append("globalThis.\(namespacePath).\(enumDef.name) = \(enumDef.name);") + } + + return lines + } + private struct NamespaceContent { var functions: [ExportedFunction] = [] var classes: [ExportedClass] = [] @@ -1410,7 +1479,9 @@ extension String { func indent(count: Int) -> String { return String(repeating: " ", count: count) + self } +} +fileprivate extension String { var capitalizedFirstLetter: String { guard !isEmpty else { return self } return prefix(1).uppercased() + dropFirst() diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift index 68c666db..26a4e9c3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumNamespace.swift @@ -21,7 +21,7 @@ // Invalid to declare @JS(namespace) here as it would be conflicting with nesting @JS class HTTPServer { @JS init() {} - @JS func call(_ method: Method) + @JS func call(_ method: Method) {} } } } @@ -49,7 +49,7 @@ enum Internal { } @JS class TestServer { @JS init() {} - @JS func call(_ method: SupportedMethod) + @JS func call(_ method: SupportedMethod) {} } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js index d9f4b87d..3e080948 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js @@ -4,6 +4,27 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export const Direction = { + North: 0, + South: 1, + East: 2, + West: 3, +}; + +export const Status = { + Loading: 0, + Success: 1, + Error: 2, +}; + +export const TSDirection = { + North: 0, + South: 1, + East: 2, + West: 3, +}; + + export async function createInstantiator(options, swift) { let instance; let memory; @@ -63,30 +84,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - const Direction = { - North: 0, - South: 1, - East: 2, - West: 3, - }; - - const Status = { - Loading: 0, - Success: 1, - Error: 2, - }; - - const TSDirection = { - North: 0, - South: 1, - East: 2, - West: 3, - }; - return { - Direction, - Status, - TSDirection, setDirection: function bjs_setDirection(direction) { instance.exports.bjs_setDirection(direction | 0); }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js index 530822a5..12613dd8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js @@ -4,6 +4,53 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export const Method = { + Get: 0, + Post: 1, + Put: 2, + Delete: 3, +}; + +export const LogLevel = { + Debug: "debug", + Info: "info", + Warning: "warning", + Error: "error", +}; + +export const Port = { + Http: 80, + Https: 443, + Development: 3000, +}; + +export const SupportedMethod = { + Get: 0, + Post: 1, +}; + + +if (typeof globalThis.Networking === 'undefined') { + globalThis.Networking = {}; +} +if (typeof globalThis.Networking.API === 'undefined') { + globalThis.Networking.API = {}; +} +if (typeof globalThis.Configuration === 'undefined') { + globalThis.Configuration = {}; +} +if (typeof globalThis.Networking.APIV2 === 'undefined') { + globalThis.Networking.APIV2 = {}; +} +if (typeof globalThis.Networking.APIV2.Internal === 'undefined') { + globalThis.Networking.APIV2.Internal = {}; +} + +globalThis.Networking.API.Method = Method; +globalThis.Configuration.LogLevel = LogLevel; +globalThis.Configuration.Port = Port; +globalThis.Networking.APIV2.Internal.SupportedMethod = SupportedMethod; + export async function createInstantiator(options, swift) { let instance; let memory; @@ -141,44 +188,12 @@ export async function createInstantiator(options, swift) { instance.exports.bjs_TestServer_call(this.pointer, method | 0); } } - const Method = { - Get: 0, - Post: 1, - Put: 2, - Delete: 3, - }; - - const LogLevel = { - Debug: "debug", - Info: "info", - Warning: "warning", - Error: "error", - }; - - const Port = { - Http: 80, - Https: 443, - Development: 3000, - }; - - const SupportedMethod = { - Get: 0, - Post: 1, - }; - const exports = { Converter, HTTPServer, TestServer, - Method, - LogLevel, - Port, - SupportedMethod, }; - if (typeof globalThis.Configuration === 'undefined') { - globalThis.Configuration = {}; - } if (typeof globalThis.Networking === 'undefined') { globalThis.Networking = {}; } @@ -197,10 +212,6 @@ export async function createInstantiator(options, swift) { globalThis.Utils.Converter = exports.Converter; globalThis.Networking.API.HTTPServer = exports.HTTPServer; globalThis.Networking.APIV2.Internal.TestServer = exports.TestServer; - globalThis.Networking.API.Method = exports.Method; - globalThis.Configuration.LogLevel = exports.LogLevel; - globalThis.Configuration.Port = exports.Port; - globalThis.Networking.APIV2.Internal.SupportedMethod = exports.SupportedMethod; return exports; }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js index e8b7adcf..68a2b19f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js @@ -4,6 +4,82 @@ // To update this file, just rebuild your project or run // `swift package bridge-js`. +export const Theme = { + Light: "light", + Dark: "dark", + Auto: "auto", +}; + +export const TSTheme = { + Light: "light", + Dark: "dark", + Auto: "auto", +}; + +export const FeatureFlag = { + Enabled: true, + Disabled: false, +}; + +export const HttpStatus = { + Ok: 200, + NotFound: 404, + ServerError: 500, +}; + +export const TSHttpStatus = { + Ok: 200, + NotFound: 404, + ServerError: 500, +}; + +export const Priority = { + Lowest: 1, + Low: 2, + Medium: 3, + High: 4, + Highest: 5, +}; + +export const FileSize = { + Tiny: 1024, + Small: 10240, + Medium: 102400, + Large: 1048576, +}; + +export const UserId = { + Guest: 0, + User: 1000, + Admin: 9999, +}; + +export const TokenId = { + Invalid: 0, + Session: 12345, + Refresh: 67890, +}; + +export const SessionId = { + None: 0, + Active: 9876543210, + Expired: 1234567890, +}; + +export const Precision = { + Rough: 0.1, + Normal: 0.01, + Fine: 0.001, +}; + +export const Ratio = { + Quarter: 0.25, + Half: 0.5, + Golden: 1.618, + Pi: 3.14159, +}; + + export async function createInstantiator(options, swift) { let instance; let memory; @@ -63,94 +139,7 @@ export async function createInstantiator(options, swift) { createExports: (instance) => { const js = swift.memory.heap; - const Theme = { - Light: "light", - Dark: "dark", - Auto: "auto", - }; - - const TSTheme = { - Light: "light", - Dark: "dark", - Auto: "auto", - }; - - const FeatureFlag = { - Enabled: true, - Disabled: false, - }; - - const HttpStatus = { - Ok: 200, - NotFound: 404, - ServerError: 500, - }; - - const TSHttpStatus = { - Ok: 200, - NotFound: 404, - ServerError: 500, - }; - - const Priority = { - Lowest: 1, - Low: 2, - Medium: 3, - High: 4, - Highest: 5, - }; - - const FileSize = { - Tiny: 1024, - Small: 10240, - Medium: 102400, - Large: 1048576, - }; - - const UserId = { - Guest: 0, - User: 1000, - Admin: 9999, - }; - - const TokenId = { - Invalid: 0, - Session: 12345, - Refresh: 67890, - }; - - const SessionId = { - None: 0, - Active: 9876543210, - Expired: 1234567890, - }; - - const Precision = { - Rough: 0.1, - Normal: 0.01, - Fine: 0.001, - }; - - const Ratio = { - Quarter: 0.25, - Half: 0.5, - Golden: 1.618, - Pi: 3.14159, - }; - return { - Theme, - TSTheme, - FeatureFlag, - HttpStatus, - TSHttpStatus, - Priority, - FileSize, - UserId, - TokenId, - SessionId, - Precision, - Ratio, setTheme: function bjs_setTheme(theme) { const themeBytes = textEncoder.encode(theme); const themeId = swift.memory.retain(themeBytes); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift index 84ad9821..991b5c6c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift @@ -25,7 +25,8 @@ public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTheme() -> Void { #if arch(wasm32) let ret = getTheme() - return ret.rawValue.withUTF8 { ptr in + var rawValue = ret.rawValue + return rawValue.withUTF8 { ptr in _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) } #else @@ -52,7 +53,8 @@ public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTSTheme() -> Void { #if arch(wasm32) let ret = getTSTheme() - return ret.rawValue.withUTF8 { ptr in + var rawValue = ret.rawValue + return rawValue.withUTF8 { ptr in _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) } #else @@ -322,7 +324,8 @@ public func _bjs_convertPriority(status: Int32) -> Int32 { public func _bjs_validateSession(session: Int64) -> Void { #if arch(wasm32) let ret = validateSession(_: SessionId(rawValue: UInt64(bitPattern: Int64(session)))!) - return ret.rawValue.withUTF8 { ptr in + var rawValue = ret.rawValue + return rawValue.withUTF8 { ptr in _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) } #else diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 307fa21e..bd080623 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -123,6 +123,153 @@ struct TestError: Error { return greeter.jsValue.object! } +// MARK: - Enum Tests + +@JS enum Direction { + case north + case south + case east + case west +} + +@JS enum Status { + case loading + case success + case error +} + +@JS enum Theme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +@JS enum HttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +@JS(enumStyle: .tsEnum) enum TSDirection { + case north + case south + case east + case west +} + +@JS(enumStyle: .tsEnum) enum TSTheme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +@JS func setDirection(_ direction: Direction) -> Direction { + return direction +} + +@JS func getDirection() -> Direction { + return .north +} + +@JS func processDirection(_ input: Direction) -> Status { + switch input { + case .north, .south: return .success + case .east, .west: return .loading + } +} + +@JS func setTheme(_ theme: Theme) -> Theme { + return theme +} + +@JS func getTheme() -> Theme { + return .light +} + +@JS func setHttpStatus(_ status: HttpStatus) -> HttpStatus { + return status +} + +@JS func getHttpStatus() -> HttpStatus { + return .ok +} + +@JS func processTheme(_ theme: Theme) -> HttpStatus { + switch theme { + case .light: return .ok + case .dark: return .notFound + case .auto: return .serverError + } +} + +@JS func setTSDirection(_ direction: TSDirection) -> TSDirection { + return direction +} + +@JS func getTSDirection() -> TSDirection { + return .north +} + +@JS func setTSTheme(_ theme: TSTheme) -> TSTheme { + return theme +} + +@JS func getTSTheme() -> TSTheme { + return .light +} + +@JS enum Utils { + @JS class Converter { + @JS init() {} + + @JS func toString(value: Int) -> String { + return String(value) + } + } +} + +@JS enum Networking { + @JS enum API { + @JS enum Method { + case get + case post + case put + case delete + } + @JS class HTTPServer { + @JS init() {} + @JS func call(_ method: Method) {} + } + } +} + +@JS enum Configuration { + @JS enum LogLevel: String { + case debug = "debug" + case info = "info" + case warning = "warning" + case error = "error" + } + + @JS enum Port: Int { + case http = 80 + case https = 443 + case development = 3000 + } +} + +@JS(namespace: "Networking.APIV2") +enum Internal { + @JS enum SupportedMethod { + case get + case post + } + @JS class TestServer { + @JS init() {} + @JS func call(_ method: SupportedMethod) {} + } +} + class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 579dd36b..15e1cfc5 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -6,6 +6,144 @@ @_spi(BridgeJS) import JavaScriptKit +extension Direction { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} + +extension Status { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .loading + case 1: + self = .success + case 2: + self = .error + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .loading: + return 0 + case .success: + return 1 + case .error: + return 2 + } + } +} + +extension TSDirection { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} + +extension Networking.API.Method { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .get + case 1: + self = .post + case 2: + self = .put + case 3: + self = .delete + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .get: + return 0 + case .post: + return 1 + case .put: + return 2 + case .delete: + return 3 + } + } +} + +extension Internal.SupportedMethod { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .get + case 1: + self = .post + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .get: + return 0 + case .post: + return 1 + } + } +} + @_expose(wasm, "bjs_roundTripVoid") @_cdecl("bjs_roundTripVoid") public func _bjs_roundTripVoid() -> Void { @@ -477,6 +615,162 @@ public func _bjs_testSwiftClassAsJSValue(greeter: UnsafeMutableRawPointer) -> In #endif } +@_expose(wasm, "bjs_setDirection") +@_cdecl("bjs_setDirection") +public func _bjs_setDirection(direction: Int32) -> Int32 { + #if arch(wasm32) + let ret = setDirection(_: Direction(bridgeJSRawValue: direction)!) + return ret.bridgeJSRawValue + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getDirection") +@_cdecl("bjs_getDirection") +public func _bjs_getDirection() -> Int32 { + #if arch(wasm32) + let ret = getDirection() + return ret.bridgeJSRawValue + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processDirection") +@_cdecl("bjs_processDirection") +public func _bjs_processDirection(input: Int32) -> Int32 { + #if arch(wasm32) + let ret = processDirection(_: Direction(bridgeJSRawValue: input)!) + return ret.bridgeJSRawValue + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTheme") +@_cdecl("bjs_setTheme") +public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { + #if arch(wasm32) + let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in + _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) + return Int(themeLen) + } + let ret = setTheme(_: Theme(rawValue: theme)!) + var rawValue = ret.rawValue + return rawValue.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTheme") +@_cdecl("bjs_getTheme") +public func _bjs_getTheme() -> Void { + #if arch(wasm32) + let ret = getTheme() + var rawValue = ret.rawValue + return rawValue.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setHttpStatus") +@_cdecl("bjs_setHttpStatus") +public func _bjs_setHttpStatus(status: Int32) -> Int32 { + #if arch(wasm32) + let ret = setHttpStatus(_: HttpStatus(rawValue: Int(status))!) + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getHttpStatus") +@_cdecl("bjs_getHttpStatus") +public func _bjs_getHttpStatus() -> Int32 { + #if arch(wasm32) + let ret = getHttpStatus() + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processTheme") +@_cdecl("bjs_processTheme") +public func _bjs_processTheme(themeBytes: Int32, themeLen: Int32) -> Int32 { + #if arch(wasm32) + let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in + _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) + return Int(themeLen) + } + let ret = processTheme(_: Theme(rawValue: theme)!) + return Int32(ret.rawValue) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTSDirection") +@_cdecl("bjs_setTSDirection") +public func _bjs_setTSDirection(direction: Int32) -> Int32 { + #if arch(wasm32) + let ret = setTSDirection(_: TSDirection(bridgeJSRawValue: direction)!) + return ret.bridgeJSRawValue + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSDirection") +@_cdecl("bjs_getTSDirection") +public func _bjs_getTSDirection() -> Int32 { + #if arch(wasm32) + let ret = getTSDirection() + return ret.bridgeJSRawValue + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_setTSTheme") +@_cdecl("bjs_setTSTheme") +public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { + #if arch(wasm32) + let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in + _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) + return Int(themeLen) + } + let ret = setTSTheme(_: TSTheme(rawValue: theme)!) + var rawValue = ret.rawValue + return rawValue.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getTSTheme") +@_cdecl("bjs_getTSTheme") +public func _bjs_getTSTheme() -> Void { + #if arch(wasm32) + let ret = getTSTheme() + var rawValue = ret.rawValue + return rawValue.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { @@ -567,4 +861,112 @@ extension Calculator: ConvertibleToJSValue { func _bjs_Calculator_wrap(_: UnsafeMutableRawPointer) -> Int32 return .object(JSObject(id: UInt32(bitPattern: _bjs_Calculator_wrap(Unmanaged.passRetained(self).toOpaque())))) } +} + +@_expose(wasm, "bjs_Converter_init") +@_cdecl("bjs_Converter_init") +public func _bjs_Converter_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Utils.Converter() + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_toString") +@_cdecl("bjs_Converter_toString") +public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int(value)) + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Converter_deinit") +@_cdecl("bjs_Converter_deinit") +public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Utils.Converter: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Converter_wrap") + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_HTTPServer_init") +@_cdecl("bjs_HTTPServer_init") +public func _bjs_HTTPServer_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Networking.API.HTTPServer() + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_HTTPServer_call") +@_cdecl("bjs_HTTPServer_call") +public func _bjs_HTTPServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Networking.API.Method(bridgeJSRawValue: method)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_HTTPServer_deinit") +@_cdecl("bjs_HTTPServer_deinit") +public func _bjs_HTTPServer_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Networking.API.HTTPServer: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_HTTPServer_wrap") + func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_HTTPServer_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_TestServer_init") +@_cdecl("bjs_TestServer_init") +public func _bjs_TestServer_init() -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Internal.TestServer() + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_TestServer_call") +@_cdecl("bjs_TestServer_call") +public func _bjs_TestServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Internal.SupportedMethod(bridgeJSRawValue: method)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_TestServer_deinit") +@_cdecl("bjs_TestServer_deinit") +public func _bjs_TestServer_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension Internal.TestServer: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_TestServer_wrap") + func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_TestServer_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 19c77f92..b4642d8a 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -126,10 +126,453 @@ ], "name" : "Calculator", "swiftCallName" : "Calculator" + }, + { + "constructor" : { + "abiName" : "bjs_Converter_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_Converter_toString", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "toString", + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "int" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "Converter", + "namespace" : [ + "Utils" + ], + "swiftCallName" : "Utils.Converter" + }, + { + "constructor" : { + "abiName" : "bjs_HTTPServer_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_HTTPServer_call", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "call", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Networking.API.Method" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "HTTPServer", + "namespace" : [ + "Networking", + "API" + ], + "swiftCallName" : "Networking.API.HTTPServer" + }, + { + "constructor" : { + "abiName" : "bjs_TestServer_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + + ] + }, + "methods" : [ + { + "abiName" : "bjs_TestServer_call", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "call", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Internal.SupportedMethod" + } + } + } + ], + "returnType" : { + "void" : { + + } + } + } + ], + "name" : "TestServer", + "namespace" : [ + "Networking", + "APIV2", + "Internal" + ], + "swiftCallName" : "Internal.TestServer" } ], "enums" : [ + { + "cases" : [ + { + "associatedValues" : [ + ], + "name" : "north" + }, + { + "associatedValues" : [ + + ], + "name" : "south" + }, + { + "associatedValues" : [ + + ], + "name" : "east" + }, + { + "associatedValues" : [ + + ], + "name" : "west" + } + ], + "emitStyle" : "const", + "name" : "Direction", + "swiftCallName" : "Direction" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "loading" + }, + { + "associatedValues" : [ + + ], + "name" : "success" + }, + { + "associatedValues" : [ + + ], + "name" : "error" + } + ], + "emitStyle" : "const", + "name" : "Status", + "swiftCallName" : "Status" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "light", + "rawValue" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark", + "rawValue" : "dark" + }, + { + "associatedValues" : [ + + ], + "name" : "auto", + "rawValue" : "auto" + } + ], + "emitStyle" : "const", + "name" : "Theme", + "rawType" : "String", + "swiftCallName" : "Theme" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "ok", + "rawValue" : "200" + }, + { + "associatedValues" : [ + + ], + "name" : "notFound", + "rawValue" : "404" + }, + { + "associatedValues" : [ + + ], + "name" : "serverError", + "rawValue" : "500" + } + ], + "emitStyle" : "const", + "name" : "HttpStatus", + "rawType" : "Int", + "swiftCallName" : "HttpStatus" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "north" + }, + { + "associatedValues" : [ + + ], + "name" : "south" + }, + { + "associatedValues" : [ + + ], + "name" : "east" + }, + { + "associatedValues" : [ + + ], + "name" : "west" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSDirection", + "swiftCallName" : "TSDirection" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "light", + "rawValue" : "light" + }, + { + "associatedValues" : [ + + ], + "name" : "dark", + "rawValue" : "dark" + }, + { + "associatedValues" : [ + + ], + "name" : "auto", + "rawValue" : "auto" + } + ], + "emitStyle" : "tsEnum", + "name" : "TSTheme", + "rawType" : "String", + "swiftCallName" : "TSTheme" + }, + { + "cases" : [ + + ], + "emitStyle" : "const", + "name" : "Utils", + "swiftCallName" : "Utils" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "get" + }, + { + "associatedValues" : [ + + ], + "name" : "post" + }, + { + "associatedValues" : [ + + ], + "name" : "put" + }, + { + "associatedValues" : [ + + ], + "name" : "delete" + } + ], + "emitStyle" : "const", + "name" : "Method", + "namespace" : [ + "Networking", + "API" + ], + "swiftCallName" : "Networking.API.Method" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "debug", + "rawValue" : "debug" + }, + { + "associatedValues" : [ + + ], + "name" : "info", + "rawValue" : "info" + }, + { + "associatedValues" : [ + + ], + "name" : "warning", + "rawValue" : "warning" + }, + { + "associatedValues" : [ + + ], + "name" : "error", + "rawValue" : "error" + } + ], + "emitStyle" : "const", + "name" : "LogLevel", + "namespace" : [ + "Configuration" + ], + "rawType" : "String", + "swiftCallName" : "Configuration.LogLevel" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "http", + "rawValue" : "80" + }, + { + "associatedValues" : [ + + ], + "name" : "https", + "rawValue" : "443" + }, + { + "associatedValues" : [ + + ], + "name" : "development", + "rawValue" : "3000" + } + ], + "emitStyle" : "const", + "name" : "Port", + "namespace" : [ + "Configuration" + ], + "rawType" : "Int", + "swiftCallName" : "Configuration.Port" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "get" + }, + { + "associatedValues" : [ + + ], + "name" : "post" + } + ], + "emitStyle" : "const", + "name" : "SupportedMethod", + "namespace" : [ + "Networking", + "APIV2", + "Internal" + ], + "swiftCallName" : "Internal.SupportedMethod" + } ], "functions" : [ { @@ -782,6 +1225,265 @@ } } + }, + { + "abiName" : "bjs_setDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setDirection", + "parameters" : [ + { + "label" : "_", + "name" : "direction", + "type" : { + "caseEnum" : { + "_0" : "Direction" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "Direction" + } + } + }, + { + "abiName" : "bjs_getDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getDirection", + "parameters" : [ + + ], + "returnType" : { + "caseEnum" : { + "_0" : "Direction" + } + } + }, + { + "abiName" : "bjs_processDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "processDirection", + "parameters" : [ + { + "label" : "_", + "name" : "input", + "type" : { + "caseEnum" : { + "_0" : "Direction" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "Status" + } + } + }, + { + "abiName" : "bjs_setTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTheme", + "parameters" : [ + { + "label" : "_", + "name" : "theme", + "type" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + }, + { + "abiName" : "bjs_getTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTheme", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + }, + { + "abiName" : "bjs_setHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setHttpStatus", + "parameters" : [ + { + "label" : "_", + "name" : "status", + "type" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_getHttpStatus", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getHttpStatus", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_processTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "processTheme", + "parameters" : [ + { + "label" : "_", + "name" : "theme", + "type" : { + "rawValueEnum" : { + "_0" : "Theme", + "_1" : "String" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "HttpStatus", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_setTSDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTSDirection", + "parameters" : [ + { + "label" : "_", + "name" : "direction", + "type" : { + "caseEnum" : { + "_0" : "TSDirection" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "TSDirection" + } + } + }, + { + "abiName" : "bjs_getTSDirection", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTSDirection", + "parameters" : [ + + ], + "returnType" : { + "caseEnum" : { + "_0" : "TSDirection" + } + } + }, + { + "abiName" : "bjs_setTSTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "setTSTheme", + "parameters" : [ + { + "label" : "_", + "name" : "theme", + "type" : { + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" + } + } + }, + { + "abiName" : "bjs_getTSTheme", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getTSTheme", + "parameters" : [ + + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "TSTheme", + "_1" : "String" + } + } } ], "moduleName" : "BridgeJSRuntimeTests" diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 88de303a..6954d87c 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -1,5 +1,9 @@ // @ts-check +import { + Direction, Status, Theme, HttpStatus, TSDirection, TSTheme +} from '../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.js'; + /** @type {import('../.build/plugins/PackageToJS/outputs/PackageTests/test.d.ts').SetupOptionsFn} */ export async function setupOptions(options, context) { Error.stackTraceLimit = 100; @@ -165,6 +169,89 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { } catch (error) { assert.fail("Expected no error"); } + + assert.equal(Direction.North, 0); + assert.equal(Direction.South, 1); + assert.equal(Direction.East, 2); + assert.equal(Direction.West, 3); + assert.equal(Status.Loading, 0); + assert.equal(Status.Success, 1); + assert.equal(Status.Error, 2); + + assert.equal(exports.setDirection(Direction.North), Direction.North); + assert.equal(exports.setDirection(Direction.South), Direction.South); + assert.equal(exports.getDirection(), Direction.North); + assert.equal(exports.processDirection(Direction.North), Status.Success); + assert.equal(exports.processDirection(Direction.East), Status.Loading); + + assert.equal(Theme.Light, "light"); + assert.equal(Theme.Dark, "dark"); + assert.equal(Theme.Auto, "auto"); + assert.equal(HttpStatus.Ok, 200); + assert.equal(HttpStatus.NotFound, 404); + assert.equal(HttpStatus.ServerError, 500); + + assert.equal(exports.setTheme(Theme.Light), Theme.Light); + assert.equal(exports.setTheme(Theme.Dark), Theme.Dark); + assert.equal(exports.getTheme(), Theme.Light); + assert.equal(exports.setHttpStatus(HttpStatus.Ok), HttpStatus.Ok); + assert.equal(exports.getHttpStatus(), HttpStatus.Ok); + assert.equal(exports.processTheme(Theme.Light), HttpStatus.Ok); + assert.equal(exports.processTheme(Theme.Dark), HttpStatus.NotFound); + + assert.equal(TSDirection.North, 0); + assert.equal(TSDirection.South, 1); + assert.equal(TSDirection.East, 2); + assert.equal(TSDirection.West, 3); + assert.equal(TSTheme.Light, "light"); + assert.equal(TSTheme.Dark, "dark"); + assert.equal(TSTheme.Auto, "auto"); + + assert.equal(exports.setTSDirection(TSDirection.North), TSDirection.North); + assert.equal(exports.getTSDirection(), TSDirection.North); + assert.equal(exports.setTSTheme(TSTheme.Light), TSTheme.Light); + assert.equal(exports.getTSTheme(), TSTheme.Light); + + assert.equal(globalThis.Networking.API.Method.Get, 0); + assert.equal(globalThis.Networking.API.Method.Post, 1); + assert.equal(globalThis.Networking.API.Method.Put, 2); + assert.equal(globalThis.Networking.API.Method.Delete, 3); + assert.equal(globalThis.Configuration.LogLevel.Debug, "debug"); + assert.equal(globalThis.Configuration.LogLevel.Info, "info"); + assert.equal(globalThis.Configuration.LogLevel.Warning, "warning"); + assert.equal(globalThis.Configuration.LogLevel.Error, "error"); + assert.equal(globalThis.Configuration.Port.Http, 80); + assert.equal(globalThis.Configuration.Port.Https, 443); + assert.equal(globalThis.Configuration.Port.Development, 3000); + assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Get, 0); + assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Post, 1); + + const converter = new exports.Converter(); + assert.equal(converter.toString(42), "42"); + assert.equal(converter.toString(123), "123"); + converter.release(); + + const httpServer = new exports.HTTPServer(); + httpServer.call(globalThis.Networking.API.Method.Get); + httpServer.call(globalThis.Networking.API.Method.Post); + httpServer.release(); + + const testServer = new exports.TestServer(); + testServer.call(globalThis.Networking.APIV2.Internal.SupportedMethod.Get); + testServer.call(globalThis.Networking.APIV2.Internal.SupportedMethod.Post); + testServer.release(); + + const globalConverter = new globalThis.Utils.Converter(); + assert.equal(globalConverter.toString(99), "99"); + globalConverter.release(); + + const globalHttpServer = new globalThis.Networking.API.HTTPServer(); + globalHttpServer.call(globalThis.Networking.API.Method.Get); + globalHttpServer.release(); + + const globalTestServer = new globalThis.Networking.APIV2.Internal.TestServer(); + globalTestServer.call(globalThis.Networking.APIV2.Internal.SupportedMethod.Post); + globalTestServer.release(); } /** @param {import('./../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} exports */ From 8cedac70eac91bc6c2197984cec783dc8a7a5688 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 21 Aug 2025 18:47:21 +0900 Subject: [PATCH 44/61] BridgeJS: Add property support Add support for Swift properties in BridgeJS with proper readonly detection. - Support stored, lazy, computed, and observed properties - Proper readonly property detection - Generate correct JavaScript property descriptors - Add comprehensive runtime and snapshot tests --- .../JavaScript/BridgeJS.ExportSwift.json | 6 + .../Sources/BridgeJSCore/ExportSwift.swift | 212 +++++--- .../Sources/BridgeJSLink/BridgeJSLink.swift | 57 ++- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 23 + .../Inputs/PropertyTypes.swift | 97 ++++ .../BridgeJSToolTests/Inputs/SwiftClass.swift | 2 +- .../PropertyTypes.Export.d.ts | 48 ++ .../BridgeJSLinkTests/PropertyTypes.Export.js | 242 +++++++++ .../BridgeJSLinkTests/SwiftClass.Export.d.ts | 1 + .../BridgeJSLinkTests/SwiftClass.Export.js | 12 + .../ExportSwiftTests/EnumNamespace.json | 9 + .../ExportSwiftTests/Namespaces.json | 9 + .../ExportSwiftTests/PropertyTypes.json | 334 ++++++++++++ .../ExportSwiftTests/PropertyTypes.swift | 373 ++++++++++++++ .../ExportSwiftTests/SwiftClass.json | 11 + .../ExportSwiftTests/SwiftClass.swift | 27 + .../BridgeJSRuntimeTests/ExportAPITests.swift | 144 +++++- .../Generated/BridgeJS.ExportSwift.swift | 475 ++++++++++++++++++ .../JavaScript/BridgeJS.ExportSwift.json | 433 ++++++++++++++++ Tests/prelude.mjs | 134 ++++- 20 files changed, 2570 insertions(+), 79 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PropertyTypes.swift create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json index 2b5ce07d..625556f2 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -47,6 +47,9 @@ } ], "name" : "PlayBridgeJS", + "properties" : [ + + ], "swiftCallName" : "PlayBridgeJS" }, { @@ -117,6 +120,9 @@ } ], "name" : "PlayBridgeJSOutput", + "properties" : [ + + ], "swiftCallName" : "PlayBridgeJSOutput" } ], diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index a5f2e108..56e01290 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -332,6 +332,75 @@ public class ExportSwift { return .skipChildren } + override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + guard node.attributes.hasJSAttribute() else { return .skipChildren } + guard case .classBody(let className, let classKey) = state else { + diagnose(node: node, message: "@JS var must be inside a @JS class") + return .skipChildren + } + + if let jsAttribute = node.attributes.firstJSAttribute, + extractNamespace(from: jsAttribute) != nil + { + diagnose( + node: jsAttribute, + message: "Namespace is not supported for property declarations", + hint: "Remove the namespace from @JS attribute" + ) + } + + // Process each binding (variable declaration) + for binding in node.bindings { + guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { + diagnose(node: binding.pattern, message: "Complex patterns not supported for @JS properties") + continue + } + + let propertyName = pattern.identifier.text + + guard let typeAnnotation = binding.typeAnnotation else { + diagnose(node: binding, message: "@JS property must have explicit type annotation") + continue + } + + guard let propertyType = self.parent.lookupType(for: typeAnnotation.type) else { + diagnoseUnsupportedType(node: typeAnnotation.type, type: typeAnnotation.type.trimmedDescription) + continue + } + + // Check if property is readonly + let isLet = node.bindingSpecifier.tokenKind == .keyword(.let) + let isGetterOnly = node.bindings.contains(where: { + switch $0.accessorBlock?.accessors { + case .accessors(let accessors): + // Has accessors - check if it only has a getter (no setter, willSet, or didSet) + return !accessors.contains(where: { accessor in + let tokenKind = accessor.accessorSpecifier.tokenKind + return tokenKind == .keyword(.set) || tokenKind == .keyword(.willSet) + || tokenKind == .keyword(.didSet) + }) + case .getter: + // Has only a getter block + return true + case nil: + // No accessor block - this is a stored property, not readonly + return false + } + }) + let isReadonly = isLet || isGetterOnly + + let exportedProperty = ExportedProperty( + name: propertyName, + type: propertyType, + isReadonly: isReadonly + ) + + exportedClassByName[classKey]?.properties.append(exportedProperty) + } + + return .skipChildren + } + override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { let name = node.name.text @@ -359,6 +428,7 @@ public class ExportSwift { swiftCallName: swiftCallName, constructor: nil, methods: [], + properties: [], namespace: effectiveNamespace ) let uniqueKey = classKey(name: name, namespace: effectiveNamespace) @@ -689,7 +759,8 @@ public class ExportSwift { class ExportedThunkBuilder { var body: [CodeBlockItemSyntax] = [] - var abiParameterForwardings: [LabeledExprSyntax] = [] + var liftedParameterExprs: [ExprSyntax] = [] + var parameters: [Parameter] = [] var abiParameterSignatures: [(name: String, type: WasmCoreType)] = [] var abiReturnType: WasmCoreType? let effects: Effects @@ -708,38 +779,19 @@ public class ExportSwift { } func liftParameter(param: Parameter) { + parameters.append(param) switch param.type { case .bool: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name) == 1") - ) - ) + liftedParameterExprs.append(ExprSyntax("\(raw: param.name) == 1")) abiParameterSignatures.append((param.name, .i32)) case .int: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.type.swiftType)(\(raw: param.name))") - ) - ) + liftedParameterExprs.append(ExprSyntax("\(raw: param.type.swiftType)(\(raw: param.name))")) abiParameterSignatures.append((param.name, .i32)) case .float: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) + liftedParameterExprs.append(ExprSyntax("\(raw: param.name)")) abiParameterSignatures.append((param.name, .f32)) case .double: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) + liftedParameterExprs.append(ExprSyntax("\(raw: param.name)")) abiParameterSignatures.append((param.name, .f64)) case .string: let bytesLabel = "\(param.name)Bytes" @@ -751,21 +803,11 @@ public class ExportSwift { } """ append(prepare) - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) + liftedParameterExprs.append(ExprSyntax("\(raw: param.name)")) abiParameterSignatures.append((bytesLabel, .i32)) abiParameterSignatures.append((lengthLabel, .i32)) case .caseEnum(let enumName): - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: enumName)(bridgeJSRawValue: \(raw: param.name))!") - ) - ) + liftedParameterExprs.append(ExprSyntax("\(raw: enumName)(bridgeJSRawValue: \(raw: param.name))!")) abiParameterSignatures.append((param.name, .i32)) case .rawValueEnum(let enumName, let rawType): if rawType == .string { @@ -778,12 +820,7 @@ public class ExportSwift { } """ append(prepare) - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: enumName)(rawValue: \(raw: param.name))!") - ) - ) + liftedParameterExprs.append(ExprSyntax("\(raw: enumName)(rawValue: \(raw: param.name))!")) abiParameterSignatures.append((bytesLabel, .i32)) abiParameterSignatures.append((lengthLabel, .i32)) } else { @@ -802,12 +839,7 @@ public class ExportSwift { conversionExpr = "\(enumName)(rawValue: \(rawType.rawValue)(\(param.name)))!" } - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax(stringLiteral: conversionExpr) - ) - ) + liftedParameterExprs.append(ExprSyntax(stringLiteral: conversionExpr)) if let wasmType = rawType.wasmCoreType { abiParameterSignatures.append((param.name, wasmType)) } @@ -817,36 +849,35 @@ public class ExportSwift { case .namespaceEnum: break case .jsObject(nil): - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("JSObject(id: UInt32(bitPattern: \(raw: param.name)))") - ) - ) + liftedParameterExprs.append(ExprSyntax("JSObject(id: UInt32(bitPattern: \(raw: param.name)))")) abiParameterSignatures.append((param.name, .i32)) case .jsObject(let name): - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: name)(takingThis: UInt32(bitPattern: \(raw: param.name)))") - ) + liftedParameterExprs.append( + ExprSyntax("\(raw: name)(takingThis: UInt32(bitPattern: \(raw: param.name)))") ) abiParameterSignatures.append((param.name, .i32)) case .swiftHeapObject: let objectExpr: ExprSyntax = "Unmanaged<\(raw: param.type.swiftType)>.fromOpaque(\(raw: param.name)).takeUnretainedValue()" - abiParameterForwardings.append( - LabeledExprSyntax(label: param.label, expression: objectExpr) - ) + liftedParameterExprs.append(objectExpr) abiParameterSignatures.append((param.name, .pointer)) case .void: break } } + private func removeFirstLiftedParameter() -> (parameter: Parameter, expr: ExprSyntax) { + let parameter = parameters.removeFirst() + let expr = liftedParameterExprs.removeFirst() + return (parameter, expr) + } + private func renderCallStatement(callee: ExprSyntax, returnType: BridgeType) -> CodeBlockItemSyntax { + let labeledParams = zip(parameters, liftedParameterExprs).map { param, expr in + LabeledExprSyntax(label: param.label, expression: expr) + } var callExpr: ExprSyntax = - "\(raw: callee)(\(raw: abiParameterForwardings.map { $0.description }.joined(separator: ", ")))" + "\(raw: callee)(\(raw: labeledParams.map { $0.description }.joined(separator: ", ")))" if effects.isAsync { callExpr = ExprSyntax( AwaitExprSyntax(awaitKeyword: .keyword(.await).with(\.trailingTrivia, .space), expression: callExpr) @@ -884,14 +915,30 @@ public class ExportSwift { } func callMethod(klassName: String, methodName: String, returnType: BridgeType) { - let _selfParam = self.abiParameterForwardings.removeFirst() + let (_, selfExpr) = removeFirstLiftedParameter() let item = renderCallStatement( - callee: "\(raw: _selfParam).\(raw: methodName)", + callee: "\(raw: selfExpr).\(raw: methodName)", returnType: returnType ) append(item) } + func callPropertyGetter(klassName: String, propertyName: String, returnType: BridgeType) { + let (_, selfExpr) = removeFirstLiftedParameter() + let retMutability = returnType == .string ? "var" : "let" + if returnType == .void { + append("\(raw: selfExpr).\(raw: propertyName)") + } else { + append("\(raw: retMutability) ret = \(raw: selfExpr).\(raw: propertyName)") + } + } + + func callPropertySetter(klassName: String, propertyName: String) { + let (_, selfExpr) = removeFirstLiftedParameter() + let (_, newValueExpr) = removeFirstLiftedParameter() + append("\(raw: selfExpr).\(raw: propertyName) = \(raw: newValueExpr)") + } + func lowerReturnValue(returnType: BridgeType) { if effects.isAsync { // Async functions always return a Promise, which is a JSObject @@ -1157,6 +1204,39 @@ public class ExportSwift { decls.append(builder.render(abiName: method.abiName)) } + // Generate property getters and setters + for property in klass.properties { + // Generate getter + let getterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) + getterBuilder.liftParameter( + param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(klass.name)) + ) + getterBuilder.callPropertyGetter( + klassName: klass.name, + propertyName: property.name, + returnType: property.type + ) + getterBuilder.lowerReturnValue(returnType: property.type) + decls.append(getterBuilder.render(abiName: property.getterAbiName(className: klass.name))) + + // Generate setter if property is not readonly + if !property.isReadonly { + let setterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) + setterBuilder.liftParameter( + param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(klass.name)) + ) + setterBuilder.liftParameter( + param: Parameter(label: "value", name: "value", type: property.type) + ) + setterBuilder.callPropertySetter( + klassName: klass.name, + propertyName: property.name + ) + setterBuilder.lowerReturnValue(returnType: .void) + decls.append(setterBuilder.render(abiName: property.setterAbiName(className: klass.name))) + } + } + do { decls.append( """ diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 046cb92d..2b220e43 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -524,13 +524,12 @@ struct BridgeJSLink { func renderFunction( name: String, parameters: [Parameter], - returnType: BridgeType, returnExpr: String?, - isMethod: Bool + declarationPrefixKeyword: String? ) -> [String] { var funcLines: [String] = [] funcLines.append( - "\(isMethod ? "" : "function ")\(name)(\(parameters.map { $0.name }.joined(separator: ", "))) {" + "\(declarationPrefixKeyword.map { "\($0) "} ?? "")\(name)(\(parameters.map { $0.name }.joined(separator: ", "))) {" ) funcLines.append(contentsOf: bodyLines.map { $0.indent(count: 4) }) funcLines.append(contentsOf: cleanupLines.map { $0.indent(count: 4) }) @@ -688,9 +687,8 @@ struct BridgeJSLink { let funcLines = thunkBuilder.renderFunction( name: function.abiName, parameters: function.parameters, - returnType: function.returnType, returnExpr: returnExpr, - isMethod: false + declarationPrefixKeyword: "function" ) var dtsLines: [String] = [] dtsLines.append( @@ -753,9 +751,8 @@ struct BridgeJSLink { contentsOf: thunkBuilder.renderFunction( name: method.name, parameters: method.parameters, - returnType: method.returnType, returnExpr: returnExpr, - isMethod: true + declarationPrefixKeyword: nil ).map { $0.indent(count: 4) } ) dtsTypeLines.append( @@ -763,6 +760,52 @@ struct BridgeJSLink { .indent(count: 4) ) } + + // Generate property getters and setters + for property in klass.properties { + // Generate getter + let getterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) + getterThunkBuilder.lowerSelf() + let getterReturnExpr = getterThunkBuilder.call( + abiName: property.getterAbiName(className: klass.name), + returnType: property.type + ) + jsLines.append( + contentsOf: getterThunkBuilder.renderFunction( + name: property.name, + parameters: [], + returnExpr: getterReturnExpr, + declarationPrefixKeyword: "get" + ).map { $0.indent(count: 4) } + ) + + // Generate setter if not readonly + if !property.isReadonly { + let setterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) + setterThunkBuilder.lowerSelf() + setterThunkBuilder.lowerParameter(param: Parameter(label: "value", name: "value", type: property.type)) + _ = setterThunkBuilder.call( + abiName: property.setterAbiName(className: klass.name), + returnType: .void + ) + jsLines.append( + contentsOf: setterThunkBuilder.renderFunction( + name: property.name, + parameters: [.init(label: nil, name: "value", type: property.type)], + returnExpr: nil, + declarationPrefixKeyword: "set" + ).map { $0.indent(count: 4) } + ) + } + + // Add TypeScript property definition + let readonly = property.isReadonly ? "readonly " : "" + dtsTypeLines.append( + "\(readonly)\(property.name): \(property.type.tsType);" + .indent(count: 4) + ) + } + jsLines.append("}") dtsTypeLines.append("}") diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 0d872160..47083729 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -174,6 +174,7 @@ public struct ExportedClass: Codable { public var swiftCallName: String public var constructor: ExportedConstructor? public var methods: [ExportedFunction] + public var properties: [ExportedProperty] public var namespace: [String]? public init( @@ -181,12 +182,14 @@ public struct ExportedClass: Codable { swiftCallName: String, constructor: ExportedConstructor? = nil, methods: [ExportedFunction], + properties: [ExportedProperty] = [], namespace: [String]? = nil ) { self.name = name self.swiftCallName = swiftCallName self.constructor = constructor self.methods = methods + self.properties = properties self.namespace = namespace } } @@ -205,6 +208,26 @@ public struct ExportedConstructor: Codable { } } +public struct ExportedProperty: Codable { + public var name: String + public var type: BridgeType + public var isReadonly: Bool + + public init(name: String, type: BridgeType, isReadonly: Bool = false) { + self.name = name + self.type = type + self.isReadonly = isReadonly + } + + public func getterAbiName(className: String) -> String { + return "bjs_\(className)_\(name)_get" + } + + public func setterAbiName(className: String) -> String { + return "bjs_\(className)_\(name)_set" + } +} + public struct ExportedSkeleton: Codable { public let moduleName: String public let functions: [ExportedFunction] diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PropertyTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PropertyTypes.swift new file mode 100644 index 00000000..68482501 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/PropertyTypes.swift @@ -0,0 +1,97 @@ +@JS class PropertyHolder { + // Primitive properties + @JS var intValue: Int + @JS var floatValue: Float + @JS var doubleValue: Double + @JS var boolValue: Bool + @JS var stringValue: String + + // Readonly primitive properties + @JS let readonlyInt: Int = 42 + @JS let readonlyFloat: Float = 3.14 + @JS let readonlyDouble: Double = 2.718281828 + @JS let readonlyBool: Bool = true + @JS let readonlyString: String = "constant" + + // JSObject property + @JS var jsObject: JSObject + + // SwiftHeapObject property (will be set later) + @JS var sibling: PropertyHolder + + // Lazy stored property - should this be supported or generate an error? + @JS lazy var lazyValue: String = "computed lazily" + + // Computed property with getter only (readonly) + @JS var computedReadonly: Int { + return intValue * 2 + } + + // Computed property with getter and setter + @JS var computedReadWrite: String { + get { + return "Value: \(intValue)" + } + set { + // Parse the number from "Value: X" format + if let range = newValue.range(of: "Value: "), + let number = Int(String(newValue[range.upperBound...])) + { + intValue = number + } + } + } + + // Property with property observers + @JS var observedProperty: Int { + willSet { + print("Will set to \(newValue)") + } + didSet { + print("Did set from \(oldValue)") + } + } + + @JS init( + intValue: Int, + floatValue: Float, + doubleValue: Double, + boolValue: Bool, + stringValue: String, + jsObject: JSObject + ) { + self.intValue = intValue + self.floatValue = floatValue + self.doubleValue = doubleValue + self.boolValue = boolValue + self.stringValue = stringValue + self.jsObject = jsObject + self.sibling = self + } + + @JS func getAllValues() -> String { + return "int:\(intValue),float:\(floatValue),double:\(doubleValue),bool:\(boolValue),string:\(stringValue)" + } +} + +@JS func createPropertyHolder( + intValue: Int, + floatValue: Float, + doubleValue: Double, + boolValue: Bool, + stringValue: String, + jsObject: JSObject +) -> PropertyHolder { + return PropertyHolder( + intValue: intValue, + floatValue: floatValue, + doubleValue: doubleValue, + boolValue: boolValue, + stringValue: stringValue, + jsObject: jsObject + ) +} + +@JS func testPropertyHolder(holder: PropertyHolder) -> String { + return holder.getAllValues() +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift index a803504f..08600d2a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift @@ -1,5 +1,5 @@ @JS class Greeter { - var name: String + @JS var name: String @JS init(name: String) { self.name = name diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.d.ts new file mode 100644 index 00000000..8f65849a --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.d.ts @@ -0,0 +1,48 @@ +// 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`. + +/// 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 PropertyHolder extends SwiftHeapObject { + getAllValues(): string; + intValue: number; + floatValue: number; + doubleValue: number; + boolValue: boolean; + stringValue: string; + readonly readonlyInt: number; + readonly readonlyFloat: number; + readonly readonlyDouble: number; + readonly readonlyBool: boolean; + readonly readonlyString: string; + jsObject: any; + sibling: PropertyHolder; + lazyValue: string; + readonly computedReadonly: number; + computedReadWrite: string; + observedProperty: number; +} +export type Exports = { + PropertyHolder: { + new(intValue: number, floatValue: number, doubleValue: number, boolValue: boolean, stringValue: string, jsObject: any): PropertyHolder; + } + createPropertyHolder(intValue: number, floatValue: number, doubleValue: number, boolValue: boolean, stringValue: string, jsObject: any): PropertyHolder; + testPropertyHolder(holder: PropertyHolder): string; +} +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/PropertyTypes.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js new file mode 100644 index 00000000..ae72be66 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js @@ -0,0 +1,242 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + // Wrapper functions for module: TestModule + if (!importObject["TestModule"]) { + importObject["TestModule"] = {}; + } + importObject["TestModule"]["bjs_PropertyHolder_wrap"] = function(pointer) { + const obj = PropertyHolder.__construct(pointer); + return swift.memory.retain(obj); + }; + + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + /// Represents a Swift heap object like a class instance or an actor instance. + class SwiftHeapObject { + static __wrap(pointer, deinit, prototype) { + const obj = Object.create(prototype); + obj.pointer = pointer; + obj.hasReleased = false; + obj.deinit = deinit; + obj.registry = new FinalizationRegistry((pointer) => { + deinit(pointer); + }); + obj.registry.register(this, obj.pointer); + return obj; + } + + release() { + this.registry.unregister(this); + this.deinit(this.pointer); + } + } + class PropertyHolder extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PropertyHolder_deinit, PropertyHolder.prototype); + } + + + constructor(intValue, floatValue, doubleValue, boolValue, stringValue, jsObject) { + const stringValueBytes = textEncoder.encode(stringValue); + const stringValueId = swift.memory.retain(stringValueBytes); + const ret = instance.exports.bjs_PropertyHolder_init(intValue, floatValue, doubleValue, boolValue, stringValueId, stringValueBytes.length, swift.memory.retain(jsObject)); + swift.memory.release(stringValueId); + return PropertyHolder.__construct(ret); + } + getAllValues() { + instance.exports.bjs_PropertyHolder_getAllValues(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + get intValue() { + const ret = instance.exports.bjs_PropertyHolder_intValue_get(this.pointer); + return ret; + } + set intValue(value) { + instance.exports.bjs_PropertyHolder_intValue_set(this.pointer, value); + } + get floatValue() { + const ret = instance.exports.bjs_PropertyHolder_floatValue_get(this.pointer); + return ret; + } + set floatValue(value) { + instance.exports.bjs_PropertyHolder_floatValue_set(this.pointer, value); + } + get doubleValue() { + const ret = instance.exports.bjs_PropertyHolder_doubleValue_get(this.pointer); + return ret; + } + set doubleValue(value) { + instance.exports.bjs_PropertyHolder_doubleValue_set(this.pointer, value); + } + get boolValue() { + const ret = instance.exports.bjs_PropertyHolder_boolValue_get(this.pointer) !== 0; + return ret; + } + set boolValue(value) { + instance.exports.bjs_PropertyHolder_boolValue_set(this.pointer, value); + } + get stringValue() { + instance.exports.bjs_PropertyHolder_stringValue_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set stringValue(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_PropertyHolder_stringValue_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } + get readonlyInt() { + const ret = instance.exports.bjs_PropertyHolder_readonlyInt_get(this.pointer); + return ret; + } + get readonlyFloat() { + const ret = instance.exports.bjs_PropertyHolder_readonlyFloat_get(this.pointer); + return ret; + } + get readonlyDouble() { + const ret = instance.exports.bjs_PropertyHolder_readonlyDouble_get(this.pointer); + return ret; + } + get readonlyBool() { + const ret = instance.exports.bjs_PropertyHolder_readonlyBool_get(this.pointer) !== 0; + return ret; + } + get readonlyString() { + instance.exports.bjs_PropertyHolder_readonlyString_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + get jsObject() { + const retId = instance.exports.bjs_PropertyHolder_jsObject_get(this.pointer); + const ret = swift.memory.getObject(retId); + swift.memory.release(retId); + return ret; + } + set jsObject(value) { + instance.exports.bjs_PropertyHolder_jsObject_set(this.pointer, swift.memory.retain(value)); + } + get sibling() { + const ret = PropertyHolder.__construct(instance.exports.bjs_PropertyHolder_sibling_get(this.pointer)); + return ret; + } + set sibling(value) { + instance.exports.bjs_PropertyHolder_sibling_set(this.pointer, value.pointer); + } + get lazyValue() { + instance.exports.bjs_PropertyHolder_lazyValue_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set lazyValue(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_PropertyHolder_lazyValue_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } + get computedReadonly() { + const ret = instance.exports.bjs_PropertyHolder_computedReadonly_get(this.pointer); + return ret; + } + get computedReadWrite() { + instance.exports.bjs_PropertyHolder_computedReadWrite_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set computedReadWrite(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_PropertyHolder_computedReadWrite_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } + get observedProperty() { + const ret = instance.exports.bjs_PropertyHolder_observedProperty_get(this.pointer); + return ret; + } + set observedProperty(value) { + instance.exports.bjs_PropertyHolder_observedProperty_set(this.pointer, value); + } + } + return { + PropertyHolder, + createPropertyHolder: function bjs_createPropertyHolder(intValue, floatValue, doubleValue, boolValue, stringValue, jsObject) { + const stringValueBytes = textEncoder.encode(stringValue); + const stringValueId = swift.memory.retain(stringValueBytes); + const ret = PropertyHolder.__construct(instance.exports.bjs_createPropertyHolder(intValue, floatValue, doubleValue, boolValue, stringValueId, stringValueBytes.length, swift.memory.retain(jsObject))); + swift.memory.release(stringValueId); + return ret; + }, + testPropertyHolder: function bjs_testPropertyHolder(holder) { + instance.exports.bjs_testPropertyHolder(holder.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + }, + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts index fd376d57..7efbe7c3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts @@ -14,6 +14,7 @@ export interface SwiftHeapObject { export interface Greeter extends SwiftHeapObject { greet(): string; changeName(name: string): void; + name: string; } export type Exports = { Greeter: { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index ab4caba3..8ca2e4e5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -113,6 +113,18 @@ export async function createInstantiator(options, swift) { instance.exports.bjs_Greeter_changeName(this.pointer, nameId, nameBytes.length); swift.memory.release(nameId); } + get name() { + instance.exports.bjs_Greeter_name_get(this.pointer); + const ret = tmpRetString; + tmpRetString = undefined; + return ret; + } + set name(value) { + const valueBytes = textEncoder.encode(value); + const valueId = swift.memory.retain(valueBytes); + instance.exports.bjs_Greeter_name_set(this.pointer, valueId, valueBytes.length); + swift.memory.release(valueId); + } } return { Greeter, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json index a9483455..2ec852f1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.json @@ -40,6 +40,9 @@ "name" : "Converter", "namespace" : [ "Utils" + ], + "properties" : [ + ], "swiftCallName" : "Utils.Converter" }, @@ -84,6 +87,9 @@ "namespace" : [ "Networking", "API" + ], + "properties" : [ + ], "swiftCallName" : "Networking.API.HTTPServer" }, @@ -129,6 +135,9 @@ "Networking", "APIV2", "Internal" + ], + "properties" : [ + ], "swiftCallName" : "Internal.TestServer" } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json index a5e960be..bb81e29a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.json @@ -41,6 +41,9 @@ "namespace" : [ "__Swift", "Foundation" + ], + "properties" : [ + ], "swiftCallName" : "Greeter" }, @@ -85,6 +88,9 @@ "namespace" : [ "Utils", "Converters" + ], + "properties" : [ + ], "swiftCallName" : "Converter" }, @@ -111,6 +117,9 @@ "namespace" : [ "__Swift", "Foundation" + ], + "properties" : [ + ], "swiftCallName" : "UUID" } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json new file mode 100644 index 00000000..e0c5e812 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.json @@ -0,0 +1,334 @@ +{ + "classes" : [ + { + "constructor" : { + "abiName" : "bjs_PropertyHolder_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "intValue", + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "label" : "floatValue", + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "label" : "doubleValue", + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "label" : "boolValue", + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "stringValue", + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "label" : "jsObject", + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_PropertyHolder_getAllValues", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getAllValues", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "PropertyHolder", + "properties" : [ + { + "isReadonly" : false, + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyInt", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyFloat", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyDouble", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyBool", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyString", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "sibling", + "type" : { + "swiftHeapObject" : { + "_0" : "PropertyHolder" + } + } + }, + { + "isReadonly" : false, + "name" : "lazyValue", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "computedReadonly", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "computedReadWrite", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "observedProperty", + "type" : { + "int" : { + + } + } + } + ], + "swiftCallName" : "PropertyHolder" + } + ], + "enums" : [ + + ], + "functions" : [ + { + "abiName" : "bjs_createPropertyHolder", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "createPropertyHolder", + "parameters" : [ + { + "label" : "intValue", + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "label" : "floatValue", + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "label" : "doubleValue", + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "label" : "boolValue", + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "stringValue", + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "label" : "jsObject", + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "PropertyHolder" + } + } + }, + { + "abiName" : "bjs_testPropertyHolder", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "testPropertyHolder", + "parameters" : [ + { + "label" : "holder", + "name" : "holder", + "type" : { + "swiftHeapObject" : { + "_0" : "PropertyHolder" + } + } + } + ], + "returnType" : { + "string" : { + + } + } + } + ], + "moduleName" : "TestModule" +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift new file mode 100644 index 00000000..84609c74 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift @@ -0,0 +1,373 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +@_expose(wasm, "bjs_createPropertyHolder") +@_cdecl("bjs_createPropertyHolder") +public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let stringValue = String(unsafeUninitializedCapacity: Int(stringValueLen)) { b in + _swift_js_init_memory(stringValueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(stringValueLen) + } + let ret = createPropertyHolder(intValue: Int(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: boolValue == 1, stringValue: stringValue, jsObject: JSObject(id: UInt32(bitPattern: jsObject))) + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testPropertyHolder") +@_cdecl("bjs_testPropertyHolder") +public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = testPropertyHolder(holder: Unmanaged.fromOpaque(holder).takeUnretainedValue()) + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_init") +@_cdecl("bjs_PropertyHolder_init") +public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let stringValue = String(unsafeUninitializedCapacity: Int(stringValueLen)) { b in + _swift_js_init_memory(stringValueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(stringValueLen) + } + let ret = PropertyHolder(intValue: Int(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: boolValue == 1, stringValue: stringValue, jsObject: JSObject(id: UInt32(bitPattern: jsObject))) + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_getAllValues") +@_cdecl("bjs_PropertyHolder_getAllValues") +public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().getAllValues() + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_intValue_get") +@_cdecl("bjs_PropertyHolder_intValue_get") +public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_intValue_set") +@_cdecl("bjs_PropertyHolder_intValue_set") +public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue = Int(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_floatValue_get") +@_cdecl("bjs_PropertyHolder_floatValue_get") +public func _bjs_PropertyHolder_floatValue_get(_self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().floatValue + return Float32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_floatValue_set") +@_cdecl("bjs_PropertyHolder_floatValue_set") +public func _bjs_PropertyHolder_floatValue_set(_self: UnsafeMutableRawPointer, value: Float32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().floatValue = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_doubleValue_get") +@_cdecl("bjs_PropertyHolder_doubleValue_get") +public func _bjs_PropertyHolder_doubleValue_get(_self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().doubleValue + return Float64(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_doubleValue_set") +@_cdecl("bjs_PropertyHolder_doubleValue_set") +public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, value: Float64) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().doubleValue = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_boolValue_get") +@_cdecl("bjs_PropertyHolder_boolValue_get") +public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue + return Int32(ret ? 1 : 0) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_boolValue_set") +@_cdecl("bjs_PropertyHolder_boolValue_set") +public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue = value == 1 + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_stringValue_get") +@_cdecl("bjs_PropertyHolder_stringValue_get") +public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_stringValue_set") +@_cdecl("bjs_PropertyHolder_stringValue_set") +public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { + #if arch(wasm32) + let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in + _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(valueLen) + } + Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyInt_get") +@_cdecl("bjs_PropertyHolder_readonlyInt_get") +public func _bjs_PropertyHolder_readonlyInt_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyInt + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyFloat_get") +@_cdecl("bjs_PropertyHolder_readonlyFloat_get") +public func _bjs_PropertyHolder_readonlyFloat_get(_self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyFloat + return Float32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyDouble_get") +@_cdecl("bjs_PropertyHolder_readonlyDouble_get") +public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyDouble + return Float64(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyBool_get") +@_cdecl("bjs_PropertyHolder_readonlyBool_get") +public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyBool + return Int32(ret ? 1 : 0) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyString_get") +@_cdecl("bjs_PropertyHolder_readonlyString_get") +public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyString + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_jsObject_get") +@_cdecl("bjs_PropertyHolder_jsObject_get") +public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_jsObject_set") +@_cdecl("bjs_PropertyHolder_jsObject_set") +public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject = JSObject(id: UInt32(bitPattern: value)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_sibling_get") +@_cdecl("bjs_PropertyHolder_sibling_get") +public func _bjs_PropertyHolder_sibling_get(_self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().sibling + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_sibling_set") +@_cdecl("bjs_PropertyHolder_sibling_set") +public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().sibling = Unmanaged.fromOpaque(value).takeUnretainedValue() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_lazyValue_get") +@_cdecl("bjs_PropertyHolder_lazyValue_get") +public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_lazyValue_set") +@_cdecl("bjs_PropertyHolder_lazyValue_set") +public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { + #if arch(wasm32) + let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in + _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(valueLen) + } + Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadonly_get") +@_cdecl("bjs_PropertyHolder_computedReadonly_get") +public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadonly + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadWrite_get") +@_cdecl("bjs_PropertyHolder_computedReadWrite_get") +public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadWrite_set") +@_cdecl("bjs_PropertyHolder_computedReadWrite_set") +public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { + #if arch(wasm32) + let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in + _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(valueLen) + } + Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_observedProperty_get") +@_cdecl("bjs_PropertyHolder_observedProperty_get") +public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_observedProperty_set") +@_cdecl("bjs_PropertyHolder_observedProperty_set") +public func _bjs_PropertyHolder_observedProperty_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty = Int(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_deinit") +@_cdecl("bjs_PropertyHolder_deinit") +public func _bjs_PropertyHolder_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PropertyHolder: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "TestModule", name: "bjs_PropertyHolder_wrap") + func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_PropertyHolder_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json index 7f8324ac..5266959a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json @@ -62,6 +62,17 @@ } ], "name" : "Greeter", + "properties" : [ + { + "isReadonly" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + } + ], "swiftCallName" : "Greeter" } ], diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift index 09589de3..bcb9f3d7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift @@ -58,6 +58,33 @@ public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: I #endif } +@_expose(wasm, "bjs_Greeter_name_get") +@_cdecl("bjs_Greeter_name_get") +public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().name + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_set") +@_cdecl("bjs_Greeter_name_set") +public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { + #if arch(wasm32) + let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in + _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(valueLen) + } + Unmanaged.fromOpaque(_self).takeUnretainedValue().name = value + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Greeter_deinit") @_cdecl("bjs_Greeter_deinit") public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index bd080623..fc2d8d6a 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -60,7 +60,8 @@ struct TestError: Error { @JS func asyncRoundTripJSObject(v: JSObject) async -> JSObject { return v } @JS class Greeter { - var name: String + @JS var name: String + @JS let prefix: String = "Hello" nonisolated(unsafe) static var onDeinit: () -> Void = {} @@ -69,7 +70,7 @@ struct TestError: Error { } @JS func greet() -> String { - return "Hello, \(name)!" + return "\(prefix), \(name)!" } @JS func changeName(name: String) { self.name = name @@ -270,6 +271,145 @@ enum Internal { } } +// MARK: - Property Tests + +// Simple class for SwiftHeapObject property testing +@JS class SimplePropertyHolder { + @JS var value: Int + + @JS init(value: Int) { + self.value = value + } +} + +// Test class for various property types +@JS class PropertyHolder { + // Primitive properties + @JS var intValue: Int + @JS var floatValue: Float + @JS var doubleValue: Double + @JS var boolValue: Bool + @JS var stringValue: String + + // Readonly primitive properties + @JS let readonlyInt: Int = 42 + @JS let readonlyFloat: Float = 3.14 + @JS let readonlyDouble: Double = 2.718281828 + @JS let readonlyBool: Bool = true + @JS let readonlyString: String = "constant" + + // JSObject property + @JS var jsObject: JSObject + + // SwiftHeapObject property + @JS var sibling: SimplePropertyHolder + + // Lazy stored property + @JS lazy var lazyValue: String = "computed lazily" + + // Computed property with getter only (readonly) + @JS var computedReadonly: Int { + return intValue * 2 + } + + // Computed property with getter and setter + @JS var computedReadWrite: String { + get { + return "Value: \(intValue)" + } + set { + // Parse the number from "Value: X" format + if let range = newValue.range(of: "Value: "), + let number = Int(String(newValue[range.upperBound...])) + { + intValue = number + } + } + } + + // Property with property observers + @JS var observedProperty: Int { + willSet { + Self.willSetCallCount += 1 + Self.lastWillSetOldValue = self.observedProperty + Self.lastWillSetNewValue = newValue + } + didSet { + Self.didSetCallCount += 1 + Self.lastDidSetOldValue = oldValue + Self.lastDidSetNewValue = self.observedProperty + } + } + + // Static properties to track observer calls + nonisolated(unsafe) static var willSetCallCount: Int = 0 + nonisolated(unsafe) static var didSetCallCount: Int = 0 + nonisolated(unsafe) static var lastWillSetOldValue: Int = 0 + nonisolated(unsafe) static var lastWillSetNewValue: Int = 0 + nonisolated(unsafe) static var lastDidSetOldValue: Int = 0 + nonisolated(unsafe) static var lastDidSetNewValue: Int = 0 + + @JS init( + intValue: Int, + floatValue: Float, + doubleValue: Double, + boolValue: Bool, + stringValue: String, + jsObject: JSObject, + sibling: SimplePropertyHolder + ) { + self.intValue = intValue + self.floatValue = floatValue + self.doubleValue = doubleValue + self.boolValue = boolValue + self.stringValue = stringValue + self.jsObject = jsObject + self.sibling = sibling + self.observedProperty = intValue // Initialize observed property + } + + @JS func getAllValues() -> String { + return "int:\(intValue),float:\(floatValue),double:\(doubleValue),bool:\(boolValue),string:\(stringValue)" + } +} + +@JS func createPropertyHolder( + intValue: Int, + floatValue: Float, + doubleValue: Double, + boolValue: Bool, + stringValue: String, + jsObject: JSObject +) -> PropertyHolder { + let sibling = SimplePropertyHolder(value: 999) + return PropertyHolder( + intValue: intValue, + floatValue: floatValue, + doubleValue: doubleValue, + boolValue: boolValue, + stringValue: stringValue, + jsObject: jsObject, + sibling: sibling + ) +} + +@JS func testPropertyHolder(holder: PropertyHolder) -> String { + return holder.getAllValues() +} + +@JS func resetObserverCounts() { + PropertyHolder.willSetCallCount = 0 + PropertyHolder.didSetCallCount = 0 + PropertyHolder.lastWillSetOldValue = 0 + PropertyHolder.lastWillSetNewValue = 0 + PropertyHolder.lastDidSetOldValue = 0 + PropertyHolder.lastDidSetNewValue = 0 +} + +@JS func getObserverStats() -> String { + return + "willSet:\(PropertyHolder.willSetCallCount),didSet:\(PropertyHolder.didSetCallCount),willSetOld:\(PropertyHolder.lastWillSetOldValue),willSetNew:\(PropertyHolder.lastWillSetNewValue),didSetOld:\(PropertyHolder.lastDidSetOldValue),didSetNew:\(PropertyHolder.lastDidSetNewValue)" +} class ExportAPITests: XCTestCase { func testAll() { var hasDeinitGreeter = false diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 15e1cfc5..d51f954e 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -771,6 +771,57 @@ public func _bjs_getTSTheme() -> Void { #endif } +@_expose(wasm, "bjs_createPropertyHolder") +@_cdecl("bjs_createPropertyHolder") +public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let stringValue = String(unsafeUninitializedCapacity: Int(stringValueLen)) { b in + _swift_js_init_memory(stringValueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(stringValueLen) + } + let ret = createPropertyHolder(intValue: Int(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: boolValue == 1, stringValue: stringValue, jsObject: JSObject(id: UInt32(bitPattern: jsObject))) + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_testPropertyHolder") +@_cdecl("bjs_testPropertyHolder") +public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = testPropertyHolder(holder: Unmanaged.fromOpaque(holder).takeUnretainedValue()) + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_resetObserverCounts") +@_cdecl("bjs_resetObserverCounts") +public func _bjs_resetObserverCounts() -> Void { + #if arch(wasm32) + resetObserverCounts() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getObserverStats") +@_cdecl("bjs_getObserverStats") +public func _bjs_getObserverStats() -> Void { + #if arch(wasm32) + var ret = getObserverStats() + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { @@ -813,6 +864,46 @@ public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: I #endif } +@_expose(wasm, "bjs_Greeter_name_get") +@_cdecl("bjs_Greeter_name_get") +public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().name + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_name_set") +@_cdecl("bjs_Greeter_name_set") +public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { + #if arch(wasm32) + let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in + _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(valueLen) + } + Unmanaged.fromOpaque(_self).takeUnretainedValue().name = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_Greeter_prefix_get") +@_cdecl("bjs_Greeter_prefix_get") +public func _bjs_Greeter_prefix_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().prefix + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_Greeter_deinit") @_cdecl("bjs_Greeter_deinit") public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { @@ -969,4 +1060,388 @@ extension Internal.TestServer: ConvertibleToJSValue { func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 return .object(JSObject(id: UInt32(bitPattern: _bjs_TestServer_wrap(Unmanaged.passRetained(self).toOpaque())))) } +} + +@_expose(wasm, "bjs_SimplePropertyHolder_init") +@_cdecl("bjs_SimplePropertyHolder_init") +public func _bjs_SimplePropertyHolder_init(value: Int32) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = SimplePropertyHolder(value: Int(value)) + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimplePropertyHolder_value_get") +@_cdecl("bjs_SimplePropertyHolder_value_get") +public func _bjs_SimplePropertyHolder_value_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().value + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimplePropertyHolder_value_set") +@_cdecl("bjs_SimplePropertyHolder_value_set") +public func _bjs_SimplePropertyHolder_value_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().value = Int(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_SimplePropertyHolder_deinit") +@_cdecl("bjs_SimplePropertyHolder_deinit") +public func _bjs_SimplePropertyHolder_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension SimplePropertyHolder: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_SimplePropertyHolder_wrap") + func _bjs_SimplePropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_SimplePropertyHolder_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_PropertyHolder_init") +@_cdecl("bjs_PropertyHolder_init") +public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32, sibling: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let stringValue = String(unsafeUninitializedCapacity: Int(stringValueLen)) { b in + _swift_js_init_memory(stringValueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(stringValueLen) + } + let ret = PropertyHolder(intValue: Int(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: boolValue == 1, stringValue: stringValue, jsObject: JSObject(id: UInt32(bitPattern: jsObject)), sibling: Unmanaged.fromOpaque(sibling).takeUnretainedValue()) + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_getAllValues") +@_cdecl("bjs_PropertyHolder_getAllValues") +public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().getAllValues() + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_intValue_get") +@_cdecl("bjs_PropertyHolder_intValue_get") +public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_intValue_set") +@_cdecl("bjs_PropertyHolder_intValue_set") +public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue = Int(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_floatValue_get") +@_cdecl("bjs_PropertyHolder_floatValue_get") +public func _bjs_PropertyHolder_floatValue_get(_self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().floatValue + return Float32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_floatValue_set") +@_cdecl("bjs_PropertyHolder_floatValue_set") +public func _bjs_PropertyHolder_floatValue_set(_self: UnsafeMutableRawPointer, value: Float32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().floatValue = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_doubleValue_get") +@_cdecl("bjs_PropertyHolder_doubleValue_get") +public func _bjs_PropertyHolder_doubleValue_get(_self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().doubleValue + return Float64(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_doubleValue_set") +@_cdecl("bjs_PropertyHolder_doubleValue_set") +public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, value: Float64) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().doubleValue = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_boolValue_get") +@_cdecl("bjs_PropertyHolder_boolValue_get") +public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue + return Int32(ret ? 1 : 0) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_boolValue_set") +@_cdecl("bjs_PropertyHolder_boolValue_set") +public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue = value == 1 + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_stringValue_get") +@_cdecl("bjs_PropertyHolder_stringValue_get") +public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_stringValue_set") +@_cdecl("bjs_PropertyHolder_stringValue_set") +public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { + #if arch(wasm32) + let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in + _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(valueLen) + } + Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyInt_get") +@_cdecl("bjs_PropertyHolder_readonlyInt_get") +public func _bjs_PropertyHolder_readonlyInt_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyInt + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyFloat_get") +@_cdecl("bjs_PropertyHolder_readonlyFloat_get") +public func _bjs_PropertyHolder_readonlyFloat_get(_self: UnsafeMutableRawPointer) -> Float32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyFloat + return Float32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyDouble_get") +@_cdecl("bjs_PropertyHolder_readonlyDouble_get") +public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointer) -> Float64 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyDouble + return Float64(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyBool_get") +@_cdecl("bjs_PropertyHolder_readonlyBool_get") +public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyBool + return Int32(ret ? 1 : 0) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_readonlyString_get") +@_cdecl("bjs_PropertyHolder_readonlyString_get") +public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyString + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_jsObject_get") +@_cdecl("bjs_PropertyHolder_jsObject_get") +public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject + return _swift_js_retain(Int32(bitPattern: ret.id)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_jsObject_set") +@_cdecl("bjs_PropertyHolder_jsObject_set") +public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject = JSObject(id: UInt32(bitPattern: value)) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_sibling_get") +@_cdecl("bjs_PropertyHolder_sibling_get") +public func _bjs_PropertyHolder_sibling_get(_self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().sibling + return Unmanaged.passRetained(ret).toOpaque() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_sibling_set") +@_cdecl("bjs_PropertyHolder_sibling_set") +public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().sibling = Unmanaged.fromOpaque(value).takeUnretainedValue() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_lazyValue_get") +@_cdecl("bjs_PropertyHolder_lazyValue_get") +public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_lazyValue_set") +@_cdecl("bjs_PropertyHolder_lazyValue_set") +public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { + #if arch(wasm32) + let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in + _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(valueLen) + } + Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadonly_get") +@_cdecl("bjs_PropertyHolder_computedReadonly_get") +public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadonly + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadWrite_get") +@_cdecl("bjs_PropertyHolder_computedReadWrite_get") +public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPointer) -> Void { + #if arch(wasm32) + var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite + return ret.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_computedReadWrite_set") +@_cdecl("bjs_PropertyHolder_computedReadWrite_set") +public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { + #if arch(wasm32) + let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in + _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) + return Int(valueLen) + } + Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite = value + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_observedProperty_get") +@_cdecl("bjs_PropertyHolder_observedProperty_get") +public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPointer) -> Int32 { + #if arch(wasm32) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty + return Int32(ret) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_observedProperty_set") +@_cdecl("bjs_PropertyHolder_observedProperty_set") +public func _bjs_PropertyHolder_observedProperty_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { + #if arch(wasm32) + Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty = Int(value) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_PropertyHolder_deinit") +@_cdecl("bjs_PropertyHolder_deinit") +public func _bjs_PropertyHolder_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PropertyHolder: ConvertibleToJSValue { + var jsValue: JSValue { + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_PropertyHolder_wrap") + func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 + return .object(JSObject(id: UInt32(bitPattern: _bjs_PropertyHolder_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index b4642d8a..30b0685f 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -62,6 +62,26 @@ } ], "name" : "Greeter", + "properties" : [ + { + "isReadonly" : false, + "name" : "name", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "prefix", + "type" : { + "string" : { + + } + } + } + ], "swiftCallName" : "Greeter" }, { @@ -125,6 +145,9 @@ } ], "name" : "Calculator", + "properties" : [ + + ], "swiftCallName" : "Calculator" }, { @@ -167,6 +190,9 @@ "name" : "Converter", "namespace" : [ "Utils" + ], + "properties" : [ + ], "swiftCallName" : "Utils.Converter" }, @@ -211,6 +237,9 @@ "namespace" : [ "Networking", "API" + ], + "properties" : [ + ], "swiftCallName" : "Networking.API.HTTPServer" }, @@ -256,8 +285,287 @@ "Networking", "APIV2", "Internal" + ], + "properties" : [ + ], "swiftCallName" : "Internal.TestServer" + }, + { + "constructor" : { + "abiName" : "bjs_SimplePropertyHolder_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "value", + "name" : "value", + "type" : { + "int" : { + + } + } + } + ] + }, + "methods" : [ + + ], + "name" : "SimplePropertyHolder", + "properties" : [ + { + "isReadonly" : false, + "name" : "value", + "type" : { + "int" : { + + } + } + } + ], + "swiftCallName" : "SimplePropertyHolder" + }, + { + "constructor" : { + "abiName" : "bjs_PropertyHolder_init", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "parameters" : [ + { + "label" : "intValue", + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "label" : "floatValue", + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "label" : "doubleValue", + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "label" : "boolValue", + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "stringValue", + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "label" : "jsObject", + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + }, + { + "label" : "sibling", + "name" : "sibling", + "type" : { + "swiftHeapObject" : { + "_0" : "SimplePropertyHolder" + } + } + } + ] + }, + "methods" : [ + { + "abiName" : "bjs_PropertyHolder_getAllValues", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getAllValues", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } + } + ], + "name" : "PropertyHolder", + "properties" : [ + { + "isReadonly" : false, + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyInt", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyFloat", + "type" : { + "float" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyDouble", + "type" : { + "double" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyBool", + "type" : { + "bool" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "readonlyString", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "sibling", + "type" : { + "swiftHeapObject" : { + "_0" : "SimplePropertyHolder" + } + } + }, + { + "isReadonly" : false, + "name" : "lazyValue", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : true, + "name" : "computedReadonly", + "type" : { + "int" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "computedReadWrite", + "type" : { + "string" : { + + } + } + }, + { + "isReadonly" : false, + "name" : "observedProperty", + "type" : { + "int" : { + + } + } + } + ], + "swiftCallName" : "PropertyHolder" } ], "enums" : [ @@ -1484,6 +1792,131 @@ "_1" : "String" } } + }, + { + "abiName" : "bjs_createPropertyHolder", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "createPropertyHolder", + "parameters" : [ + { + "label" : "intValue", + "name" : "intValue", + "type" : { + "int" : { + + } + } + }, + { + "label" : "floatValue", + "name" : "floatValue", + "type" : { + "float" : { + + } + } + }, + { + "label" : "doubleValue", + "name" : "doubleValue", + "type" : { + "double" : { + + } + } + }, + { + "label" : "boolValue", + "name" : "boolValue", + "type" : { + "bool" : { + + } + } + }, + { + "label" : "stringValue", + "name" : "stringValue", + "type" : { + "string" : { + + } + } + }, + { + "label" : "jsObject", + "name" : "jsObject", + "type" : { + "jsObject" : { + + } + } + } + ], + "returnType" : { + "swiftHeapObject" : { + "_0" : "PropertyHolder" + } + } + }, + { + "abiName" : "bjs_testPropertyHolder", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "testPropertyHolder", + "parameters" : [ + { + "label" : "holder", + "name" : "holder", + "type" : { + "swiftHeapObject" : { + "_0" : "PropertyHolder" + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "abiName" : "bjs_resetObserverCounts", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "resetObserverCounts", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + }, + { + "abiName" : "bjs_getObserverStats", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "getObserverStats", + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + } } ], "moduleName" : "BridgeJSRuntimeTests" diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 6954d87c..1459f21a 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -135,17 +135,145 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { const g = new exports.Greeter("John"); assert.equal(g.greet(), "Hello, John!"); - g.changeName("Jane"); + + // Test property getters + assert.equal(g.name, "John"); + assert.equal(g.prefix, "Hello"); + + // Test property setters + g.name = "Jane"; + assert.equal(g.name, "Jane"); assert.equal(g.greet(), "Hello, Jane!"); - exports.takeGreeter(g, "Jay"); + + // Test readonly property (should still be readable) + assert.equal(g.prefix, "Hello"); + + // Test method-based name change still works + g.changeName("Jay"); + assert.equal(g.name, "Jay"); assert.equal(g.greet(), "Hello, Jay!"); + // Test that property changes via JS are reflected in Swift methods + g.name = "Alice"; + assert.equal(g.greet(), "Hello, Alice!"); + exports.takeGreeter(g, "Bob"); + assert.equal(g.name, "Bob"); + assert.equal(g.greet(), "Hello, Bob!"); + const g2 = exports.roundTripSwiftHeapObject(g) - assert.equal(g2.greet(), "Hello, Jay!"); + assert.equal(g2.greet(), "Hello, Bob!"); + assert.equal(g2.name, "Bob"); + assert.equal(g2.prefix, "Hello"); g2.release(); g.release(); + // Test PropertyHolder with various types + const testObj = { testProp: "test" }; + const sibling = new exports.SimplePropertyHolder(999); + const ph = new exports.PropertyHolder( + 123, // intValue + 3.14, // floatValue + 2.718, // doubleValue + true, // boolValue + "test", // stringValue + testObj, // jsObject + sibling // sibling + ); + + // Test primitive property getters + assert.equal(ph.intValue, 123); + assert.equal(ph.floatValue, 3.140000104904175); // Float32 precision + assert.equal(ph.doubleValue, 2.718); + assert.equal(ph.boolValue, true); + assert.equal(ph.stringValue, "test"); + + // Test readonly property getters + assert.equal(ph.readonlyInt, 42); + assert.equal(ph.readonlyFloat, 3.140000104904175); // Float32 precision + assert.equal(ph.readonlyDouble, 2.718281828); + assert.equal(ph.readonlyBool, true); + assert.equal(ph.readonlyString, "constant"); + + // Test JSObject property + assert.equal(ph.jsObject, testObj); + + // Test SwiftHeapObject property (sibling should be a SimplePropertyHolder with value 999) + assert.equal(ph.sibling.value, 999); + + // Test primitive property setters + ph.intValue = 456; + ph.floatValue = 6.28; + ph.doubleValue = 1.414; + ph.boolValue = false; + ph.stringValue = "updated"; + + assert.equal(ph.intValue, 456); + assert.equal(ph.floatValue, 6.280000209808350); // Float32 precision + assert.equal(ph.doubleValue, 1.414); + assert.equal(ph.boolValue, false); + assert.equal(ph.stringValue, "updated"); + + // Test JSObject property setter + const newObj = { newProp: "new" }; + ph.jsObject = newObj; + assert.equal(ph.jsObject, newObj); + + // Test SwiftHeapObject property with different object + const ph2 = exports.createPropertyHolder(999, 1.1, 2.2, false, "other", testObj); + const newSibling = new exports.SimplePropertyHolder(123); + ph.sibling = newSibling; + assert.equal(ph.sibling.value, 123); + + // Test lazy property + assert.equal(ph.lazyValue, "computed lazily"); + ph.lazyValue = "modified lazy"; + assert.equal(ph.lazyValue, "modified lazy"); + + // Test computed read-write property + assert.equal(ph.computedReadWrite, "Value: 456"); + ph.computedReadWrite = "Value: 777"; + assert.equal(ph.intValue, 777); // Should have parsed and set intValue + assert.equal(ph.computedReadWrite, "Value: 777"); + + // Test computed readonly property + assert.equal(ph.computedReadonly, 1554); // intValue * 2 = 777 * 2 + + // Test property with observers + // Sync observedProperty to match current intValue, then reset counters for clean test + ph.observedProperty = 777; // Sync with current intValue after computed property changed it + exports.resetObserverCounts(); // Reset counters to start fresh test + + // Set property from JavaScript and verify observers are called + ph.observedProperty = 100; + assert.equal(ph.observedProperty, 100); + let afterSetStats = exports.getObserverStats(); + + // Verify willSet and didSet were called + // The stats should show: willSet:1,didSet:1,willSetOld:777,willSetNew:100,didSetOld:777,didSetNew:100 + assert(afterSetStats.includes("willSet:1"), `willSet should be called once, got: ${afterSetStats}`); + assert(afterSetStats.includes("didSet:1"), `didSet should be called once, got: ${afterSetStats}`); + assert(afterSetStats.includes("willSetOld:777"), `willSet should see old value 777, got: ${afterSetStats}`); + assert(afterSetStats.includes("willSetNew:100"), `willSet should see new value 100, got: ${afterSetStats}`); + assert(afterSetStats.includes("didSetOld:777"), `didSet should see old value 777, got: ${afterSetStats}`); + assert(afterSetStats.includes("didSetNew:100"), `didSet should see new value 100, got: ${afterSetStats}`); + + // Set property to a different value and verify observers are called again + ph.observedProperty = 200; + assert.equal(ph.observedProperty, 200); + let afterSecondSetStats = exports.getObserverStats(); + + // Now should be: willSet:2,didSet:2,willSetOld:100,willSetNew:200,didSetOld:100,didSetNew:200 + assert(afterSecondSetStats.includes("willSet:2"), `willSet should be called twice, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("didSet:2"), `didSet should be called twice, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("willSetOld:100"), `willSet should see old value 100 on second call, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("willSetNew:200"), `willSet should see new value 200 on second call, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("didSetOld:100"), `didSet should see old value 100 on second call, got: ${afterSecondSetStats}`); + assert(afterSecondSetStats.includes("didSetNew:200"), `didSet should see new value 200 on second call, got: ${afterSecondSetStats}`); + + ph.release(); + ph2.release(); + // Test class without @JS init constructor const calc = exports.createCalculator(); assert.equal(calc.square(5), 25); From 2699257a4652d1d7e144835ca9dbcf2d93e00eee Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 23 Aug 2025 16:10:02 +0900 Subject: [PATCH 45/61] BridgeJS: Standardize lift/lower pattern across Swift and JavaScript Replace global intrinsic functions with consistent extension-based system. Each bridged type now defines standardized lift/lower operations: Swift side (type extensions): - bridgeJSLowerParameter/Return: Swift -> Wasm core types - bridgeJSLiftParameter/Return: Wasm core types -> Swift JavaScript side (JSGlueGen): - Corresponding lift/lower functions for JS <-> Wasm interop Adds CodeFragmentPrinter for improved code organization. Reduces generated code complexity across both Swift and JS. --- .../Sources/Generated/BridgeJS.ImportTS.swift | 6 +- .../Generated/BridgeJS.ExportSwift.swift | 34 +- .../Generated/BridgeJS.ImportTS.swift | 11 +- .../Sources/BridgeJSCore/ExportSwift.swift | 84 ++-- .../Sources/BridgeJSCore/ImportTS.swift | 35 +- .../Sources/BridgeJSLink/BridgeJSLink.swift | 410 ++++++++---------- .../BridgeJSLink/CodeFragmentPrinter.swift | 41 ++ .../Sources/BridgeJSLink/JSGlueGen.swift | 317 ++++++++++++++ .../BridgeJSLinkTests/Async.Export.js | 56 +-- .../BridgeJSLinkTests/Async.Import.js | 2 +- .../BridgeJSLinkTests/EnumCase.Export.js | 6 +- .../BridgeJSLinkTests/EnumNamespace.Export.js | 4 +- .../BridgeJSLinkTests/EnumRawType.Export.js | 4 +- .../MultipleImportedTypes.Import.js | 2 +- .../PrimitiveParameters.Import.js | 2 +- .../PrimitiveReturn.Export.js | 4 +- .../PrimitiveReturn.Import.js | 2 +- .../BridgeJSLinkTests/PropertyTypes.Export.js | 24 +- .../TS2SkeletonLike.Import.js | 2 +- .../TypeScriptClass.Import.js | 3 +- .../ExportSwiftTests/Async.swift | 26 +- .../ExportSwiftTests/EnumNamespace.swift | 6 +- .../ExportSwiftTests/EnumRawType.swift | 37 +- .../ExportSwiftTests/Namespaces.swift | 36 +- .../PrimitiveParameters.swift | 2 +- .../ExportSwiftTests/PrimitiveReturn.swift | 2 +- .../ExportSwiftTests/PropertyTypes.swift | 80 +--- .../ExportSwiftTests/StringParameter.swift | 6 +- .../ExportSwiftTests/StringReturn.swift | 6 +- .../ExportSwiftTests/SwiftClass.swift | 30 +- .../ImportTSTests/ArrayParameter.swift | 6 +- .../__Snapshots__/ImportTSTests/Async.swift | 10 +- .../ImportTSTests/Interface.swift | 8 +- .../ImportTSTests/MultipleImportedTypes.swift | 70 +-- .../ImportTSTests/PrimitiveParameters.swift | 2 +- .../ImportTSTests/PrimitiveReturn.swift | 2 +- .../ImportTSTests/StringParameter.swift | 12 +- .../ImportTSTests/StringReturn.swift | 5 +- .../ImportTSTests/TS2SkeletonLike.swift | 46 +- .../ImportTSTests/TypeScriptClass.swift | 34 +- .../JavaScriptKit/BridgeJSInstrincics.swift | 229 ++++++++-- .../include/BridgeJSInstrincics.h | 2 + .../Generated/BridgeJS.ExportSwift.swift | 248 ++++------- .../Generated/BridgeJS.ImportTS.swift | 73 +--- 44 files changed, 1033 insertions(+), 994 deletions(-) create mode 100644 Plugins/BridgeJS/Sources/BridgeJSLink/CodeFragmentPrinter.swift create mode 100644 Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift diff --git a/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift b/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift index 871938cf..afce5aca 100644 --- a/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift +++ b/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift @@ -45,11 +45,7 @@ func benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> Vo fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_benchmarkRunner(nameId, Int32(bitPattern: body.id)) + bjs_benchmarkRunner(name.bridgeJSLowerParameter(), body.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift index 3936b254..5deb14b9 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift @@ -22,15 +22,7 @@ public func _bjs_PlayBridgeJS_init() -> UnsafeMutableRawPointer { public func _bjs_PlayBridgeJS_update(_self: UnsafeMutableRawPointer, swiftSourceBytes: Int32, swiftSourceLen: Int32, dtsSourceBytes: Int32, dtsSourceLen: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) do { - let swiftSource = String(unsafeUninitializedCapacity: Int(swiftSourceLen)) { b in - _swift_js_init_memory(swiftSourceBytes, b.baseAddress.unsafelyUnwrapped) - return Int(swiftSourceLen) - } - let dtsSource = String(unsafeUninitializedCapacity: Int(dtsSourceLen)) { b in - _swift_js_init_memory(dtsSourceBytes, b.baseAddress.unsafelyUnwrapped) - return Int(dtsSourceLen) - } - let ret = try Unmanaged.fromOpaque(_self).takeUnretainedValue().update(swiftSource: swiftSource, dtsSource: dtsSource) + let ret = try Unmanaged.fromOpaque(_self).takeUnretainedValue().update(swiftSource: String.bridgeJSLiftParameter(swiftSourceBytes, swiftSourceLen), dtsSource: String.bridgeJSLiftParameter(dtsSourceBytes, dtsSourceLen)) return Unmanaged.passRetained(ret).toOpaque() } catch let error { if let error = error.thrownValue.object { @@ -68,10 +60,8 @@ extension PlayBridgeJS: ConvertibleToJSValue { @_cdecl("bjs_PlayBridgeJSOutput_outputJs") public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().outputJs() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().outputJs() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -81,10 +71,8 @@ public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PlayBridgeJSOutput_outputDts") public func _bjs_PlayBridgeJSOutput_outputDts(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().outputDts() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().outputDts() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -94,10 +82,8 @@ public func _bjs_PlayBridgeJSOutput_outputDts(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PlayBridgeJSOutput_importSwiftGlue") public func _bjs_PlayBridgeJSOutput_importSwiftGlue(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().importSwiftGlue() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().importSwiftGlue() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -107,10 +93,8 @@ public func _bjs_PlayBridgeJSOutput_importSwiftGlue(_self: UnsafeMutableRawPoint @_cdecl("bjs_PlayBridgeJSOutput_exportSwiftGlue") public func _bjs_PlayBridgeJSOutput_exportSwiftGlue(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().exportSwiftGlue() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().exportSwiftGlue() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift index 5d82db08..3cee3b6e 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift @@ -42,18 +42,11 @@ struct TS2Skeleton { fatalError("Only available on WebAssembly") } #endif - var ts = ts - let tsId = ts.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_TS2Skeleton_convert(Int32(bitPattern: self.this.id), tsId) + let ret = bjs_TS2Skeleton_convert(self.this.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 56e01290..f170c03d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -782,10 +782,14 @@ public class ExportSwift { parameters.append(param) switch param.type { case .bool: - liftedParameterExprs.append(ExprSyntax("\(raw: param.name) == 1")) + liftedParameterExprs.append( + ExprSyntax("\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: param.name))") + ) abiParameterSignatures.append((param.name, .i32)) case .int: - liftedParameterExprs.append(ExprSyntax("\(raw: param.type.swiftType)(\(raw: param.name))")) + liftedParameterExprs.append( + ExprSyntax("\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: param.name))") + ) abiParameterSignatures.append((param.name, .i32)) case .float: liftedParameterExprs.append(ExprSyntax("\(raw: param.name)")) @@ -796,14 +800,11 @@ public class ExportSwift { case .string: let bytesLabel = "\(param.name)Bytes" let lengthLabel = "\(param.name)Len" - let prepare: CodeBlockItemSyntax = """ - let \(raw: param.name) = String(unsafeUninitializedCapacity: Int(\(raw: lengthLabel))) { b in - _swift_js_init_memory(\(raw: bytesLabel), b.baseAddress.unsafelyUnwrapped) - return Int(\(raw: lengthLabel)) - } - """ - append(prepare) - liftedParameterExprs.append(ExprSyntax("\(raw: param.name)")) + liftedParameterExprs.append( + ExprSyntax( + "\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: bytesLabel), \(raw: lengthLabel))" + ) + ) abiParameterSignatures.append((bytesLabel, .i32)) abiParameterSignatures.append((lengthLabel, .i32)) case .caseEnum(let enumName): @@ -813,21 +814,19 @@ public class ExportSwift { if rawType == .string { let bytesLabel = "\(param.name)Bytes" let lengthLabel = "\(param.name)Len" - let prepare: CodeBlockItemSyntax = """ - let \(raw: param.name) = String(unsafeUninitializedCapacity: Int(\(raw: lengthLabel))) { b in - _swift_js_init_memory(\(raw: bytesLabel), b.baseAddress.unsafelyUnwrapped) - return Int(\(raw: lengthLabel)) - } - """ - append(prepare) - liftedParameterExprs.append(ExprSyntax("\(raw: enumName)(rawValue: \(raw: param.name))!")) + liftedParameterExprs.append( + ExprSyntax( + "\(raw: enumName)(rawValue: String.bridgeJSLiftParameter(\(raw: bytesLabel), \(raw: lengthLabel)))!" + ) + ) abiParameterSignatures.append((bytesLabel, .i32)) abiParameterSignatures.append((lengthLabel, .i32)) } else { let conversionExpr: String switch rawType { case .bool: - conversionExpr = "\(enumName)(rawValue: \(param.name) != 0)!" + conversionExpr = + "\(enumName)(rawValue: \(param.type.swiftType).bridgeJSLiftParameter(\(param.name)))" case .uint, .uint32, .uint64: if rawType == .uint64 { conversionExpr = @@ -848,12 +847,9 @@ public class ExportSwift { break case .namespaceEnum: break - case .jsObject(nil): - liftedParameterExprs.append(ExprSyntax("JSObject(id: UInt32(bitPattern: \(raw: param.name)))")) - abiParameterSignatures.append((param.name, .i32)) case .jsObject(let name): liftedParameterExprs.append( - ExprSyntax("\(raw: name)(takingThis: UInt32(bitPattern: \(raw: param.name)))") + ExprSyntax("\(raw: name ?? "JSObject").bridgeJSLiftParameter(\(raw: param.name))") ) abiParameterSignatures.append((param.name, .i32)) case .swiftHeapObject: @@ -896,16 +892,10 @@ public class ExportSwift { return CodeBlockItemSyntax(item: .init(StmtSyntax("return \(raw: callExpr).jsValue"))) } - let retMutability: String - if returnType == .string { - retMutability = "var" - } else { - retMutability = "let" - } if returnType == .void { return CodeBlockItemSyntax(item: .init(ExpressionStmtSyntax(expression: callExpr))) } else { - return CodeBlockItemSyntax(item: .init(DeclSyntax("\(raw: retMutability) ret = \(raw: callExpr)"))) + return CodeBlockItemSyntax(item: .init(DeclSyntax("let ret = \(raw: callExpr)"))) } } @@ -925,11 +915,10 @@ public class ExportSwift { func callPropertyGetter(klassName: String, propertyName: String, returnType: BridgeType) { let (_, selfExpr) = removeFirstLiftedParameter() - let retMutability = returnType == .string ? "var" : "let" if returnType == .void { append("\(raw: selfExpr).\(raw: propertyName)") } else { - append("\(raw: retMutability) ret = \(raw: selfExpr).\(raw: propertyName)") + append("let ret = \(raw: selfExpr).\(raw: propertyName)") } } @@ -988,15 +977,9 @@ public class ExportSwift { case .int, .float, .double: append("return \(raw: abiReturnType!.swiftType)(ret)") case .bool: - append("return Int32(ret ? 1 : 0)") + append("return ret.bridgeJSLowerReturn()") case .string: - append( - """ - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } - """ - ) + append("return ret.bridgeJSLowerReturn()") case .caseEnum: abiReturnType = .i32 append("return ret.bridgeJSRawValue") @@ -1004,10 +987,7 @@ public class ExportSwift { if rawType == .string { append( """ - var rawValue = ret.rawValue - return rawValue.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + return ret.rawValue.bridgeJSLowerReturn() """ ) } else { @@ -1028,18 +1008,8 @@ public class ExportSwift { } case .associatedValueEnum: break; case .namespaceEnum: break; - case .jsObject(nil): - append( - """ - return _swift_js_retain(Int32(bitPattern: ret.id)) - """ - ) - case .jsObject(_?): - append( - """ - return _swift_js_retain(Int32(bitPattern: ret.this.id)) - """ - ) + case .jsObject: + append("return ret.bridgeJSLowerReturn()") case .swiftHeapObject: // Perform a manual retain on the object, which will be balanced by a // release called via FinalizationRegistry @@ -1058,7 +1028,7 @@ public class ExportSwift { let ret = JSPromise.async { \(CodeBlockItemListSyntax(self.body)) }.jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() """ } else if effects.isThrows { body = """ diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index bcbae469..9475c784 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -73,7 +73,7 @@ public struct ImportTS { abiParameterForwardings.append( LabeledExprSyntax( label: param.label, - expression: ExprSyntax("Int32(\(raw: param.name) ? 1 : 0)") + expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()") ) ) abiParameterSignatures.append((param.name, .i32)) @@ -102,24 +102,10 @@ public struct ImportTS { ) abiParameterSignatures.append((param.name, .f64)) case .string: - let stringIdName = "\(param.name)Id" - body.append( - """ - var \(raw: param.name) = \(raw: param.name) - - """ - ) - body.append( - """ - let \(raw: stringIdName) = \(raw: param.name).withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - """ - ) abiParameterForwardings.append( LabeledExprSyntax( label: param.label, - expression: ExprSyntax("\(raw: stringIdName)") + expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()") ) ) abiParameterSignatures.append((param.name, .i32)) @@ -130,14 +116,14 @@ public struct ImportTS { abiParameterForwardings.append( LabeledExprSyntax( label: param.label, - expression: ExprSyntax("Int32(bitPattern: \(raw: param.name).this.id)") + expression: ExprSyntax("\(raw: param.name).this.bridgeJSLowerParameter()") ) ) case .jsObject(nil): abiParameterForwardings.append( LabeledExprSyntax( label: param.label, - expression: ExprSyntax("Int32(bitPattern: \(raw: param.name).id)") + expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()") ) ) abiParameterSignatures.append((param.name, .i32)) @@ -163,7 +149,7 @@ public struct ImportTS { switch returnType { case .bool: abiReturnType = .i32 - body.append("return ret == 1") + body.append("return \(raw: returnType.swiftType).bridgeJSLiftReturn(ret)") case .int: abiReturnType = .i32 body.append("return \(raw: returnType.swiftType)(ret)") @@ -175,14 +161,7 @@ public struct ImportTS { body.append("return \(raw: returnType.swiftType)(ret)") case .string: abiReturnType = .i32 - body.append( - """ - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } - """ - ) + body.append("return \(raw: returnType.swiftType).bridgeJSLiftReturn(ret)") case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum: throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") case .jsObject(let name): @@ -190,7 +169,7 @@ public struct ImportTS { if let name = name { body.append("return \(raw: name)(takingThis: ret)") } else { - body.append("return JSObject(id: UInt32(bitPattern: ret))") + body.append("return JSObject.bridgeJSLiftReturn(ret)") } case .swiftHeapObject(_): throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures") diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 2b220e43..f704b828 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -90,7 +90,7 @@ struct BridgeJSLink { for skeleton in exportedSkeletons { for klass in skeleton.classes { - let (jsType, dtsType, dtsExportEntry) = renderExportedClass(klass) + let (jsType, dtsType, dtsExportEntry) = try renderExportedClass(klass) classLines.append(contentsOf: jsType) exportsLines.append("\(klass.name),") dtsExportLines.append(contentsOf: dtsExportEntry) @@ -131,7 +131,7 @@ struct BridgeJSLink { } for function in skeleton.functions { - var (js, dts) = renderExportedFunction(function: function) + var (js, dts) = try renderExportedFunction(function: function) if function.namespace != nil { namespacedFunctions.append(function) @@ -222,16 +222,16 @@ struct BridgeJSLink { // To update this file, just rebuild your project or run // `swift package bridge-js`. - \(topLevelEnumsSection)\(namespaceAssignmentsSection)export async function createInstantiator(options, swift) { - let instance; - let memory; - let setException; - const textDecoder = new TextDecoder("utf-8"); - const textEncoder = new TextEncoder("utf-8"); + \(topLevelEnumsSection)\(namespaceAssignmentsSection)export async function createInstantiator(options, \(JSGlueVariableScope.reservedSwift)) { + let \(JSGlueVariableScope.reservedInstance); + let \(JSGlueVariableScope.reservedMemory); + let \(JSGlueVariableScope.reservedSetException); + const \(JSGlueVariableScope.reservedTextDecoder) = new TextDecoder("utf-8"); + const \(JSGlueVariableScope.reservedTextEncoder) = new TextEncoder("utf-8"); - let tmpRetString; - let tmpRetBytes; - let tmpRetException; + let \(JSGlueVariableScope.reservedStorageToReturnString); + let \(JSGlueVariableScope.reservedStorageToReturnBytes); + let \(JSGlueVariableScope.reservedStorageToReturnException); return { /** * @param {WebAssembly.Imports} importObject @@ -241,45 +241,45 @@ struct BridgeJSLink { importObject["bjs"] = bjs; const imports = options.getImports(importsContext); bjs["swift_js_return_string"] = function(ptr, len) { - const bytes = new Uint8Array(memory.buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); - tmpRetString = textDecoder.decode(bytes); + const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); + \(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedTextDecoder).decode(bytes); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { - const source = swift.memory.getObject(sourceId); - const bytes = new Uint8Array(memory.buffer, bytesPtr); + const source = \(JSGlueVariableScope.reservedSwift).memory.getObject(sourceId); + const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, bytesPtr); bytes.set(source); } bjs["swift_js_make_js_string"] = function(ptr, len) { - const bytes = new Uint8Array(memory.buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); - return swift.memory.retain(textDecoder.decode(bytes)); + const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr, len)\(sharedMemory ? ".slice()" : ""); + return \(JSGlueVariableScope.reservedSwift).memory.retain(\(JSGlueVariableScope.reservedTextDecoder).decode(bytes)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { - const target = new Uint8Array(memory.buffer, ptr, len); - target.set(tmpRetBytes); - tmpRetBytes = undefined; + const target = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr, len); + target.set(\(JSGlueVariableScope.reservedStorageToReturnBytes)); + \(JSGlueVariableScope.reservedStorageToReturnBytes) = undefined; } bjs["swift_js_throw"] = function(id) { - tmpRetException = swift.memory.retainByRef(id); + \(JSGlueVariableScope.reservedStorageToReturnException) = \(JSGlueVariableScope.reservedSwift).memory.retainByRef(id); } bjs["swift_js_retain"] = function(id) { - return swift.memory.retainByRef(id); + return \(JSGlueVariableScope.reservedSwift).memory.retainByRef(id); } bjs["swift_js_release"] = function(id) { - swift.memory.release(id); + \(JSGlueVariableScope.reservedSwift).memory.release(id); } \(renderSwiftClassWrappers().map { $0.indent(count: 12) }.joined(separator: "\n")) \(importObjectBuilders.flatMap { $0.importedLines }.map { $0.indent(count: 12) }.joined(separator: "\n")) }, setInstance: (i) => { - instance = i; - memory = instance.exports.memory; - setException = (error) => { - instance.exports._swift_js_exception.value = swift.memory.retain(error) + \(JSGlueVariableScope.reservedInstance) = i; + \(JSGlueVariableScope.reservedMemory) = \(JSGlueVariableScope.reservedInstance).exports.memory; + \(JSGlueVariableScope.reservedSetException) = (error) => { + \(JSGlueVariableScope.reservedInstance).exports._swift_js_exception.value = \(JSGlueVariableScope.reservedSwift).memory.retain(error) } }, /** @param {WebAssembly.Instance} instance */ createExports: (instance) => { - const js = swift.memory.heap; + const js = \(JSGlueVariableScope.reservedSwift).memory.heap; \(exportsSection) } } @@ -343,7 +343,7 @@ struct BridgeJSLink { let wrapperFunctionName = "bjs_\(klass.name)_wrap" wrapperLines.append("importObject[\"\(moduleName)\"][\"\(wrapperFunctionName)\"] = function(pointer) {") wrapperLines.append(" const obj = \(klass.name).__construct(pointer);") - wrapperLines.append(" return swift.memory.retain(obj);") + wrapperLines.append(" return \(JSGlueVariableScope.reservedSwift).memory.retain(obj);") wrapperLines.append("};") } } @@ -384,125 +384,66 @@ struct BridgeJSLink { } class ExportedThunkBuilder { - var bodyLines: [String] = [] - var cleanupLines: [String] = [] + var body: CodeFragmentPrinter + var cleanupCode: CodeFragmentPrinter var parameterForwardings: [String] = [] let effects: Effects + let scope: JSGlueVariableScope init(effects: Effects) { self.effects = effects + self.scope = JSGlueVariableScope() + self.body = CodeFragmentPrinter() + self.cleanupCode = CodeFragmentPrinter() } - func lowerParameter(param: Parameter) { - switch param.type { - case .void: return - case .int, .float, .double, .bool: - parameterForwardings.append(param.name) - case .string: - let bytesLabel = "\(param.name)Bytes" - let bytesIdLabel = "\(param.name)Id" - bodyLines.append("const \(bytesLabel) = textEncoder.encode(\(param.name));") - bodyLines.append("const \(bytesIdLabel) = swift.memory.retain(\(bytesLabel));") - cleanupLines.append("swift.memory.release(\(bytesIdLabel));") - parameterForwardings.append(bytesIdLabel) - parameterForwardings.append("\(bytesLabel).length") - case .caseEnum(_): - parameterForwardings.append("\(param.name) | 0") - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - let bytesLabel = "\(param.name)Bytes" - let bytesIdLabel = "\(param.name)Id" - bodyLines.append("const \(bytesLabel) = textEncoder.encode(\(param.name));") - bodyLines.append("const \(bytesIdLabel) = swift.memory.retain(\(bytesLabel));") - cleanupLines.append("swift.memory.release(\(bytesIdLabel));") - parameterForwardings.append(bytesIdLabel) - parameterForwardings.append("\(bytesLabel).length") - case .bool: - parameterForwardings.append("\(param.name) ? 1 : 0") - default: - parameterForwardings.append("\(param.name)") - } - case .associatedValueEnum: - parameterForwardings.append("0") - parameterForwardings.append("0") - case .namespaceEnum: - break - case .jsObject: - parameterForwardings.append("swift.memory.retain(\(param.name))") - case .swiftHeapObject: - parameterForwardings.append("\(param.name).pointer") - } + func lowerParameter(param: Parameter) throws { + let loweringFragment = try IntrinsicJSFragment.lowerParameter(type: param.type) + assert( + loweringFragment.parameters.count == 1, + "Lowering fragment should have exactly one parameter to lower" + ) + let loweredValues = loweringFragment.printCode([param.name], scope, body, cleanupCode) + parameterForwardings.append(contentsOf: loweredValues) } func lowerSelf() { parameterForwardings.append("this.pointer") } - func call(abiName: String, returnType: BridgeType) -> String? { + func call(abiName: String, returnType: BridgeType) throws -> String? { if effects.isAsync { - return _call(abiName: abiName, returnType: .jsObject(nil)) + return try _call(abiName: abiName, returnType: .jsObject(nil)) } else { - return _call(abiName: abiName, returnType: returnType) + return try _call(abiName: abiName, returnType: returnType) } } - private func _call(abiName: String, returnType: BridgeType) -> String? { + private func _call(abiName: String, returnType: BridgeType) throws -> String? { let call = "instance.exports.\(abiName)(\(parameterForwardings.joined(separator: ", ")))" - var returnExpr: String? - - switch returnType { - case .void: - bodyLines.append("\(call);") - case .string: - bodyLines.append("\(call);") - bodyLines.append("const ret = tmpRetString;") - bodyLines.append("tmpRetString = undefined;") - returnExpr = "ret" - case .caseEnum(_): - bodyLines.append("const ret = \(call);") - returnExpr = "ret" - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - bodyLines.append("\(call);") - bodyLines.append("const ret = tmpRetString;") - bodyLines.append("tmpRetString = undefined;") - returnExpr = "ret" - case .bool: - bodyLines.append("const ret = \(call);") - returnExpr = "ret !== 0" - default: - bodyLines.append("const ret = \(call);") - returnExpr = "ret" - } - case .associatedValueEnum: - bodyLines.append("\(call);") - returnExpr = "\"\"" - case .namespaceEnum: - break - case .int, .float, .double: - bodyLines.append("const ret = \(call);") - returnExpr = "ret" - case .bool: - bodyLines.append("const ret = \(call) !== 0;") - returnExpr = "ret" - case .jsObject: - bodyLines.append("const retId = \(call);") - // TODO: Implement "take" operation - bodyLines.append("const ret = swift.memory.getObject(retId);") - bodyLines.append("swift.memory.release(retId);") - returnExpr = "ret" - case .swiftHeapObject(let name): - bodyLines.append("const ret = \(name).__construct(\(call));") - returnExpr = "ret" + let liftingFragment = try IntrinsicJSFragment.liftReturn(type: returnType) + assert( + liftingFragment.parameters.count <= 1, + "Lifting fragment should have at most one parameter to lift" + ) + let fragmentArguments: [String] + if liftingFragment.parameters.isEmpty { + body.write("\(call);") + fragmentArguments = [] + } else { + let returnVariable = scope.variable("ret") + body.write("const \(returnVariable) = \(call);") + fragmentArguments = [returnVariable] } - return returnExpr + let liftedValues = liftingFragment.printCode(fragmentArguments, scope, body, cleanupCode) + assert(liftedValues.count <= 1, "Lifting fragment should produce at most one value") + return liftedValues.first } func callConstructor(abiName: String) -> String { - let call = "instance.exports.\(abiName)(\(parameterForwardings.joined(separator: ", ")))" - bodyLines.append("const ret = \(call);") + let call = + "\(JSGlueVariableScope.reservedInstance).exports.\(abiName)(\(parameterForwardings.joined(separator: ", ")))" + body.write("const ret = \(call);") return "ret" } @@ -510,12 +451,13 @@ struct BridgeJSLink { guard effects.isThrows else { return [] } + let exceptionVariable = JSGlueVariableScope.reservedStorageToReturnException return [ - "if (tmpRetException) {", + "if (\(exceptionVariable)) {", // TODO: Implement "take" operation - " const error = swift.memory.getObject(tmpRetException);", - " swift.memory.release(tmpRetException);", - " tmpRetException = undefined;", + " const error = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(exceptionVariable));", + " \(JSGlueVariableScope.reservedSwift).memory.release(\(exceptionVariable));", + " \(exceptionVariable) = undefined;", " throw error;", "}", ] @@ -531,8 +473,8 @@ struct BridgeJSLink { funcLines.append( "\(declarationPrefixKeyword.map { "\($0) "} ?? "")\(name)(\(parameters.map { $0.name }.joined(separator: ", "))) {" ) - funcLines.append(contentsOf: bodyLines.map { $0.indent(count: 4) }) - funcLines.append(contentsOf: cleanupLines.map { $0.indent(count: 4) }) + funcLines.append(contentsOf: body.lines.map { $0.indent(count: 4) }) + funcLines.append(contentsOf: cleanupCode.lines.map { $0.indent(count: 4) }) funcLines.append(contentsOf: checkExceptionLines().map { $0.indent(count: 4) }) if let returnExpr = returnExpr { funcLines.append("return \(returnExpr);".indent(count: 4)) @@ -678,12 +620,12 @@ struct BridgeJSLink { return (jsLines, dtsLines) } - func renderExportedFunction(function: ExportedFunction) -> (js: [String], dts: [String]) { + func renderExportedFunction(function: ExportedFunction) throws -> (js: [String], dts: [String]) { let thunkBuilder = ExportedThunkBuilder(effects: function.effects) for param in function.parameters { - thunkBuilder.lowerParameter(param: param) + try thunkBuilder.lowerParameter(param: param) } - let returnExpr = thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) + let returnExpr = try thunkBuilder.call(abiName: function.abiName, returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( name: function.abiName, parameters: function.parameters, @@ -698,7 +640,9 @@ struct BridgeJSLink { return (funcLines, dtsLines) } - func renderExportedClass(_ klass: ExportedClass) -> (js: [String], dtsType: [String], dtsExportEntry: [String]) { + func renderExportedClass( + _ klass: ExportedClass + ) throws -> (js: [String], dtsType: [String], dtsExportEntry: [String]) { var jsLines: [String] = [] var dtsTypeLines: [String] = [] var dtsExportEntryLines: [String] = [] @@ -721,14 +665,14 @@ struct BridgeJSLink { if let constructor: ExportedConstructor = klass.constructor { let thunkBuilder = ExportedThunkBuilder(effects: constructor.effects) for param in constructor.parameters { - thunkBuilder.lowerParameter(param: param) + try thunkBuilder.lowerParameter(param: param) } var funcLines: [String] = [] funcLines.append("") funcLines.append("constructor(\(constructor.parameters.map { $0.name }.joined(separator: ", "))) {") let returnExpr = thunkBuilder.callConstructor(abiName: constructor.abiName) - funcLines.append(contentsOf: thunkBuilder.bodyLines.map { $0.indent(count: 4) }) - funcLines.append(contentsOf: thunkBuilder.cleanupLines.map { $0.indent(count: 4) }) + funcLines.append(contentsOf: thunkBuilder.body.lines.map { $0.indent(count: 4) }) + funcLines.append(contentsOf: thunkBuilder.cleanupCode.lines.map { $0.indent(count: 4) }) funcLines.append(contentsOf: thunkBuilder.checkExceptionLines().map { $0.indent(count: 4) }) funcLines.append("return \(klass.name).__construct(\(returnExpr));".indent(count: 4)) funcLines.append("}") @@ -744,9 +688,9 @@ struct BridgeJSLink { let thunkBuilder = ExportedThunkBuilder(effects: method.effects) thunkBuilder.lowerSelf() for param in method.parameters { - thunkBuilder.lowerParameter(param: param) + try thunkBuilder.lowerParameter(param: param) } - let returnExpr = thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) + let returnExpr = try thunkBuilder.call(abiName: method.abiName, returnType: method.returnType) jsLines.append( contentsOf: thunkBuilder.renderFunction( name: method.name, @@ -766,7 +710,7 @@ struct BridgeJSLink { // Generate getter let getterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) getterThunkBuilder.lowerSelf() - let getterReturnExpr = getterThunkBuilder.call( + let getterReturnExpr = try getterThunkBuilder.call( abiName: property.getterAbiName(className: klass.name), returnType: property.type ) @@ -783,8 +727,10 @@ struct BridgeJSLink { if !property.isReadonly { let setterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) setterThunkBuilder.lowerSelf() - setterThunkBuilder.lowerParameter(param: Parameter(label: "value", name: "value", type: property.type)) - _ = setterThunkBuilder.call( + try setterThunkBuilder.lowerParameter( + param: Parameter(label: "value", name: "value", type: property.type) + ) + _ = try setterThunkBuilder.call( abiName: property.setterAbiName(className: klass.name), returnType: .void ) @@ -877,46 +823,38 @@ struct BridgeJSLink { } class ImportedThunkBuilder { - var bodyLines: [String] = [] + let body: CodeFragmentPrinter + let scope: JSGlueVariableScope + let cleanupCode: CodeFragmentPrinter var parameterNames: [String] = [] var parameterForwardings: [String] = [] + init() { + self.body = CodeFragmentPrinter() + self.scope = JSGlueVariableScope() + self.cleanupCode = CodeFragmentPrinter() + } + func liftSelf() { parameterNames.append("self") } - func liftParameter(param: Parameter) { - parameterNames.append(param.name) - switch param.type { - case .string: - let stringObjectName = "\(param.name)Object" - // TODO: Implement "take" operation - bodyLines.append("const \(stringObjectName) = swift.memory.getObject(\(param.name));") - bodyLines.append("swift.memory.release(\(param.name));") - parameterForwardings.append(stringObjectName) - case .caseEnum(_): - parameterForwardings.append(param.name) - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - let stringObjectName = "\(param.name)Object" - bodyLines.append("const \(stringObjectName) = swift.memory.getObject(\(param.name));") - bodyLines.append("swift.memory.release(\(param.name));") - parameterForwardings.append(stringObjectName) - case .bool: - parameterForwardings.append("\(param.name) !== 0") - default: - parameterForwardings.append(param.name) - } - case .associatedValueEnum: - parameterForwardings.append("\"\"") - case .namespaceEnum: - break - case .jsObject: - parameterForwardings.append("swift.memory.getObject(\(param.name))") - default: - parameterForwardings.append(param.name) + func liftParameter(param: Parameter) throws { + let liftingFragment = try IntrinsicJSFragment.liftParameter(type: param.type) + assert( + liftingFragment.parameters.count >= 1, + "Lifting fragment should have at least one parameter to lift" + ) + let valuesToLift: [String] + if liftingFragment.parameters.count == 1 { + parameterNames.append(param.name) + valuesToLift = [scope.variable(param.name)] + } else { + valuesToLift = liftingFragment.parameters.map { scope.variable(param.name + $0.capitalizedFirstLetter) } } + let liftedValues = liftingFragment.printCode(valuesToLift, scope, body, cleanupCode) + assert(liftedValues.count == 1, "Lifting fragment should produce exactly one value") + parameterForwardings.append(contentsOf: liftedValues) } func renderFunction( @@ -929,7 +867,7 @@ struct BridgeJSLink { "function \(name)(\(parameterNames.joined(separator: ", "))) {" ) funcLines.append("try {".indent(count: 4)) - funcLines.append(contentsOf: bodyLines.map { $0.indent(count: 8) }) + funcLines.append(contentsOf: body.lines.map { $0.indent(count: 8) }) if let returnExpr = returnExpr { funcLines.append("return \(returnExpr);".indent(count: 8)) } @@ -943,71 +881,69 @@ struct BridgeJSLink { return funcLines } - func call(name: String, returnType: BridgeType) { - let call = "imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" - if returnType == .void { - bodyLines.append("\(call);") + func call(name: String, returnType: BridgeType) throws -> String? { + return try self.call(calleeExpr: "imports.\(name)", returnType: returnType) + } + + private func call(calleeExpr: String, returnType: BridgeType) throws -> String? { + let callExpr = "\(calleeExpr)(\(parameterForwardings.joined(separator: ", ")))" + return try self.call(callExpr: callExpr, returnType: returnType) + } + + private func call(callExpr: String, returnType: BridgeType) throws -> String? { + let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: returnType) + let returnExpr: String? + if loweringFragment.parameters.count == 0 { + body.write("\(callExpr);") + returnExpr = nil } else { - bodyLines.append("let ret = \(call);") + let resultVariable = scope.variable("ret") + body.write("let \(resultVariable) = \(callExpr);") + returnExpr = resultVariable } + return try lowerReturnValue( + returnType: returnType, + returnExpr: returnExpr, + loweringFragment: loweringFragment + ) } - func callConstructor(name: String) { + func callConstructor(name: String) throws -> String? { let call = "new imports.\(name)(\(parameterForwardings.joined(separator: ", ")))" - bodyLines.append("let ret = \(call);") + let type: BridgeType = .jsObject(name) + let loweringFragment = try IntrinsicJSFragment.lowerReturn(type: type) + return try lowerReturnValue(returnType: type, returnExpr: call, loweringFragment: loweringFragment) } - func callMethod(name: String, returnType: BridgeType) { - let call = "swift.memory.getObject(self).\(name)(\(parameterForwardings.joined(separator: ", ")))" - if returnType == .void { - bodyLines.append("\(call);") - } else { - bodyLines.append("let ret = \(call);") - } + func callMethod(name: String, returnType: BridgeType) throws -> String? { + return try call( + calleeExpr: "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name)", + returnType: returnType + ) } - func callPropertyGetter(name: String, returnType: BridgeType) { - let call = "swift.memory.getObject(self).\(name)" - bodyLines.append("let ret = \(call);") + func callPropertyGetter(name: String, returnType: BridgeType) throws -> String? { + return try call( + callExpr: "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name)", + returnType: returnType + ) } func callPropertySetter(name: String, returnType: BridgeType) { - let call = "swift.memory.getObject(self).\(name) = \(parameterForwardings.joined(separator: ", "))" - bodyLines.append("\(call);") + let call = + "\(JSGlueVariableScope.reservedSwift).memory.getObject(self).\(name) = \(parameterForwardings.joined(separator: ", "))" + body.write("\(call);") } - func lowerReturnValue(returnType: BridgeType) throws -> String? { - switch returnType { - case .void: - return nil - case .string: - bodyLines.append("tmpRetBytes = textEncoder.encode(ret);") - return "tmpRetBytes.length" - case .caseEnum(_): - return "ret" - case .rawValueEnum(_, let rawType): - switch rawType { - case .string: - bodyLines.append("tmpRetBytes = textEncoder.encode(ret);") - return "tmpRetBytes.length" - case .bool: - return "ret ? 1 : 0" - default: - return "ret" - } - case .associatedValueEnum: - return nil - case .namespaceEnum: - return nil - case .int, .float, .double: - return "ret" - case .bool: - return "ret !== 0" - case .jsObject: - return "swift.memory.retain(ret)" - case .swiftHeapObject: - throw BridgeJSLinkError(message: "Swift heap object is not supported in imported functions") - } + private func lowerReturnValue( + returnType: BridgeType, + returnExpr: String?, + loweringFragment: IntrinsicJSFragment + ) throws -> String? { + assert(loweringFragment.parameters.count <= 1, "Lowering fragment should have at most one parameter") + let loweredValues = loweringFragment.printCode(returnExpr.map { [$0] } ?? [], scope, body, cleanupCode) + assert(loweredValues.count <= 1, "Lowering fragment should produce at most one value") + return loweredValues.first } } @@ -1385,10 +1321,9 @@ struct BridgeJSLink { ) throws { let thunkBuilder = ImportedThunkBuilder() for param in function.parameters { - thunkBuilder.liftParameter(param: param) + try thunkBuilder.liftParameter(param: param) } - thunkBuilder.call(name: function.name, returnType: function.returnType) - let returnExpr = try thunkBuilder.lowerReturnValue(returnType: function.returnType) + let returnExpr = try thunkBuilder.call(name: function.name, returnType: function.returnType) let funcLines = thunkBuilder.renderFunction( name: function.abiName(context: nil), returnExpr: returnExpr, @@ -1420,8 +1355,7 @@ struct BridgeJSLink { property: property, abiName: getterAbiName, emitCall: { thunkBuilder in - thunkBuilder.callPropertyGetter(name: property.name, returnType: property.type) - return try thunkBuilder.lowerReturnValue(returnType: property.type) + return try thunkBuilder.callPropertyGetter(name: property.name, returnType: property.type) } ) importObjectBuilder.assignToImportObject(name: getterAbiName, function: js) @@ -1433,7 +1367,7 @@ struct BridgeJSLink { property: property, abiName: setterAbiName, emitCall: { thunkBuilder in - thunkBuilder.liftParameter( + try thunkBuilder.liftParameter( param: Parameter(label: nil, name: "newValue", type: property.type) ) thunkBuilder.callPropertySetter(name: property.name, returnType: property.type) @@ -1458,11 +1392,10 @@ struct BridgeJSLink { ) throws { let thunkBuilder = ImportedThunkBuilder() for param in constructor.parameters { - thunkBuilder.liftParameter(param: param) + try thunkBuilder.liftParameter(param: param) } let returnType = BridgeType.jsObject(type.name) - thunkBuilder.callConstructor(name: type.name) - let returnExpr = try thunkBuilder.lowerReturnValue(returnType: returnType) + let returnExpr = try thunkBuilder.callConstructor(name: type.name) let abiName = constructor.abiName(context: type) let funcLines = thunkBuilder.renderFunction( name: abiName, @@ -1501,10 +1434,9 @@ struct BridgeJSLink { let thunkBuilder = ImportedThunkBuilder() thunkBuilder.liftSelf() for param in method.parameters { - thunkBuilder.liftParameter(param: param) + try thunkBuilder.liftParameter(param: param) } - thunkBuilder.callMethod(name: method.name, returnType: method.returnType) - let returnExpr = try thunkBuilder.lowerReturnValue(returnType: method.returnType) + let returnExpr = try thunkBuilder.callMethod(name: method.name, returnType: method.returnType) let funcLines = thunkBuilder.renderFunction( name: method.abiName(context: context), returnExpr: returnExpr, diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/CodeFragmentPrinter.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/CodeFragmentPrinter.swift new file mode 100644 index 00000000..c4624e69 --- /dev/null +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/CodeFragmentPrinter.swift @@ -0,0 +1,41 @@ +/// A printer for code fragments. +final class CodeFragmentPrinter { + private(set) var lines: [String] = [] + private var indentLevel: Int = 0 + + init(header: String = "") { + self.lines.append(contentsOf: header.split(separator: "\n").map { String($0) }) + } + + func nextLine() { + lines.append("") + } + + func write(_ line: S) { + lines.append(String(repeating: " ", count: indentLevel * 4) + String(line)) + } + + func write(lines: [String]) { + for line in lines { + write(line) + } + } + + func write(contentsOf printer: CodeFragmentPrinter) { + self.write(lines: printer.lines) + } + + func indent() { + indentLevel += 1 + } + + func unindent() { + indentLevel -= 1 + } + + func indent(_ body: () throws -> Void) rethrows { + indentLevel += 1 + try body() + indentLevel -= 1 + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift new file mode 100644 index 00000000..ae74844f --- /dev/null +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -0,0 +1,317 @@ +#if canImport(BridgeJSSkeleton) +import BridgeJSSkeleton +#endif + +/// A scope for variables for JS glue code +final class JSGlueVariableScope { + // MARK: - Reserved variables + + static let reservedSwift = "swift" + static let reservedInstance = "instance" + static let reservedMemory = "memory" + static let reservedSetException = "setException" + static let reservedStorageToReturnString = "tmpRetString" + static let reservedStorageToReturnBytes = "tmpRetBytes" + static let reservedStorageToReturnException = "tmpRetException" + static let reservedTextEncoder = "textEncoder" + static let reservedTextDecoder = "textDecoder" + + private var variables: Set = [ + reservedSwift, + reservedMemory, + reservedStorageToReturnString, + reservedStorageToReturnBytes, + reservedStorageToReturnException, + reservedTextEncoder, + reservedTextDecoder, + ] + + /// Returns a unique variable name in the scope based on the given name hint. + /// + /// - Parameter hint: A hint for the variable name. + /// - Returns: A unique variable name. + func variable(_ hint: String) -> String { + if variables.insert(hint).inserted { + return hint + } + var suffixedName: String + var suffix = 1 + repeat { + suffixedName = hint + suffix.description + suffix += 1 + } while !variables.insert(suffixedName).inserted + return suffixedName + } +} + +/// A fragment of JS code used to convert a value between Swift and JS. +/// +/// See `BridgeJSInstrincics.swift` in the main JavaScriptKit module for Swift side lowering/lifting implementation. +struct IntrinsicJSFragment: Sendable { + /// The names of the parameters that the fragment expects. + let parameters: [String] + + /// Prints the fragment code. + /// + /// - Parameters: + /// - arguments: The arguments that the fragment expects. An argument may be an expression with side effects, + /// so the callee is responsible for evaluating the arguments only once. + /// - scope: The scope of the variables. + /// - printer: The printer to print the main fragment code. + /// - cleanupCode: The printer to print the code that is expected to be executed at the end of the caller of the + /// fragment. + /// - Returns: List of result expressions. + let printCode: + @Sendable ( + _ arguments: [String], + _ scope: JSGlueVariableScope, + _ printer: CodeFragmentPrinter, + _ cleanupCode: CodeFragmentPrinter + ) -> [String] + + /// A fragment that does nothing + static let void = IntrinsicJSFragment( + parameters: [], + printCode: { _, _, _, _ in + return [] + } + ) + + /// A fragment that returns the argument as is. + static let identity = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return [arguments[0]] + } + ) + + /// NOTE: JavaScript engine itself converts booleans to integers when passing them to + /// Wasm functions, so we don't need to do anything here + static let boolLowerParameter = identity + static let boolLiftReturn = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(arguments[0]) !== 0"] + } + ) + static let boolLiftParameter = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(arguments[0]) !== 0"] + } + ) + static let boolLowerReturn = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(arguments[0]) ? 1 : 0"] + } + ) + + static let stringLowerParameter = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + let argument = arguments[0] + let bytesLabel = scope.variable("\(argument)Bytes") + let bytesIdLabel = scope.variable("\(argument)Id") + printer.write("const \(bytesLabel) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(argument));") + printer.write("const \(bytesIdLabel) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(bytesLabel));") + cleanupCode.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(bytesIdLabel));") + return [bytesIdLabel, "\(bytesLabel).length"] + } + ) + static let stringLiftReturn = IntrinsicJSFragment( + parameters: [], + printCode: { arguments, scope, printer, cleanupCode in + let resultLabel = scope.variable("ret") + printer.write("const \(resultLabel) = \(JSGlueVariableScope.reservedStorageToReturnString);") + printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;") + return [resultLabel] + } + ) + static let stringLiftParameter = IntrinsicJSFragment( + parameters: ["objectId"], + printCode: { arguments, scope, printer, cleanupCode in + let objectId = arguments[0] + let objectLabel = scope.variable("\(objectId)Object") + // TODO: Implement "take" operation + printer.write("const \(objectLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(objectId));") + printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(objectId));") + return [objectLabel] + } + ) + static let stringLowerReturn = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + printer.write( + "\(JSGlueVariableScope.reservedStorageToReturnBytes) = \(JSGlueVariableScope.reservedTextEncoder).encode(\(arguments[0]));" + ) + return ["\(JSGlueVariableScope.reservedStorageToReturnBytes).length"] + } + ) + + static let jsObjectLowerParameter = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["swift.memory.retain(\(arguments[0]))"] + } + ) + static let jsObjectLiftReturn = IntrinsicJSFragment( + parameters: ["retId"], + printCode: { arguments, scope, printer, cleanupCode in + // TODO: Implement "take" operation + let resultLabel = scope.variable("ret") + let retId = arguments[0] + printer.write("const \(resultLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(retId));") + printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(retId));") + return [resultLabel] + } + ) + static let jsObjectLiftParameter = IntrinsicJSFragment( + parameters: ["objectId"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(JSGlueVariableScope.reservedSwift).memory.getObject(\(arguments[0]))"] + } + ) + static let jsObjectLowerReturn = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(JSGlueVariableScope.reservedSwift).memory.retain(\(arguments[0]))"] + } + ) + + static let swiftHeapObjectLowerParameter = IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(arguments[0]).pointer"] + } + ) + static func swiftHeapObjectLiftReturn(_ name: String) -> IntrinsicJSFragment { + return IntrinsicJSFragment( + parameters: ["value"], + printCode: { arguments, scope, printer, cleanupCode in + return ["\(name).__construct(\(arguments[0]))"] + } + ) + } + + // MARK: - ExportSwift + + /// Returns a fragment that lowers a JS value to Wasm core values for parameters + static func lowerParameter(type: BridgeType) throws -> IntrinsicJSFragment { + switch type { + case .int, .float, .double, .bool: return .identity + case .string: return .stringLowerParameter + case .jsObject: return .jsObjectLowerParameter + case .swiftHeapObject: + return .swiftHeapObjectLowerParameter + case .void: return .void + case .caseEnum: return .identity + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: return .stringLowerParameter + default: return .identity + } + case .associatedValueEnum(let string): + throw BridgeJSLinkError( + message: "Associated value enums are not supported to be passed as parameters: \(string)" + ) + case .namespaceEnum(let string): + throw BridgeJSLinkError(message: "Namespace enums are not supported to be passed as parameters: \(string)") + } + } + + /// Returns a fragment that lifts a Wasm core value to a JS value for return values + static func liftReturn(type: BridgeType) throws -> IntrinsicJSFragment { + switch type { + case .int, .float, .double: return .identity + case .bool: return .boolLiftReturn + case .string: return .stringLiftReturn + case .jsObject: return .jsObjectLiftReturn + case .swiftHeapObject(let name): return .swiftHeapObjectLiftReturn(name) + case .void: return .void + case .caseEnum: return .identity + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: return .stringLiftReturn + case .bool: return .boolLiftReturn + default: return .identity + } + case .associatedValueEnum(let string): + throw BridgeJSLinkError( + message: "Associated value enums are not supported to be returned from functions: \(string)" + ) + case .namespaceEnum(let string): + throw BridgeJSLinkError( + message: "Namespace enums are not supported to be returned from functions: \(string)" + ) + } + } + + // MARK: - ImportedJS + + /// Returns a fragment that lifts Wasm core values to JS values for parameters + static func liftParameter(type: BridgeType) throws -> IntrinsicJSFragment { + switch type { + case .int, .float, .double: return .identity + case .bool: return .boolLiftParameter + case .string: return .stringLiftParameter + case .jsObject: return .jsObjectLiftParameter + case .swiftHeapObject(let name): + throw BridgeJSLinkError( + message: + "Swift heap objects are not supported to be passed as parameters to imported JS functions: \(name)" + ) + case .void: + throw BridgeJSLinkError( + message: "Void can't appear in parameters of imported JS functions" + ) + case .caseEnum: return .identity + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: return .stringLiftParameter + case .bool: return .boolLiftParameter + default: return .identity + } + case .associatedValueEnum(let string): + throw BridgeJSLinkError( + message: + "Associated value enums are not supported to be passed as parameters to imported JS functions: \(string)" + ) + case .namespaceEnum(let string): + throw BridgeJSLinkError( + message: + "Namespace enums are not supported to be passed as parameters to imported JS functions: \(string)" + ) + } + } + + /// Returns a fragment that lowers a JS value to Wasm core values for return values + static func lowerReturn(type: BridgeType) throws -> IntrinsicJSFragment { + switch type { + case .int, .float, .double: return .identity + case .bool: return .boolLowerReturn + case .string: return .stringLowerReturn + case .jsObject: return .jsObjectLowerReturn + case .swiftHeapObject: + throw BridgeJSLinkError( + message: "Swift heap objects are not supported to be returned from imported JS functions" + ) + case .void: return .void + case .caseEnum: return .identity + case .rawValueEnum(_, let rawType): + switch rawType { + case .string: return .stringLowerReturn + case .bool: return .boolLowerReturn + default: return .identity + } + case .associatedValueEnum(let string): + throw BridgeJSLinkError( + message: "Associated value enums are not supported to be returned from imported JS functions: \(string)" + ) + case .namespaceEnum(let string): + throw BridgeJSLinkError( + message: "Namespace enums are not supported to be returned from imported JS functions: \(string)" + ) + } + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js index 1da2f58e..97c1e215 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Export.js @@ -65,49 +65,49 @@ export async function createInstantiator(options, swift) { return { asyncReturnVoid: function bjs_asyncReturnVoid() { - const retId = instance.exports.bjs_asyncReturnVoid(); - const ret = swift.memory.getObject(retId); - swift.memory.release(retId); - return ret; + const ret = instance.exports.bjs_asyncReturnVoid(); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; }, asyncRoundTripInt: function bjs_asyncRoundTripInt(v) { - const retId = instance.exports.bjs_asyncRoundTripInt(v); - const ret = swift.memory.getObject(retId); - swift.memory.release(retId); - return ret; + const ret = instance.exports.bjs_asyncRoundTripInt(v); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; }, asyncRoundTripString: function bjs_asyncRoundTripString(v) { const vBytes = textEncoder.encode(v); const vId = swift.memory.retain(vBytes); - const retId = instance.exports.bjs_asyncRoundTripString(vId, vBytes.length); - const ret = swift.memory.getObject(retId); - swift.memory.release(retId); + const ret = instance.exports.bjs_asyncRoundTripString(vId, vBytes.length); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); swift.memory.release(vId); - return ret; + return ret1; }, asyncRoundTripBool: function bjs_asyncRoundTripBool(v) { - const retId = instance.exports.bjs_asyncRoundTripBool(v); - const ret = swift.memory.getObject(retId); - swift.memory.release(retId); - return ret; + const ret = instance.exports.bjs_asyncRoundTripBool(v); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; }, asyncRoundTripFloat: function bjs_asyncRoundTripFloat(v) { - const retId = instance.exports.bjs_asyncRoundTripFloat(v); - const ret = swift.memory.getObject(retId); - swift.memory.release(retId); - return ret; + const ret = instance.exports.bjs_asyncRoundTripFloat(v); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; }, asyncRoundTripDouble: function bjs_asyncRoundTripDouble(v) { - const retId = instance.exports.bjs_asyncRoundTripDouble(v); - const ret = swift.memory.getObject(retId); - swift.memory.release(retId); - return ret; + const ret = instance.exports.bjs_asyncRoundTripDouble(v); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; }, asyncRoundTripJSObject: function bjs_asyncRoundTripJSObject(v) { - const retId = instance.exports.bjs_asyncRoundTripJSObject(swift.memory.retain(v)); - const ret = swift.memory.getObject(retId); - swift.memory.release(retId); - return ret; + const ret = instance.exports.bjs_asyncRoundTripJSObject(swift.memory.retain(v)); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; }, }; }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js index 21d11fa4..0b07aedd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.Import.js @@ -82,7 +82,7 @@ export async function createInstantiator(options, swift) { } TestModule["bjs_asyncRoundTripBool"] = function bjs_asyncRoundTripBool(v) { try { - let ret = imports.asyncRoundTripBool(v); + let ret = imports.asyncRoundTripBool(v !== 0); return swift.memory.retain(ret); } catch (error) { setException(error); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js index 3e080948..42a98f62 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js @@ -86,18 +86,18 @@ export async function createInstantiator(options, swift) { return { setDirection: function bjs_setDirection(direction) { - instance.exports.bjs_setDirection(direction | 0); + instance.exports.bjs_setDirection(direction); }, getDirection: function bjs_getDirection() { const ret = instance.exports.bjs_getDirection(); return ret; }, processDirection: function bjs_processDirection(input) { - const ret = instance.exports.bjs_processDirection(input | 0); + const ret = instance.exports.bjs_processDirection(input); return ret; }, setTSDirection: function bjs_setTSDirection(direction) { - instance.exports.bjs_setTSDirection(direction | 0); + instance.exports.bjs_setTSDirection(direction); }, getTSDirection: function bjs_getTSDirection() { const ret = instance.exports.bjs_getTSDirection(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js index 12613dd8..677e02c9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Export.js @@ -171,7 +171,7 @@ export async function createInstantiator(options, swift) { return HTTPServer.__construct(ret); } call(method) { - instance.exports.bjs_HTTPServer_call(this.pointer, method | 0); + instance.exports.bjs_HTTPServer_call(this.pointer, method); } } class TestServer extends SwiftHeapObject { @@ -185,7 +185,7 @@ export async function createInstantiator(options, swift) { return TestServer.__construct(ret); } call(method) { - instance.exports.bjs_TestServer_call(this.pointer, method | 0); + instance.exports.bjs_TestServer_call(this.pointer, method); } } const exports = { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js index 68a2b19f..2be034b4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.Export.js @@ -165,7 +165,7 @@ export async function createInstantiator(options, swift) { return ret; }, setFeatureFlag: function bjs_setFeatureFlag(flag) { - instance.exports.bjs_setFeatureFlag(flag ? 1 : 0); + instance.exports.bjs_setFeatureFlag(flag); }, getFeatureFlag: function bjs_getFeatureFlag() { const ret = instance.exports.bjs_getFeatureFlag(); @@ -235,7 +235,7 @@ export async function createInstantiator(options, swift) { return ret; }, setFeatureFlag: function bjs_setFeatureFlag(featureFlag) { - instance.exports.bjs_setFeatureFlag(featureFlag ? 1 : 0); + instance.exports.bjs_setFeatureFlag(featureFlag); }, getFeatureFlag: function bjs_getFeatureFlag() { const ret = instance.exports.bjs_getFeatureFlag(); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js index 394d996b..d9a13b5e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MultipleImportedTypes.Import.js @@ -83,7 +83,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_DatabaseConnection_isConnected_get"] = function bjs_DatabaseConnection_isConnected_get(self) { try { let ret = swift.memory.getObject(self).isConnected; - return ret !== 0; + return ret ? 1 : 0; } catch (error) { setException(error); return 0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js index 3b93b2dd..b0dbaa19 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.Import.js @@ -53,7 +53,7 @@ export async function createInstantiator(options, swift) { const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; TestModule["bjs_check"] = function bjs_check(a, b) { try { - imports.check(a, b); + imports.check(a, b !== 0); } catch (error) { setException(error); } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js index 53332b97..594dc9d5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Export.js @@ -77,8 +77,8 @@ export async function createInstantiator(options, swift) { return ret; }, checkBool: function bjs_checkBool() { - const ret = instance.exports.bjs_checkBool() !== 0; - return ret; + const ret = instance.exports.bjs_checkBool(); + return ret !== 0; }, }; }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js index 1892eb46..a61149cd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.Import.js @@ -63,7 +63,7 @@ export async function createInstantiator(options, swift) { TestModule["bjs_checkBoolean"] = function bjs_checkBoolean() { try { let ret = imports.checkBoolean(); - return ret !== 0; + return ret ? 1 : 0; } catch (error) { setException(error); return 0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js index ae72be66..ffc1eab6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.Export.js @@ -129,8 +129,8 @@ export async function createInstantiator(options, swift) { instance.exports.bjs_PropertyHolder_doubleValue_set(this.pointer, value); } get boolValue() { - const ret = instance.exports.bjs_PropertyHolder_boolValue_get(this.pointer) !== 0; - return ret; + const ret = instance.exports.bjs_PropertyHolder_boolValue_get(this.pointer); + return ret !== 0; } set boolValue(value) { instance.exports.bjs_PropertyHolder_boolValue_set(this.pointer, value); @@ -160,8 +160,8 @@ export async function createInstantiator(options, swift) { return ret; } get readonlyBool() { - const ret = instance.exports.bjs_PropertyHolder_readonlyBool_get(this.pointer) !== 0; - return ret; + const ret = instance.exports.bjs_PropertyHolder_readonlyBool_get(this.pointer); + return ret !== 0; } get readonlyString() { instance.exports.bjs_PropertyHolder_readonlyString_get(this.pointer); @@ -170,17 +170,17 @@ export async function createInstantiator(options, swift) { return ret; } get jsObject() { - const retId = instance.exports.bjs_PropertyHolder_jsObject_get(this.pointer); - const ret = swift.memory.getObject(retId); - swift.memory.release(retId); - return ret; + const ret = instance.exports.bjs_PropertyHolder_jsObject_get(this.pointer); + const ret1 = swift.memory.getObject(ret); + swift.memory.release(ret); + return ret1; } set jsObject(value) { instance.exports.bjs_PropertyHolder_jsObject_set(this.pointer, swift.memory.retain(value)); } get sibling() { - const ret = PropertyHolder.__construct(instance.exports.bjs_PropertyHolder_sibling_get(this.pointer)); - return ret; + const ret = instance.exports.bjs_PropertyHolder_sibling_get(this.pointer); + return PropertyHolder.__construct(ret); } set sibling(value) { instance.exports.bjs_PropertyHolder_sibling_set(this.pointer, value.pointer); @@ -226,9 +226,9 @@ export async function createInstantiator(options, swift) { createPropertyHolder: function bjs_createPropertyHolder(intValue, floatValue, doubleValue, boolValue, stringValue, jsObject) { const stringValueBytes = textEncoder.encode(stringValue); const stringValueId = swift.memory.retain(stringValueBytes); - const ret = PropertyHolder.__construct(instance.exports.bjs_createPropertyHolder(intValue, floatValue, doubleValue, boolValue, stringValueId, stringValueBytes.length, swift.memory.retain(jsObject))); + const ret = instance.exports.bjs_createPropertyHolder(intValue, floatValue, doubleValue, boolValue, stringValueId, stringValueBytes.length, swift.memory.retain(jsObject)); swift.memory.release(stringValueId); - return ret; + return PropertyHolder.__construct(ret); }, testPropertyHolder: function bjs_testPropertyHolder(holder) { instance.exports.bjs_testPropertyHolder(holder.pointer); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js index 705c6a37..c7671805 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TS2SkeletonLike.Import.js @@ -96,7 +96,7 @@ export async function createInstantiator(options, swift) { const tsObject = swift.memory.getObject(ts); swift.memory.release(ts); let ret = swift.memory.getObject(self).validate(tsObject); - return ret !== 0; + return ret ? 1 : 0; } catch (error) { setException(error); return 0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js index c7d622ea..48d15c7e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/TypeScriptClass.Import.js @@ -55,8 +55,7 @@ export async function createInstantiator(options, swift) { try { const nameObject = swift.memory.getObject(name); swift.memory.release(name); - let ret = new imports.Greeter(nameObject); - return swift.memory.retain(ret); + return swift.memory.retain(new imports.Greeter(nameObject)); } catch (error) { setException(error); return 0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift index 10a3a24d..fe31c9f1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift @@ -13,7 +13,7 @@ public func _bjs_asyncReturnVoid() -> Int32 { let ret = JSPromise.async { await asyncReturnVoid() } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -24,9 +24,9 @@ public func _bjs_asyncReturnVoid() -> Int32 { public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripInt(_: Int(v)).jsValue + return await asyncRoundTripInt(_: Int.bridgeJSLiftParameter(v)).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -37,13 +37,9 @@ public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - let v = String(unsafeUninitializedCapacity: Int(vLen)) { b in - _swift_js_init_memory(vBytes, b.baseAddress.unsafelyUnwrapped) - return Int(vLen) - } - return await asyncRoundTripString(_: v).jsValue + return await asyncRoundTripString(_: String.bridgeJSLiftParameter(vBytes, vLen)).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -54,9 +50,9 @@ public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripBool(_: v == 1).jsValue + return await asyncRoundTripBool(_: Bool.bridgeJSLiftParameter(v)).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -69,7 +65,7 @@ public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { let ret = JSPromise.async { return await asyncRoundTripFloat(_: v).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -82,7 +78,7 @@ public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { let ret = JSPromise.async { return await asyncRoundTripDouble(_: v).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -93,9 +89,9 @@ public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripJSObject(_: JSObject(id: UInt32(bitPattern: v))).jsValue + return await asyncRoundTripJSObject(_: JSObject.bridgeJSLiftParameter(v)).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift index 9517ad80..e2a6588a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift @@ -73,10 +73,8 @@ public func _bjs_Converter_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_Converter_toString") public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int(value)) - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift index 991b5c6c..9f8f2ec3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift @@ -10,11 +10,7 @@ @_cdecl("bjs_setTheme") public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { #if arch(wasm32) - let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in - _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) - return Int(themeLen) - } - setTheme(_: Theme(rawValue: theme)!) + setTheme(_: Theme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) #else fatalError("Only available on WebAssembly") #endif @@ -25,10 +21,7 @@ public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTheme() -> Void { #if arch(wasm32) let ret = getTheme() - var rawValue = ret.rawValue - return rawValue.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + return ret.rawValue.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -38,11 +31,7 @@ public func _bjs_getTheme() -> Void { @_cdecl("bjs_setTSTheme") public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { #if arch(wasm32) - let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in - _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) - return Int(themeLen) - } - setTSTheme(_: TSTheme(rawValue: theme)!) + setTSTheme(_: TSTheme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) #else fatalError("Only available on WebAssembly") #endif @@ -53,10 +42,7 @@ public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTSTheme() -> Void { #if arch(wasm32) let ret = getTSTheme() - var rawValue = ret.rawValue - return rawValue.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + return ret.rawValue.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -66,7 +52,7 @@ public func _bjs_getTSTheme() -> Void { @_cdecl("bjs_setFeatureFlag") public func _bjs_setFeatureFlag(flag: Int32) -> Void { #if arch(wasm32) - setFeatureFlag(_: FeatureFlag(rawValue: flag != 0)!) + setFeatureFlag(_: FeatureFlag(rawValue: FeatureFlag.bridgeJSLiftParameter(flag))) #else fatalError("Only available on WebAssembly") #endif @@ -276,7 +262,7 @@ public func _bjs_getRatio() -> Float64 { @_cdecl("bjs_setFeatureFlag") public func _bjs_setFeatureFlag(featureFlag: Int32) -> Void { #if arch(wasm32) - setFeatureFlag(_: FeatureFlag(rawValue: featureFlag != 0)!) + setFeatureFlag(_: FeatureFlag(rawValue: FeatureFlag.bridgeJSLiftParameter(featureFlag))) #else fatalError("Only available on WebAssembly") #endif @@ -297,11 +283,7 @@ public func _bjs_getFeatureFlag() -> Int32 { @_cdecl("bjs_processTheme") public func _bjs_processTheme(themeBytes: Int32, themeLen: Int32) -> Int32 { #if arch(wasm32) - let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in - _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) - return Int(themeLen) - } - let ret = processTheme(_: Theme(rawValue: theme)!) + let ret = processTheme(_: Theme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) return Int32(ret.rawValue) #else fatalError("Only available on WebAssembly") @@ -324,10 +306,7 @@ public func _bjs_convertPriority(status: Int32) -> Int32 { public func _bjs_validateSession(session: Int64) -> Void { #if arch(wasm32) let ret = validateSession(_: SessionId(rawValue: UInt64(bitPattern: Int64(session)))!) - var rawValue = ret.rawValue - return rawValue.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + return ret.rawValue.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift index 21937c6c..e0128c5d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift @@ -10,10 +10,8 @@ @_cdecl("bjs_plainFunction") public func _bjs_plainFunction() -> Void { #if arch(wasm32) - var ret = plainFunction() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = plainFunction() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -23,10 +21,8 @@ public func _bjs_plainFunction() -> Void { @_cdecl("bjs_namespacedFunction") public func _bjs_namespacedFunction() -> Void { #if arch(wasm32) - var ret = namespacedFunction() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = namespacedFunction() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -36,11 +32,7 @@ public func _bjs_namespacedFunction() -> Void { @_cdecl("bjs_Greeter_init") public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - let ret = Greeter(name: name) + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) return Unmanaged.passRetained(ret).toOpaque() #else fatalError("Only available on WebAssembly") @@ -51,10 +43,8 @@ public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutable @_cdecl("bjs_Greeter_greet") public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -89,10 +79,8 @@ public func _bjs_Converter_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_Converter_toString") public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int(value)) - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -116,10 +104,8 @@ extension Converter: ConvertibleToJSValue { @_cdecl("bjs_UUID_uuidString") public func _bjs_UUID_uuidString(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().uuidString() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().uuidString() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift index c686c426..8f2e54c3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift @@ -10,7 +10,7 @@ @_cdecl("bjs_check") public func _bjs_check(a: Int32, b: Float32, c: Float64, d: Int32) -> Void { #if arch(wasm32) - check(a: Int(a), b: b, c: c, d: d == 1) + check(a: Int.bridgeJSLiftParameter(a), b: b, c: c, d: Bool.bridgeJSLiftParameter(d)) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift index 7356f2c8..cc31d23e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift @@ -44,7 +44,7 @@ public func _bjs_checkDouble() -> Float64 { public func _bjs_checkBool() -> Int32 { #if arch(wasm32) let ret = checkBool() - return Int32(ret ? 1 : 0) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift index 84609c74..74d3a58d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift @@ -10,11 +10,7 @@ @_cdecl("bjs_createPropertyHolder") public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let stringValue = String(unsafeUninitializedCapacity: Int(stringValueLen)) { b in - _swift_js_init_memory(stringValueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(stringValueLen) - } - let ret = createPropertyHolder(intValue: Int(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: boolValue == 1, stringValue: stringValue, jsObject: JSObject(id: UInt32(bitPattern: jsObject))) + let ret = createPropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLen), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) return Unmanaged.passRetained(ret).toOpaque() #else fatalError("Only available on WebAssembly") @@ -25,10 +21,8 @@ public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doub @_cdecl("bjs_testPropertyHolder") public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = testPropertyHolder(holder: Unmanaged.fromOpaque(holder).takeUnretainedValue()) - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = testPropertyHolder(holder: Unmanaged.fromOpaque(holder).takeUnretainedValue()) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -38,11 +32,7 @@ public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { @_cdecl("bjs_PropertyHolder_init") public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let stringValue = String(unsafeUninitializedCapacity: Int(stringValueLen)) { b in - _swift_js_init_memory(stringValueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(stringValueLen) - } - let ret = PropertyHolder(intValue: Int(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: boolValue == 1, stringValue: stringValue, jsObject: JSObject(id: UInt32(bitPattern: jsObject))) + let ret = PropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLen), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) return Unmanaged.passRetained(ret).toOpaque() #else fatalError("Only available on WebAssembly") @@ -53,10 +43,8 @@ public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubl @_cdecl("bjs_PropertyHolder_getAllValues") public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().getAllValues() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().getAllValues() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -77,7 +65,7 @@ public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_intValue_set") public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue = Int(value) + Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -130,7 +118,7 @@ public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue - return Int32(ret ? 1 : 0) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -140,7 +128,7 @@ public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_boolValue_set") public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue = value == 1 + Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue = Bool.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -150,10 +138,8 @@ public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, va @_cdecl("bjs_PropertyHolder_stringValue_get") public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -163,11 +149,7 @@ public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_stringValue_set") public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { #if arch(wasm32) - let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in - _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(valueLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue = value + Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue = String.bridgeJSLiftParameter(valueBytes, valueLen) #else fatalError("Only available on WebAssembly") #endif @@ -211,7 +193,7 @@ public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointe public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyBool - return Int32(ret ? 1 : 0) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -221,10 +203,8 @@ public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_readonlyString_get") public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyString - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyString + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -235,7 +215,7 @@ public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointe public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -245,7 +225,7 @@ public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_jsObject_set") public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject = JSObject(id: UInt32(bitPattern: value)) + Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject = JSObject.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -276,10 +256,8 @@ public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, valu @_cdecl("bjs_PropertyHolder_lazyValue_get") public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -289,11 +267,7 @@ public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_lazyValue_set") public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { #if arch(wasm32) - let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in - _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(valueLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue = value + Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue = String.bridgeJSLiftParameter(valueBytes, valueLen) #else fatalError("Only available on WebAssembly") #endif @@ -314,10 +288,8 @@ public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPoin @_cdecl("bjs_PropertyHolder_computedReadWrite_get") public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -327,11 +299,7 @@ public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPoi @_cdecl("bjs_PropertyHolder_computedReadWrite_set") public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { #if arch(wasm32) - let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in - _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(valueLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite = value + Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite = String.bridgeJSLiftParameter(valueBytes, valueLen) #else fatalError("Only available on WebAssembly") #endif @@ -352,7 +320,7 @@ public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPoin @_cdecl("bjs_PropertyHolder_observedProperty_set") public func _bjs_PropertyHolder_observedProperty_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty = Int(value) + Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift index 69bd66b5..0da63382 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift @@ -10,11 +10,7 @@ @_cdecl("bjs_checkString") public func _bjs_checkString(aBytes: Int32, aLen: Int32) -> Void { #if arch(wasm32) - let a = String(unsafeUninitializedCapacity: Int(aLen)) { b in - _swift_js_init_memory(aBytes, b.baseAddress.unsafelyUnwrapped) - return Int(aLen) - } - checkString(a: a) + checkString(a: String.bridgeJSLiftParameter(aBytes, aLen)) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift index 536f0623..0475def5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringReturn.swift @@ -10,10 +10,8 @@ @_cdecl("bjs_checkString") public func _bjs_checkString() -> Void { #if arch(wasm32) - var ret = checkString() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = checkString() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift index bcb9f3d7..78f8c149 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift @@ -20,11 +20,7 @@ public func _bjs_takeGreeter(greeter: UnsafeMutableRawPointer) -> Void { @_cdecl("bjs_Greeter_init") public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - let ret = Greeter(name: name) + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) return Unmanaged.passRetained(ret).toOpaque() #else fatalError("Only available on WebAssembly") @@ -35,10 +31,8 @@ public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutable @_cdecl("bjs_Greeter_greet") public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -48,11 +42,7 @@ public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { @_cdecl("bjs_Greeter_changeName") public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: name) + Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) #else fatalError("Only available on WebAssembly") #endif @@ -62,10 +52,8 @@ public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: I @_cdecl("bjs_Greeter_name_get") public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().name - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().name + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -75,11 +63,7 @@ public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { @_cdecl("bjs_Greeter_name_set") public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { #if arch(wasm32) - let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in - _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(valueLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().name = value + Unmanaged.fromOpaque(_self).takeUnretainedValue().name = String.bridgeJSLiftParameter(valueBytes, valueLen) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift index 34841ae8..f8974961 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift @@ -15,7 +15,7 @@ func checkArray(_ a: JSObject) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_checkArray(Int32(bitPattern: a.id)) + bjs_checkArray(a.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -30,7 +30,7 @@ func checkArrayWithLength(_ a: JSObject, _ b: Double) throws(JSException) -> Voi fatalError("Only available on WebAssembly") } #endif - bjs_checkArrayWithLength(Int32(bitPattern: a.id), b) + bjs_checkArrayWithLength(a.bridgeJSLowerParameter(), b) if let error = _swift_js_take_exception() { throw error } @@ -45,7 +45,7 @@ func checkArray(_ a: JSObject) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_checkArray(Int32(bitPattern: a.id)) + bjs_checkArray(a.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift index aa11cd0c..ea9a9ab6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift @@ -47,11 +47,7 @@ func asyncRoundTripString(_ v: String) throws(JSException) -> JSPromise { fatalError("Only available on WebAssembly") } #endif - var v = v - let vId = v.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_asyncRoundTripString(vId) + let ret = bjs_asyncRoundTripString(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -67,7 +63,7 @@ func asyncRoundTripBool(_ v: Bool) throws(JSException) -> JSPromise { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_asyncRoundTripBool(Int32(v ? 1 : 0)) + let ret = bjs_asyncRoundTripBool(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -115,7 +111,7 @@ func asyncRoundTripJSObject(_ v: JSObject) throws(JSException) -> JSPromise { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_asyncRoundTripJSObject(Int32(bitPattern: v.id)) + let ret = bjs_asyncRoundTripJSObject(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift index be9f524e..a4ee884f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift @@ -42,11 +42,11 @@ struct Animatable { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Animatable_animate(Int32(bitPattern: self.this.id), Int32(bitPattern: keyframes.id), Int32(bitPattern: options.id)) + let ret = bjs_Animatable_animate(self.this.bridgeJSLowerParameter(), keyframes.bridgeJSLowerParameter(), options.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return JSObject(id: UInt32(bitPattern: ret)) + return JSObject.bridgeJSLiftReturn(ret) } func getAnimations(_ options: JSObject) throws(JSException) -> JSObject { @@ -58,11 +58,11 @@ struct Animatable { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Animatable_getAnimations(Int32(bitPattern: self.this.id), Int32(bitPattern: options.id)) + let ret = bjs_Animatable_getAnimations(self.this.bridgeJSLowerParameter(), options.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return JSObject(id: UInt32(bitPattern: ret)) + return JSObject.bridgeJSLiftReturn(ret) } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift index d3e06e81..ab07c967 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift @@ -15,7 +15,7 @@ func createDatabaseConnection(_ config: JSObject) throws(JSException) -> Databas fatalError("Only available on WebAssembly") } #endif - let ret = bjs_createDatabaseConnection(Int32(bitPattern: config.id)) + let ret = bjs_createDatabaseConnection(config.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -31,11 +31,7 @@ func createLogger(_ level: String) throws(JSException) -> Logger { fatalError("Only available on WebAssembly") } #endif - var level = level - let levelId = level.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_createLogger(levelId) + let ret = bjs_createLogger(level.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -79,11 +75,11 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_DatabaseConnection_isConnected_get(Int32(bitPattern: self.this.id)) + let ret = bjs_DatabaseConnection_isConnected_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return ret == 1 + return Bool.bridgeJSLiftReturn(ret) } } @@ -97,7 +93,7 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_DatabaseConnection_connectionTimeout_get(Int32(bitPattern: self.this.id)) + let ret = bjs_DatabaseConnection_connectionTimeout_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -114,7 +110,7 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - bjs_DatabaseConnection_connectionTimeout_set(Int32(bitPattern: self.this.id), newValue) + bjs_DatabaseConnection_connectionTimeout_set(self.this.bridgeJSLowerParameter(), newValue) if let error = _swift_js_take_exception() { throw error } @@ -129,11 +125,7 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - var url = url - let urlId = url.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_DatabaseConnection_connect(Int32(bitPattern: self.this.id), urlId) + bjs_DatabaseConnection_connect(self.this.bridgeJSLowerParameter(), url.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -148,15 +140,11 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - var query = query - let queryId = query.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_DatabaseConnection_execute(Int32(bitPattern: self.this.id), queryId) + let ret = bjs_DatabaseConnection_execute(self.this.bridgeJSLowerParameter(), query.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return JSObject(id: UInt32(bitPattern: ret)) + return JSObject.bridgeJSLiftReturn(ret) } } @@ -182,14 +170,11 @@ struct Logger { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Logger_level_get(Int32(bitPattern: self.this.id)) + let ret = bjs_Logger_level_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -202,11 +187,7 @@ struct Logger { fatalError("Only available on WebAssembly") } #endif - var message = message - let messageId = message.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_Logger_log(Int32(bitPattern: self.this.id), messageId) + bjs_Logger_log(self.this.bridgeJSLowerParameter(), message.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -221,11 +202,7 @@ struct Logger { fatalError("Only available on WebAssembly") } #endif - var message = message - let messageId = message.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_Logger_error(Int32(bitPattern: self.this.id), messageId, Int32(bitPattern: error.id)) + bjs_Logger_error(self.this.bridgeJSLowerParameter(), message.bridgeJSLowerParameter(), error.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -254,14 +231,11 @@ struct ConfigManager { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_ConfigManager_configPath_get(Int32(bitPattern: self.this.id)) + let ret = bjs_ConfigManager_configPath_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -274,15 +248,11 @@ struct ConfigManager { fatalError("Only available on WebAssembly") } #endif - var key = key - let keyId = key.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_ConfigManager_get(Int32(bitPattern: self.this.id), keyId) + let ret = bjs_ConfigManager_get(self.this.bridgeJSLowerParameter(), key.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return JSObject(id: UInt32(bitPattern: ret)) + return JSObject.bridgeJSLiftReturn(ret) } func set(_ key: String, _ value: JSObject) throws(JSException) -> Void { @@ -294,11 +264,7 @@ struct ConfigManager { fatalError("Only available on WebAssembly") } #endif - var key = key - let keyId = key.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_ConfigManager_set(Int32(bitPattern: self.this.id), keyId, Int32(bitPattern: value.id)) + bjs_ConfigManager_set(self.this.bridgeJSLowerParameter(), key.bridgeJSLowerParameter(), value.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift index c47f5f40..43b66797 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift @@ -15,7 +15,7 @@ func check(_ a: Double, _ b: Bool) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_check(a, Int32(b ? 1 : 0)) + bjs_check(a, b.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift index cf26a52f..77dcc710 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift @@ -35,5 +35,5 @@ func checkBoolean() throws(JSException) -> Bool { if let error = _swift_js_take_exception() { throw error } - return ret == 1 + return Bool.bridgeJSLiftReturn(ret) } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift index aabffacc..7ecbbac9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift @@ -15,11 +15,7 @@ func checkString(_ a: String) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - var a = a - let aId = a.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_checkString(aId) + bjs_checkString(a.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -34,11 +30,7 @@ func checkStringWithLength(_ a: String, _ b: Double) throws(JSException) -> Void fatalError("Only available on WebAssembly") } #endif - var a = a - let aId = a.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_checkStringWithLength(aId, b) + bjs_checkStringWithLength(a.bridgeJSLowerParameter(), b) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift index af7b5162..05bb8aea 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringReturn.swift @@ -19,8 +19,5 @@ func checkString() throws(JSException) -> String { if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift index 95e9da8c..68acd5bf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift @@ -31,11 +31,7 @@ func createCodeGenerator(_ format: String) throws(JSException) -> CodeGenerator fatalError("Only available on WebAssembly") } #endif - var format = format - let formatId = format.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_createCodeGenerator(formatId) + let ret = bjs_createCodeGenerator(format.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -63,14 +59,11 @@ struct TypeScriptProcessor { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_TypeScriptProcessor_version_get(Int32(bitPattern: self.this.id)) + let ret = bjs_TypeScriptProcessor_version_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -83,18 +76,11 @@ struct TypeScriptProcessor { fatalError("Only available on WebAssembly") } #endif - var ts = ts - let tsId = ts.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_TypeScriptProcessor_convert(Int32(bitPattern: self.this.id), tsId) + let ret = bjs_TypeScriptProcessor_convert(self.this.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } func validate(_ ts: String) throws(JSException) -> Bool { @@ -106,15 +92,11 @@ struct TypeScriptProcessor { fatalError("Only available on WebAssembly") } #endif - var ts = ts - let tsId = ts.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_TypeScriptProcessor_validate(Int32(bitPattern: self.this.id), tsId) + let ret = bjs_TypeScriptProcessor_validate(self.this.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return ret == 1 + return Bool.bridgeJSLiftReturn(ret) } } @@ -140,14 +122,11 @@ struct CodeGenerator { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_CodeGenerator_outputFormat_get(Int32(bitPattern: self.this.id)) + let ret = bjs_CodeGenerator_outputFormat_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -160,14 +139,11 @@ struct CodeGenerator { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_CodeGenerator_generate(Int32(bitPattern: self.this.id), Int32(bitPattern: input.id)) + let ret = bjs_CodeGenerator_generate(self.this.bridgeJSLowerParameter(), input.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift index 7a1f2a2c..f22bfda2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift @@ -26,11 +26,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_Greeter_init(nameId) + let ret = bjs_Greeter_init(name.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -47,14 +43,11 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Greeter_name_get(Int32(bitPattern: self.this.id)) + let ret = bjs_Greeter_name_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -67,11 +60,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - var newValue = newValue - let newValueId = newValue.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_Greeter_name_set(Int32(bitPattern: self.this.id), newValueId) + bjs_Greeter_name_set(self.this.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -87,7 +76,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Greeter_age_get(Int32(bitPattern: self.this.id)) + let ret = bjs_Greeter_age_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -104,14 +93,11 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Greeter_greet(Int32(bitPattern: self.this.id)) + let ret = bjs_Greeter_greet(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } func changeName(_ name: String) throws(JSException) -> Void { @@ -123,11 +109,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_Greeter_changeName(Int32(bitPattern: self.this.id), nameId) + bjs_Greeter_changeName(self.this.bridgeJSLowerParameter(), name.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Sources/JavaScriptKit/BridgeJSInstrincics.swift b/Sources/JavaScriptKit/BridgeJSInstrincics.swift index 1a8bad7f..2756cb59 100644 --- a/Sources/JavaScriptKit/BridgeJSInstrincics.swift +++ b/Sources/JavaScriptKit/BridgeJSInstrincics.swift @@ -1,3 +1,8 @@ +/// BridgeJS Intrinsics +/// +/// This file contains low-level intrinsic functions that are used by code generated +/// by the BridgeJS system. + import _CJavaScriptKit #if !arch(wasm32) @@ -6,60 +11,31 @@ import _CJavaScriptKit } #endif -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_return_string") -@_spi(BridgeJS) public func _swift_js_return_string(_ ptr: UnsafePointer?, _ len: Int32) -#else -@_spi(BridgeJS) public func _swift_js_return_string(_ ptr: UnsafePointer?, _ len: Int32) { - _onlyAvailableOnWasm() -} -#endif - -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_init_memory") -@_spi(BridgeJS) public func _swift_js_init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) -#else -@_spi(BridgeJS) public func _swift_js_init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) { - _onlyAvailableOnWasm() -} -#endif - -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_retain") -@_spi(BridgeJS) public func _swift_js_retain(_ ptr: Int32) -> Int32 -#else -@_spi(BridgeJS) public func _swift_js_retain(_ ptr: Int32) -> Int32 { - _onlyAvailableOnWasm() -} -#endif +// MARK: Exception Handling #if arch(wasm32) @_extern(wasm, module: "bjs", name: "swift_js_throw") @_spi(BridgeJS) public func _swift_js_throw(_ id: Int32) #else +/// Throws a JavaScript exception from Swift code. +/// +/// This function is called by the BridgeJS code generator when a Swift function throws +/// an error. The exception object is retained and stored for later retrieval by the +/// JavaScript-side runtime code. +/// +/// - Parameter id: The ID of the JavaScript exception object to throw @_spi(BridgeJS) public func _swift_js_throw(_ id: Int32) { _onlyAvailableOnWasm() } #endif -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_make_js_string") -@_spi(BridgeJS) public func _swift_js_make_js_string(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 -#else -@_spi(BridgeJS) public func _swift_js_make_js_string(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 { - _onlyAvailableOnWasm() -} -#endif - -#if arch(wasm32) -@_extern(wasm, module: "bjs", name: "swift_js_init_memory_with_result") -@_spi(BridgeJS) public func _swift_js_init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) -#else -@_spi(BridgeJS) public func _swift_js_init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) { - _onlyAvailableOnWasm() -} -#endif - +/// Retrieves and clears any pending JavaScript exception. +/// +/// This function checks for any JavaScript exceptions that were thrown during +/// the execution of imported JavaScript functions. If an exception exists, it +/// is retrieved, cleared from the global storage, and returned as a `JSException`. +/// +/// - Returns: A `JSException` if an exception was pending, `nil` otherwise @_spi(BridgeJS) @_transparent public func _swift_js_take_exception() -> JSException? { #if arch(wasm32) let value = _swift_js_exception_get() @@ -72,3 +48,168 @@ import _CJavaScriptKit _onlyAvailableOnWasm() #endif } + +// MARK: Type lowering/lifting +// +// The following part defines the parameter and return value lowering/lifting +// for a given type. They follow the following pattern: +// +// ```swift +// extension <#TargetType#> { +// // MARK: ImportTS +// @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> <#WasmCoreType#> { +// } +// @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ ...) -> <#Self#> { +// } +// // MARK: ExportSwift +// @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ ...) -> <#Self#> { +// } +// @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> <#WasmCoreType#> { +// } +// } +// ``` +// +// where: +// - <#TargetType#>: the higher-level type to be lowered/lifted +// - <#WasmCoreType#>: the corresponding WebAssembly core type or Void +// - `func bridgeJSLowerParameter()`: lower the given higher-level parameter to a WebAssembly core type +// - `func bridgeJSLiftReturn(_ ...) -> <#TargetType#>`: lift the given Wasm core type return value to a higher-level type +// - `func bridgeJSLiftParameter(_ ...) -> <#TargetType#>`: lift the given Wasm core type parameters to a higher-level type +// - `func bridgeJSLowerReturn() -> <#WasmCoreType#>`: lower the given higher-level return value to a Wasm core type +// +// See JSGlueGen.swift in BridgeJSLink for JS-side lowering/lifting implementation. + +extension Bool { + // MARK: ImportTS + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + self ? 1 : 0 + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Bool { + value == 1 + } + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Bool { + value == 1 + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + self ? 1 : 0 + } +} + +extension Int { + // MARK: ImportTS + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + Int32(self) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Int { + Int(value) + } + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Int { + Int(value) + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + Int32(self) + } +} + +extension String { + // MARK: ImportTS + + @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> Int32 { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_make_js_string") + func _swift_js_make_js_string(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 + #else + /// Creates a JavaScript string from UTF-8 data in WebAssembly memory + func _swift_js_make_js_string(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 { + _onlyAvailableOnWasm() + } + #endif + return self.withUTF8 { b in + _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) + } + } + + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ bytesCount: Int32) -> String { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_init_memory_with_result") + func _swift_js_init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) + #else + /// Initializes WebAssembly memory with result data of JavaScript function call + func _swift_js_init_memory_with_result(_ ptr: UnsafePointer?, _ len: Int32) { + _onlyAvailableOnWasm() + } + guard #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) else { _onlyAvailableOnWasm() } + #endif + return String(unsafeUninitializedCapacity: Int(bytesCount)) { b in + _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(bytesCount)) + return Int(bytesCount) + } + } + + // MARK: ExportSwift + + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ bytes: Int32, _ count: Int32) -> String { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_init_memory") + func _swift_js_init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) + #else + /// Initializes a part of WebAssembly memory with Uint8Array a JavaScript object + func _swift_js_init_memory(_ sourceId: Int32, _ ptr: UnsafeMutablePointer?) { + _onlyAvailableOnWasm() + } + guard #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) else { _onlyAvailableOnWasm() } + #endif + return String(unsafeUninitializedCapacity: Int(count)) { b in + _swift_js_init_memory(bytes, b.baseAddress.unsafelyUnwrapped) + return Int(count) + } + } + + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_return_string") + func _swift_js_return_string(_ ptr: UnsafePointer?, _ len: Int32) + #else + /// Write a string to reserved string storage to be returned to JavaScript + func _swift_js_return_string(_ ptr: UnsafePointer?, _ len: Int32) { + _onlyAvailableOnWasm() + } + #endif + return self.withUTF8 { ptr in + _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + } + } +} + +extension JSObject { + // MARK: ImportTS + + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + Int32(bitPattern: self.id) + } + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ id: Int32) -> JSObject { + JSObject(id: JavaScriptObjectRef(bitPattern: id)) + } + + // MARK: ExportSwift + + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ id: Int32) -> JSObject { + JSObject(id: JavaScriptObjectRef(bitPattern: id)) + } + + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Int32 { + #if arch(wasm32) + @_extern(wasm, module: "bjs", name: "swift_js_retain") + func _swift_js_retain(_ id: Int32) -> Int32 + #else + /// Retains a JavaScript object reference to ensure the object id + /// remains valid after the function returns + func _swift_js_retain(_ id: Int32) -> Int32 { + _onlyAvailableOnWasm() + } + #endif + return _swift_js_retain(Int32(bitPattern: self.id)) + } +} diff --git a/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h b/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h index 4f5ba93c..7ca09860 100644 --- a/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h +++ b/Sources/_CJavaScriptKit/include/BridgeJSInstrincics.h @@ -5,6 +5,8 @@ #include "WasmGlobalMacros.h" #if __wasm__ +// Global thread-local pointer storage for temporarily storing JS exceptions +// thrown from imported JavaScript functions. WASM_GLOBAL_DEFINE_INLINE_ACCESSORS(_swift_js_exception, i32, int32_t) #endif diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index d51f954e..524d7424 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -158,7 +158,7 @@ public func _bjs_roundTripVoid() -> Void { @_cdecl("bjs_roundTripInt") public func _bjs_roundTripInt(v: Int32) -> Int32 { #if arch(wasm32) - let ret = roundTripInt(v: Int(v)) + let ret = roundTripInt(v: Int.bridgeJSLiftParameter(v)) return Int32(ret) #else fatalError("Only available on WebAssembly") @@ -191,8 +191,8 @@ public func _bjs_roundTripDouble(v: Float64) -> Float64 { @_cdecl("bjs_roundTripBool") public func _bjs_roundTripBool(v: Int32) -> Int32 { #if arch(wasm32) - let ret = roundTripBool(v: v == 1) - return Int32(ret ? 1 : 0) + let ret = roundTripBool(v: Bool.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -202,14 +202,8 @@ public func _bjs_roundTripBool(v: Int32) -> Int32 { @_cdecl("bjs_roundTripString") public func _bjs_roundTripString(vBytes: Int32, vLen: Int32) -> Void { #if arch(wasm32) - let v = String(unsafeUninitializedCapacity: Int(vLen)) { b in - _swift_js_init_memory(vBytes, b.baseAddress.unsafelyUnwrapped) - return Int(vLen) - } - var ret = roundTripString(v: v) - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = roundTripString(v: String.bridgeJSLiftParameter(vBytes, vLen)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -230,8 +224,8 @@ public func _bjs_roundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> UnsafeM @_cdecl("bjs_roundTripJSObject") public func _bjs_roundTripJSObject(v: Int32) -> Int32 { #if arch(wasm32) - let ret = roundTripJSObject(v: JSObject(id: UInt32(bitPattern: v))) - return _swift_js_retain(Int32(bitPattern: ret.id)) + let ret = roundTripJSObject(v: JSObject.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -242,7 +236,7 @@ public func _bjs_roundTripJSObject(v: Int32) -> Int32 { public func _bjs_throwsSwiftError(shouldThrow: Int32) -> Void { #if arch(wasm32) do { - try throwsSwiftError(shouldThrow: shouldThrow == 1) + try throwsSwiftError(shouldThrow: Bool.bridgeJSLiftParameter(shouldThrow)) } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -291,10 +285,8 @@ public func _bjs_throwsWithIntResult() -> Int32 { public func _bjs_throwsWithStringResult() -> Void { #if arch(wasm32) do { - var ret = try throwsWithStringResult() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = try throwsWithStringResult() + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -319,7 +311,7 @@ public func _bjs_throwsWithBoolResult() -> Int32 { #if arch(wasm32) do { let ret = try throwsWithBoolResult() - return Int32(ret ? 1 : 0) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -419,7 +411,7 @@ public func _bjs_throwsWithJSObjectResult() -> Int32 { #if arch(wasm32) do { let ret = try throwsWithJSObjectResult() - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -445,7 +437,7 @@ public func _bjs_asyncRoundTripVoid() -> Int32 { let ret = JSPromise.async { await asyncRoundTripVoid() } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -456,9 +448,9 @@ public func _bjs_asyncRoundTripVoid() -> Int32 { public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripInt(v: Int(v)).jsValue + return await asyncRoundTripInt(v: Int.bridgeJSLiftParameter(v)).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -471,7 +463,7 @@ public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { let ret = JSPromise.async { return await asyncRoundTripFloat(v: v).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -484,7 +476,7 @@ public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { let ret = JSPromise.async { return await asyncRoundTripDouble(v: v).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -495,9 +487,9 @@ public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripBool(v: v == 1).jsValue + return await asyncRoundTripBool(v: Bool.bridgeJSLiftParameter(v)).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -508,13 +500,9 @@ public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - let v = String(unsafeUninitializedCapacity: Int(vLen)) { b in - _swift_js_init_memory(vBytes, b.baseAddress.unsafelyUnwrapped) - return Int(vLen) - } - return await asyncRoundTripString(v: v).jsValue + return await asyncRoundTripString(v: String.bridgeJSLiftParameter(vBytes, vLen)).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -527,7 +515,7 @@ public func _bjs_asyncRoundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> In let ret = JSPromise.async { return await asyncRoundTripSwiftHeapObject(v: Unmanaged.fromOpaque(v).takeUnretainedValue()).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -538,9 +526,9 @@ public func _bjs_asyncRoundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> In public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripJSObject(v: JSObject(id: UInt32(bitPattern: v))).jsValue + return await asyncRoundTripJSObject(v: JSObject.bridgeJSLiftParameter(v)).jsValue } .jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -550,11 +538,7 @@ public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { @_cdecl("bjs_takeGreeter") public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - takeGreeter(g: Unmanaged.fromOpaque(g).takeUnretainedValue(), name: name) + takeGreeter(g: Unmanaged.fromOpaque(g).takeUnretainedValue(), name: String.bridgeJSLiftParameter(nameBytes, nameLen)) #else fatalError("Only available on WebAssembly") #endif @@ -575,7 +559,7 @@ public func _bjs_createCalculator() -> UnsafeMutableRawPointer { @_cdecl("bjs_useCalculator") public func _bjs_useCalculator(calc: UnsafeMutableRawPointer, x: Int32, y: Int32) -> Int32 { #if arch(wasm32) - let ret = useCalculator(calc: Unmanaged.fromOpaque(calc).takeUnretainedValue(), x: Int(x), y: Int(y)) + let ret = useCalculator(calc: Unmanaged.fromOpaque(calc).takeUnretainedValue(), x: Int.bridgeJSLiftParameter(x), y: Int.bridgeJSLiftParameter(y)) return Int32(ret) #else fatalError("Only available on WebAssembly") @@ -587,7 +571,7 @@ public func _bjs_useCalculator(calc: UnsafeMutableRawPointer, x: Int32, y: Int32 public func _bjs_testGreeterToJSValue() -> Int32 { #if arch(wasm32) let ret = testGreeterToJSValue() - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -598,7 +582,7 @@ public func _bjs_testGreeterToJSValue() -> Int32 { public func _bjs_testCalculatorToJSValue() -> Int32 { #if arch(wasm32) let ret = testCalculatorToJSValue() - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -609,7 +593,7 @@ public func _bjs_testCalculatorToJSValue() -> Int32 { public func _bjs_testSwiftClassAsJSValue(greeter: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) let ret = testSwiftClassAsJSValue(greeter: Unmanaged.fromOpaque(greeter).takeUnretainedValue()) - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -652,15 +636,8 @@ public func _bjs_processDirection(input: Int32) -> Int32 { @_cdecl("bjs_setTheme") public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { #if arch(wasm32) - let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in - _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) - return Int(themeLen) - } - let ret = setTheme(_: Theme(rawValue: theme)!) - var rawValue = ret.rawValue - return rawValue.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = setTheme(_: Theme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) + return ret.rawValue.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -671,10 +648,7 @@ public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTheme() -> Void { #if arch(wasm32) let ret = getTheme() - var rawValue = ret.rawValue - return rawValue.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + return ret.rawValue.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -706,11 +680,7 @@ public func _bjs_getHttpStatus() -> Int32 { @_cdecl("bjs_processTheme") public func _bjs_processTheme(themeBytes: Int32, themeLen: Int32) -> Int32 { #if arch(wasm32) - let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in - _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) - return Int(themeLen) - } - let ret = processTheme(_: Theme(rawValue: theme)!) + let ret = processTheme(_: Theme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) return Int32(ret.rawValue) #else fatalError("Only available on WebAssembly") @@ -743,15 +713,8 @@ public func _bjs_getTSDirection() -> Int32 { @_cdecl("bjs_setTSTheme") public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { #if arch(wasm32) - let theme = String(unsafeUninitializedCapacity: Int(themeLen)) { b in - _swift_js_init_memory(themeBytes, b.baseAddress.unsafelyUnwrapped) - return Int(themeLen) - } - let ret = setTSTheme(_: TSTheme(rawValue: theme)!) - var rawValue = ret.rawValue - return rawValue.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = setTSTheme(_: TSTheme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) + return ret.rawValue.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -762,10 +725,7 @@ public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTSTheme() -> Void { #if arch(wasm32) let ret = getTSTheme() - var rawValue = ret.rawValue - return rawValue.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + return ret.rawValue.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -775,11 +735,7 @@ public func _bjs_getTSTheme() -> Void { @_cdecl("bjs_createPropertyHolder") public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let stringValue = String(unsafeUninitializedCapacity: Int(stringValueLen)) { b in - _swift_js_init_memory(stringValueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(stringValueLen) - } - let ret = createPropertyHolder(intValue: Int(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: boolValue == 1, stringValue: stringValue, jsObject: JSObject(id: UInt32(bitPattern: jsObject))) + let ret = createPropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLen), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) return Unmanaged.passRetained(ret).toOpaque() #else fatalError("Only available on WebAssembly") @@ -790,10 +746,8 @@ public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doub @_cdecl("bjs_testPropertyHolder") public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = testPropertyHolder(holder: Unmanaged.fromOpaque(holder).takeUnretainedValue()) - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = testPropertyHolder(holder: Unmanaged.fromOpaque(holder).takeUnretainedValue()) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -813,10 +767,8 @@ public func _bjs_resetObserverCounts() -> Void { @_cdecl("bjs_getObserverStats") public func _bjs_getObserverStats() -> Void { #if arch(wasm32) - var ret = getObserverStats() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = getObserverStats() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -826,11 +778,7 @@ public func _bjs_getObserverStats() -> Void { @_cdecl("bjs_Greeter_init") public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - let ret = Greeter(name: name) + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) return Unmanaged.passRetained(ret).toOpaque() #else fatalError("Only available on WebAssembly") @@ -841,10 +789,8 @@ public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutable @_cdecl("bjs_Greeter_greet") public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -854,11 +800,7 @@ public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { @_cdecl("bjs_Greeter_changeName") public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { #if arch(wasm32) - let name = String(unsafeUninitializedCapacity: Int(nameLen)) { b in - _swift_js_init_memory(nameBytes, b.baseAddress.unsafelyUnwrapped) - return Int(nameLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: name) + Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) #else fatalError("Only available on WebAssembly") #endif @@ -868,10 +810,8 @@ public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: I @_cdecl("bjs_Greeter_name_get") public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().name - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().name + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -881,11 +821,7 @@ public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { @_cdecl("bjs_Greeter_name_set") public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { #if arch(wasm32) - let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in - _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(valueLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().name = value + Unmanaged.fromOpaque(_self).takeUnretainedValue().name = String.bridgeJSLiftParameter(valueBytes, valueLen) #else fatalError("Only available on WebAssembly") #endif @@ -895,10 +831,8 @@ public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: In @_cdecl("bjs_Greeter_prefix_get") public func _bjs_Greeter_prefix_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().prefix - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().prefix + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -922,7 +856,7 @@ extension Greeter: ConvertibleToJSValue { @_cdecl("bjs_Calculator_square") public func _bjs_Calculator_square(_self: UnsafeMutableRawPointer, value: Int32) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().square(value: Int(value)) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().square(value: Int.bridgeJSLiftParameter(value)) return Int32(ret) #else fatalError("Only available on WebAssembly") @@ -933,7 +867,7 @@ public func _bjs_Calculator_square(_self: UnsafeMutableRawPointer, value: Int32) @_cdecl("bjs_Calculator_add") public func _bjs_Calculator_add(_self: UnsafeMutableRawPointer, a: Int32, b: Int32) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().add(a: Int(a), b: Int(b)) + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().add(a: Int.bridgeJSLiftParameter(a), b: Int.bridgeJSLiftParameter(b)) return Int32(ret) #else fatalError("Only available on WebAssembly") @@ -969,10 +903,8 @@ public func _bjs_Converter_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_Converter_toString") public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int(value)) - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1066,7 +998,7 @@ extension Internal.TestServer: ConvertibleToJSValue { @_cdecl("bjs_SimplePropertyHolder_init") public func _bjs_SimplePropertyHolder_init(value: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = SimplePropertyHolder(value: Int(value)) + let ret = SimplePropertyHolder(value: Int.bridgeJSLiftParameter(value)) return Unmanaged.passRetained(ret).toOpaque() #else fatalError("Only available on WebAssembly") @@ -1088,7 +1020,7 @@ public func _bjs_SimplePropertyHolder_value_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_SimplePropertyHolder_value_set") public func _bjs_SimplePropertyHolder_value_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().value = Int(value) + Unmanaged.fromOpaque(_self).takeUnretainedValue().value = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1112,11 +1044,7 @@ extension SimplePropertyHolder: ConvertibleToJSValue { @_cdecl("bjs_PropertyHolder_init") public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32, sibling: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { #if arch(wasm32) - let stringValue = String(unsafeUninitializedCapacity: Int(stringValueLen)) { b in - _swift_js_init_memory(stringValueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(stringValueLen) - } - let ret = PropertyHolder(intValue: Int(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: boolValue == 1, stringValue: stringValue, jsObject: JSObject(id: UInt32(bitPattern: jsObject)), sibling: Unmanaged.fromOpaque(sibling).takeUnretainedValue()) + let ret = PropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLen), jsObject: JSObject.bridgeJSLiftParameter(jsObject), sibling: Unmanaged.fromOpaque(sibling).takeUnretainedValue()) return Unmanaged.passRetained(ret).toOpaque() #else fatalError("Only available on WebAssembly") @@ -1127,10 +1055,8 @@ public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubl @_cdecl("bjs_PropertyHolder_getAllValues") public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().getAllValues() - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().getAllValues() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1151,7 +1077,7 @@ public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_intValue_set") public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue = Int(value) + Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1204,7 +1130,7 @@ public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue - return Int32(ret ? 1 : 0) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1214,7 +1140,7 @@ public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_boolValue_set") public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue = value == 1 + Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue = Bool.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1224,10 +1150,8 @@ public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, va @_cdecl("bjs_PropertyHolder_stringValue_get") public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1237,11 +1161,7 @@ public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_stringValue_set") public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { #if arch(wasm32) - let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in - _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(valueLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue = value + Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue = String.bridgeJSLiftParameter(valueBytes, valueLen) #else fatalError("Only available on WebAssembly") #endif @@ -1285,7 +1205,7 @@ public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointe public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyBool - return Int32(ret ? 1 : 0) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1295,10 +1215,8 @@ public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_readonlyString_get") public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyString - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyString + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1309,7 +1227,7 @@ public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointe public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject - return _swift_js_retain(Int32(bitPattern: ret.id)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1319,7 +1237,7 @@ public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_jsObject_set") public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject = JSObject(id: UInt32(bitPattern: value)) + Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject = JSObject.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1350,10 +1268,8 @@ public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, valu @_cdecl("bjs_PropertyHolder_lazyValue_get") public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1363,11 +1279,7 @@ public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_lazyValue_set") public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { #if arch(wasm32) - let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in - _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(valueLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue = value + Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue = String.bridgeJSLiftParameter(valueBytes, valueLen) #else fatalError("Only available on WebAssembly") #endif @@ -1388,10 +1300,8 @@ public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPoin @_cdecl("bjs_PropertyHolder_computedReadWrite_get") public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - var ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite - return ret.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - } + let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1401,11 +1311,7 @@ public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPoi @_cdecl("bjs_PropertyHolder_computedReadWrite_set") public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { #if arch(wasm32) - let value = String(unsafeUninitializedCapacity: Int(valueLen)) { b in - _swift_js_init_memory(valueBytes, b.baseAddress.unsafelyUnwrapped) - return Int(valueLen) - } - Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite = value + Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite = String.bridgeJSLiftParameter(valueBytes, valueLen) #else fatalError("Only available on WebAssembly") #endif @@ -1426,7 +1332,7 @@ public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPoin @_cdecl("bjs_PropertyHolder_observedProperty_set") public func _bjs_PropertyHolder_observedProperty_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty = Int(value) + Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift index 255853fa..733a085f 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift @@ -46,11 +46,11 @@ func jsRoundTripBool(_ v: Bool) throws(JSException) -> Bool { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsRoundTripBool(Int32(v ? 1 : 0)) + let ret = bjs_jsRoundTripBool(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return ret == 1 + return Bool.bridgeJSLiftReturn(ret) } func jsRoundTripString(_ v: String) throws(JSException) -> String { @@ -62,18 +62,11 @@ func jsRoundTripString(_ v: String) throws(JSException) -> String { fatalError("Only available on WebAssembly") } #endif - var v = v - let vId = v.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_jsRoundTripString(vId) + let ret = bjs_jsRoundTripString(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } func jsThrowOrVoid(_ shouldThrow: Bool) throws(JSException) -> Void { @@ -85,7 +78,7 @@ func jsThrowOrVoid(_ shouldThrow: Bool) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_jsThrowOrVoid(Int32(shouldThrow ? 1 : 0)) + bjs_jsThrowOrVoid(shouldThrow.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -100,7 +93,7 @@ func jsThrowOrNumber(_ shouldThrow: Bool) throws(JSException) -> Double { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsThrowOrNumber(Int32(shouldThrow ? 1 : 0)) + let ret = bjs_jsThrowOrNumber(shouldThrow.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -116,11 +109,11 @@ func jsThrowOrBool(_ shouldThrow: Bool) throws(JSException) -> Bool { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsThrowOrBool(Int32(shouldThrow ? 1 : 0)) + let ret = bjs_jsThrowOrBool(shouldThrow.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return ret == 1 + return Bool.bridgeJSLiftReturn(ret) } func jsThrowOrString(_ shouldThrow: Bool) throws(JSException) -> String { @@ -132,14 +125,11 @@ func jsThrowOrString(_ shouldThrow: Bool) throws(JSException) -> String { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsThrowOrString(Int32(shouldThrow ? 1 : 0)) + let ret = bjs_jsThrowOrString(shouldThrow.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } func runAsyncWorks() throws(JSException) -> JSPromise { @@ -178,15 +168,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - var prefix = prefix - let prefixId = prefix.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - let ret = bjs_JsGreeter_init(nameId, prefixId) + let ret = bjs_JsGreeter_init(name.bridgeJSLowerParameter(), prefix.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -203,14 +185,11 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_JsGreeter_name_get(Int32(bitPattern: self.this.id)) + let ret = bjs_JsGreeter_name_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -223,11 +202,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - var newValue = newValue - let newValueId = newValue.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_JsGreeter_name_set(Int32(bitPattern: self.this.id), newValueId) + bjs_JsGreeter_name_set(self.this.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -243,14 +218,11 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_JsGreeter_prefix_get(Int32(bitPattern: self.this.id)) + let ret = bjs_JsGreeter_prefix_get(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } } @@ -263,14 +235,11 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_JsGreeter_greet(Int32(bitPattern: self.this.id)) + let ret = bjs_JsGreeter_greet(self.this.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return String(unsafeUninitializedCapacity: Int(ret)) { b in - _swift_js_init_memory_with_result(b.baseAddress.unsafelyUnwrapped, Int32(ret)) - return Int(ret) - } + return String.bridgeJSLiftReturn(ret) } func changeName(_ name: String) throws(JSException) -> Void { @@ -282,11 +251,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - var name = name - let nameId = name.withUTF8 { b in - _swift_js_make_js_string(b.baseAddress.unsafelyUnwrapped, Int32(b.count)) - } - bjs_JsGreeter_changeName(Int32(bitPattern: self.this.id), nameId) + bjs_JsGreeter_changeName(self.this.bridgeJSLowerParameter(), name.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } From 0df766ef91d4de35bf402130908f26f853b4fd62 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 23 Aug 2025 21:42:53 +0900 Subject: [PATCH 46/61] ExportSwift: Remove unused className variable Clean up variable declaration in VariableDeclSyntax visitor. --- Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index f170c03d..e3c1a1c8 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -334,7 +334,7 @@ public class ExportSwift { override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { guard node.attributes.hasJSAttribute() else { return .skipChildren } - guard case .classBody(let className, let classKey) = state else { + guard case .classBody(_, let classKey) = state else { diagnose(node: node, message: "@JS var must be inside a @JS class") return .skipChildren } From e5465823345cf0d535a33596380b549fe87f4b24 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 23 Aug 2025 23:07:05 +0900 Subject: [PATCH 47/61] BridgeJS: Add protocol-based intrinsic system Introduce _BridgedSwiftTypeLoweredIntoSingleWasmCoreType protocol to formalize lift/lower operations. All basic types (Bool, Int, Float, Double) now conform to this protocol with standardized bridgeJS* methods. Add _BridgedSwiftHeapObject and _BridgedSwiftEnumNoPayload protocols for heap objects and enums respectively. Update generated code and test snapshots to use new protocol-based intrinsic functions. --- .../Generated/BridgeJS.ExportSwift.swift | 32 +- .../Sources/BridgeJSCore/ExportSwift.swift | 342 ++++++++------- .../Sources/BridgeJSCore/ImportTS.swift | 9 +- .../Sources/BridgeJSCore/Utilities.swift | 6 + .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 2 +- .../ExportSwiftTests/Async.swift | 8 +- .../ExportSwiftTests/EnumCase.swift | 63 ++- .../ExportSwiftTests/EnumNamespace.swift | 79 +++- .../ExportSwiftTests/EnumRawType.swift | 116 ++++-- .../ExportSwiftTests/Namespaces.swift | 38 +- .../PrimitiveParameters.swift | 2 +- .../ExportSwiftTests/PrimitiveReturn.swift | 6 +- .../ExportSwiftTests/PropertyTypes.swift | 100 ++--- .../ExportSwiftTests/StringParameter.swift | 4 +- .../ExportSwiftTests/SwiftClass.swift | 28 +- .../JavaScriptKit/BridgeJSInstrincics.swift | 121 +++++- .../Generated/BridgeJS.ExportSwift.swift | 391 ++++++++++++------ .../WebWorkerDedicatedExecutorTests.swift | 1 + 18 files changed, 870 insertions(+), 478 deletions(-) create mode 100644 Plugins/BridgeJS/Sources/BridgeJSCore/Utilities.swift diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift index 5deb14b9..9e4515f5 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ExportSwift.swift @@ -11,7 +11,7 @@ public func _bjs_PlayBridgeJS_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = PlayBridgeJS() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -19,11 +19,11 @@ public func _bjs_PlayBridgeJS_init() -> UnsafeMutableRawPointer { @_expose(wasm, "bjs_PlayBridgeJS_update") @_cdecl("bjs_PlayBridgeJS_update") -public func _bjs_PlayBridgeJS_update(_self: UnsafeMutableRawPointer, swiftSourceBytes: Int32, swiftSourceLen: Int32, dtsSourceBytes: Int32, dtsSourceLen: Int32) -> UnsafeMutableRawPointer { +public func _bjs_PlayBridgeJS_update(_self: UnsafeMutableRawPointer, swiftSourceBytes: Int32, swiftSourceLength: Int32, dtsSourceBytes: Int32, dtsSourceLength: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) do { - let ret = try Unmanaged.fromOpaque(_self).takeUnretainedValue().update(swiftSource: String.bridgeJSLiftParameter(swiftSourceBytes, swiftSourceLen), dtsSource: String.bridgeJSLiftParameter(dtsSourceBytes, dtsSourceLen)) - return Unmanaged.passRetained(ret).toOpaque() + let ret = try PlayBridgeJS.bridgeJSLiftParameter(_self).update(swiftSource: String.bridgeJSLiftParameter(swiftSourceBytes, swiftSourceLength), dtsSource: String.bridgeJSLiftParameter(dtsSourceBytes, dtsSourceLength)) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -48,10 +48,16 @@ public func _bjs_PlayBridgeJS_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension PlayBridgeJS: ConvertibleToJSValue { +extension PlayBridgeJS: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJS_wrap") func _bjs_PlayBridgeJS_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PlayBridgeJS_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJS_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -60,7 +66,7 @@ extension PlayBridgeJS: ConvertibleToJSValue { @_cdecl("bjs_PlayBridgeJSOutput_outputJs") public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().outputJs() + let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).outputJs() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -71,7 +77,7 @@ public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PlayBridgeJSOutput_outputDts") public func _bjs_PlayBridgeJSOutput_outputDts(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().outputDts() + let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).outputDts() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -82,7 +88,7 @@ public func _bjs_PlayBridgeJSOutput_outputDts(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PlayBridgeJSOutput_importSwiftGlue") public func _bjs_PlayBridgeJSOutput_importSwiftGlue(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().importSwiftGlue() + let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).importSwiftGlue() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -93,7 +99,7 @@ public func _bjs_PlayBridgeJSOutput_importSwiftGlue(_self: UnsafeMutableRawPoint @_cdecl("bjs_PlayBridgeJSOutput_exportSwiftGlue") public func _bjs_PlayBridgeJSOutput_exportSwiftGlue(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().exportSwiftGlue() + let ret = PlayBridgeJSOutput.bridgeJSLiftParameter(_self).exportSwiftGlue() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -106,10 +112,16 @@ public func _bjs_PlayBridgeJSOutput_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension PlayBridgeJSOutput: ConvertibleToJSValue { +extension PlayBridgeJSOutput: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJSOutput_wrap") func _bjs_PlayBridgeJSOutput_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PlayBridgeJSOutput_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJSOutput_wrap(Unmanaged.passRetained(self).toOpaque())))) } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index e3c1a1c8..cefb334f 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -51,7 +51,7 @@ public class ExportSwift { /// - Returns: A tuple containing the generated Swift code and a skeleton /// describing the exported APIs public func finalize() throws -> (outputSwift: String, outputSkeleton: ExportedSkeleton)? { - guard let outputSwift = renderSwiftGlue() else { + guard let outputSwift = try renderSwiftGlue() else { return nil } return ( @@ -710,22 +710,26 @@ public class ExportSwift { @_spi(BridgeJS) import JavaScriptKit """ - func renderSwiftGlue() -> String? { + func renderSwiftGlue() throws -> String? { var decls: [DeclSyntax] = [] guard exportedFunctions.count > 0 || exportedClasses.count > 0 || exportedEnums.count > 0 else { return nil } decls.append(Self.prelude) - for enumDef in exportedEnums where enumDef.enumType == .simple { - decls.append(renderCaseEnumHelpers(enumDef)) + for enumDef in exportedEnums { + if enumDef.enumType == .simple { + decls.append(renderCaseEnumHelpers(enumDef)) + } else { + decls.append("extension \(raw: enumDef.swiftCallName): _BridgedSwiftEnumNoPayload {}") + } } for function in exportedFunctions { - decls.append(renderSingleExportedFunction(function: function)) + decls.append(try renderSingleExportedFunction(function: function)) } for klass in exportedClasses { - decls.append(contentsOf: renderSingleExportedClass(klass: klass)) + decls.append(contentsOf: try renderSingleExportedClass(klass: klass)) } let format = BasicFormat() return decls.map { $0.formatted(using: format).description }.joined(separator: "\n\n") @@ -746,11 +750,24 @@ public class ExportSwift { return """ extension \(raw: typeName) { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> \(raw: typeName) { + return \(raw: typeName)(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> \(raw: typeName) { + return \(raw: typeName)(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { \(raw: initSwitch) } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { \(raw: valueSwitch) } } @@ -778,87 +795,22 @@ public class ExportSwift { self.body.append(item) } - func liftParameter(param: Parameter) { + func liftParameter(param: Parameter) throws { parameters.append(param) - switch param.type { - case .bool: - liftedParameterExprs.append( - ExprSyntax("\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: param.name))") - ) - abiParameterSignatures.append((param.name, .i32)) - case .int: - liftedParameterExprs.append( - ExprSyntax("\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: param.name))") - ) - abiParameterSignatures.append((param.name, .i32)) - case .float: - liftedParameterExprs.append(ExprSyntax("\(raw: param.name)")) - abiParameterSignatures.append((param.name, .f32)) - case .double: - liftedParameterExprs.append(ExprSyntax("\(raw: param.name)")) - abiParameterSignatures.append((param.name, .f64)) - case .string: - let bytesLabel = "\(param.name)Bytes" - let lengthLabel = "\(param.name)Len" - liftedParameterExprs.append( - ExprSyntax( - "\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: bytesLabel), \(raw: lengthLabel))" - ) - ) - abiParameterSignatures.append((bytesLabel, .i32)) - abiParameterSignatures.append((lengthLabel, .i32)) - case .caseEnum(let enumName): - liftedParameterExprs.append(ExprSyntax("\(raw: enumName)(bridgeJSRawValue: \(raw: param.name))!")) - abiParameterSignatures.append((param.name, .i32)) - case .rawValueEnum(let enumName, let rawType): - if rawType == .string { - let bytesLabel = "\(param.name)Bytes" - let lengthLabel = "\(param.name)Len" - liftedParameterExprs.append( - ExprSyntax( - "\(raw: enumName)(rawValue: String.bridgeJSLiftParameter(\(raw: bytesLabel), \(raw: lengthLabel)))!" - ) - ) - abiParameterSignatures.append((bytesLabel, .i32)) - abiParameterSignatures.append((lengthLabel, .i32)) - } else { - let conversionExpr: String - switch rawType { - case .bool: - conversionExpr = - "\(enumName)(rawValue: \(param.type.swiftType).bridgeJSLiftParameter(\(param.name)))" - case .uint, .uint32, .uint64: - if rawType == .uint64 { - conversionExpr = - "\(enumName)(rawValue: \(rawType.rawValue)(bitPattern: Int64(\(param.name))))!" - } else { - conversionExpr = "\(enumName)(rawValue: \(rawType.rawValue)(bitPattern: \(param.name)))!" - } - default: - conversionExpr = "\(enumName)(rawValue: \(rawType.rawValue)(\(param.name)))!" - } - - liftedParameterExprs.append(ExprSyntax(stringLiteral: conversionExpr)) - if let wasmType = rawType.wasmCoreType { - abiParameterSignatures.append((param.name, wasmType)) - } - } - case .associatedValueEnum(_): - break - case .namespaceEnum: - break - case .jsObject(let name): - liftedParameterExprs.append( - ExprSyntax("\(raw: name ?? "JSObject").bridgeJSLiftParameter(\(raw: param.name))") + let liftingInfo = try param.type.liftParameterInfo() + let argumentsToLift: [String] + if liftingInfo.parameters.count == 1 { + argumentsToLift = [param.name] + } else { + argumentsToLift = liftingInfo.parameters.map { (name, _) in param.name + name.capitalizedFirstLetter } + } + liftedParameterExprs.append( + ExprSyntax( + "\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))" ) - abiParameterSignatures.append((param.name, .i32)) - case .swiftHeapObject: - let objectExpr: ExprSyntax = - "Unmanaged<\(raw: param.type.swiftType)>.fromOpaque(\(raw: param.name)).takeUnretainedValue()" - liftedParameterExprs.append(objectExpr) - abiParameterSignatures.append((param.name, .pointer)) - case .void: - break + ) + for (name, type) in zip(argumentsToLift, liftingInfo.parameters.map { $0.type }) { + abiParameterSignatures.append((name, type)) } } @@ -928,97 +880,28 @@ public class ExportSwift { append("\(raw: selfExpr).\(raw: propertyName) = \(raw: newValueExpr)") } - func lowerReturnValue(returnType: BridgeType) { + func lowerReturnValue(returnType: BridgeType) throws { if effects.isAsync { // Async functions always return a Promise, which is a JSObject - _lowerReturnValue(returnType: .jsObject(nil)) + try _lowerReturnValue(returnType: .jsObject(nil)) } else { - _lowerReturnValue(returnType: returnType) + try _lowerReturnValue(returnType: returnType) } } - private func _lowerReturnValue(returnType: BridgeType) { - switch returnType { - case .void: - abiReturnType = nil - case .bool: - abiReturnType = .i32 - case .int: - abiReturnType = .i32 - case .float: - abiReturnType = .f32 - case .double: - abiReturnType = .f64 - case .string: - abiReturnType = nil - case .jsObject: - abiReturnType = .i32 - case .swiftHeapObject: - // UnsafeMutableRawPointer is returned as an i32 pointer - abiReturnType = .pointer - case .caseEnum: - abiReturnType = .i32 - case .rawValueEnum(_, let rawType): - abiReturnType = rawType == .string ? nil : rawType.wasmCoreType - case .associatedValueEnum: - abiReturnType = nil - case .namespaceEnum: - abiReturnType = nil + private func _lowerReturnValue(returnType: BridgeType) throws { + let loweringInfo = try returnType.loweringReturnInfo() + abiReturnType = loweringInfo.returnType + if returnType == .void { + return } - if effects.isAsync { // The return value of async function (T of `(...) async -> T`) is // handled by the JSPromise.async, so we don't need to do anything here. return } - switch returnType { - case .void: break - case .int, .float, .double: - append("return \(raw: abiReturnType!.swiftType)(ret)") - case .bool: - append("return ret.bridgeJSLowerReturn()") - case .string: - append("return ret.bridgeJSLowerReturn()") - case .caseEnum: - abiReturnType = .i32 - append("return ret.bridgeJSRawValue") - case .rawValueEnum(_, let rawType): - if rawType == .string { - append( - """ - return ret.rawValue.bridgeJSLowerReturn() - """ - ) - } else { - switch rawType { - case .bool: - append("return Int32(ret.rawValue ? 1 : 0)") - case .int, .int32, .uint, .uint32: - append("return Int32(ret.rawValue)") - case .int64, .uint64: - append("return Int64(ret.rawValue)") - case .float: - append("return Float32(ret.rawValue)") - case .double: - append("return Float64(ret.rawValue)") - default: - append("return Int32(ret.rawValue)") - } - } - case .associatedValueEnum: break; - case .namespaceEnum: break; - case .jsObject: - append("return ret.bridgeJSLowerReturn()") - case .swiftHeapObject: - // Perform a manual retain on the object, which will be balanced by a - // release called via FinalizationRegistry - append( - """ - return Unmanaged.passRetained(ret).toOpaque() - """ - ) - } + append("return ret.bridgeJSLowerReturn()") } func render(abiName: String) -> DeclSyntax { @@ -1088,13 +971,13 @@ public class ExportSwift { } } - func renderSingleExportedFunction(function: ExportedFunction) -> DeclSyntax { + func renderSingleExportedFunction(function: ExportedFunction) throws -> DeclSyntax { let builder = ExportedThunkBuilder(effects: function.effects) for param in function.parameters { - builder.liftParameter(param: param) + try builder.liftParameter(param: param) } builder.call(name: function.name, returnType: function.returnType) - builder.lowerReturnValue(returnType: function.returnType) + try builder.lowerReturnValue(returnType: function.returnType) return builder.render(abiName: function.abiName) } @@ -1145,32 +1028,32 @@ public class ExportSwift { /// Unmanaged.fromOpaque(pointer).release() /// } /// ``` - func renderSingleExportedClass(klass: ExportedClass) -> [DeclSyntax] { + func renderSingleExportedClass(klass: ExportedClass) throws -> [DeclSyntax] { var decls: [DeclSyntax] = [] if let constructor = klass.constructor { let builder = ExportedThunkBuilder(effects: constructor.effects) for param in constructor.parameters { - builder.liftParameter(param: param) + try builder.liftParameter(param: param) } builder.call(name: klass.swiftCallName, returnType: BridgeType.swiftHeapObject(klass.name)) - builder.lowerReturnValue(returnType: BridgeType.swiftHeapObject(klass.name)) + try builder.lowerReturnValue(returnType: BridgeType.swiftHeapObject(klass.name)) decls.append(builder.render(abiName: constructor.abiName)) } for method in klass.methods { let builder = ExportedThunkBuilder(effects: method.effects) - builder.liftParameter( + try builder.liftParameter( param: Parameter(label: nil, name: "_self", type: BridgeType.swiftHeapObject(klass.swiftCallName)) ) for param in method.parameters { - builder.liftParameter(param: param) + try builder.liftParameter(param: param) } builder.callMethod( klassName: klass.swiftCallName, methodName: method.name, returnType: method.returnType ) - builder.lowerReturnValue(returnType: method.returnType) + try builder.lowerReturnValue(returnType: method.returnType) decls.append(builder.render(abiName: method.abiName)) } @@ -1178,7 +1061,7 @@ public class ExportSwift { for property in klass.properties { // Generate getter let getterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) - getterBuilder.liftParameter( + try getterBuilder.liftParameter( param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(klass.name)) ) getterBuilder.callPropertyGetter( @@ -1186,23 +1069,23 @@ public class ExportSwift { propertyName: property.name, returnType: property.type ) - getterBuilder.lowerReturnValue(returnType: property.type) + try getterBuilder.lowerReturnValue(returnType: property.type) decls.append(getterBuilder.render(abiName: property.getterAbiName(className: klass.name))) // Generate setter if property is not readonly if !property.isReadonly { let setterBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false)) - setterBuilder.liftParameter( + try setterBuilder.liftParameter( param: Parameter(label: nil, name: "_self", type: .swiftHeapObject(klass.name)) ) - setterBuilder.liftParameter( + try setterBuilder.liftParameter( param: Parameter(label: "value", name: "value", type: property.type) ) setterBuilder.callPropertySetter( klassName: klass.name, propertyName: property.name ) - setterBuilder.lowerReturnValue(returnType: .void) + try setterBuilder.lowerReturnValue(returnType: .void) decls.append(setterBuilder.render(abiName: property.setterAbiName(className: klass.name))) } } @@ -1232,7 +1115,7 @@ public class ExportSwift { /// For a class named `Greeter`, this generates: /// /// ```swift - /// extension Greeter: ConvertibleToJSValue { + /// extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { /// var jsValue: JSValue { /// @_extern(wasm, module: "MyModule", name: "bjs_Greeter_wrap") /// func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 @@ -1245,10 +1128,16 @@ public class ExportSwift { let externFunctionName = "bjs_\(klass.name)_wrap" return """ - extension \(raw: klass.swiftCallName): ConvertibleToJSValue { + extension \(raw: klass.swiftCallName): ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "\(raw: moduleName)", name: "\(raw: externFunctionName)") func \(raw: wrapFunctionName)(_: UnsafeMutableRawPointer) -> Int32 + #else + func \(raw: wrapFunctionName)(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: \(raw: wrapFunctionName)(Unmanaged.passRetained(self).toOpaque())))) } } @@ -1325,4 +1214,95 @@ extension BridgeType { case .namespaceEnum(let name): return name } } + + struct LiftingIntrinsicInfo: Sendable { + let parameters: [(name: String, type: WasmCoreType)] + + static let bool = LiftingIntrinsicInfo(parameters: [("value", .i32)]) + static let int = LiftingIntrinsicInfo(parameters: [("value", .i32)]) + static let float = LiftingIntrinsicInfo(parameters: [("value", .f32)]) + static let double = LiftingIntrinsicInfo(parameters: [("value", .f64)]) + static let string = LiftingIntrinsicInfo(parameters: [("bytes", .i32), ("length", .i32)]) + static let jsObject = LiftingIntrinsicInfo(parameters: [("value", .i32)]) + static let swiftHeapObject = LiftingIntrinsicInfo(parameters: [("value", .pointer)]) + static let void = LiftingIntrinsicInfo(parameters: []) + static let caseEnum = LiftingIntrinsicInfo(parameters: [("value", .i32)]) + } + + func liftParameterInfo() throws -> LiftingIntrinsicInfo { + switch self { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .jsObject: return .jsObject + case .swiftHeapObject: return .swiftHeapObject + case .void: return .void + case .caseEnum: return .caseEnum + case .rawValueEnum(_, let rawType): + switch rawType { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .int32: return .int + case .int64: return .int + case .uint: return .int + case .uint32: return .int + case .uint64: return .int + } + case .associatedValueEnum: + throw BridgeJSCoreError("Associated value enums are not supported to pass as parameters") + case .namespaceEnum: + throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") + } + } + + struct LoweringIntrinsicInfo: Sendable { + let returnType: WasmCoreType? + + static let bool = LoweringIntrinsicInfo(returnType: .i32) + static let int = LoweringIntrinsicInfo(returnType: .i32) + static let float = LoweringIntrinsicInfo(returnType: .f32) + static let double = LoweringIntrinsicInfo(returnType: .f64) + static let string = LoweringIntrinsicInfo(returnType: nil) + static let jsObject = LoweringIntrinsicInfo(returnType: .i32) + static let swiftHeapObject = LoweringIntrinsicInfo(returnType: .pointer) + static let void = LoweringIntrinsicInfo(returnType: nil) + static let caseEnum = LoweringIntrinsicInfo(returnType: .i32) + static let rawValueEnum = LoweringIntrinsicInfo(returnType: .i32) + } + + func loweringReturnInfo() throws -> LoweringIntrinsicInfo { + switch self { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .jsObject: return .jsObject + case .swiftHeapObject: return .swiftHeapObject + case .void: return .void + case .caseEnum: return .caseEnum + case .rawValueEnum(_, let rawType): + switch rawType { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .int32: return .int + case .int64: return .int + case .uint: return .int + case .uint32: return .int + case .uint64: return .int + } + case .associatedValueEnum: + throw BridgeJSCoreError("Associated value enums are not supported to pass as parameters") + case .namespaceEnum: + throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters") + } + } } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 9475c784..bf836637 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -371,7 +371,7 @@ public struct ImportTS { try builder.lowerParameter(param: newValue) builder.call(returnType: .void) return builder.renderThunkDecl( - name: "set\(property.name.capitalizedFirstLetter())", + name: "set\(property.name.capitalizedFirstLetter)", parameters: [newValue], returnType: .void ) @@ -483,10 +483,3 @@ public struct ImportTS { ) } } - -fileprivate extension String { - func capitalizedFirstLetter() -> String { - guard !isEmpty else { return self } - return prefix(1).uppercased() + dropFirst() - } -} diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/Utilities.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/Utilities.swift new file mode 100644 index 00000000..fb3a6e16 --- /dev/null +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/Utilities.swift @@ -0,0 +1,6 @@ +extension String { + var capitalizedFirstLetter: String { + guard !isEmpty else { return self } + return prefix(1).uppercased() + dropFirst() + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 47083729..76385d41 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -10,7 +10,7 @@ public enum BridgeType: Codable, Equatable { case namespaceEnum(String) } -public enum WasmCoreType: String, Codable { +public enum WasmCoreType: String, Codable, Sendable { case i32, i64, f32, f64, pointer } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift index fe31c9f1..33ddda25 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Async.swift @@ -34,10 +34,10 @@ public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { @_expose(wasm, "bjs_asyncRoundTripString") @_cdecl("bjs_asyncRoundTripString") -public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { +public func _bjs_asyncRoundTripString(vBytes: Int32, vLength: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripString(_: String.bridgeJSLiftParameter(vBytes, vLen)).jsValue + return await asyncRoundTripString(_: String.bridgeJSLiftParameter(vBytes, vLength)).jsValue } .jsObject return ret.bridgeJSLowerReturn() #else @@ -63,7 +63,7 @@ public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripFloat(_: v).jsValue + return await asyncRoundTripFloat(_: Float.bridgeJSLiftParameter(v)).jsValue } .jsObject return ret.bridgeJSLowerReturn() #else @@ -76,7 +76,7 @@ public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripDouble(_: v).jsValue + return await asyncRoundTripDouble(_: Double.bridgeJSLiftParameter(v)).jsValue } .jsObject return ret.bridgeJSLowerReturn() #else diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift index 363ade82..8b087111 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift @@ -7,7 +7,20 @@ @_spi(BridgeJS) import JavaScriptKit extension Direction { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Direction { + return Direction(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Direction { + return Direction(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .north @@ -22,7 +35,7 @@ extension Direction { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .north: return 0 @@ -37,7 +50,20 @@ extension Direction { } extension Status { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Status { + return Status(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Status { + return Status(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .loading @@ -50,7 +76,7 @@ extension Status { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .loading: return 0 @@ -63,7 +89,20 @@ extension Status { } extension TSDirection { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> TSDirection { + return TSDirection(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> TSDirection { + return TSDirection(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .north @@ -78,7 +117,7 @@ extension TSDirection { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .north: return 0 @@ -96,7 +135,7 @@ extension TSDirection { @_cdecl("bjs_setDirection") public func _bjs_setDirection(direction: Int32) -> Void { #if arch(wasm32) - setDirection(_: Direction(bridgeJSRawValue: direction)!) + setDirection(_: Direction.bridgeJSLiftParameter(direction)) #else fatalError("Only available on WebAssembly") #endif @@ -107,7 +146,7 @@ public func _bjs_setDirection(direction: Int32) -> Void { public func _bjs_getDirection() -> Int32 { #if arch(wasm32) let ret = getDirection() - return ret.bridgeJSRawValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -117,8 +156,8 @@ public func _bjs_getDirection() -> Int32 { @_cdecl("bjs_processDirection") public func _bjs_processDirection(input: Int32) -> Int32 { #if arch(wasm32) - let ret = processDirection(_: Direction(bridgeJSRawValue: input)!) - return ret.bridgeJSRawValue + let ret = processDirection(_: Direction.bridgeJSLiftParameter(input)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -128,7 +167,7 @@ public func _bjs_processDirection(input: Int32) -> Int32 { @_cdecl("bjs_setTSDirection") public func _bjs_setTSDirection(direction: Int32) -> Void { #if arch(wasm32) - setTSDirection(_: TSDirection(bridgeJSRawValue: direction)!) + setTSDirection(_: TSDirection.bridgeJSLiftParameter(direction)) #else fatalError("Only available on WebAssembly") #endif @@ -139,7 +178,7 @@ public func _bjs_setTSDirection(direction: Int32) -> Void { public func _bjs_getTSDirection() -> Int32 { #if arch(wasm32) let ret = getTSDirection() - return ret.bridgeJSRawValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift index e2a6588a..bb0fbe43 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumNamespace.swift @@ -6,8 +6,24 @@ @_spi(BridgeJS) import JavaScriptKit +extension Utils: _BridgedSwiftEnumNoPayload { +} + extension Networking.API.Method { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Networking.API.Method { + return Networking.API.Method(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Networking.API.Method { + return Networking.API.Method(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .get @@ -22,7 +38,7 @@ extension Networking.API.Method { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .get: return 0 @@ -36,8 +52,27 @@ extension Networking.API.Method { } } +extension Configuration.LogLevel: _BridgedSwiftEnumNoPayload { +} + +extension Configuration.Port: _BridgedSwiftEnumNoPayload { +} + extension Internal.SupportedMethod { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Internal.SupportedMethod { + return Internal.SupportedMethod(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Internal.SupportedMethod { + return Internal.SupportedMethod(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .get @@ -48,7 +83,7 @@ extension Internal.SupportedMethod { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .get: return 0 @@ -63,7 +98,7 @@ extension Internal.SupportedMethod { public func _bjs_Converter_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = Utils.Converter() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -73,7 +108,7 @@ public func _bjs_Converter_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_Converter_toString") public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int.bridgeJSLiftParameter(value)) + let ret = Utils.Converter.bridgeJSLiftParameter(_self).toString(value: Int.bridgeJSLiftParameter(value)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -86,10 +121,16 @@ public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Utils.Converter: ConvertibleToJSValue { +extension Utils.Converter: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_Converter_wrap") func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -99,7 +140,7 @@ extension Utils.Converter: ConvertibleToJSValue { public func _bjs_HTTPServer_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = Networking.API.HTTPServer() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -109,7 +150,7 @@ public func _bjs_HTTPServer_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_HTTPServer_call") public func _bjs_HTTPServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Networking.API.Method(bridgeJSRawValue: method)!) + Networking.API.HTTPServer.bridgeJSLiftParameter(_self).call(_: Networking.API.Method.bridgeJSLiftParameter(method)) #else fatalError("Only available on WebAssembly") #endif @@ -121,10 +162,16 @@ public func _bjs_HTTPServer_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Networking.API.HTTPServer: ConvertibleToJSValue { +extension Networking.API.HTTPServer: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_HTTPServer_wrap") func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_HTTPServer_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -134,7 +181,7 @@ extension Networking.API.HTTPServer: ConvertibleToJSValue { public func _bjs_TestServer_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = Internal.TestServer() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -144,7 +191,7 @@ public func _bjs_TestServer_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_TestServer_call") public func _bjs_TestServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Internal.SupportedMethod(bridgeJSRawValue: method)!) + Internal.TestServer.bridgeJSLiftParameter(_self).call(_: Internal.SupportedMethod.bridgeJSLiftParameter(method)) #else fatalError("Only available on WebAssembly") #endif @@ -156,10 +203,16 @@ public func _bjs_TestServer_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Internal.TestServer: ConvertibleToJSValue { +extension Internal.TestServer: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_TestServer_wrap") func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_TestServer_wrap(Unmanaged.passRetained(self).toOpaque())))) } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift index 9f8f2ec3..e0a32e84 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumRawType.swift @@ -6,11 +6,47 @@ @_spi(BridgeJS) import JavaScriptKit +extension Theme: _BridgedSwiftEnumNoPayload { +} + +extension TSTheme: _BridgedSwiftEnumNoPayload { +} + +extension FeatureFlag: _BridgedSwiftEnumNoPayload { +} + +extension HttpStatus: _BridgedSwiftEnumNoPayload { +} + +extension TSHttpStatus: _BridgedSwiftEnumNoPayload { +} + +extension Priority: _BridgedSwiftEnumNoPayload { +} + +extension FileSize: _BridgedSwiftEnumNoPayload { +} + +extension UserId: _BridgedSwiftEnumNoPayload { +} + +extension TokenId: _BridgedSwiftEnumNoPayload { +} + +extension SessionId: _BridgedSwiftEnumNoPayload { +} + +extension Precision: _BridgedSwiftEnumNoPayload { +} + +extension Ratio: _BridgedSwiftEnumNoPayload { +} + @_expose(wasm, "bjs_setTheme") @_cdecl("bjs_setTheme") -public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { +public func _bjs_setTheme(themeBytes: Int32, themeLength: Int32) -> Void { #if arch(wasm32) - setTheme(_: Theme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) + setTheme(_: Theme.bridgeJSLiftParameter(themeBytes, themeLength)) #else fatalError("Only available on WebAssembly") #endif @@ -21,7 +57,7 @@ public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTheme() -> Void { #if arch(wasm32) let ret = getTheme() - return ret.rawValue.bridgeJSLowerReturn() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -29,9 +65,9 @@ public func _bjs_getTheme() -> Void { @_expose(wasm, "bjs_setTSTheme") @_cdecl("bjs_setTSTheme") -public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { +public func _bjs_setTSTheme(themeBytes: Int32, themeLength: Int32) -> Void { #if arch(wasm32) - setTSTheme(_: TSTheme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) + setTSTheme(_: TSTheme.bridgeJSLiftParameter(themeBytes, themeLength)) #else fatalError("Only available on WebAssembly") #endif @@ -42,7 +78,7 @@ public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTSTheme() -> Void { #if arch(wasm32) let ret = getTSTheme() - return ret.rawValue.bridgeJSLowerReturn() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -52,7 +88,7 @@ public func _bjs_getTSTheme() -> Void { @_cdecl("bjs_setFeatureFlag") public func _bjs_setFeatureFlag(flag: Int32) -> Void { #if arch(wasm32) - setFeatureFlag(_: FeatureFlag(rawValue: FeatureFlag.bridgeJSLiftParameter(flag))) + setFeatureFlag(_: FeatureFlag.bridgeJSLiftParameter(flag)) #else fatalError("Only available on WebAssembly") #endif @@ -63,7 +99,7 @@ public func _bjs_setFeatureFlag(flag: Int32) -> Void { public func _bjs_getFeatureFlag() -> Int32 { #if arch(wasm32) let ret = getFeatureFlag() - return Int32(ret.rawValue ? 1 : 0) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -73,7 +109,7 @@ public func _bjs_getFeatureFlag() -> Int32 { @_cdecl("bjs_setHttpStatus") public func _bjs_setHttpStatus(status: Int32) -> Void { #if arch(wasm32) - setHttpStatus(_: HttpStatus(rawValue: Int(status))!) + setHttpStatus(_: HttpStatus.bridgeJSLiftParameter(status)) #else fatalError("Only available on WebAssembly") #endif @@ -84,7 +120,7 @@ public func _bjs_setHttpStatus(status: Int32) -> Void { public func _bjs_getHttpStatus() -> Int32 { #if arch(wasm32) let ret = getHttpStatus() - return Int32(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -94,7 +130,7 @@ public func _bjs_getHttpStatus() -> Int32 { @_cdecl("bjs_setTSHttpStatus") public func _bjs_setTSHttpStatus(status: Int32) -> Void { #if arch(wasm32) - setTSHttpStatus(_: TSHttpStatus(rawValue: Int(status))!) + setTSHttpStatus(_: TSHttpStatus.bridgeJSLiftParameter(status)) #else fatalError("Only available on WebAssembly") #endif @@ -105,7 +141,7 @@ public func _bjs_setTSHttpStatus(status: Int32) -> Void { public func _bjs_getTSHttpStatus() -> Int32 { #if arch(wasm32) let ret = getTSHttpStatus() - return Int32(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -115,7 +151,7 @@ public func _bjs_getTSHttpStatus() -> Int32 { @_cdecl("bjs_setPriority") public func _bjs_setPriority(priority: Int32) -> Void { #if arch(wasm32) - setPriority(_: Priority(rawValue: Int32(priority))!) + setPriority(_: Priority.bridgeJSLiftParameter(priority)) #else fatalError("Only available on WebAssembly") #endif @@ -126,7 +162,7 @@ public func _bjs_setPriority(priority: Int32) -> Void { public func _bjs_getPriority() -> Int32 { #if arch(wasm32) let ret = getPriority() - return Int32(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -134,9 +170,9 @@ public func _bjs_getPriority() -> Int32 { @_expose(wasm, "bjs_setFileSize") @_cdecl("bjs_setFileSize") -public func _bjs_setFileSize(size: Int64) -> Void { +public func _bjs_setFileSize(size: Int32) -> Void { #if arch(wasm32) - setFileSize(_: FileSize(rawValue: Int64(size))!) + setFileSize(_: FileSize.bridgeJSLiftParameter(size)) #else fatalError("Only available on WebAssembly") #endif @@ -144,10 +180,10 @@ public func _bjs_setFileSize(size: Int64) -> Void { @_expose(wasm, "bjs_getFileSize") @_cdecl("bjs_getFileSize") -public func _bjs_getFileSize() -> Int64 { +public func _bjs_getFileSize() -> Int32 { #if arch(wasm32) let ret = getFileSize() - return Int64(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -157,7 +193,7 @@ public func _bjs_getFileSize() -> Int64 { @_cdecl("bjs_setUserId") public func _bjs_setUserId(id: Int32) -> Void { #if arch(wasm32) - setUserId(_: UserId(rawValue: UInt(bitPattern: id))!) + setUserId(_: UserId.bridgeJSLiftParameter(id)) #else fatalError("Only available on WebAssembly") #endif @@ -168,7 +204,7 @@ public func _bjs_setUserId(id: Int32) -> Void { public func _bjs_getUserId() -> Int32 { #if arch(wasm32) let ret = getUserId() - return Int32(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -178,7 +214,7 @@ public func _bjs_getUserId() -> Int32 { @_cdecl("bjs_setTokenId") public func _bjs_setTokenId(token: Int32) -> Void { #if arch(wasm32) - setTokenId(_: TokenId(rawValue: UInt32(bitPattern: token))!) + setTokenId(_: TokenId.bridgeJSLiftParameter(token)) #else fatalError("Only available on WebAssembly") #endif @@ -189,7 +225,7 @@ public func _bjs_setTokenId(token: Int32) -> Void { public func _bjs_getTokenId() -> Int32 { #if arch(wasm32) let ret = getTokenId() - return Int32(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -197,9 +233,9 @@ public func _bjs_getTokenId() -> Int32 { @_expose(wasm, "bjs_setSessionId") @_cdecl("bjs_setSessionId") -public func _bjs_setSessionId(session: Int64) -> Void { +public func _bjs_setSessionId(session: Int32) -> Void { #if arch(wasm32) - setSessionId(_: SessionId(rawValue: UInt64(bitPattern: Int64(session)))!) + setSessionId(_: SessionId.bridgeJSLiftParameter(session)) #else fatalError("Only available on WebAssembly") #endif @@ -207,10 +243,10 @@ public func _bjs_setSessionId(session: Int64) -> Void { @_expose(wasm, "bjs_getSessionId") @_cdecl("bjs_getSessionId") -public func _bjs_getSessionId() -> Int64 { +public func _bjs_getSessionId() -> Int32 { #if arch(wasm32) let ret = getSessionId() - return Int64(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -220,7 +256,7 @@ public func _bjs_getSessionId() -> Int64 { @_cdecl("bjs_setPrecision") public func _bjs_setPrecision(precision: Float32) -> Void { #if arch(wasm32) - setPrecision(_: Precision(rawValue: Float(precision))!) + setPrecision(_: Precision.bridgeJSLiftParameter(precision)) #else fatalError("Only available on WebAssembly") #endif @@ -231,7 +267,7 @@ public func _bjs_setPrecision(precision: Float32) -> Void { public func _bjs_getPrecision() -> Float32 { #if arch(wasm32) let ret = getPrecision() - return Float32(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -241,7 +277,7 @@ public func _bjs_getPrecision() -> Float32 { @_cdecl("bjs_setRatio") public func _bjs_setRatio(ratio: Float64) -> Void { #if arch(wasm32) - setRatio(_: Ratio(rawValue: Double(ratio))!) + setRatio(_: Ratio.bridgeJSLiftParameter(ratio)) #else fatalError("Only available on WebAssembly") #endif @@ -252,7 +288,7 @@ public func _bjs_setRatio(ratio: Float64) -> Void { public func _bjs_getRatio() -> Float64 { #if arch(wasm32) let ret = getRatio() - return Float64(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -262,7 +298,7 @@ public func _bjs_getRatio() -> Float64 { @_cdecl("bjs_setFeatureFlag") public func _bjs_setFeatureFlag(featureFlag: Int32) -> Void { #if arch(wasm32) - setFeatureFlag(_: FeatureFlag(rawValue: FeatureFlag.bridgeJSLiftParameter(featureFlag))) + setFeatureFlag(_: FeatureFlag.bridgeJSLiftParameter(featureFlag)) #else fatalError("Only available on WebAssembly") #endif @@ -273,7 +309,7 @@ public func _bjs_setFeatureFlag(featureFlag: Int32) -> Void { public func _bjs_getFeatureFlag() -> Int32 { #if arch(wasm32) let ret = getFeatureFlag() - return Int32(ret.rawValue ? 1 : 0) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -281,10 +317,10 @@ public func _bjs_getFeatureFlag() -> Int32 { @_expose(wasm, "bjs_processTheme") @_cdecl("bjs_processTheme") -public func _bjs_processTheme(themeBytes: Int32, themeLen: Int32) -> Int32 { +public func _bjs_processTheme(themeBytes: Int32, themeLength: Int32) -> Int32 { #if arch(wasm32) - let ret = processTheme(_: Theme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) - return Int32(ret.rawValue) + let ret = processTheme(_: Theme.bridgeJSLiftParameter(themeBytes, themeLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -294,8 +330,8 @@ public func _bjs_processTheme(themeBytes: Int32, themeLen: Int32) -> Int32 { @_cdecl("bjs_convertPriority") public func _bjs_convertPriority(status: Int32) -> Int32 { #if arch(wasm32) - let ret = convertPriority(_: HttpStatus(rawValue: Int(status))!) - return Int32(ret.rawValue) + let ret = convertPriority(_: HttpStatus.bridgeJSLiftParameter(status)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -303,10 +339,10 @@ public func _bjs_convertPriority(status: Int32) -> Int32 { @_expose(wasm, "bjs_validateSession") @_cdecl("bjs_validateSession") -public func _bjs_validateSession(session: Int64) -> Void { +public func _bjs_validateSession(session: Int32) -> Void { #if arch(wasm32) - let ret = validateSession(_: SessionId(rawValue: UInt64(bitPattern: Int64(session)))!) - return ret.rawValue.bridgeJSLowerReturn() + let ret = validateSession(_: SessionId.bridgeJSLiftParameter(session)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift index e0128c5d..f0e7f657 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/Namespaces.swift @@ -30,10 +30,10 @@ public func _bjs_namespacedFunction() -> Void { @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") -public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { +public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) - return Unmanaged.passRetained(ret).toOpaque() + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -43,7 +43,7 @@ public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutable @_cdecl("bjs_Greeter_greet") public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() + let ret = Greeter.bridgeJSLiftParameter(_self).greet() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -56,10 +56,16 @@ public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Greeter: ConvertibleToJSValue { +extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_Greeter_wrap") func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -69,7 +75,7 @@ extension Greeter: ConvertibleToJSValue { public func _bjs_Converter_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = Converter() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -79,7 +85,7 @@ public func _bjs_Converter_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_Converter_toString") public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int.bridgeJSLiftParameter(value)) + let ret = Converter.bridgeJSLiftParameter(_self).toString(value: Int.bridgeJSLiftParameter(value)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -92,10 +98,16 @@ public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Converter: ConvertibleToJSValue { +extension Converter: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_Converter_wrap") func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -104,7 +116,7 @@ extension Converter: ConvertibleToJSValue { @_cdecl("bjs_UUID_uuidString") public func _bjs_UUID_uuidString(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().uuidString() + let ret = UUID.bridgeJSLiftParameter(_self).uuidString() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -117,10 +129,16 @@ public func _bjs_UUID_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension UUID: ConvertibleToJSValue { +extension UUID: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_UUID_wrap") func _bjs_UUID_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_UUID_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_UUID_wrap(Unmanaged.passRetained(self).toOpaque())))) } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift index 8f2e54c3..33097726 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveParameters.swift @@ -10,7 +10,7 @@ @_cdecl("bjs_check") public func _bjs_check(a: Int32, b: Float32, c: Float64, d: Int32) -> Void { #if arch(wasm32) - check(a: Int.bridgeJSLiftParameter(a), b: b, c: c, d: Bool.bridgeJSLiftParameter(d)) + check(a: Int.bridgeJSLiftParameter(a), b: Float.bridgeJSLiftParameter(b), c: Double.bridgeJSLiftParameter(c), d: Bool.bridgeJSLiftParameter(d)) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift index cc31d23e..63e5f03c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PrimitiveReturn.swift @@ -11,7 +11,7 @@ public func _bjs_checkInt() -> Int32 { #if arch(wasm32) let ret = checkInt() - return Int32(ret) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -22,7 +22,7 @@ public func _bjs_checkInt() -> Int32 { public func _bjs_checkFloat() -> Float32 { #if arch(wasm32) let ret = checkFloat() - return Float32(ret) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -33,7 +33,7 @@ public func _bjs_checkFloat() -> Float32 { public func _bjs_checkDouble() -> Float64 { #if arch(wasm32) let ret = checkDouble() - return Float64(ret) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift index 74d3a58d..822e5846 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/PropertyTypes.swift @@ -8,10 +8,10 @@ @_expose(wasm, "bjs_createPropertyHolder") @_cdecl("bjs_createPropertyHolder") -public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { +public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = createPropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLen), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) - return Unmanaged.passRetained(ret).toOpaque() + let ret = createPropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: Float.bridgeJSLiftParameter(floatValue), doubleValue: Double.bridgeJSLiftParameter(doubleValue), boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLength), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -21,7 +21,7 @@ public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doub @_cdecl("bjs_testPropertyHolder") public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = testPropertyHolder(holder: Unmanaged.fromOpaque(holder).takeUnretainedValue()) + let ret = testPropertyHolder(holder: PropertyHolder.bridgeJSLiftParameter(holder)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -30,10 +30,10 @@ public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_PropertyHolder_init") @_cdecl("bjs_PropertyHolder_init") -public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { +public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = PropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLen), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) - return Unmanaged.passRetained(ret).toOpaque() + let ret = PropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: Float.bridgeJSLiftParameter(floatValue), doubleValue: Double.bridgeJSLiftParameter(doubleValue), boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLength), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -43,7 +43,7 @@ public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubl @_cdecl("bjs_PropertyHolder_getAllValues") public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().getAllValues() + let ret = PropertyHolder.bridgeJSLiftParameter(_self).getAllValues() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -54,8 +54,8 @@ public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_intValue_get") public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue - return Int32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).intValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -65,7 +65,7 @@ public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_intValue_set") public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue = Int.bridgeJSLiftParameter(value) + PropertyHolder.bridgeJSLiftParameter(_self).intValue = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -75,8 +75,8 @@ public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, val @_cdecl("bjs_PropertyHolder_floatValue_get") public func _bjs_PropertyHolder_floatValue_get(_self: UnsafeMutableRawPointer) -> Float32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().floatValue - return Float32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).floatValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -86,7 +86,7 @@ public func _bjs_PropertyHolder_floatValue_get(_self: UnsafeMutableRawPointer) - @_cdecl("bjs_PropertyHolder_floatValue_set") public func _bjs_PropertyHolder_floatValue_set(_self: UnsafeMutableRawPointer, value: Float32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().floatValue = value + PropertyHolder.bridgeJSLiftParameter(_self).floatValue = Float.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -96,8 +96,8 @@ public func _bjs_PropertyHolder_floatValue_set(_self: UnsafeMutableRawPointer, v @_cdecl("bjs_PropertyHolder_doubleValue_get") public func _bjs_PropertyHolder_doubleValue_get(_self: UnsafeMutableRawPointer) -> Float64 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().doubleValue - return Float64(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).doubleValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -107,7 +107,7 @@ public func _bjs_PropertyHolder_doubleValue_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_doubleValue_set") public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, value: Float64) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().doubleValue = value + PropertyHolder.bridgeJSLiftParameter(_self).doubleValue = Double.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -117,7 +117,7 @@ public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, @_cdecl("bjs_PropertyHolder_boolValue_get") public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue + let ret = PropertyHolder.bridgeJSLiftParameter(_self).boolValue return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -128,7 +128,7 @@ public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_boolValue_set") public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue = Bool.bridgeJSLiftParameter(value) + PropertyHolder.bridgeJSLiftParameter(_self).boolValue = Bool.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -138,7 +138,7 @@ public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, va @_cdecl("bjs_PropertyHolder_stringValue_get") public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue + let ret = PropertyHolder.bridgeJSLiftParameter(_self).stringValue return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -147,9 +147,9 @@ public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) @_expose(wasm, "bjs_PropertyHolder_stringValue_set") @_cdecl("bjs_PropertyHolder_stringValue_set") -public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { +public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue = String.bridgeJSLiftParameter(valueBytes, valueLen) + PropertyHolder.bridgeJSLiftParameter(_self).stringValue = String.bridgeJSLiftParameter(valueBytes, valueLength) #else fatalError("Only available on WebAssembly") #endif @@ -159,8 +159,8 @@ public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, @_cdecl("bjs_PropertyHolder_readonlyInt_get") public func _bjs_PropertyHolder_readonlyInt_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyInt - return Int32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyInt + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -170,8 +170,8 @@ public func _bjs_PropertyHolder_readonlyInt_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_readonlyFloat_get") public func _bjs_PropertyHolder_readonlyFloat_get(_self: UnsafeMutableRawPointer) -> Float32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyFloat - return Float32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyFloat + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -181,8 +181,8 @@ public func _bjs_PropertyHolder_readonlyFloat_get(_self: UnsafeMutableRawPointer @_cdecl("bjs_PropertyHolder_readonlyDouble_get") public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointer) -> Float64 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyDouble - return Float64(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyDouble + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -192,7 +192,7 @@ public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointe @_cdecl("bjs_PropertyHolder_readonlyBool_get") public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyBool + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyBool return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -203,7 +203,7 @@ public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_readonlyString_get") public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyString + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyString return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -214,7 +214,7 @@ public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointe @_cdecl("bjs_PropertyHolder_jsObject_get") public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject + let ret = PropertyHolder.bridgeJSLiftParameter(_self).jsObject return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -225,7 +225,7 @@ public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_jsObject_set") public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject = JSObject.bridgeJSLiftParameter(value) + PropertyHolder.bridgeJSLiftParameter(_self).jsObject = JSObject.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -235,8 +235,8 @@ public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, val @_cdecl("bjs_PropertyHolder_sibling_get") public func _bjs_PropertyHolder_sibling_get(_self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().sibling - return Unmanaged.passRetained(ret).toOpaque() + let ret = PropertyHolder.bridgeJSLiftParameter(_self).sibling + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -246,7 +246,7 @@ public func _bjs_PropertyHolder_sibling_get(_self: UnsafeMutableRawPointer) -> U @_cdecl("bjs_PropertyHolder_sibling_set") public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().sibling = Unmanaged.fromOpaque(value).takeUnretainedValue() + PropertyHolder.bridgeJSLiftParameter(_self).sibling = PropertyHolder.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -256,7 +256,7 @@ public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, valu @_cdecl("bjs_PropertyHolder_lazyValue_get") public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue + let ret = PropertyHolder.bridgeJSLiftParameter(_self).lazyValue return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -265,9 +265,9 @@ public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> @_expose(wasm, "bjs_PropertyHolder_lazyValue_set") @_cdecl("bjs_PropertyHolder_lazyValue_set") -public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { +public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue = String.bridgeJSLiftParameter(valueBytes, valueLen) + PropertyHolder.bridgeJSLiftParameter(_self).lazyValue = String.bridgeJSLiftParameter(valueBytes, valueLength) #else fatalError("Only available on WebAssembly") #endif @@ -277,8 +277,8 @@ public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, va @_cdecl("bjs_PropertyHolder_computedReadonly_get") public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadonly - return Int32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).computedReadonly + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -288,7 +288,7 @@ public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPoin @_cdecl("bjs_PropertyHolder_computedReadWrite_get") public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite + let ret = PropertyHolder.bridgeJSLiftParameter(_self).computedReadWrite return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -297,9 +297,9 @@ public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPoi @_expose(wasm, "bjs_PropertyHolder_computedReadWrite_set") @_cdecl("bjs_PropertyHolder_computedReadWrite_set") -public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { +public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite = String.bridgeJSLiftParameter(valueBytes, valueLen) + PropertyHolder.bridgeJSLiftParameter(_self).computedReadWrite = String.bridgeJSLiftParameter(valueBytes, valueLength) #else fatalError("Only available on WebAssembly") #endif @@ -309,8 +309,8 @@ public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPoi @_cdecl("bjs_PropertyHolder_observedProperty_get") public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty - return Int32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).observedProperty + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -320,7 +320,7 @@ public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPoin @_cdecl("bjs_PropertyHolder_observedProperty_set") public func _bjs_PropertyHolder_observedProperty_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty = Int.bridgeJSLiftParameter(value) + PropertyHolder.bridgeJSLiftParameter(_self).observedProperty = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -332,10 +332,16 @@ public func _bjs_PropertyHolder_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension PropertyHolder: ConvertibleToJSValue { +extension PropertyHolder: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_PropertyHolder_wrap") func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_PropertyHolder_wrap(Unmanaged.passRetained(self).toOpaque())))) } } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift index 0da63382..723a639c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/StringParameter.swift @@ -8,9 +8,9 @@ @_expose(wasm, "bjs_checkString") @_cdecl("bjs_checkString") -public func _bjs_checkString(aBytes: Int32, aLen: Int32) -> Void { +public func _bjs_checkString(aBytes: Int32, aLength: Int32) -> Void { #if arch(wasm32) - checkString(a: String.bridgeJSLiftParameter(aBytes, aLen)) + checkString(a: String.bridgeJSLiftParameter(aBytes, aLength)) #else fatalError("Only available on WebAssembly") #endif diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift index 78f8c149..5a354b43 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift @@ -10,7 +10,7 @@ @_cdecl("bjs_takeGreeter") public func _bjs_takeGreeter(greeter: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - takeGreeter(greeter: Unmanaged.fromOpaque(greeter).takeUnretainedValue()) + takeGreeter(greeter: Greeter.bridgeJSLiftParameter(greeter)) #else fatalError("Only available on WebAssembly") #endif @@ -18,10 +18,10 @@ public func _bjs_takeGreeter(greeter: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") -public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { +public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) - return Unmanaged.passRetained(ret).toOpaque() + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -31,7 +31,7 @@ public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutable @_cdecl("bjs_Greeter_greet") public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() + let ret = Greeter.bridgeJSLiftParameter(_self).greet() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -40,9 +40,9 @@ public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_Greeter_changeName") @_cdecl("bjs_Greeter_changeName") -public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { +public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) + Greeter.bridgeJSLiftParameter(_self).changeName(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) #else fatalError("Only available on WebAssembly") #endif @@ -52,7 +52,7 @@ public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: I @_cdecl("bjs_Greeter_name_get") public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().name + let ret = Greeter.bridgeJSLiftParameter(_self).name return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -61,9 +61,9 @@ public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_Greeter_name_set") @_cdecl("bjs_Greeter_name_set") -public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { +public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().name = String.bridgeJSLiftParameter(valueBytes, valueLen) + Greeter.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) #else fatalError("Only available on WebAssembly") #endif @@ -75,10 +75,16 @@ public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Greeter: ConvertibleToJSValue { +extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_Greeter_wrap") func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) } } \ No newline at end of file diff --git a/Sources/JavaScriptKit/BridgeJSInstrincics.swift b/Sources/JavaScriptKit/BridgeJSInstrincics.swift index 2756cb59..4d609dc7 100644 --- a/Sources/JavaScriptKit/BridgeJSInstrincics.swift +++ b/Sources/JavaScriptKit/BridgeJSInstrincics.swift @@ -79,7 +79,24 @@ import _CJavaScriptKit // // See JSGlueGen.swift in BridgeJSLink for JS-side lowering/lifting implementation. -extension Bool { +/// A protocol that Swift types that can be lowered into a single Wasm core type. +public protocol _BridgedSwiftTypeLoweredIntoWasmCoreType { + associatedtype WasmCoreType +} + +/// A protocol that Swift types that can appear as parameters or return values on +/// The conformance is automatically synthesized by the BridgeJS code generator. +public protocol _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + associatedtype WasmCoreType + // MARK: ImportTS + consuming func bridgeJSLowerParameter() -> WasmCoreType + static func bridgeJSLiftReturn(_ value: WasmCoreType) -> Self + // MARK: ExportSwift + static func bridgeJSLiftParameter(_ value: WasmCoreType) -> Self + consuming func bridgeJSLowerReturn() -> WasmCoreType +} + +extension Bool: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { // MARK: ImportTS @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { self ? 1 : 0 @@ -96,7 +113,7 @@ extension Bool { } } -extension Int { +extension Int: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { // MARK: ImportTS @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { Int32(self) @@ -113,6 +130,40 @@ extension Int { } } +extension Float: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + // MARK: ImportTS + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Float32 { + Float32(self) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Float32) -> Float { + Float(value) + } + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Float32) -> Float { + Float(value) + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Float32 { + Float32(self) + } +} + +extension Double: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + // MARK: ImportTS + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Float64 { + Float64(self) + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Float64) -> Double { + Double(value) + } + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Float64) -> Double { + Double(value) + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Float64 { + Float64(self) + } +} + extension String { // MARK: ImportTS @@ -213,3 +264,69 @@ extension JSObject { return _swift_js_retain(Int32(bitPattern: self.id)) } } + +/// A protocol that Swift heap objects exposed to JavaScript via `@JS class` must conform to. +/// +/// The conformance is automatically synthesized by the BridgeJS code generator. +public protocol _BridgedSwiftHeapObject: AnyObject {} + +/// Define the lowering/lifting for `_BridgedSwiftHeapObject` +extension _BridgedSwiftHeapObject { + + // MARK: ImportTS + @available(*, unavailable, message: "Swift heap objects are not supported to be passed to imported JS functions") + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Void {} + @available( + *, + unavailable, + message: "Swift heap objects are not supported to be returned from imported JS functions" + ) + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ pointer: UnsafeMutableRawPointer) -> Void {} + + // MARK: ExportSwift + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ pointer: UnsafeMutableRawPointer) -> Self { + Unmanaged.fromOpaque(pointer).takeUnretainedValue() + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> UnsafeMutableRawPointer { + // Perform a manual retain on the object, which will be balanced by a release called via FinalizationRegistry + return Unmanaged.passRetained(self).toOpaque() + } +} + +/// A protocol that Swift enum types that do not have a payload can conform to. +/// +/// The conformance is automatically synthesized by the BridgeJS code generator. +public protocol _BridgedSwiftEnumNoPayload {} + +extension _BridgedSwiftEnumNoPayload where Self: RawRepresentable, RawValue == String { + // MARK: ImportTS + @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> Int32 { rawValue.bridgeJSLowerParameter() } + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ bytesCount: Int32) -> Self { + Self(rawValue: .bridgeJSLiftReturn(bytesCount))! + } + + // MARK: ExportSwift + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ bytes: Int32, _ count: Int32) -> Self { + Self(rawValue: .bridgeJSLiftParameter(bytes, count))! + } + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void { rawValue.bridgeJSLowerReturn() } +} + +extension _BridgedSwiftEnumNoPayload +where Self: RawRepresentable, RawValue: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { + // MARK: ImportTS + @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> RawValue.WasmCoreType { + rawValue.bridgeJSLowerParameter() + } + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ value: RawValue.WasmCoreType) -> Self { + Self(rawValue: .bridgeJSLiftReturn(value))! + } + + // MARK: ExportSwift + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ value: RawValue.WasmCoreType) -> Self { + Self(rawValue: .bridgeJSLiftParameter(value))! + } + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> RawValue.WasmCoreType { + rawValue.bridgeJSLowerReturn() + } +} diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 524d7424..e5999f4d 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -7,7 +7,20 @@ @_spi(BridgeJS) import JavaScriptKit extension Direction { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Direction { + return Direction(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Direction { + return Direction(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .north @@ -22,7 +35,7 @@ extension Direction { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .north: return 0 @@ -37,7 +50,20 @@ extension Direction { } extension Status { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Status { + return Status(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Status { + return Status(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .loading @@ -50,7 +76,7 @@ extension Status { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .loading: return 0 @@ -62,8 +88,27 @@ extension Status { } } +extension Theme: _BridgedSwiftEnumNoPayload { +} + +extension HttpStatus: _BridgedSwiftEnumNoPayload { +} + extension TSDirection { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> TSDirection { + return TSDirection(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> TSDirection { + return TSDirection(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .north @@ -78,7 +123,7 @@ extension TSDirection { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .north: return 0 @@ -92,8 +137,27 @@ extension TSDirection { } } +extension TSTheme: _BridgedSwiftEnumNoPayload { +} + +extension Utils: _BridgedSwiftEnumNoPayload { +} + extension Networking.API.Method { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Networking.API.Method { + return Networking.API.Method(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Networking.API.Method { + return Networking.API.Method(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .get @@ -108,7 +172,7 @@ extension Networking.API.Method { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .get: return 0 @@ -122,8 +186,27 @@ extension Networking.API.Method { } } +extension Configuration.LogLevel: _BridgedSwiftEnumNoPayload { +} + +extension Configuration.Port: _BridgedSwiftEnumNoPayload { +} + extension Internal.SupportedMethod { - init?(bridgeJSRawValue: Int32) { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> Internal.SupportedMethod { + return Internal.SupportedMethod(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> Internal.SupportedMethod { + return Internal.SupportedMethod(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { switch bridgeJSRawValue { case 0: self = .get @@ -134,7 +217,7 @@ extension Internal.SupportedMethod { } } - var bridgeJSRawValue: Int32 { + private var bridgeJSRawValue: Int32 { switch self { case .get: return 0 @@ -159,7 +242,7 @@ public func _bjs_roundTripVoid() -> Void { public func _bjs_roundTripInt(v: Int32) -> Int32 { #if arch(wasm32) let ret = roundTripInt(v: Int.bridgeJSLiftParameter(v)) - return Int32(ret) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -169,8 +252,8 @@ public func _bjs_roundTripInt(v: Int32) -> Int32 { @_cdecl("bjs_roundTripFloat") public func _bjs_roundTripFloat(v: Float32) -> Float32 { #if arch(wasm32) - let ret = roundTripFloat(v: v) - return Float32(ret) + let ret = roundTripFloat(v: Float.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -180,8 +263,8 @@ public func _bjs_roundTripFloat(v: Float32) -> Float32 { @_cdecl("bjs_roundTripDouble") public func _bjs_roundTripDouble(v: Float64) -> Float64 { #if arch(wasm32) - let ret = roundTripDouble(v: v) - return Float64(ret) + let ret = roundTripDouble(v: Double.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -200,9 +283,9 @@ public func _bjs_roundTripBool(v: Int32) -> Int32 { @_expose(wasm, "bjs_roundTripString") @_cdecl("bjs_roundTripString") -public func _bjs_roundTripString(vBytes: Int32, vLen: Int32) -> Void { +public func _bjs_roundTripString(vBytes: Int32, vLength: Int32) -> Void { #if arch(wasm32) - let ret = roundTripString(v: String.bridgeJSLiftParameter(vBytes, vLen)) + let ret = roundTripString(v: String.bridgeJSLiftParameter(vBytes, vLength)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -213,8 +296,8 @@ public func _bjs_roundTripString(vBytes: Int32, vLen: Int32) -> Void { @_cdecl("bjs_roundTripSwiftHeapObject") public func _bjs_roundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = roundTripSwiftHeapObject(v: Unmanaged.fromOpaque(v).takeUnretainedValue()) - return Unmanaged.passRetained(ret).toOpaque() + let ret = roundTripSwiftHeapObject(v: Greeter.bridgeJSLiftParameter(v)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -261,7 +344,7 @@ public func _bjs_throwsWithIntResult() -> Int32 { #if arch(wasm32) do { let ret = try throwsWithIntResult() - return Int32(ret) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -336,7 +419,7 @@ public func _bjs_throwsWithFloatResult() -> Float32 { #if arch(wasm32) do { let ret = try throwsWithFloatResult() - return Float32(ret) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -361,7 +444,7 @@ public func _bjs_throwsWithDoubleResult() -> Float64 { #if arch(wasm32) do { let ret = try throwsWithDoubleResult() - return Float64(ret) + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -386,7 +469,7 @@ public func _bjs_throwsWithSwiftHeapObjectResult() -> UnsafeMutableRawPointer { #if arch(wasm32) do { let ret = try throwsWithSwiftHeapObjectResult() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() } catch let error { if let error = error.thrownValue.object { withExtendedLifetime(error) { @@ -461,7 +544,7 @@ public func _bjs_asyncRoundTripInt(v: Int32) -> Int32 { public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripFloat(v: v).jsValue + return await asyncRoundTripFloat(v: Float.bridgeJSLiftParameter(v)).jsValue } .jsObject return ret.bridgeJSLowerReturn() #else @@ -474,7 +557,7 @@ public func _bjs_asyncRoundTripFloat(v: Float32) -> Int32 { public func _bjs_asyncRoundTripDouble(v: Float64) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripDouble(v: v).jsValue + return await asyncRoundTripDouble(v: Double.bridgeJSLiftParameter(v)).jsValue } .jsObject return ret.bridgeJSLowerReturn() #else @@ -497,10 +580,10 @@ public func _bjs_asyncRoundTripBool(v: Int32) -> Int32 { @_expose(wasm, "bjs_asyncRoundTripString") @_cdecl("bjs_asyncRoundTripString") -public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { +public func _bjs_asyncRoundTripString(vBytes: Int32, vLength: Int32) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripString(v: String.bridgeJSLiftParameter(vBytes, vLen)).jsValue + return await asyncRoundTripString(v: String.bridgeJSLiftParameter(vBytes, vLength)).jsValue } .jsObject return ret.bridgeJSLowerReturn() #else @@ -513,7 +596,7 @@ public func _bjs_asyncRoundTripString(vBytes: Int32, vLen: Int32) -> Int32 { public func _bjs_asyncRoundTripSwiftHeapObject(v: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) let ret = JSPromise.async { - return await asyncRoundTripSwiftHeapObject(v: Unmanaged.fromOpaque(v).takeUnretainedValue()).jsValue + return await asyncRoundTripSwiftHeapObject(v: Greeter.bridgeJSLiftParameter(v)).jsValue } .jsObject return ret.bridgeJSLowerReturn() #else @@ -536,9 +619,9 @@ public func _bjs_asyncRoundTripJSObject(v: Int32) -> Int32 { @_expose(wasm, "bjs_takeGreeter") @_cdecl("bjs_takeGreeter") -public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { +public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameLength: Int32) -> Void { #if arch(wasm32) - takeGreeter(g: Unmanaged.fromOpaque(g).takeUnretainedValue(), name: String.bridgeJSLiftParameter(nameBytes, nameLen)) + takeGreeter(g: Greeter.bridgeJSLiftParameter(g), name: String.bridgeJSLiftParameter(nameBytes, nameLength)) #else fatalError("Only available on WebAssembly") #endif @@ -549,7 +632,7 @@ public func _bjs_takeGreeter(g: UnsafeMutableRawPointer, nameBytes: Int32, nameL public func _bjs_createCalculator() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = createCalculator() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -559,8 +642,8 @@ public func _bjs_createCalculator() -> UnsafeMutableRawPointer { @_cdecl("bjs_useCalculator") public func _bjs_useCalculator(calc: UnsafeMutableRawPointer, x: Int32, y: Int32) -> Int32 { #if arch(wasm32) - let ret = useCalculator(calc: Unmanaged.fromOpaque(calc).takeUnretainedValue(), x: Int.bridgeJSLiftParameter(x), y: Int.bridgeJSLiftParameter(y)) - return Int32(ret) + let ret = useCalculator(calc: Calculator.bridgeJSLiftParameter(calc), x: Int.bridgeJSLiftParameter(x), y: Int.bridgeJSLiftParameter(y)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -592,7 +675,7 @@ public func _bjs_testCalculatorToJSValue() -> Int32 { @_cdecl("bjs_testSwiftClassAsJSValue") public func _bjs_testSwiftClassAsJSValue(greeter: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = testSwiftClassAsJSValue(greeter: Unmanaged.fromOpaque(greeter).takeUnretainedValue()) + let ret = testSwiftClassAsJSValue(greeter: Greeter.bridgeJSLiftParameter(greeter)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -603,8 +686,8 @@ public func _bjs_testSwiftClassAsJSValue(greeter: UnsafeMutableRawPointer) -> In @_cdecl("bjs_setDirection") public func _bjs_setDirection(direction: Int32) -> Int32 { #if arch(wasm32) - let ret = setDirection(_: Direction(bridgeJSRawValue: direction)!) - return ret.bridgeJSRawValue + let ret = setDirection(_: Direction.bridgeJSLiftParameter(direction)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -615,7 +698,7 @@ public func _bjs_setDirection(direction: Int32) -> Int32 { public func _bjs_getDirection() -> Int32 { #if arch(wasm32) let ret = getDirection() - return ret.bridgeJSRawValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -625,8 +708,8 @@ public func _bjs_getDirection() -> Int32 { @_cdecl("bjs_processDirection") public func _bjs_processDirection(input: Int32) -> Int32 { #if arch(wasm32) - let ret = processDirection(_: Direction(bridgeJSRawValue: input)!) - return ret.bridgeJSRawValue + let ret = processDirection(_: Direction.bridgeJSLiftParameter(input)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -634,10 +717,10 @@ public func _bjs_processDirection(input: Int32) -> Int32 { @_expose(wasm, "bjs_setTheme") @_cdecl("bjs_setTheme") -public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { +public func _bjs_setTheme(themeBytes: Int32, themeLength: Int32) -> Void { #if arch(wasm32) - let ret = setTheme(_: Theme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) - return ret.rawValue.bridgeJSLowerReturn() + let ret = setTheme(_: Theme.bridgeJSLiftParameter(themeBytes, themeLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -648,7 +731,7 @@ public func _bjs_setTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTheme() -> Void { #if arch(wasm32) let ret = getTheme() - return ret.rawValue.bridgeJSLowerReturn() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -658,8 +741,8 @@ public func _bjs_getTheme() -> Void { @_cdecl("bjs_setHttpStatus") public func _bjs_setHttpStatus(status: Int32) -> Int32 { #if arch(wasm32) - let ret = setHttpStatus(_: HttpStatus(rawValue: Int(status))!) - return Int32(ret.rawValue) + let ret = setHttpStatus(_: HttpStatus.bridgeJSLiftParameter(status)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -670,7 +753,7 @@ public func _bjs_setHttpStatus(status: Int32) -> Int32 { public func _bjs_getHttpStatus() -> Int32 { #if arch(wasm32) let ret = getHttpStatus() - return Int32(ret.rawValue) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -678,10 +761,10 @@ public func _bjs_getHttpStatus() -> Int32 { @_expose(wasm, "bjs_processTheme") @_cdecl("bjs_processTheme") -public func _bjs_processTheme(themeBytes: Int32, themeLen: Int32) -> Int32 { +public func _bjs_processTheme(themeBytes: Int32, themeLength: Int32) -> Int32 { #if arch(wasm32) - let ret = processTheme(_: Theme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) - return Int32(ret.rawValue) + let ret = processTheme(_: Theme.bridgeJSLiftParameter(themeBytes, themeLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -691,8 +774,8 @@ public func _bjs_processTheme(themeBytes: Int32, themeLen: Int32) -> Int32 { @_cdecl("bjs_setTSDirection") public func _bjs_setTSDirection(direction: Int32) -> Int32 { #if arch(wasm32) - let ret = setTSDirection(_: TSDirection(bridgeJSRawValue: direction)!) - return ret.bridgeJSRawValue + let ret = setTSDirection(_: TSDirection.bridgeJSLiftParameter(direction)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -703,7 +786,7 @@ public func _bjs_setTSDirection(direction: Int32) -> Int32 { public func _bjs_getTSDirection() -> Int32 { #if arch(wasm32) let ret = getTSDirection() - return ret.bridgeJSRawValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -711,10 +794,10 @@ public func _bjs_getTSDirection() -> Int32 { @_expose(wasm, "bjs_setTSTheme") @_cdecl("bjs_setTSTheme") -public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { +public func _bjs_setTSTheme(themeBytes: Int32, themeLength: Int32) -> Void { #if arch(wasm32) - let ret = setTSTheme(_: TSTheme(rawValue: String.bridgeJSLiftParameter(themeBytes, themeLen))!) - return ret.rawValue.bridgeJSLowerReturn() + let ret = setTSTheme(_: TSTheme.bridgeJSLiftParameter(themeBytes, themeLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -725,7 +808,7 @@ public func _bjs_setTSTheme(themeBytes: Int32, themeLen: Int32) -> Void { public func _bjs_getTSTheme() -> Void { #if arch(wasm32) let ret = getTSTheme() - return ret.rawValue.bridgeJSLowerReturn() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -733,10 +816,10 @@ public func _bjs_getTSTheme() -> Void { @_expose(wasm, "bjs_createPropertyHolder") @_cdecl("bjs_createPropertyHolder") -public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { +public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = createPropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLen), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) - return Unmanaged.passRetained(ret).toOpaque() + let ret = createPropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: Float.bridgeJSLiftParameter(floatValue), doubleValue: Double.bridgeJSLiftParameter(doubleValue), boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLength), jsObject: JSObject.bridgeJSLiftParameter(jsObject)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -746,7 +829,7 @@ public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doub @_cdecl("bjs_testPropertyHolder") public func _bjs_testPropertyHolder(holder: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = testPropertyHolder(holder: Unmanaged.fromOpaque(holder).takeUnretainedValue()) + let ret = testPropertyHolder(holder: PropertyHolder.bridgeJSLiftParameter(holder)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -776,10 +859,10 @@ public func _bjs_getObserverStats() -> Void { @_expose(wasm, "bjs_Greeter_init") @_cdecl("bjs_Greeter_init") -public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutableRawPointer { +public func _bjs_Greeter_init(nameBytes: Int32, nameLength: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) - return Unmanaged.passRetained(ret).toOpaque() + let ret = Greeter(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -789,7 +872,7 @@ public func _bjs_Greeter_init(nameBytes: Int32, nameLen: Int32) -> UnsafeMutable @_cdecl("bjs_Greeter_greet") public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().greet() + let ret = Greeter.bridgeJSLiftParameter(_self).greet() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -798,9 +881,9 @@ public func _bjs_Greeter_greet(_self: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_Greeter_changeName") @_cdecl("bjs_Greeter_changeName") -public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLen: Int32) -> Void { +public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: Int32, nameLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().changeName(name: String.bridgeJSLiftParameter(nameBytes, nameLen)) + Greeter.bridgeJSLiftParameter(_self).changeName(name: String.bridgeJSLiftParameter(nameBytes, nameLength)) #else fatalError("Only available on WebAssembly") #endif @@ -810,7 +893,7 @@ public func _bjs_Greeter_changeName(_self: UnsafeMutableRawPointer, nameBytes: I @_cdecl("bjs_Greeter_name_get") public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().name + let ret = Greeter.bridgeJSLiftParameter(_self).name return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -819,9 +902,9 @@ public func _bjs_Greeter_name_get(_self: UnsafeMutableRawPointer) -> Void { @_expose(wasm, "bjs_Greeter_name_set") @_cdecl("bjs_Greeter_name_set") -public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { +public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().name = String.bridgeJSLiftParameter(valueBytes, valueLen) + Greeter.bridgeJSLiftParameter(_self).name = String.bridgeJSLiftParameter(valueBytes, valueLength) #else fatalError("Only available on WebAssembly") #endif @@ -831,7 +914,7 @@ public func _bjs_Greeter_name_set(_self: UnsafeMutableRawPointer, valueBytes: In @_cdecl("bjs_Greeter_prefix_get") public func _bjs_Greeter_prefix_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().prefix + let ret = Greeter.bridgeJSLiftParameter(_self).prefix return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -844,10 +927,16 @@ public func _bjs_Greeter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Greeter: ConvertibleToJSValue { +extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Greeter_wrap") func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -856,8 +945,8 @@ extension Greeter: ConvertibleToJSValue { @_cdecl("bjs_Calculator_square") public func _bjs_Calculator_square(_self: UnsafeMutableRawPointer, value: Int32) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().square(value: Int.bridgeJSLiftParameter(value)) - return Int32(ret) + let ret = Calculator.bridgeJSLiftParameter(_self).square(value: Int.bridgeJSLiftParameter(value)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -867,8 +956,8 @@ public func _bjs_Calculator_square(_self: UnsafeMutableRawPointer, value: Int32) @_cdecl("bjs_Calculator_add") public func _bjs_Calculator_add(_self: UnsafeMutableRawPointer, a: Int32, b: Int32) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().add(a: Int.bridgeJSLiftParameter(a), b: Int.bridgeJSLiftParameter(b)) - return Int32(ret) + let ret = Calculator.bridgeJSLiftParameter(_self).add(a: Int.bridgeJSLiftParameter(a), b: Int.bridgeJSLiftParameter(b)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -880,10 +969,16 @@ public func _bjs_Calculator_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Calculator: ConvertibleToJSValue { +extension Calculator: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Calculator_wrap") func _bjs_Calculator_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Calculator_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_Calculator_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -893,7 +988,7 @@ extension Calculator: ConvertibleToJSValue { public func _bjs_Converter_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = Utils.Converter() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -903,7 +998,7 @@ public func _bjs_Converter_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_Converter_toString") public func _bjs_Converter_toString(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().toString(value: Int.bridgeJSLiftParameter(value)) + let ret = Utils.Converter.bridgeJSLiftParameter(_self).toString(value: Int.bridgeJSLiftParameter(value)) return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -916,10 +1011,16 @@ public func _bjs_Converter_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Utils.Converter: ConvertibleToJSValue { +extension Utils.Converter: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Converter_wrap") func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_Converter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_Converter_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -929,7 +1030,7 @@ extension Utils.Converter: ConvertibleToJSValue { public func _bjs_HTTPServer_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = Networking.API.HTTPServer() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -939,7 +1040,7 @@ public func _bjs_HTTPServer_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_HTTPServer_call") public func _bjs_HTTPServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Networking.API.Method(bridgeJSRawValue: method)!) + Networking.API.HTTPServer.bridgeJSLiftParameter(_self).call(_: Networking.API.Method.bridgeJSLiftParameter(method)) #else fatalError("Only available on WebAssembly") #endif @@ -951,10 +1052,16 @@ public func _bjs_HTTPServer_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Networking.API.HTTPServer: ConvertibleToJSValue { +extension Networking.API.HTTPServer: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_HTTPServer_wrap") func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_HTTPServer_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_HTTPServer_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -964,7 +1071,7 @@ extension Networking.API.HTTPServer: ConvertibleToJSValue { public func _bjs_TestServer_init() -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = Internal.TestServer() - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -974,7 +1081,7 @@ public func _bjs_TestServer_init() -> UnsafeMutableRawPointer { @_cdecl("bjs_TestServer_call") public func _bjs_TestServer_call(_self: UnsafeMutableRawPointer, method: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().call(_: Internal.SupportedMethod(bridgeJSRawValue: method)!) + Internal.TestServer.bridgeJSLiftParameter(_self).call(_: Internal.SupportedMethod.bridgeJSLiftParameter(method)) #else fatalError("Only available on WebAssembly") #endif @@ -986,10 +1093,16 @@ public func _bjs_TestServer_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension Internal.TestServer: ConvertibleToJSValue { +extension Internal.TestServer: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_TestServer_wrap") func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_TestServer_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_TestServer_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @@ -999,7 +1112,7 @@ extension Internal.TestServer: ConvertibleToJSValue { public func _bjs_SimplePropertyHolder_init(value: Int32) -> UnsafeMutableRawPointer { #if arch(wasm32) let ret = SimplePropertyHolder(value: Int.bridgeJSLiftParameter(value)) - return Unmanaged.passRetained(ret).toOpaque() + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1009,8 +1122,8 @@ public func _bjs_SimplePropertyHolder_init(value: Int32) -> UnsafeMutableRawPoin @_cdecl("bjs_SimplePropertyHolder_value_get") public func _bjs_SimplePropertyHolder_value_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().value - return Int32(ret) + let ret = SimplePropertyHolder.bridgeJSLiftParameter(_self).value + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1020,7 +1133,7 @@ public func _bjs_SimplePropertyHolder_value_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_SimplePropertyHolder_value_set") public func _bjs_SimplePropertyHolder_value_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().value = Int.bridgeJSLiftParameter(value) + SimplePropertyHolder.bridgeJSLiftParameter(_self).value = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1032,20 +1145,26 @@ public func _bjs_SimplePropertyHolder_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension SimplePropertyHolder: ConvertibleToJSValue { +extension SimplePropertyHolder: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_SimplePropertyHolder_wrap") func _bjs_SimplePropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_SimplePropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_SimplePropertyHolder_wrap(Unmanaged.passRetained(self).toOpaque())))) } } @_expose(wasm, "bjs_PropertyHolder_init") @_cdecl("bjs_PropertyHolder_init") -public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLen: Int32, jsObject: Int32, sibling: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { +public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32, sibling: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = PropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: floatValue, doubleValue: doubleValue, boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLen), jsObject: JSObject.bridgeJSLiftParameter(jsObject), sibling: Unmanaged.fromOpaque(sibling).takeUnretainedValue()) - return Unmanaged.passRetained(ret).toOpaque() + let ret = PropertyHolder(intValue: Int.bridgeJSLiftParameter(intValue), floatValue: Float.bridgeJSLiftParameter(floatValue), doubleValue: Double.bridgeJSLiftParameter(doubleValue), boolValue: Bool.bridgeJSLiftParameter(boolValue), stringValue: String.bridgeJSLiftParameter(stringValueBytes, stringValueLength), jsObject: JSObject.bridgeJSLiftParameter(jsObject), sibling: SimplePropertyHolder.bridgeJSLiftParameter(sibling)) + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1055,7 +1174,7 @@ public func _bjs_PropertyHolder_init(intValue: Int32, floatValue: Float32, doubl @_cdecl("bjs_PropertyHolder_getAllValues") public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().getAllValues() + let ret = PropertyHolder.bridgeJSLiftParameter(_self).getAllValues() return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -1066,8 +1185,8 @@ public func _bjs_PropertyHolder_getAllValues(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_intValue_get") public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue - return Int32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).intValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1077,7 +1196,7 @@ public func _bjs_PropertyHolder_intValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_intValue_set") public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().intValue = Int.bridgeJSLiftParameter(value) + PropertyHolder.bridgeJSLiftParameter(_self).intValue = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1087,8 +1206,8 @@ public func _bjs_PropertyHolder_intValue_set(_self: UnsafeMutableRawPointer, val @_cdecl("bjs_PropertyHolder_floatValue_get") public func _bjs_PropertyHolder_floatValue_get(_self: UnsafeMutableRawPointer) -> Float32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().floatValue - return Float32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).floatValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1098,7 +1217,7 @@ public func _bjs_PropertyHolder_floatValue_get(_self: UnsafeMutableRawPointer) - @_cdecl("bjs_PropertyHolder_floatValue_set") public func _bjs_PropertyHolder_floatValue_set(_self: UnsafeMutableRawPointer, value: Float32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().floatValue = value + PropertyHolder.bridgeJSLiftParameter(_self).floatValue = Float.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1108,8 +1227,8 @@ public func _bjs_PropertyHolder_floatValue_set(_self: UnsafeMutableRawPointer, v @_cdecl("bjs_PropertyHolder_doubleValue_get") public func _bjs_PropertyHolder_doubleValue_get(_self: UnsafeMutableRawPointer) -> Float64 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().doubleValue - return Float64(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).doubleValue + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1119,7 +1238,7 @@ public func _bjs_PropertyHolder_doubleValue_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_doubleValue_set") public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, value: Float64) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().doubleValue = value + PropertyHolder.bridgeJSLiftParameter(_self).doubleValue = Double.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1129,7 +1248,7 @@ public func _bjs_PropertyHolder_doubleValue_set(_self: UnsafeMutableRawPointer, @_cdecl("bjs_PropertyHolder_boolValue_get") public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue + let ret = PropertyHolder.bridgeJSLiftParameter(_self).boolValue return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -1140,7 +1259,7 @@ public func _bjs_PropertyHolder_boolValue_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_boolValue_set") public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().boolValue = Bool.bridgeJSLiftParameter(value) + PropertyHolder.bridgeJSLiftParameter(_self).boolValue = Bool.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1150,7 +1269,7 @@ public func _bjs_PropertyHolder_boolValue_set(_self: UnsafeMutableRawPointer, va @_cdecl("bjs_PropertyHolder_stringValue_get") public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue + let ret = PropertyHolder.bridgeJSLiftParameter(_self).stringValue return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -1159,9 +1278,9 @@ public func _bjs_PropertyHolder_stringValue_get(_self: UnsafeMutableRawPointer) @_expose(wasm, "bjs_PropertyHolder_stringValue_set") @_cdecl("bjs_PropertyHolder_stringValue_set") -public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { +public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().stringValue = String.bridgeJSLiftParameter(valueBytes, valueLen) + PropertyHolder.bridgeJSLiftParameter(_self).stringValue = String.bridgeJSLiftParameter(valueBytes, valueLength) #else fatalError("Only available on WebAssembly") #endif @@ -1171,8 +1290,8 @@ public func _bjs_PropertyHolder_stringValue_set(_self: UnsafeMutableRawPointer, @_cdecl("bjs_PropertyHolder_readonlyInt_get") public func _bjs_PropertyHolder_readonlyInt_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyInt - return Int32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyInt + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1182,8 +1301,8 @@ public func _bjs_PropertyHolder_readonlyInt_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_readonlyFloat_get") public func _bjs_PropertyHolder_readonlyFloat_get(_self: UnsafeMutableRawPointer) -> Float32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyFloat - return Float32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyFloat + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1193,8 +1312,8 @@ public func _bjs_PropertyHolder_readonlyFloat_get(_self: UnsafeMutableRawPointer @_cdecl("bjs_PropertyHolder_readonlyDouble_get") public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointer) -> Float64 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyDouble - return Float64(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyDouble + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1204,7 +1323,7 @@ public func _bjs_PropertyHolder_readonlyDouble_get(_self: UnsafeMutableRawPointe @_cdecl("bjs_PropertyHolder_readonlyBool_get") public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyBool + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyBool return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -1215,7 +1334,7 @@ public func _bjs_PropertyHolder_readonlyBool_get(_self: UnsafeMutableRawPointer) @_cdecl("bjs_PropertyHolder_readonlyString_get") public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().readonlyString + let ret = PropertyHolder.bridgeJSLiftParameter(_self).readonlyString return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -1226,7 +1345,7 @@ public func _bjs_PropertyHolder_readonlyString_get(_self: UnsafeMutableRawPointe @_cdecl("bjs_PropertyHolder_jsObject_get") public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject + let ret = PropertyHolder.bridgeJSLiftParameter(_self).jsObject return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -1237,7 +1356,7 @@ public func _bjs_PropertyHolder_jsObject_get(_self: UnsafeMutableRawPointer) -> @_cdecl("bjs_PropertyHolder_jsObject_set") public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().jsObject = JSObject.bridgeJSLiftParameter(value) + PropertyHolder.bridgeJSLiftParameter(_self).jsObject = JSObject.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1247,8 +1366,8 @@ public func _bjs_PropertyHolder_jsObject_set(_self: UnsafeMutableRawPointer, val @_cdecl("bjs_PropertyHolder_sibling_get") public func _bjs_PropertyHolder_sibling_get(_self: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().sibling - return Unmanaged.passRetained(ret).toOpaque() + let ret = PropertyHolder.bridgeJSLiftParameter(_self).sibling + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1258,7 +1377,7 @@ public func _bjs_PropertyHolder_sibling_get(_self: UnsafeMutableRawPointer) -> U @_cdecl("bjs_PropertyHolder_sibling_set") public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, value: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().sibling = Unmanaged.fromOpaque(value).takeUnretainedValue() + PropertyHolder.bridgeJSLiftParameter(_self).sibling = SimplePropertyHolder.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1268,7 +1387,7 @@ public func _bjs_PropertyHolder_sibling_set(_self: UnsafeMutableRawPointer, valu @_cdecl("bjs_PropertyHolder_lazyValue_get") public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue + let ret = PropertyHolder.bridgeJSLiftParameter(_self).lazyValue return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -1277,9 +1396,9 @@ public func _bjs_PropertyHolder_lazyValue_get(_self: UnsafeMutableRawPointer) -> @_expose(wasm, "bjs_PropertyHolder_lazyValue_set") @_cdecl("bjs_PropertyHolder_lazyValue_set") -public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { +public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().lazyValue = String.bridgeJSLiftParameter(valueBytes, valueLen) + PropertyHolder.bridgeJSLiftParameter(_self).lazyValue = String.bridgeJSLiftParameter(valueBytes, valueLength) #else fatalError("Only available on WebAssembly") #endif @@ -1289,8 +1408,8 @@ public func _bjs_PropertyHolder_lazyValue_set(_self: UnsafeMutableRawPointer, va @_cdecl("bjs_PropertyHolder_computedReadonly_get") public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadonly - return Int32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).computedReadonly + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1300,7 +1419,7 @@ public func _bjs_PropertyHolder_computedReadonly_get(_self: UnsafeMutableRawPoin @_cdecl("bjs_PropertyHolder_computedReadWrite_get") public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPointer) -> Void { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite + let ret = PropertyHolder.bridgeJSLiftParameter(_self).computedReadWrite return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") @@ -1309,9 +1428,9 @@ public func _bjs_PropertyHolder_computedReadWrite_get(_self: UnsafeMutableRawPoi @_expose(wasm, "bjs_PropertyHolder_computedReadWrite_set") @_cdecl("bjs_PropertyHolder_computedReadWrite_set") -public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLen: Int32) -> Void { +public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPointer, valueBytes: Int32, valueLength: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().computedReadWrite = String.bridgeJSLiftParameter(valueBytes, valueLen) + PropertyHolder.bridgeJSLiftParameter(_self).computedReadWrite = String.bridgeJSLiftParameter(valueBytes, valueLength) #else fatalError("Only available on WebAssembly") #endif @@ -1321,8 +1440,8 @@ public func _bjs_PropertyHolder_computedReadWrite_set(_self: UnsafeMutableRawPoi @_cdecl("bjs_PropertyHolder_observedProperty_get") public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPointer) -> Int32 { #if arch(wasm32) - let ret = Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty - return Int32(ret) + let ret = PropertyHolder.bridgeJSLiftParameter(_self).observedProperty + return ret.bridgeJSLowerReturn() #else fatalError("Only available on WebAssembly") #endif @@ -1332,7 +1451,7 @@ public func _bjs_PropertyHolder_observedProperty_get(_self: UnsafeMutableRawPoin @_cdecl("bjs_PropertyHolder_observedProperty_set") public func _bjs_PropertyHolder_observedProperty_set(_self: UnsafeMutableRawPointer, value: Int32) -> Void { #if arch(wasm32) - Unmanaged.fromOpaque(_self).takeUnretainedValue().observedProperty = Int.bridgeJSLiftParameter(value) + PropertyHolder.bridgeJSLiftParameter(_self).observedProperty = Int.bridgeJSLiftParameter(value) #else fatalError("Only available on WebAssembly") #endif @@ -1344,10 +1463,16 @@ public func _bjs_PropertyHolder_deinit(pointer: UnsafeMutableRawPointer) { Unmanaged.fromOpaque(pointer).release() } -extension PropertyHolder: ConvertibleToJSValue { +extension PropertyHolder: ConvertibleToJSValue, _BridgedSwiftHeapObject { var jsValue: JSValue { + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_PropertyHolder_wrap") func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PropertyHolder_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_PropertyHolder_wrap(Unmanaged.passRetained(self).toOpaque())))) } } \ No newline at end of file diff --git a/Tests/JavaScriptEventLoopTests/WebWorkerDedicatedExecutorTests.swift b/Tests/JavaScriptEventLoopTests/WebWorkerDedicatedExecutorTests.swift index b6c2bd8d..aae8c2ce 100644 --- a/Tests/JavaScriptEventLoopTests/WebWorkerDedicatedExecutorTests.swift +++ b/Tests/JavaScriptEventLoopTests/WebWorkerDedicatedExecutorTests.swift @@ -2,6 +2,7 @@ import XCTest @testable import JavaScriptEventLoop +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) final class WebWorkerDedicatedExecutorTests: XCTestCase { actor MyActor { let executor: WebWorkerDedicatedExecutor From 177be43a5f72f7a910c77a7c81a78fe63e49d25c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 23 Aug 2025 23:37:19 +0900 Subject: [PATCH 48/61] BridgeJS: Complete protocol-based lift/lower for ImportTS and _JSBridgedClass Add _JSBridgedClass protocol with lift/lower operations for JavaScript objects imported into Swift. Update ImportTS code generation to use new protocol-based bridgeJS* methods. Update all generated ImportTS Swift files and test snapshots to use the new bridgeJS* method calls instead of legacy functions. --- .../Sources/Generated/BridgeJS.ImportTS.swift | 2 +- .../Generated/BridgeJS.ImportTS.swift | 16 +- .../Sources/BridgeJSCore/ImportTS.swift | 188 ++++++++---------- .../ImportTSTests/ArrayParameter.swift | 2 +- .../__Snapshots__/ImportTSTests/Async.swift | 20 +- .../ImportTSTests/Interface.swift | 18 +- .../ImportTSTests/MultipleImportedTypes.swift | 66 +++--- .../ImportTSTests/PrimitiveParameters.swift | 2 +- .../ImportTSTests/PrimitiveReturn.swift | 2 +- .../ImportTSTests/StringParameter.swift | 2 +- .../ImportTSTests/TS2SkeletonLike.swift | 38 ++-- .../ImportTSTests/TypeAlias.swift | 2 +- .../ImportTSTests/TypeScriptClass.swift | 26 +-- .../BasicObjects/JSPromise.swift | 4 - .../JavaScriptKit/BridgeJSInstrincics.swift | 14 ++ Sources/JavaScriptKit/JSBridgedType.swift | 15 +- .../Generated/BridgeJS.ImportTS.swift | 32 ++- 17 files changed, 208 insertions(+), 241 deletions(-) diff --git a/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift b/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift index afce5aca..bc7f0b17 100644 --- a/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift +++ b/Benchmarks/Sources/Generated/BridgeJS.ImportTS.swift @@ -30,7 +30,7 @@ func benchmarkHelperNoopWithNumber(_ n: Double) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_benchmarkHelperNoopWithNumber(n) + bjs_benchmarkHelperNoopWithNumber(n.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift index 3cee3b6e..4d35ef74 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.ImportTS.swift @@ -19,18 +19,14 @@ func createTS2Skeleton() throws(JSException) -> TS2Skeleton { if let error = _swift_js_take_exception() { throw error } - return TS2Skeleton(takingThis: ret) + return TS2Skeleton.bridgeJSLiftReturn(ret) } -struct TS2Skeleton { - let this: JSObject +struct TS2Skeleton: _JSBridgedClass { + let jsObject: JSObject - init(this: JSObject) { - self.this = this - } - - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } func convert(_ ts: String) throws(JSException) -> String { @@ -42,7 +38,7 @@ struct TS2Skeleton { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_TS2Skeleton_convert(self.this.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) + let ret = bjs_TS2Skeleton_convert(self.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index bf836637..3e09e5f6 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -68,70 +68,19 @@ public struct ImportTS { } func lowerParameter(param: Parameter) throws { - switch param.type { - case .bool: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()") - ) - ) - abiParameterSignatures.append((param.name, .i32)) - case .int: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((param.name, .i32)) - case .float: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((param.name, .f32)) - case .double: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name)") - ) - ) - abiParameterSignatures.append((param.name, .f64)) - case .string: - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()") - ) - ) - abiParameterSignatures.append((param.name, .i32)) - case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum: - throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") - case .jsObject(_?): - abiParameterSignatures.append((param.name, .i32)) - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name).this.bridgeJSLowerParameter()") - ) - ) - case .jsObject(nil): - abiParameterForwardings.append( - LabeledExprSyntax( - label: param.label, - expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()") - ) + let loweringInfo = try param.type.loweringParameterInfo() + assert( + loweringInfo.loweredParameters.count == 1, + "For now, we require a single parameter to be lowered to a single Wasm core type" + ) + let (_, type) = loweringInfo.loweredParameters[0] + abiParameterForwardings.append( + LabeledExprSyntax( + label: param.label, + expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()") ) - abiParameterSignatures.append((param.name, .i32)) - case .swiftHeapObject(_): - throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures") - case .void: - break - } + ) + abiParameterSignatures.append((param.name, type)) } func call(returnType: BridgeType) { @@ -146,36 +95,12 @@ public struct ImportTS { } func liftReturnValue(returnType: BridgeType) throws { - switch returnType { - case .bool: - abiReturnType = .i32 - body.append("return \(raw: returnType.swiftType).bridgeJSLiftReturn(ret)") - case .int: - abiReturnType = .i32 - body.append("return \(raw: returnType.swiftType)(ret)") - case .float: - abiReturnType = .f32 - body.append("return \(raw: returnType.swiftType)(ret)") - case .double: - abiReturnType = .f64 - body.append("return \(raw: returnType.swiftType)(ret)") - case .string: - abiReturnType = .i32 - body.append("return \(raw: returnType.swiftType).bridgeJSLiftReturn(ret)") - case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum: - throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") - case .jsObject(let name): - abiReturnType = .i32 - if let name = name { - body.append("return \(raw: name)(takingThis: ret)") - } else { - body.append("return JSObject.bridgeJSLiftReturn(ret)") - } - case .swiftHeapObject(_): - throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures") - case .void: - break + let liftingInfo = try returnType.liftingReturnInfo() + abiReturnType = liftingInfo.valueToLift + if returnType == .void { + return } + body.append("return \(raw: returnType.swiftType).bridgeJSLiftReturn(ret)") } func assignThis(returnType: BridgeType) { @@ -183,7 +108,7 @@ public struct ImportTS { preconditionFailure("assignThis can only be called with a jsObject return type") } abiReturnType = .i32 - body.append("self.this = JSObject(id: UInt32(bitPattern: ret))") + body.append("self.jsObject = JSObject(id: UInt32(bitPattern: ret))") } func renderImportDecl() -> DeclSyntax { @@ -410,25 +335,22 @@ public struct ImportTS { let classDecl = try StructDeclSyntax( leadingTrivia: Self.renderDocumentation(documentation: type.documentation), name: .identifier(name), + inheritanceClause: InheritanceClauseSyntax( + inheritedTypesBuilder: { + InheritedTypeSyntax(type: TypeSyntax("_JSBridgedClass")) + } + ), memberBlockBuilder: { DeclSyntax( """ - let this: JSObject + let jsObject: JSObject """ ).with(\.trailingTrivia, .newlines(2)) DeclSyntax( """ - init(this: JSObject) { - self.this = this - } - """ - ).with(\.trailingTrivia, .newlines(2)) - - DeclSyntax( - """ - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } """ ).with(\.trailingTrivia, .newlines(2)) @@ -483,3 +405,61 @@ public struct ImportTS { ) } } + +extension BridgeType { + struct LoweringParameterInfo { + let loweredParameters: [(name: String, type: WasmCoreType)] + + static let bool = LoweringParameterInfo(loweredParameters: [("value", .i32)]) + static let int = LoweringParameterInfo(loweredParameters: [("value", .i32)]) + static let float = LoweringParameterInfo(loweredParameters: [("value", .f32)]) + static let double = LoweringParameterInfo(loweredParameters: [("value", .f64)]) + static let string = LoweringParameterInfo(loweredParameters: [("value", .i32)]) + static let jsObject = LoweringParameterInfo(loweredParameters: [("value", .i32)]) + static let void = LoweringParameterInfo(loweredParameters: []) + } + + func loweringParameterInfo() throws -> LoweringParameterInfo { + switch self { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .jsObject: return .jsObject + case .void: return .void + case .swiftHeapObject: + throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures") + case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum: + throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") + } + } + + struct LiftingReturnInfo { + let valueToLift: WasmCoreType? + + static let bool = LiftingReturnInfo(valueToLift: .i32) + static let int = LiftingReturnInfo(valueToLift: .i32) + static let float = LiftingReturnInfo(valueToLift: .f32) + static let double = LiftingReturnInfo(valueToLift: .f64) + static let string = LiftingReturnInfo(valueToLift: .i32) + static let jsObject = LiftingReturnInfo(valueToLift: .i32) + static let void = LiftingReturnInfo(valueToLift: nil) + } + + func liftingReturnInfo() throws -> LiftingReturnInfo { + switch self { + case .bool: return .bool + case .int: return .int + case .float: return .float + case .double: return .double + case .string: return .string + case .jsObject: return .jsObject + case .void: return .void + case .swiftHeapObject: + throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures") + case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum: + throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports") + } + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift index f8974961..96fac13d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/ArrayParameter.swift @@ -30,7 +30,7 @@ func checkArrayWithLength(_ a: JSObject, _ b: Double) throws(JSException) -> Voi fatalError("Only available on WebAssembly") } #endif - bjs_checkArrayWithLength(a.bridgeJSLowerParameter(), b) + bjs_checkArrayWithLength(a.bridgeJSLowerParameter(), b.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift index ea9a9ab6..a8ecf8d5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Async.swift @@ -19,7 +19,7 @@ func asyncReturnVoid() throws(JSException) -> JSPromise { if let error = _swift_js_take_exception() { throw error } - return JSPromise(takingThis: ret) + return JSPromise.bridgeJSLiftReturn(ret) } func asyncRoundTripInt(_ v: Double) throws(JSException) -> JSPromise { @@ -31,11 +31,11 @@ func asyncRoundTripInt(_ v: Double) throws(JSException) -> JSPromise { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_asyncRoundTripInt(v) + let ret = bjs_asyncRoundTripInt(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return JSPromise(takingThis: ret) + return JSPromise.bridgeJSLiftReturn(ret) } func asyncRoundTripString(_ v: String) throws(JSException) -> JSPromise { @@ -51,7 +51,7 @@ func asyncRoundTripString(_ v: String) throws(JSException) -> JSPromise { if let error = _swift_js_take_exception() { throw error } - return JSPromise(takingThis: ret) + return JSPromise.bridgeJSLiftReturn(ret) } func asyncRoundTripBool(_ v: Bool) throws(JSException) -> JSPromise { @@ -67,7 +67,7 @@ func asyncRoundTripBool(_ v: Bool) throws(JSException) -> JSPromise { if let error = _swift_js_take_exception() { throw error } - return JSPromise(takingThis: ret) + return JSPromise.bridgeJSLiftReturn(ret) } func asyncRoundTripFloat(_ v: Double) throws(JSException) -> JSPromise { @@ -79,11 +79,11 @@ func asyncRoundTripFloat(_ v: Double) throws(JSException) -> JSPromise { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_asyncRoundTripFloat(v) + let ret = bjs_asyncRoundTripFloat(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return JSPromise(takingThis: ret) + return JSPromise.bridgeJSLiftReturn(ret) } func asyncRoundTripDouble(_ v: Double) throws(JSException) -> JSPromise { @@ -95,11 +95,11 @@ func asyncRoundTripDouble(_ v: Double) throws(JSException) -> JSPromise { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_asyncRoundTripDouble(v) + let ret = bjs_asyncRoundTripDouble(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return JSPromise(takingThis: ret) + return JSPromise.bridgeJSLiftReturn(ret) } func asyncRoundTripJSObject(_ v: JSObject) throws(JSException) -> JSPromise { @@ -115,5 +115,5 @@ func asyncRoundTripJSObject(_ v: JSObject) throws(JSException) -> JSPromise { if let error = _swift_js_take_exception() { throw error } - return JSPromise(takingThis: ret) + return JSPromise.bridgeJSLiftReturn(ret) } \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift index a4ee884f..48a0db2b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift @@ -19,18 +19,14 @@ func returnAnimatable() throws(JSException) -> Animatable { if let error = _swift_js_take_exception() { throw error } - return Animatable(takingThis: ret) + return Animatable.bridgeJSLiftReturn(ret) } -struct Animatable { - let this: JSObject +struct Animatable: _BridgedJSClass { + let jsObject: JSObject - init(this: JSObject) { - self.this = this - } - - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } func animate(_ keyframes: JSObject, _ options: JSObject) throws(JSException) -> JSObject { @@ -42,7 +38,7 @@ struct Animatable { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Animatable_animate(self.this.bridgeJSLowerParameter(), keyframes.bridgeJSLowerParameter(), options.bridgeJSLowerParameter()) + let ret = bjs_Animatable_animate(self.bridgeJSLowerParameter(), keyframes.bridgeJSLowerParameter(), options.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -58,7 +54,7 @@ struct Animatable { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Animatable_getAnimations(self.this.bridgeJSLowerParameter(), options.bridgeJSLowerParameter()) + let ret = bjs_Animatable_getAnimations(self.bridgeJSLowerParameter(), options.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift index ab07c967..c2f39d4f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift @@ -19,7 +19,7 @@ func createDatabaseConnection(_ config: JSObject) throws(JSException) -> Databas if let error = _swift_js_take_exception() { throw error } - return DatabaseConnection(takingThis: ret) + return DatabaseConnection.bridgeJSLiftReturn(ret) } func createLogger(_ level: String) throws(JSException) -> Logger { @@ -35,7 +35,7 @@ func createLogger(_ level: String) throws(JSException) -> Logger { if let error = _swift_js_take_exception() { throw error } - return Logger(takingThis: ret) + return Logger.bridgeJSLiftReturn(ret) } func getConfigManager() throws(JSException) -> ConfigManager { @@ -51,18 +51,14 @@ func getConfigManager() throws(JSException) -> ConfigManager { if let error = _swift_js_take_exception() { throw error } - return ConfigManager(takingThis: ret) + return ConfigManager.bridgeJSLiftReturn(ret) } -struct DatabaseConnection { - let this: JSObject +struct DatabaseConnection: _BridgedJSClass { + let jsObject: JSObject - init(this: JSObject) { - self.this = this - } - - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } var isConnected: Bool { @@ -75,7 +71,7 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_DatabaseConnection_isConnected_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_DatabaseConnection_isConnected_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -93,11 +89,11 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_DatabaseConnection_connectionTimeout_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_DatabaseConnection_connectionTimeout_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return Double(ret) + return Double.bridgeJSLiftReturn(ret) } } @@ -110,7 +106,7 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - bjs_DatabaseConnection_connectionTimeout_set(self.this.bridgeJSLowerParameter(), newValue) + bjs_DatabaseConnection_connectionTimeout_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -125,7 +121,7 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - bjs_DatabaseConnection_connect(self.this.bridgeJSLowerParameter(), url.bridgeJSLowerParameter()) + bjs_DatabaseConnection_connect(self.bridgeJSLowerParameter(), url.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -140,7 +136,7 @@ struct DatabaseConnection { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_DatabaseConnection_execute(self.this.bridgeJSLowerParameter(), query.bridgeJSLowerParameter()) + let ret = bjs_DatabaseConnection_execute(self.bridgeJSLowerParameter(), query.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -149,15 +145,11 @@ struct DatabaseConnection { } -struct Logger { - let this: JSObject - - init(this: JSObject) { - self.this = this - } +struct Logger: _BridgedJSClass { + let jsObject: JSObject - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } var level: String { @@ -170,7 +162,7 @@ struct Logger { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Logger_level_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_Logger_level_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -187,7 +179,7 @@ struct Logger { fatalError("Only available on WebAssembly") } #endif - bjs_Logger_log(self.this.bridgeJSLowerParameter(), message.bridgeJSLowerParameter()) + bjs_Logger_log(self.bridgeJSLowerParameter(), message.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -202,7 +194,7 @@ struct Logger { fatalError("Only available on WebAssembly") } #endif - bjs_Logger_error(self.this.bridgeJSLowerParameter(), message.bridgeJSLowerParameter(), error.bridgeJSLowerParameter()) + bjs_Logger_error(self.bridgeJSLowerParameter(), message.bridgeJSLowerParameter(), error.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -210,15 +202,11 @@ struct Logger { } -struct ConfigManager { - let this: JSObject - - init(this: JSObject) { - self.this = this - } +struct ConfigManager: _BridgedJSClass { + let jsObject: JSObject - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } var configPath: String { @@ -231,7 +219,7 @@ struct ConfigManager { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_ConfigManager_configPath_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_ConfigManager_configPath_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -248,7 +236,7 @@ struct ConfigManager { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_ConfigManager_get(self.this.bridgeJSLowerParameter(), key.bridgeJSLowerParameter()) + let ret = bjs_ConfigManager_get(self.bridgeJSLowerParameter(), key.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -264,7 +252,7 @@ struct ConfigManager { fatalError("Only available on WebAssembly") } #endif - bjs_ConfigManager_set(self.this.bridgeJSLowerParameter(), key.bridgeJSLowerParameter(), value.bridgeJSLowerParameter()) + bjs_ConfigManager_set(self.bridgeJSLowerParameter(), key.bridgeJSLowerParameter(), value.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift index 43b66797..30f66a26 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveParameters.swift @@ -15,7 +15,7 @@ func check(_ a: Double, _ b: Bool) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_check(a, b.bridgeJSLowerParameter()) + bjs_check(a.bridgeJSLowerParameter(), b.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift index 77dcc710..29ba81c6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/PrimitiveReturn.swift @@ -19,7 +19,7 @@ func checkNumber() throws(JSException) -> Double { if let error = _swift_js_take_exception() { throw error } - return Double(ret) + return Double.bridgeJSLiftReturn(ret) } func checkBoolean() throws(JSException) -> Bool { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift index 7ecbbac9..99215a30 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/StringParameter.swift @@ -30,7 +30,7 @@ func checkStringWithLength(_ a: String, _ b: Double) throws(JSException) -> Void fatalError("Only available on WebAssembly") } #endif - bjs_checkStringWithLength(a.bridgeJSLowerParameter(), b) + bjs_checkStringWithLength(a.bridgeJSLowerParameter(), b.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift index 68acd5bf..2a1a76ce 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift @@ -19,7 +19,7 @@ func createTS2Skeleton() throws(JSException) -> TypeScriptProcessor { if let error = _swift_js_take_exception() { throw error } - return TypeScriptProcessor(takingThis: ret) + return TypeScriptProcessor.bridgeJSLiftReturn(ret) } func createCodeGenerator(_ format: String) throws(JSException) -> CodeGenerator { @@ -35,18 +35,14 @@ func createCodeGenerator(_ format: String) throws(JSException) -> CodeGenerator if let error = _swift_js_take_exception() { throw error } - return CodeGenerator(takingThis: ret) + return CodeGenerator.bridgeJSLiftReturn(ret) } -struct TypeScriptProcessor { - let this: JSObject +struct TypeScriptProcessor: _BridgedJSClass { + let jsObject: JSObject - init(this: JSObject) { - self.this = this - } - - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } var version: String { @@ -59,7 +55,7 @@ struct TypeScriptProcessor { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_TypeScriptProcessor_version_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_TypeScriptProcessor_version_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -76,7 +72,7 @@ struct TypeScriptProcessor { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_TypeScriptProcessor_convert(self.this.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) + let ret = bjs_TypeScriptProcessor_convert(self.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -92,7 +88,7 @@ struct TypeScriptProcessor { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_TypeScriptProcessor_validate(self.this.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) + let ret = bjs_TypeScriptProcessor_validate(self.bridgeJSLowerParameter(), ts.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -101,15 +97,11 @@ struct TypeScriptProcessor { } -struct CodeGenerator { - let this: JSObject - - init(this: JSObject) { - self.this = this - } +struct CodeGenerator: _BridgedJSClass { + let jsObject: JSObject - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } var outputFormat: String { @@ -122,7 +114,7 @@ struct CodeGenerator { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_CodeGenerator_outputFormat_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_CodeGenerator_outputFormat_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -139,7 +131,7 @@ struct CodeGenerator { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_CodeGenerator_generate(self.this.bridgeJSLowerParameter(), input.bridgeJSLowerParameter()) + let ret = bjs_CodeGenerator_generate(self.bridgeJSLowerParameter(), input.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift index 7523101f..d8b18463 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeAlias.swift @@ -15,7 +15,7 @@ func checkSimple(_ a: Double) throws(JSException) -> Void { fatalError("Only available on WebAssembly") } #endif - bjs_checkSimple(a) + bjs_checkSimple(a.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift index f22bfda2..649eb8cf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift @@ -6,15 +6,11 @@ @_spi(BridgeJS) import JavaScriptKit -struct Greeter { - let this: JSObject +struct Greeter: _BridgedJSClass { + let jsObject: JSObject - init(this: JSObject) { - self.this = this - } - - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } init(_ name: String) throws(JSException) { @@ -30,7 +26,7 @@ struct Greeter { if let error = _swift_js_take_exception() { throw error } - self.this = JSObject(id: UInt32(bitPattern: ret)) + self.jsObject = JSObject(id: UInt32(bitPattern: ret)) } var name: String { @@ -43,7 +39,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Greeter_name_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_Greeter_name_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -60,7 +56,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - bjs_Greeter_name_set(self.this.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) + bjs_Greeter_name_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -76,11 +72,11 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Greeter_age_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_Greeter_age_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return Double(ret) + return Double.bridgeJSLiftReturn(ret) } } @@ -93,7 +89,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_Greeter_greet(self.this.bridgeJSLowerParameter()) + let ret = bjs_Greeter_greet(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -109,7 +105,7 @@ struct Greeter { fatalError("Only available on WebAssembly") } #endif - bjs_Greeter_changeName(self.this.bridgeJSLowerParameter(), name.bridgeJSLowerParameter()) + bjs_Greeter_changeName(self.bridgeJSLowerParameter(), name.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index ec2a7724..201e8fa6 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -23,10 +23,6 @@ public final class JSPromise: JSBridgedClass { self.init(from: jsObject) } - @_spi(BridgeJS) public convenience init(takingThis: Int32) { - self.init(unsafelyWrapping: JSObject(id: UInt32(bitPattern: takingThis))) - } - /// Creates a new `JSPromise` instance from a given JavaScript `Promise` object. If `value` /// is not an object and is not an instance of JavaScript `Promise`, this function will /// return `nil`. diff --git a/Sources/JavaScriptKit/BridgeJSInstrincics.swift b/Sources/JavaScriptKit/BridgeJSInstrincics.swift index 4d609dc7..969fb4e1 100644 --- a/Sources/JavaScriptKit/BridgeJSInstrincics.swift +++ b/Sources/JavaScriptKit/BridgeJSInstrincics.swift @@ -293,6 +293,20 @@ extension _BridgedSwiftHeapObject { } } +extension _JSBridgedClass { + // MARK: ImportTS + @_spi(BridgeJS) public consuming func bridgeJSLowerParameter() -> Int32 { jsObject.bridgeJSLowerParameter() } + @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ id: Int32) -> Self { + Self(unsafelyWrapping: JSObject.bridgeJSLiftReturn(id)) + } + + // MARK: ExportSwift + @_spi(BridgeJS) public static func bridgeJSLiftParameter(_ id: Int32) -> Self { + Self(unsafelyWrapping: JSObject.bridgeJSLiftParameter(id)) + } + @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Int32 { jsObject.bridgeJSLowerReturn() } +} + /// A protocol that Swift enum types that do not have a payload can conform to. /// /// The conformance is automatically synthesized by the BridgeJS code generator. diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index 92739079..ffa65f4a 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -13,8 +13,21 @@ extension JSBridgedType { public var description: String { jsValue.description } } +/// A protocol that Swift classes that are exposed to JavaScript via `@JS class` conform to. +/// +/// The conformance is automatically synthesized by the BridgeJS code generator. +public protocol _JSBridgedClass { + /// The JavaScript object wrapped by this instance. + /// You may assume that `jsObject instanceof Self.constructor == true` + var jsObject: JSObject { get } + + /// Create an instance wrapping the given JavaScript object. + /// You may assume that `jsObject instanceof Self.constructor` + init(unsafelyWrapping jsObject: JSObject) +} + /// Conform to this protocol when your Swift class wraps a JavaScript class. -public protocol JSBridgedClass: JSBridgedType { +public protocol JSBridgedClass: JSBridgedType, _JSBridgedClass { /// The constructor function for the JavaScript class static var constructor: JSFunction? { get } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift index 733a085f..19bc82fc 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ImportTS.swift @@ -30,11 +30,11 @@ func jsRoundTripNumber(_ v: Double) throws(JSException) -> Double { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_jsRoundTripNumber(v) + let ret = bjs_jsRoundTripNumber(v.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } - return Double(ret) + return Double.bridgeJSLiftReturn(ret) } func jsRoundTripBool(_ v: Bool) throws(JSException) -> Bool { @@ -97,7 +97,7 @@ func jsThrowOrNumber(_ shouldThrow: Bool) throws(JSException) -> Double { if let error = _swift_js_take_exception() { throw error } - return Double(ret) + return Double.bridgeJSLiftReturn(ret) } func jsThrowOrBool(_ shouldThrow: Bool) throws(JSException) -> Bool { @@ -145,18 +145,14 @@ func runAsyncWorks() throws(JSException) -> JSPromise { if let error = _swift_js_take_exception() { throw error } - return JSPromise(takingThis: ret) + return JSPromise.bridgeJSLiftReturn(ret) } -struct JsGreeter { - let this: JSObject +struct JsGreeter: _JSBridgedClass { + let jsObject: JSObject - init(this: JSObject) { - self.this = this - } - - init(takingThis this: Int32) { - self.this = JSObject(id: UInt32(bitPattern: this)) + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject } init(_ name: String, _ prefix: String) throws(JSException) { @@ -172,7 +168,7 @@ struct JsGreeter { if let error = _swift_js_take_exception() { throw error } - self.this = JSObject(id: UInt32(bitPattern: ret)) + self.jsObject = JSObject(id: UInt32(bitPattern: ret)) } var name: String { @@ -185,7 +181,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_JsGreeter_name_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_JsGreeter_name_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -202,7 +198,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - bjs_JsGreeter_name_set(self.this.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) + bjs_JsGreeter_name_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -218,7 +214,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_JsGreeter_prefix_get(self.this.bridgeJSLowerParameter()) + let ret = bjs_JsGreeter_prefix_get(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -235,7 +231,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - let ret = bjs_JsGreeter_greet(self.this.bridgeJSLowerParameter()) + let ret = bjs_JsGreeter_greet(self.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } @@ -251,7 +247,7 @@ struct JsGreeter { fatalError("Only available on WebAssembly") } #endif - bjs_JsGreeter_changeName(self.this.bridgeJSLowerParameter(), name.bridgeJSLowerParameter()) + bjs_JsGreeter_changeName(self.bridgeJSLowerParameter(), name.bridgeJSLowerParameter()) if let error = _swift_js_take_exception() { throw error } From 9aad68d14fa1232b3f5e220a3a5429b2938072c6 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 24 Aug 2025 01:18:11 +0900 Subject: [PATCH 49/61] BridgeJS: Provide default implementation for BridgeJS functions This is a workaround for the issue that reference to imported bjs wasm functions can't be eliminated in debug builds, even if they are not reachable at runtime without explicit BridgeJS usage. --- Plugins/PackageToJS/Templates/instantiate.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Plugins/PackageToJS/Templates/instantiate.js b/Plugins/PackageToJS/Templates/instantiate.js index 5d6fe6b9..9b35efd1 100644 --- a/Plugins/PackageToJS/Templates/instantiate.js +++ b/Plugins/PackageToJS/Templates/instantiate.js @@ -27,7 +27,20 @@ async function createInstantiator(options, swift) { * @param {WebAssembly.Imports} importObject * @param {unknown} importsContext */ - addImports: (importObject, importsContext) => {}, + addImports: (importObject, importsContext) => { + // Provide a default implementation for BridgeJS functions that are not + // used at runtime without BridgeJS but required to instantiate the module. + const unexpectedBjsCall = () => { throw new Error("Unexpected call to BridgeJS function") } + importObject["bjs"] = { + swift_js_return_string: unexpectedBjsCall, + swift_js_init_memory: unexpectedBjsCall, + swift_js_make_js_string: unexpectedBjsCall, + swift_js_init_memory_with_result: unexpectedBjsCall, + swift_js_throw: unexpectedBjsCall, + swift_js_retain: unexpectedBjsCall, + swift_js_release: unexpectedBjsCall, + } + }, /** @param {WebAssembly.Instance} instance */ setInstance: (instance) => {}, /** @param {WebAssembly.Instance} instance */ From 7a31c5e749b9dc3820579a6dbbb248e2ff04989a Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 24 Aug 2025 01:52:38 +0900 Subject: [PATCH 50/61] BridgeJS: Update test snapshot files --- .../__Snapshots__/ImportTSTests/Interface.swift | 2 +- .../__Snapshots__/ImportTSTests/MultipleImportedTypes.swift | 6 +++--- .../__Snapshots__/ImportTSTests/TS2SkeletonLike.swift | 4 ++-- .../__Snapshots__/ImportTSTests/TypeScriptClass.swift | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift index 48a0db2b..68f14808 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/Interface.swift @@ -22,7 +22,7 @@ func returnAnimatable() throws(JSException) -> Animatable { return Animatable.bridgeJSLiftReturn(ret) } -struct Animatable: _BridgedJSClass { +struct Animatable: _JSBridgedClass { let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift index c2f39d4f..810df368 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/MultipleImportedTypes.swift @@ -54,7 +54,7 @@ func getConfigManager() throws(JSException) -> ConfigManager { return ConfigManager.bridgeJSLiftReturn(ret) } -struct DatabaseConnection: _BridgedJSClass { +struct DatabaseConnection: _JSBridgedClass { let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { @@ -145,7 +145,7 @@ struct DatabaseConnection: _BridgedJSClass { } -struct Logger: _BridgedJSClass { +struct Logger: _JSBridgedClass { let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { @@ -202,7 +202,7 @@ struct Logger: _BridgedJSClass { } -struct ConfigManager: _BridgedJSClass { +struct ConfigManager: _JSBridgedClass { let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift index 2a1a76ce..0b17f13b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TS2SkeletonLike.swift @@ -38,7 +38,7 @@ func createCodeGenerator(_ format: String) throws(JSException) -> CodeGenerator return CodeGenerator.bridgeJSLiftReturn(ret) } -struct TypeScriptProcessor: _BridgedJSClass { +struct TypeScriptProcessor: _JSBridgedClass { let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { @@ -97,7 +97,7 @@ struct TypeScriptProcessor: _BridgedJSClass { } -struct CodeGenerator: _BridgedJSClass { +struct CodeGenerator: _JSBridgedClass { let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift index 649eb8cf..455b38bc 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/TypeScriptClass.swift @@ -6,7 +6,7 @@ @_spi(BridgeJS) import JavaScriptKit -struct Greeter: _BridgedJSClass { +struct Greeter: _JSBridgedClass { let jsObject: JSObject init(unsafelyWrapping jsObject: JSObject) { From 5f6573833079dcb11fdddf70b10a513f3157cf5e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 24 Aug 2025 01:52:41 +0900 Subject: [PATCH 51/61] BridgeJS: Repair PlayBridgeJS build --- .../Sources/PlayBridgeJS/BridgeJSUtilities | 1 + Plugins/BridgeJS/Package.swift | 7 ++++++- .../BridgeJS/Sources/BridgeJSCore/ExportSwift.swift | 3 +++ Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift | 3 +++ .../BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift | 10 +++------- .../Utilities.swift | 2 +- Plugins/PackageToJS/Sources/BridgeJSUtilities | 1 + Sources/BridgeJSTool/BridgeJSUtilities | 1 + 8 files changed, 19 insertions(+), 9 deletions(-) create mode 120000 Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSUtilities rename Plugins/BridgeJS/Sources/{BridgeJSCore => BridgeJSUtilities}/Utilities.swift (71%) create mode 120000 Plugins/PackageToJS/Sources/BridgeJSUtilities create mode 120000 Sources/BridgeJSTool/BridgeJSUtilities diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSUtilities b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSUtilities new file mode 120000 index 00000000..a43f43e2 --- /dev/null +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/BridgeJSUtilities @@ -0,0 +1 @@ +../../../../Plugins/BridgeJS/Sources/BridgeJSUtilities \ No newline at end of file diff --git a/Plugins/BridgeJS/Package.swift b/Plugins/BridgeJS/Package.swift index 9ac96d95..bb7daac2 100644 --- a/Plugins/BridgeJS/Package.swift +++ b/Plugins/BridgeJS/Package.swift @@ -29,6 +29,7 @@ let package = Package( name: "BridgeJSCore", dependencies: [ "BridgeJSSkeleton", + "BridgeJSUtilities", .product(name: "SwiftParser", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftBasicFormat", package: "swift-syntax"), @@ -36,10 +37,14 @@ let package = Package( ] ), .target(name: "BridgeJSSkeleton"), + .target(name: "BridgeJSUtilities"), .target( name: "BridgeJSLink", - dependencies: ["BridgeJSSkeleton"] + dependencies: [ + "BridgeJSSkeleton", + "BridgeJSUtilities", + ] ), .testTarget( diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index cefb334f..76ce16d6 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -4,6 +4,9 @@ import SwiftSyntaxBuilder #if canImport(BridgeJSSkeleton) import BridgeJSSkeleton #endif +#if canImport(BridgeJSUtilities) +import BridgeJSUtilities +#endif /// Exports Swift functions and classes to JavaScript /// diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 3e09e5f6..148157d3 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -4,6 +4,9 @@ import SwiftSyntaxBuilder #if canImport(BridgeJSSkeleton) import BridgeJSSkeleton #endif +#if canImport(BridgeJSUtilities) +import BridgeJSUtilities +#endif /// Imports TypeScript declarations and generates Swift bridge code /// diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index f704b828..360a6274 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -3,6 +3,9 @@ import struct Foundation.Data #if canImport(BridgeJSSkeleton) import BridgeJSSkeleton #endif +#if canImport(BridgeJSUtilities) +import BridgeJSUtilities +#endif struct BridgeJSLink { /// The exported skeletons @@ -1456,13 +1459,6 @@ extension String { } } -fileprivate extension String { - var capitalizedFirstLetter: String { - guard !isEmpty else { return self } - return prefix(1).uppercased() + dropFirst() - } -} - extension BridgeType { var tsType: String { switch self { diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/Utilities.swift b/Plugins/BridgeJS/Sources/BridgeJSUtilities/Utilities.swift similarity index 71% rename from Plugins/BridgeJS/Sources/BridgeJSCore/Utilities.swift rename to Plugins/BridgeJS/Sources/BridgeJSUtilities/Utilities.swift index fb3a6e16..f091e4a3 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/Utilities.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSUtilities/Utilities.swift @@ -1,5 +1,5 @@ extension String { - var capitalizedFirstLetter: String { + public var capitalizedFirstLetter: String { guard !isEmpty else { return self } return prefix(1).uppercased() + dropFirst() } diff --git a/Plugins/PackageToJS/Sources/BridgeJSUtilities b/Plugins/PackageToJS/Sources/BridgeJSUtilities new file mode 120000 index 00000000..52bf489d --- /dev/null +++ b/Plugins/PackageToJS/Sources/BridgeJSUtilities @@ -0,0 +1 @@ +../../../Plugins/BridgeJS/Sources/BridgeJSUtilities \ No newline at end of file diff --git a/Sources/BridgeJSTool/BridgeJSUtilities b/Sources/BridgeJSTool/BridgeJSUtilities new file mode 120000 index 00000000..75db3bd9 --- /dev/null +++ b/Sources/BridgeJSTool/BridgeJSUtilities @@ -0,0 +1 @@ +../../Plugins/BridgeJS/Sources/BridgeJSUtilities \ No newline at end of file From 07c1ef730bd215bdb5dcd88018e769550b15fa9c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 24 Aug 2025 09:35:40 +0900 Subject: [PATCH 52/61] BridgeJS: Clean up protocol definitions and documentation Remove unused _BridgedSwiftTypeLoweredIntoWasmCoreType protocol and fix incomplete documentation comment for the remaining protocol. --- Sources/JavaScriptKit/BridgeJSInstrincics.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sources/JavaScriptKit/BridgeJSInstrincics.swift b/Sources/JavaScriptKit/BridgeJSInstrincics.swift index 969fb4e1..56b87fa5 100644 --- a/Sources/JavaScriptKit/BridgeJSInstrincics.swift +++ b/Sources/JavaScriptKit/BridgeJSInstrincics.swift @@ -79,13 +79,7 @@ import _CJavaScriptKit // // See JSGlueGen.swift in BridgeJSLink for JS-side lowering/lifting implementation. -/// A protocol that Swift types that can be lowered into a single Wasm core type. -public protocol _BridgedSwiftTypeLoweredIntoWasmCoreType { - associatedtype WasmCoreType -} - /// A protocol that Swift types that can appear as parameters or return values on -/// The conformance is automatically synthesized by the BridgeJS code generator. public protocol _BridgedSwiftTypeLoweredIntoSingleWasmCoreType { associatedtype WasmCoreType // MARK: ImportTS From 5b3c55e6b52ad10002ea3eb1d7695967d86013cf Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 24 Aug 2025 11:15:34 +0900 Subject: [PATCH 53/61] BridgeJS: Emit TypeScript errors to stderr instead of stdout --- Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js index f708082c..41f6e419 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/cli.js @@ -22,13 +22,13 @@ class DiagnosticEngine { getCurrentDirectory: () => ts.sys.getCurrentDirectory(), }; } - + /** * @param {readonly ts.Diagnostic[]} diagnostics */ tsDiagnose(diagnostics) { const message = ts.formatDiagnosticsWithColorAndContext(diagnostics, this.formattHost); - console.log(message); + process.stderr.write(message, "utf-8"); } static LEVELS = { From 4cc348e9f21d0fac25cb5ea42eb567d6537c532f Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 25 Aug 2025 11:31:20 +0900 Subject: [PATCH 54/61] PlayBridgeJS: Use @typescript/vfs to load lib.d.ts --- .../PlayBridgeJS/Sources/JavaScript/app.js | 68 ++++++++++++------- Examples/PlayBridgeJS/index.html | 3 +- Examples/PlayBridgeJS/package.json | 5 ++ 3 files changed, 49 insertions(+), 27 deletions(-) create mode 100644 Examples/PlayBridgeJS/package.json diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index 9e1d39e2..97f3ef28 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -3,6 +3,11 @@ import { EditorSystem } from './editor.js'; import ts from 'typescript'; import { TypeProcessor } from './processor.js'; import { CodeShareManager } from './code-share.js'; +import { + createSystem, + createDefaultMapFromCDN, + createVirtualCompilerHost +} from '@typescript/vfs'; /** * @typedef {import('../../.build/plugins/PackageToJS/outputs/Package/bridge-js.js').PlayBridgeJS} PlayBridgeJS @@ -81,12 +86,11 @@ export class BridgeJSPlayground { try { // Import the BridgeJS module const { init } = await import("../../.build/plugins/PackageToJS/outputs/Package/index.js"); + const virtualHost = await this.createTS2SkeletonFactory(); const { exports } = await init({ - getImports: () => { - return { - createTS2Skeleton: this.createTS2Skeleton - }; - } + getImports: () => ({ + createTS2Skeleton: () => this.createTS2Skeleton(virtualHost) + }) }); this.playBridgeJS = new exports.PlayBridgeJS(); console.log('BridgeJS initialized successfully'); @@ -171,32 +175,44 @@ export class BridgeJSPlayground { }); } - createTS2Skeleton() { + async createTS2SkeletonFactory() { + const createVirtualHost = async () => { + const fsMap = await createDefaultMapFromCDN( + { target: ts.ScriptTarget.ES2015 }, + ts.version, + true, + ts + ); + + const system = createSystem(fsMap); + + const compilerOptions = { + target: ts.ScriptTarget.ES2015, + lib: ["es2015", "dom"], + }; + + return createVirtualCompilerHost(system, compilerOptions, ts); + } + return await createVirtualHost(); + } + + /** + * @param {ReturnType} virtualHost + */ + createTS2Skeleton(virtualHost) { return { + /** + * @param {string} dtsCode + * @returns {string} + */ convert: (dtsCode) => { - const virtualFilePath = "bridge-js.d.ts" - const virtualHost = { - fileExists: fileName => fileName === virtualFilePath, - readFile: fileName => dtsCode, - getSourceFile: (fileName, languageVersion) => { - const sourceText = dtsCode; - if (sourceText === undefined) return undefined; - return ts.createSourceFile(fileName, sourceText, languageVersion); - }, - getDefaultLibFileName: options => "lib.d.ts", - writeFile: (fileName, data) => { - console.log(`[emit] ${fileName}:\n${data}`); - }, - getCurrentDirectory: () => "", - getDirectories: () => [], - getCanonicalFileName: fileName => fileName, - getNewLine: () => "\n", - useCaseSensitiveFileNames: () => true - } // Create TypeScript program from d.ts content + const virtualFilePath = "bridge-js.d.ts" + const sourceFile = ts.createSourceFile(virtualFilePath, dtsCode, ts.ScriptTarget.ES2015); + virtualHost.updateFile(sourceFile); const tsProgram = ts.createProgram({ rootNames: [virtualFilePath], - host: virtualHost, + host: virtualHost.compilerHost, options: { noEmit: true, declaration: true, diff --git a/Examples/PlayBridgeJS/index.html b/Examples/PlayBridgeJS/index.html index 2a584132..0283f100 100644 --- a/Examples/PlayBridgeJS/index.html +++ b/Examples/PlayBridgeJS/index.html @@ -9,7 +9,8 @@ diff --git a/Examples/PlayBridgeJS/package.json b/Examples/PlayBridgeJS/package.json new file mode 100644 index 00000000..2feb6565 --- /dev/null +++ b/Examples/PlayBridgeJS/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@typescript/vfs": "^1.6.1" + } +} From 71f756bc3ad4eef848dddff539d17673eb3be302 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 25 Aug 2025 11:41:34 +0900 Subject: [PATCH 55/61] PlayBridgeJS: Add progress bar for initialization steps --- .../PlayBridgeJS/Sources/JavaScript/app.js | 58 ++++++++++++++++++- .../Sources/JavaScript/styles.css | 41 +++++++++++++ Examples/PlayBridgeJS/index.html | 6 ++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/app.js b/Examples/PlayBridgeJS/Sources/JavaScript/app.js index 97f3ef28..5a3d6199 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/app.js +++ b/Examples/PlayBridgeJS/Sources/JavaScript/app.js @@ -43,6 +43,14 @@ export class BridgeJSPlayground { this.copyButton = /** @type {HTMLButtonElement} */ (document.getElementById('copyButton')); /** @type {HTMLButtonElement} */ this.closeShareDialogButton = /** @type {HTMLButtonElement} */ (document.getElementById('closeShareDialog')); + + // Progress UI elements + /** @type {HTMLDivElement | null} */ + this.progressBar = /** @type {HTMLDivElement} */ (document.getElementById('progressBar')); + /** @type {HTMLDivElement | null} */ + this.progressFill = /** @type {HTMLDivElement} */ (document.getElementById('progressFill')); + /** @type {HTMLDivElement | null} */ + this.progressLabel = /** @type {HTMLDivElement} */ (document.getElementById('progressLabel')); } /** @@ -55,16 +63,20 @@ export class BridgeJSPlayground { } try { + this.showProgress('Starting…', 5); // Initialize editor system await this.editorSystem.init(); + this.setProgress('Editor ready', 30); // Initialize BridgeJS await this.initializeBridgeJS(); + this.setProgress('BridgeJS ready', 70); // Set up event listeners this.setupEventListeners(); // Check for shared code in URL + this.setProgress('Checking shared code…', 80); const sharedCode = await CodeShareManager.extractCodeFromUrl(); if (sharedCode) { this.editorSystem.setInputs(sharedCode); @@ -72,12 +84,15 @@ export class BridgeJSPlayground { // Load sample code this.editorSystem.setInputs(sampleCode); } - + this.setProgress('Finalizing…', 95); this.isInitialized = true; console.log('BridgeJS Playground initialized successfully'); + this.setProgress('Ready', 100); + setTimeout(() => this.hideProgress(), 400); } catch (error) { console.error('Failed to initialize BridgeJS Playground:', error); this.showError('Failed to initialize application: ' + error.message); + this.hideProgress(); } } @@ -85,13 +100,16 @@ export class BridgeJSPlayground { async initializeBridgeJS() { try { // Import the BridgeJS module + this.setProgress('Loading BridgeJS…', 50); const { init } = await import("../../.build/plugins/PackageToJS/outputs/Package/index.js"); const virtualHost = await this.createTS2SkeletonFactory(); + this.setProgress('Preparing TypeScript host…', 60); const { exports } = await init({ getImports: () => ({ createTS2Skeleton: () => this.createTS2Skeleton(virtualHost) }) }); + this.setProgress('Creating runtime…', 65); this.playBridgeJS = new exports.PlayBridgeJS(); console.log('BridgeJS initialized successfully'); } catch (error) { @@ -284,4 +302,42 @@ export class BridgeJSPlayground { hideError() { this.errorDisplay.classList.remove('show'); } + + /** + * Shows progress bar. + * @param {string} label + * @param {number} percent + */ + showProgress(label, percent) { + if (this.progressBar) { + this.progressBar.classList.add('show'); + this.progressBar.classList.remove('hidden'); + } + this.setProgress(label, percent); + } + + /** + * Updates progress label and percentage. + * @param {string} label + * @param {number} percent + */ + setProgress(label, percent) { + if (this.progressLabel) { + this.progressLabel.textContent = label; + } + if (this.progressFill) { + const clamped = Math.max(0, Math.min(100, Number(percent) || 0)); + this.progressFill.style.width = clamped + '%'; + } + } + + /** + * Hides progress bar. + */ + hideProgress() { + if (this.progressBar) { + this.progressBar.classList.remove('show'); + this.progressBar.classList.add('hidden'); + } + } } \ No newline at end of file diff --git a/Examples/PlayBridgeJS/Sources/JavaScript/styles.css b/Examples/PlayBridgeJS/Sources/JavaScript/styles.css index 1a8414e2..da62834e 100644 --- a/Examples/PlayBridgeJS/Sources/JavaScript/styles.css +++ b/Examples/PlayBridgeJS/Sources/JavaScript/styles.css @@ -221,6 +221,47 @@ body { word-break: break-word; } +/* Progress bar */ +.progress { + margin: 16px auto 0 auto; + max-width: 640px; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease; +} + +.progress.show { + opacity: 1; + visibility: visible; +} + +.progress.hidden { + opacity: 0; + visibility: hidden; +} + +.progress-track { + height: 8px; + background-color: var(--color-fill-tertiary); + border: 1px solid var(--color-border); + border-radius: 999px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + width: 0%; + background-color: var(--color-figure-blue); + transition: width 0.2s ease; +} + +.progress-label { + margin-top: 8px; + text-align: center; + font-size: 12px; + color: var(--color-secondary-label); +} + .main-content { flex: 1; display: grid; diff --git a/Examples/PlayBridgeJS/index.html b/Examples/PlayBridgeJS/index.html index 0283f100..799c36a1 100644 --- a/Examples/PlayBridgeJS/index.html +++ b/Examples/PlayBridgeJS/index.html @@ -42,6 +42,12 @@

Share Your Code

+ From 1b19e6210e538d7f15286f4f9f96e079c70b0d81 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 25 Aug 2025 08:31:36 +0900 Subject: [PATCH 56/61] BridgeJS: Documentation overhaul --- .../BridgeJS/Exporting-Swift-to-JavaScript.md | 604 +----------------- .../Exporting-Swift/Exporting-Swift-Class.md | 94 +++ .../Exporting-Swift/Exporting-Swift-Enum.md | 408 ++++++++++++ .../Exporting-Swift-Function.md | 153 +++++ .../Exporting-Swift/Using-Namespace.md | 95 +++ .../Importing-TypeScript-into-Swift.md | 9 + .../Importing-TS-Class.md | 64 ++ .../Importing-TS-Function.md | 60 ++ .../Importing-TS-Interface.md | 34 + .../Importing-TS-TypeAlias.md | 47 ++ .../Articles/BridgeJS/Supported-Types.md | 18 + .../Documentation.docc/Documentation.md | 3 +- 12 files changed, 991 insertions(+), 598 deletions(-) create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Using-Namespace.md create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Class.md create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Function.md create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Interface.md create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-TypeAlias.md create mode 100644 Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md index ad7932f1..7c512982 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift-to-JavaScript.md @@ -6,6 +6,8 @@ Learn how to make your Swift code callable from JavaScript. > Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + BridgeJS allows you to expose Swift functions, classes, and methods to JavaScript by using the `@JS` attribute. This enables JavaScript code to call into Swift code running in WebAssembly. ## Configuring the BridgeJS plugin @@ -57,603 +59,11 @@ This command will: > Note: For larger projects, you may want to generate the BridgeJS code ahead of time to improve build performance. See for more information. -## Marking Swift Code for Export - -### Functions - -To export a Swift function to JavaScript, mark it with the `@JS` attribute and make it `public`: - -```swift -import JavaScriptKit - -@JS public func calculateTotal(price: Double, quantity: Int) -> Double { - return price * Double(quantity) -} - -@JS public func formatCurrency(amount: Double) -> String { - return "$\(String(format: "%.2f", amount))" -} -``` - -These functions will be accessible from JavaScript: - -```javascript -const total = exports.calculateTotal(19.99, 3); -const formattedTotal = exports.formatCurrency(total); -console.log(formattedTotal); // "$59.97" -``` - -The generated TypeScript declarations for these functions would look like: - -```typescript -export type Exports = { - calculateTotal(price: number, quantity: number): number; - formatCurrency(amount: number): string; -} -``` - -### Classes - -To export a Swift class, mark both the class and any members you want to expose: - -```swift -import JavaScriptKit - -@JS class ShoppingCart { - private var items: [(name: String, price: Double, quantity: Int)] = [] - - @JS init() {} - - @JS public func addItem(name: String, price: Double, quantity: Int) { - items.append((name, price, quantity)) - } - - @JS public func removeItem(atIndex index: Int) { - guard index >= 0 && index < items.count else { return } - items.remove(at: index) - } - - @JS public func getTotal() -> Double { - return items.reduce(0) { $0 + $1.price * Double($1.quantity) } - } - - @JS public func getItemCount() -> Int { - return items.count - } - - // This method won't be accessible from JavaScript (no @JS) - var debugDescription: String { - return "Cart with \(items.count) items, total: \(getTotal())" - } -} -``` - -In JavaScript: - -```javascript -import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; -const { exports } = await init({}); - -const cart = new exports.ShoppingCart(); -cart.addItem("Laptop", 999.99, 1); -cart.addItem("Mouse", 24.99, 2); -console.log(`Items in cart: ${cart.getItemCount()}`); -console.log(`Total: $${cart.getTotal().toFixed(2)}`); -``` - -The generated TypeScript declarations for this class would look like: - -```typescript -// Base interface for Swift reference types -export interface SwiftHeapObject { - release(): void; -} - -// ShoppingCart interface with all exported methods -export interface ShoppingCart extends SwiftHeapObject { - addItem(name: string, price: number, quantity: number): void; - removeItem(atIndex: number): void; - getTotal(): number; - getItemCount(): number; -} - -export type Exports = { - ShoppingCart: { - new(): ShoppingCart; - } -} -``` - - -### Enum Support - -BridgeJS supports two output styles for enums, controlled by the `enumStyle` parameter: - -- **`.const` (default)**: Generates const objects with union types -- **`.tsEnum`**: Generates native TypeScript enum declarations - **only available for case enums and raw value enums with String or numeric raw types** - -Examples output of both styles can be found below. - -#### Case Enums - -**Swift Definition:** - -```swift -@JS enum Direction { - case north - case south - case east - case west -} - -@JS(enumStyle: .tsEnum) enum TSDirection { - case north - case south - case east - case west -} - -@JS enum Status { - case loading - case success - case error -} -``` - -**Generated TypeScript Declaration:** - -```typescript -// Const object style (default) -const Direction: { - readonly North: 0; - readonly South: 1; - readonly East: 2; - readonly West: 3; -}; -type Direction = typeof Direction[keyof typeof Direction]; - -// Native TypeScript enum style -enum TSDirection { - North = 0, - South = 1, - East = 2, - West = 3, -} - -const Status: { - readonly Loading: 0; - readonly Success: 1; - readonly Error: 2; -}; -type Status = typeof Status[keyof typeof Status]; -``` - -**Usage in TypeScript:** - -```typescript -const direction: Direction = exports.Direction.North; -const tsDirection: TSDirection = exports.TSDirection.North; -const status: Status = exports.Status.Loading; - -exports.setDirection(exports.Direction.South); -exports.setTSDirection(exports.TSDirection.East); -const currentDirection: Direction = exports.getDirection(); -const currentTSDirection: TSDirection = exports.getTSDirection(); - -const result: Status = exports.processDirection(exports.Direction.East); - -function handleDirection(direction: Direction) { - switch (direction) { - case exports.Direction.North: - console.log("Going north"); - break; - case exports.Direction.South: - console.log("Going south"); - break; - // TypeScript will warn about missing cases - } -} -``` - -BridgeJS also generates convenience initializers and computed properties for each case-style enum, allowing the rest of the Swift glue code to remain minimal and consistent. This avoids repetitive switch statements in every function that passes enum values between JavaScript and Swift. - -```swift -extension Direction { - init?(bridgeJSRawValue: Int32) { - switch bridgeJSRawValue { - case 0: - self = .north - case 1: - self = .south - case 2: - self = .east - case 3: - self = .west - default: - return nil - } - } - - var bridgeJSRawValue: Int32 { - switch self { - case .north: - return 0 - case .south: - return 1 - case .east: - return 2 - case .west: - return 3 - } - } -} -... -@_expose(wasm, "bjs_setDirection") -@_cdecl("bjs_setDirection") -public func _bjs_setDirection(direction: Int32) -> Void { - #if arch(wasm32) - setDirection(_: Direction(bridgeJSRawValue: direction)!) - #else - fatalError("Only available on WebAssembly") - #endif -} - -@_expose(wasm, "bjs_getDirection") -@_cdecl("bjs_getDirection") -public func _bjs_getDirection() -> Int32 { - #if arch(wasm32) - let ret = getDirection() - return ret.bridgeJSRawValue - #else - fatalError("Only available on WebAssembly") - #endif -} -``` - -#### Raw Value Enums - -##### String Raw Values - -**Swift Definition:** - -```swift -// Default const object style -@JS enum Theme: String { - case light = "light" - case dark = "dark" - case auto = "auto" -} - -// Native TypeScript enum style -@JS(enumStyle: .tsEnum) enum TSTheme: String { - case light = "light" - case dark = "dark" - case auto = "auto" -} -``` - -**Generated TypeScript Declaration:** - -```typescript -// Const object style (default) -const Theme: { - readonly Light: "light"; - readonly Dark: "dark"; - readonly Auto: "auto"; -}; -type Theme = typeof Theme[keyof typeof Theme]; - -// Native TypeScript enum style -enum TSTheme { - Light = "light", - Dark = "dark", - Auto = "auto", -} -``` - -**Usage in TypeScript:** - -```typescript -// Both styles work similarly in usage -const theme: Theme = exports.Theme.Dark; -const tsTheme: TSTheme = exports.TSTheme.Dark; -exports.setTheme(exports.Theme.Light); -const currentTheme: Theme = exports.getTheme(); -const status: HttpStatus = exports.processTheme(exports.Theme.Auto); -``` - -##### Integer Raw Values - -**Swift Definition:** - -```swift -// Default const object style -@JS enum HttpStatus: Int { - case ok = 200 - case notFound = 404 - case serverError = 500 -} - -// Native TypeScript enum style -@JS(enumStyle: .tsEnum) enum TSHttpStatus: Int { - case ok = 200 - case notFound = 404 - case serverError = 500 -} - -@JS enum Priority: Int32 { - case lowest = 1 - case low = 2 - case medium = 3 - case high = 4 - case highest = 5 -} -``` - -**Generated TypeScript Declaration:** - -```typescript -// Const object style (default) -const HttpStatus: { - readonly Ok: 200; - readonly NotFound: 404; - readonly ServerError: 500; -}; -type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus]; - -// Native TypeScript enum style -enum TSHttpStatus { - Ok = 200, - NotFound = 404, - ServerError = 500, -} - -const Priority: { - readonly Lowest: 1; - readonly Low: 2; - readonly Medium: 3; - readonly High: 4; - readonly Highest: 5; -}; -type Priority = typeof Priority[keyof typeof Priority]; -``` - -**Usage in TypeScript:** - -```typescript -const status: HttpStatus = exports.HttpStatus.Ok; -const tsStatus: TSHttpStatus = exports.TSHttpStatus.Ok; -const priority: Priority = exports.Priority.High; - -exports.setHttpStatus(exports.HttpStatus.NotFound); -exports.setPriority(exports.Priority.Medium); - -const convertedPriority: Priority = exports.convertPriority(exports.HttpStatus.Ok); -``` - -### Namespace Enums - -Namespace enums are empty enums (containing no cases) used for organizing related types and functions into hierarchical namespaces. - -**Swift Definition:** - -```swift -@JS enum Utils { - @JS class Converter { - @JS init() {} - - @JS func toString(value: Int) -> String { - return String(value) - } - } -} - -// Nested namespace enums with no @JS(namespace:) macro used -@JS enum Networking { - @JS enum API { - @JS enum Method { - case get - case post - } - - @JS class HTTPServer { - @JS init() {} - @JS func call(_ method: Method) - } - } -} - -// Top level enum can still use explicit namespace via @JS(namespace:) -@JS(namespace: "Networking.APIV2") -enum Internal { - @JS enum SupportedMethod { - case get - case post - } - - @JS class TestServer { - @JS init() {} - @JS func call(_ method: SupportedMethod) - } -} -``` - -**Generated TypeScript Declaration:** - -```typescript -declare global { - namespace Utils { - class Converter { - constructor(); - toString(value: number): string; - } - } - namespace Networking { - namespace API { - class HTTPServer { - constructor(); - call(method: Networking.API.Method): void; - } - const Method: { - readonly Get: 0; - readonly Post: 1; - }; - type Method = typeof Method[keyof typeof Method]; - } - namespace APIV2 { - namespace Internal { - class TestServer { - constructor(); - call(method: Internal.SupportedMethod): void; - } - const SupportedMethod: { - readonly Get: 0; - readonly Post: 1; - }; - type SupportedMethod = typeof SupportedMethod[keyof typeof SupportedMethod]; - } - } - } -} -``` - -**Usage in TypeScript:** - -```typescript -// Access nested classes through namespaces -const converter = new globalThis.Utils.Converter(); -const result: string = converter.toString(42) - -const server = new globalThis.Networking.API.HTTPServer(); -const method: Networking.API.Method = globalThis.Networking.API.Method.Get; -server.call(method) - -const testServer = new globalThis.Networking.APIV2.Internal.TestServer(); -const supportedMethod: Internal.SupportedMethod = globalThis.Networking.APIV2.Internal.SupportedMethod.Post; -testServer.call(supportedMethod); -``` - -Things to remember when using enums for namespacing: - -1. Only enums with no cases will be used for namespaces -2. Top-level enums can use `@JS(namespace: "Custom.Path")` to place themselves in custom namespaces, which will be used as "base namespace" for all nested elements as well -3. Classes and enums nested within namespace enums **cannot** use `@JS(namespace:)` - this would create conflicting namespace declarations - -**Invalid Usage:** - -```swift -@JS enum Utils { - // Invalid - nested items cannot specify their own namespace - @JS(namespace: "Custom") class Helper { - @JS init() {} - } -} -``` - -**Valid Usage:** - -```swift -// Valid - top-level enum with explicit namespace -@JS(namespace: "Custom.Utils") -enum Helper { - @JS class Converter { - @JS init() {} - } -} -``` - -#### Associated Value Enums - -Associated value enums are not currently supported, but are planned for future releases. - -## Using Namespaces - -The `@JS` macro supports organizing your exported Swift code into namespaces using dot-separated strings. This allows you to create hierarchical structures in JavaScript that mirror your Swift code organization. - -### Functions with Namespaces - -You can export functions to specific namespaces by providing a namespace parameter: - -```swift -import JavaScriptKit - -// Export a function to a custom namespace -@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { - return "namespaced" -} -``` - -This function will be accessible in JavaScript through its namespace hierarchy: - -```javascript -// Access the function through its namespace -const result = globalThis.MyModule.Utils.namespacedFunction(); -console.log(result); // "namespaced" -``` - -The generated TypeScript declaration will reflect the namespace structure: - -```typescript -declare global { - namespace MyModule { - namespace Utils { - function namespacedFunction(): string; - } - } -} -``` - -### Classes with Namespaces - -For classes, you only need to specify the namespace on the top-level class declaration. All exported methods within the class will be part of that namespace: - -```swift -import JavaScriptKit - -@JS(namespace: "__Swift.Foundation") class Greeter { - var name: String - - @JS init(name: String) { - self.name = name - } - - @JS func greet() -> String { - return "Hello, " + self.name + "!" - } - - func changeName(name: String) { - self.name = name - } -} -``` - -In JavaScript, this class is accessible through its namespace: - -```javascript -// Create instances through namespaced constructors -const greeter = new globalThis.__Swift.Foundation.Greeter("World"); -console.log(greeter.greet()); // "Hello, World!" -``` - -The generated TypeScript declaration will organize the class within its namespace: - -```typescript -declare global { - namespace __Swift { - namespace Foundation { - class Greeter { - constructor(name: string); - greet(): string; - } - } - } -} - -export interface Greeter extends SwiftHeapObject { - greet(): string; -} -``` +## Topics -Using namespaces can be preferable for projects with many global functions, as they help prevent naming collisions. Namespaces also provide intuitive hierarchies for organizing your exported Swift code, and they do not affect the code generated by `@JS` declarations without namespaces. +- +- +- +- 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 new file mode 100644 index 00000000..d91d0616 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Class.md @@ -0,0 +1,94 @@ +# Exporting Swift Classes to JS + +Learn how to export Swift classes to JavaScript. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +To export a Swift class, mark both the class and any members you want to expose: + +```swift +import JavaScriptKit + +@JS class ShoppingCart { + private var items: [(name: String, price: Double, quantity: Int)] = [] + + @JS init() {} + + @JS public func addItem(name: String, price: Double, quantity: Int) { + items.append((name, price, quantity)) + } + + @JS public func removeItem(atIndex index: Int) { + guard index >= 0 && index < items.count else { return } + items.remove(at: index) + } + + @JS public func getTotal() -> Double { + return items.reduce(0) { $0 + $1.price * Double($1.quantity) } + } + + @JS public func getItemCount() -> Int { + return items.count + } + + // This method won't be accessible from JavaScript (no @JS) + var debugDescription: String { + return "Cart with \(items.count) items, total: \(getTotal())" + } +} +``` + +In JavaScript: + +```javascript +import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; +const { exports } = await init({}); + +const cart = new exports.ShoppingCart(); +cart.addItem("Laptop", 999.99, 1); +cart.addItem("Mouse", 24.99, 2); +console.log(`Items in cart: ${cart.getItemCount()}`); +console.log(`Total: $${cart.getTotal().toFixed(2)}`); +``` + +The generated TypeScript declarations for this class would look like: + +```typescript +// Base interface for Swift reference types +export interface SwiftHeapObject { + release(): void; +} + +// ShoppingCart interface with all exported methods +export interface ShoppingCart extends SwiftHeapObject { + addItem(name: string, price: number, quantity: number): void; + removeItem(atIndex: number): void; + getTotal(): number; + getItemCount(): number; +} + +export type Exports = { + ShoppingCart: { + new(): ShoppingCart; + } +} +``` + +## Supported Features + +| Swift Feature | Status | +|:--------------|:-------| +| Initializers: `init()` | ✅ | +| Initializers that throw JSException: `init() throws(JSException)` | ✅ | +| Initializers that throw any exception: `init() throws` | ❌ | +| Async initializers: `init() async` | ❌ | +| Deinitializers: `deinit` | ✅ | +| Stored properties: `var`, `let` (with `willSet`, `didSet`) | ✅ | +| Computed properties: `var x: X { get set }` | ✅ | +| Computed properties with effects: `var x: X { get async throws }` | 🚧 | +| Methods: `func` | ✅ (See ) | +| Static/class methods: `static func`, `class func` | 🚧 | +| Subscripts: `subscript()` | ❌ | +| Generics | ❌ | 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 new file mode 100644 index 00000000..e4bc1d7f --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Enum.md @@ -0,0 +1,408 @@ +# Exporting Swift Enums to JS + +Learn how to export Swift enums to JavaScript. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS supports two output styles for enums, controlled by the `enumStyle` parameter: + +- **`.const` (default)**: Generates const objects with union types +- **`.tsEnum`**: Generates native TypeScript enum declarations - **only available for case enums and raw value enums with String or numeric raw types** + +Examples output of both styles can be found below. + +#### Case Enums + +**Swift Definition:** + +```swift +@JS enum Direction { + case north + case south + case east + case west +} + +@JS(enumStyle: .tsEnum) enum TSDirection { + case north + case south + case east + case west +} + +@JS enum Status { + case loading + case success + case error +} +``` + +**Generated TypeScript Declaration:** + +```typescript +// Const object style (default) +const Direction: { + readonly North: 0; + readonly South: 1; + readonly East: 2; + readonly West: 3; +}; +type Direction = typeof Direction[keyof typeof Direction]; + +// Native TypeScript enum style +enum TSDirection { + North = 0, + South = 1, + East = 2, + West = 3, +} + +const Status: { + readonly Loading: 0; + readonly Success: 1; + readonly Error: 2; +}; +type Status = typeof Status[keyof typeof Status]; +``` + +**Usage in TypeScript:** + +```typescript +const direction: Direction = exports.Direction.North; +const tsDirection: TSDirection = exports.TSDirection.North; +const status: Status = exports.Status.Loading; + +exports.setDirection(exports.Direction.South); +exports.setTSDirection(exports.TSDirection.East); +const currentDirection: Direction = exports.getDirection(); +const currentTSDirection: TSDirection = exports.getTSDirection(); + +const result: Status = exports.processDirection(exports.Direction.East); + +function handleDirection(direction: Direction) { + switch (direction) { + case exports.Direction.North: + console.log("Going north"); + break; + case exports.Direction.South: + console.log("Going south"); + break; + // TypeScript will warn about missing cases + } +} +``` + +BridgeJS also generates convenience initializers and computed properties for each case-style enum, allowing the rest of the Swift glue code to remain minimal and consistent. This avoids repetitive switch statements in every function that passes enum values between JavaScript and Swift. + +```swift +extension Direction { + init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .north + case 1: + self = .south + case 2: + self = .east + case 3: + self = .west + default: + return nil + } + } + + var bridgeJSRawValue: Int32 { + switch self { + case .north: + return 0 + case .south: + return 1 + case .east: + return 2 + case .west: + return 3 + } + } +} +... +@_expose(wasm, "bjs_setDirection") +@_cdecl("bjs_setDirection") +public func _bjs_setDirection(direction: Int32) -> Void { + #if arch(wasm32) + setDirection(_: Direction(bridgeJSRawValue: direction)!) + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_getDirection") +@_cdecl("bjs_getDirection") +public func _bjs_getDirection() -> Int32 { + #if arch(wasm32) + let ret = getDirection() + return ret.bridgeJSRawValue + #else + fatalError("Only available on WebAssembly") + #endif +} +``` + +#### Raw Value Enums + +##### String Raw Values + +**Swift Definition:** + +```swift +// Default const object style +@JS enum Theme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} + +// Native TypeScript enum style +@JS(enumStyle: .tsEnum) enum TSTheme: String { + case light = "light" + case dark = "dark" + case auto = "auto" +} +``` + +**Generated TypeScript Declaration:** + +```typescript +// Const object style (default) +const Theme: { + readonly Light: "light"; + readonly Dark: "dark"; + readonly Auto: "auto"; +}; +type Theme = typeof Theme[keyof typeof Theme]; + +// Native TypeScript enum style +enum TSTheme { + Light = "light", + Dark = "dark", + Auto = "auto", +} +``` + +**Usage in TypeScript:** + +```typescript +// Both styles work similarly in usage +const theme: Theme = exports.Theme.Dark; +const tsTheme: TSTheme = exports.TSTheme.Dark; + +exports.setTheme(exports.Theme.Light); +const currentTheme: Theme = exports.getTheme(); + +const status: HttpStatus = exports.processTheme(exports.Theme.Auto); +``` + +##### Integer Raw Values + +**Swift Definition:** + +```swift +// Default const object style +@JS enum HttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +// Native TypeScript enum style +@JS(enumStyle: .tsEnum) enum TSHttpStatus: Int { + case ok = 200 + case notFound = 404 + case serverError = 500 +} + +@JS enum Priority: Int32 { + case lowest = 1 + case low = 2 + case medium = 3 + case high = 4 + case highest = 5 +} +``` + +**Generated TypeScript Declaration:** + +```typescript +// Const object style (default) +const HttpStatus: { + readonly Ok: 200; + readonly NotFound: 404; + readonly ServerError: 500; +}; +type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus]; + +// Native TypeScript enum style +enum TSHttpStatus { + Ok = 200, + NotFound = 404, + ServerError = 500, +} + +const Priority: { + readonly Lowest: 1; + readonly Low: 2; + readonly Medium: 3; + readonly High: 4; + readonly Highest: 5; +}; +type Priority = typeof Priority[keyof typeof Priority]; +``` + +**Usage in TypeScript:** + +```typescript +const status: HttpStatus = exports.HttpStatus.Ok; +const tsStatus: TSHttpStatus = exports.TSHttpStatus.Ok; +const priority: Priority = exports.Priority.High; + +exports.setHttpStatus(exports.HttpStatus.NotFound); +exports.setPriority(exports.Priority.Medium); + +const convertedPriority: Priority = exports.convertPriority(exports.HttpStatus.Ok); +``` + +### Namespace Enums + +Namespace enums are empty enums (containing no cases) used for organizing related types and functions into hierarchical namespaces. + +**Swift Definition:** + +```swift +@JS enum Utils { + @JS class Converter { + @JS init() {} + + @JS func toString(value: Int) -> String { + return String(value) + } + } +} + +// Nested namespace enums with no @JS(namespace:) macro used +@JS enum Networking { + @JS enum API { + @JS enum Method { + case get + case post + } + + @JS class HTTPServer { + @JS init() {} + @JS func call(_ method: Method) + } + } +} + +// Top level enum can still use explicit namespace via @JS(namespace:) +@JS(namespace: "Networking.APIV2") +enum Internal { + @JS enum SupportedMethod { + case get + case post + } + + @JS class TestServer { + @JS init() {} + @JS func call(_ method: SupportedMethod) + } +} +``` + +**Generated TypeScript Declaration:** + +```typescript +declare global { + namespace Utils { + class Converter { + constructor(); + toString(value: number): string; + } + } + namespace Networking { + namespace API { + class HTTPServer { + constructor(); + call(method: Networking.API.Method): void; + } + const Method: { + readonly Get: 0; + readonly Post: 1; + }; + type Method = typeof Method[keyof typeof Method]; + } + namespace APIV2 { + namespace Internal { + class TestServer { + constructor(); + call(method: Internal.SupportedMethod): void; + } + const SupportedMethod: { + readonly Get: 0; + readonly Post: 1; + }; + type SupportedMethod = typeof SupportedMethod[keyof typeof SupportedMethod]; + } + } + } +} +``` + +**Usage in TypeScript:** + +```typescript +// Access nested classes through namespaces +const converter = new globalThis.Utils.Converter(); +const result: string = converter.toString(42) + +const server = new globalThis.Networking.API.HTTPServer(); +const method: Networking.API.Method = globalThis.Networking.API.Method.Get; +server.call(method) + +const testServer = new globalThis.Networking.APIV2.Internal.TestServer(); +const supportedMethod: Internal.SupportedMethod = globalThis.Networking.APIV2.Internal.SupportedMethod.Post; +testServer.call(supportedMethod); +``` + +Things to remember when using enums for namespacing: + +1. Only enums with no cases will be used for namespaces +2. Top-level enums can use `@JS(namespace: "Custom.Path")` to place themselves in custom namespaces, which will be used as "base namespace" for all nested elements as well +3. Classes and enums nested within namespace enums **cannot** use `@JS(namespace:)` - this would create conflicting namespace declarations + +**Invalid Usage:** + +```swift +@JS enum Utils { + // Invalid - nested items cannot specify their own namespace + @JS(namespace: "Custom") class Helper { + @JS init() {} + } +} +``` + +**Valid Usage:** + +```swift +// Valid - top-level enum with explicit namespace +@JS(namespace: "Custom.Utils") +enum Helper { + @JS class Converter { + @JS init() {} + } +} +``` + +#### Associated Value Enums + +Associated value enums are not currently supported, but are planned for future releases. 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 new file mode 100644 index 00000000..fff45f11 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Exporting-Swift-Function.md @@ -0,0 +1,153 @@ +# Exporting Swift Functions to JS + +Learn how to export Swift functions to JavaScript. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +To export a Swift function to JavaScript, mark it with the `@JS` attribute and make it `public`: + +```swift +import JavaScriptKit + +@JS public func calculateTotal(price: Double, quantity: Int) -> Double { + return price * Double(quantity) +} + +@JS public func formatCurrency(amount: Double) -> String { + return "$\(String(format: "%.2f", amount))" +} +``` + +These functions will be accessible from JavaScript: + +```javascript +const total = exports.calculateTotal(19.99, 3); +const formattedTotal = exports.formatCurrency(total); +console.log(formattedTotal); // "$59.97" +``` + +The generated TypeScript declarations for these functions would look like: + +```typescript +export type Exports = { + calculateTotal(price: number, quantity: number): number; + formatCurrency(amount: number): string; +} +``` + +### Throwing functions + +Swift functions can throw JavaScript errors using `throws(JSException)`. + +```swift +import JavaScriptKit + +@JS public func findUser(id: Int) throws(JSException) -> String { + if id <= 0 { + throw JSException(JSError(message: "Invalid ID").jsValue) + } + return "User_\(id)" +} +``` + +From JavaScript, call with `try/catch`: + +```javascript +try { + const name = exports.findUser(42); + console.log(name); +} catch (e) { + console.error("findUser failed:", e); +} +``` + +Generated TypeScript type: + +```typescript +export type Exports = { + findUser(id: number): string; // throws at runtime +} +``` + +Notes: +- Only `throws(JSException)` is supported. Plain `throws` is not supported. +- Thrown values are surfaced to JS as normal JS exceptions. + +### Async functions + +Async Swift functions are exposed as Promise-returning JS functions. + +```swift +import JavaScriptKit + +@JS public func fetchCount(endpoint: String) async -> Int { + // Simulate async work + try? await Task.sleep(nanoseconds: 50_000_000) + return endpoint.count +} +``` + +Usage from JavaScript: + +```javascript +const count = await exports.fetchCount("/items"); +``` + +Generated TypeScript type: + +```typescript +export type Exports = { + fetchCount(endpoint: string): Promise; +} +``` + +### Async + throws + +Async throwing functions become Promise-returning JS functions that reject on error. + +```swift +import JavaScriptKit + +@JS public func loadProfile(userId: Int) async throws(JSException) -> String { + if userId <= 0 { throw JSException(JSError(message: "Bad userId").jsValue) } + try? await Task.sleep(nanoseconds: 50_000_000) + return "Profile_\(userId)" +} +``` + +JavaScript usage: + +```javascript +try { + const profile = await exports.loadProfile(1); + console.log(profile); +} catch (e) { + console.error("loadProfile failed:", e); +} +``` + +TypeScript: + +```typescript +export type Exports = { + loadProfile(userId: number): Promise; +} +``` + +## Supported Features + +| Swift Feature | Status | +|:--------------|:-------| +| Primitive parameter/result types: (e.g. `Bool`, `Int`, `Double`) | ✅ | +| `String` parameter/result type | ✅ | +| `@JS class` parameter/result type | ✅ | +| `@JS enum` parameter/result type | ✅ | +| `JSObject` parameter/result type | ✅ | +| Throwing JS exception: `func x() throws(JSException)` | ✅ | +| Throwing any exception: `func x() throws` | ❌ | +| Async methods: `func x() async` | ✅ | +| Generics | ❌ | +| Opaque types: `func x() -> some P`, `func y(_: some P)` | ❌ | +| Default parameter values: `func x(_ foo: String = "")` | ❌ | \ No newline at end of file diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Using-Namespace.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Using-Namespace.md new file mode 100644 index 00000000..2026c6a2 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Exporting-Swift/Using-Namespace.md @@ -0,0 +1,95 @@ +# Using Namespaces + +Learn how to organize exported Swift code into JavaScript namespaces. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +The `@JS` macro supports organizing your exported Swift code into namespaces using dot-separated strings. This allows you to create hierarchical structures in JavaScript that mirror your Swift code organization. + +### Functions with Namespaces + +You can export functions to specific namespaces by providing a namespace parameter: + +```swift +import JavaScriptKit + +// Export a function to a custom namespace +@JS(namespace: "MyModule.Utils") func namespacedFunction() -> String { + return "namespaced" +} +``` + +This function will be accessible in JavaScript through its namespace hierarchy: + +```javascript +// Access the function through its namespace +const result = globalThis.MyModule.Utils.namespacedFunction(); +console.log(result); // "namespaced" +``` + +The generated TypeScript declaration will reflect the namespace structure: + +```typescript +declare global { + namespace MyModule { + namespace Utils { + function namespacedFunction(): string; + } + } +} +``` + +### Classes with Namespaces + +For classes, you only need to specify the namespace on the top-level class declaration. All exported methods within the class will be part of that namespace: + +```swift +import JavaScriptKit + +@JS(namespace: "__Swift.Foundation") class Greeter { + var name: String + + @JS init(name: String) { + self.name = name + } + + @JS func greet() -> String { + return "Hello, " + self.name + "!" + } + + func changeName(name: String) { + self.name = name + } +} +``` + +In JavaScript, this class is accessible through its namespace: + +```javascript +// Create instances through namespaced constructors +const greeter = new globalThis.__Swift.Foundation.Greeter("World"); +console.log(greeter.greet()); // "Hello, World!" +``` + +The generated TypeScript declaration will organize the class within its namespace: + +```typescript +declare global { + namespace __Swift { + namespace Foundation { + class Greeter { + constructor(name: string); + greet(): string; + } + } + } +} + +export interface Greeter extends SwiftHeapObject { + greet(): string; +} +``` + +Using namespaces can be preferable for projects with many global functions, as they help prevent naming collisions. Namespaces also provide intuitive hierarchies for organizing your exported Swift code, and they do not affect the code generated by `@JS` declarations without namespaces. \ No newline at end of file diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript-into-Swift.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript-into-Swift.md index 853c8800..b091f714 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript-into-Swift.md +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript-into-Swift.md @@ -6,6 +6,8 @@ Learn how to leverage TypeScript definitions to create type-safe bindings for Ja > Important: This feature is still experimental. No API stability is guaranteed, and the API may change in future releases. +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + BridgeJS enables seamless integration between Swift and JavaScript by automatically generating Swift bindings from TypeScript declaration files (`.d.ts`). This provides type-safe access to JavaScript APIs directly from your Swift code. The key benefits of this approach over `@dynamicMemberLookup`-based APIs include: @@ -174,3 +176,10 @@ const { exports } = await init({ // Call the entry point of your Swift application exports.run(); ``` + +## Topics + +- +- +- +- diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Class.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Class.md new file mode 100644 index 00000000..e04bb9e7 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Class.md @@ -0,0 +1,64 @@ +# Importing TypeScript Classes into Swift + +Learn how TypeScript classes map to Swift when importing APIs. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS reads class declarations in your `bridge-js.d.ts` and generates Swift structs that represent JS objects. Constructors, methods, and properties are bridged via thunks that call into your JavaScript implementations at runtime. + +## Example + +TypeScript definition (`bridge-js.d.ts`): + +```typescript +export class Greeter { + readonly id: string; + message: string; + constructor(id: string, name: string); + greet(): string; +} +``` + +Generated Swift API: + +```swift +struct Greeter { + init(id: String, name: String) throws(JSException) + + // Properties + // Readonly property + var id: String { get throws(JSException) } + // Writable property + var message: String { get throws(JSException) } + func setMessage(_ newValue: String) throws(JSException) + + // Methods + func greet() throws(JSException) -> String +} +``` + +Notes: +- Property setters are emitted as `set(_:)` functions, not Swift `set` accessors since `set` accessors can't have `throws` +- All thunks throw `JSException` if the underlying JS throws. + +JavaScript implementation wiring (provided by your app): + +```javascript +// index.js +import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; + +class Greeter { + readonly id: string; + message: string; + constructor(id: string, name: string) { ... } + greet(): string { ... } +} + +const { exports } = await init({ + getImports() { + return { Greeter }; + } +}); +``` diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Function.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Function.md new file mode 100644 index 00000000..060ac390 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Function.md @@ -0,0 +1,60 @@ +# Importing TypeScript Functions into Swift + +Learn how functions declared in TypeScript become callable Swift functions. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS reads your `bridge-js.d.ts` and generates Swift thunks that call into JavaScript implementations provided at runtime. Each imported function becomes a top-level Swift function with the same name and a throwing signature. + +### Example + +TypeScript definition (`bridge-js.d.ts`): + +```typescript +export function add(a: number, b: number): number; +export function setTitle(title: string): void; +export function fetchUser(id: string): Promise; +``` + +Generated Swift signatures: + +```swift +func add(a: Double, b: Double) throws(JSException) -> Double +func setTitle(title: String) throws(JSException) +func fetchUser(id: String) throws(JSException) -> JSPromise +``` + +JavaScript implementation wiring (provided by your app): + +```javascript +// index.js +import { init } from "./.build/plugins/PackageToJS/outputs/Package/index.js"; + +const { exports } = await init({ + getImports() { + return { + add: (a, b) => a + b, + setTitle: (title) => { document.title = title }, + fetchUser: (id) => fetch(`/api/users/${id}`).then(r => r.json()), + }; + } +}); +``` + +### Error handling + +- All imported Swift functions are generated as `throws(JSException)` and will throw if the underlying JS implementation throws. + +## Supported features + +| Feature | Status | +|:--|:--| +| Primitive parameter/result types: (e.g. `boolean`, `number`) | ✅ | +| `string` parameter/result type | ✅ | +| Enums in signatures | ❌ | +| Async function | ✅ | +| Generics | ❌ | + + diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Interface.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Interface.md new file mode 100644 index 00000000..0e0aed0d --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-Interface.md @@ -0,0 +1,34 @@ +# Importing TypeScript Interfaces into Swift + +Learn how TypeScript interfaces become Swift value types with methods and properties. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS converts TS interfaces to Swift structs conforming to an internal bridging protocol and provides thunks for methods and properties that call into your JavaScript implementations. + +> Note: Interfaces are bridged very similarly to classes. Methods and properties map the same way. See for more details. + +### Example + +TypeScript definition (`bridge-js.d.ts`): + +```typescript +export interface HTMLElement { + readonly innerText: string; + className: string; + appendChild(child: HTMLElement): void; +} +``` + +Generated Swift API: + +```swift +struct HTMLElement { + var innerText: String { get throws(JSException) } + var className: String { get throws(JSException) } + func setClassName(_ newValue: String) throws(JSException) + func appendChild(_ child: HTMLElement) throws(JSException) +} +``` diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-TypeAlias.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-TypeAlias.md new file mode 100644 index 00000000..b5242ee3 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Importing-TypeScript/Importing-TS-TypeAlias.md @@ -0,0 +1,47 @@ +# Importing TypeScript Type Aliases into Swift + +Understand how TypeScript type aliases are handled when generating Swift bindings. + +## Overview + +> Tip: You can quickly preview what interfaces will be exposed on the Swift/TypeScript sides using the [BridgeJS Playground](https://swiftwasm.org/JavaScriptKit/PlayBridgeJS/). + +BridgeJS resolves TypeScript aliases while importing. If an alias names an anonymous object type, Swift will generate a corresponding bridged struct using that name. + +> Note: When a type alias names an anonymous object type, its bridging behavior (constructors not applicable, but methods/properties if referenced) mirrors class/interface importing. See for more details. + +### Examples + +```typescript +// Primitive alias → maps to the underlying primitive +export type Price = number; + +// Object-shaped alias with a name → becomes a named bridged type when referenced +export type User = { + readonly id: string; + name: string; + age: Price; +} + +export function getUser(): User; +``` + +Generated Swift (simplified): + +```swift +// Price → Double + +struct User { + // Readonly property + var id: String { get throws(JSException) } + + // Writable properties + var name: String { get throws(JSException) } + func setName(_ newValue: String) throws(JSException) + + var age: Double { get throws(JSException) } + func setAge(_ newValue: Double) throws(JSException) +} + +func getUser() throws(JSException) -> User +``` diff --git a/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md new file mode 100644 index 00000000..71b92573 --- /dev/null +++ b/Sources/JavaScriptKit/Documentation.docc/Articles/BridgeJS/Supported-Types.md @@ -0,0 +1,18 @@ +# TypeScript and Swift Type Mapping + +Use this page as a quick reference for how common TypeScript types appear in Swift when importing, and how exported Swift types surface on the TypeScript side. + +## Type mapping + +| TypeScript type | Swift type | +|:--|:--| +| `number` | `Double` | +| `string` | `String` | +| `boolean` | `Bool` | +| TODO | `Array` | +| TODO | `Dictionary` | +| TODO | `Optional` | +| `T \| undefined` | TODO | +| `Promise` | `JSPromise` | +| `any` / `unknown` / `object` | `JSObject` | +| Other types | `JSObject` | diff --git a/Sources/JavaScriptKit/Documentation.docc/Documentation.md b/Sources/JavaScriptKit/Documentation.docc/Documentation.md index 0a410916..13a6a321 100644 --- a/Sources/JavaScriptKit/Documentation.docc/Documentation.md +++ b/Sources/JavaScriptKit/Documentation.docc/Documentation.md @@ -58,6 +58,7 @@ Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Ex - - +- - - @@ -65,4 +66,4 @@ Check out the [examples](https://github.com/swiftwasm/JavaScriptKit/tree/main/Ex - ``JSValue`` - ``JSObject`` -- ``JS(namespace:)`` +- ``JS(namespace:enumStyle:)`` From 095d4e73f3c4828a675664d4c4e30d8f87b5e231 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 25 Aug 2025 14:18:23 +0900 Subject: [PATCH 57/61] BridgeJS: Skip importing TS declarations with invalid Swift identifiers --- .../Sources/BridgeJSCore/ImportTS.swift | 12 +- .../TS2Skeleton/JavaScript/src/processor.js | 30 ++- .../Inputs/InvalidPropertyNames.d.ts | 23 ++ .../InvalidPropertyNames.Import.d.ts | 29 +++ .../InvalidPropertyNames.Import.js | 168 ++++++++++++++ .../ImportTSTests/InvalidPropertyNames.swift | 205 ++++++++++++++++++ 6 files changed, 461 insertions(+), 6 deletions(-) create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js create mode 100644 Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 148157d3..9fc8a62d 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -156,7 +156,7 @@ public struct ImportTS { func renderThunkDecl(name: String, parameters: [Parameter], returnType: BridgeType) -> DeclSyntax { return DeclSyntax( FunctionDeclSyntax( - name: .identifier(name), + name: .identifier(name.backtickIfNeeded()), signature: FunctionSignatureSyntax( parameterClause: FunctionParameterClauseSyntax(parametersBuilder: { for param in parameters { @@ -315,7 +315,9 @@ public struct ImportTS { bindingsBuilder: { PatternBindingListSyntax { PatternBindingSyntax( - pattern: IdentifierPatternSyntax(identifier: .identifier(property.name)), + pattern: IdentifierPatternSyntax( + identifier: .identifier(property.name.backtickIfNeeded()) + ), typeAnnotation: TypeAnnotationSyntax( type: IdentifierTypeSyntax(name: .identifier(property.type.swiftType)) ), @@ -466,3 +468,9 @@ extension BridgeType { } } } + +extension String { + func backtickIfNeeded() -> String { + return self.isValidSwiftIdentifier(for: .variableName) ? self : "`\(self)`" + } +} diff --git a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js index aeaf6a2d..cdcff719 100644 --- a/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js +++ b/Plugins/BridgeJS/Sources/TS2Skeleton/JavaScript/src/processor.js @@ -142,6 +142,10 @@ export class TypeProcessor { */ visitFunctionLikeDecl(node) { if (!node.name) return null; + const name = node.name.getText(); + if (!isValidSwiftDeclName(name)) { + return null; + } const signature = this.checker.getSignatureFromDeclaration(node); if (!signature) return null; @@ -158,7 +162,7 @@ export class TypeProcessor { const documentation = this.getFullJSDocText(node); return { - name: node.name.getText(), + name, parameters, returnType: bridgeReturnType, documentation, @@ -206,11 +210,17 @@ export class TypeProcessor { */ visitPropertyDecl(node) { if (!node.name) return null; + + const propertyName = node.name.getText(); + if (!isValidSwiftDeclName(propertyName)) { + return null; + } + const type = this.checker.getTypeAtLocation(node) const bridgeType = this.visitType(type, node); const isReadonly = node.modifiers?.some(m => m.kind === ts.SyntaxKind.ReadonlyKeyword) ?? false; const documentation = this.getFullJSDocText(node); - return { name: node.name.getText(), type: bridgeType, isReadonly, documentation }; + return { name: propertyName, type: bridgeType, isReadonly, documentation }; } /** @@ -225,7 +235,7 @@ export class TypeProcessor { } /** - * @param {ts.ClassDeclaration} node + * @param {ts.ClassDeclaration} node * @returns {ImportTypeSkeleton | null} */ visitClassDecl(node) { @@ -442,4 +452,16 @@ function isTypeReference(type) { isObjectType(type) && (type.objectFlags & ts.ObjectFlags.Reference) !== 0 ); -} \ No newline at end of file +} + +/** + * Check if a declaration name is valid for Swift generation + * @param {string} name - Declaration name to check + * @returns {boolean} True if the name is valid for Swift + * @private + */ +export function isValidSwiftDeclName(name) { + // https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/ + const swiftIdentifierRegex = /^[_\p{ID_Start}][\p{ID_Continue}\u{200C}\u{200D}]*$/u; + return swiftIdentifierRegex.test(name); +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts new file mode 100644 index 00000000..d21f3c20 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/InvalidPropertyNames.d.ts @@ -0,0 +1,23 @@ +interface ArrayBufferLike { + readonly byteLength: number; + readonly [Symbol.toStringTag]: string; + slice(begin: number, end: number): ArrayBufferLike; +} + +interface WeirdNaming { + normalProperty: string; + "property-with-dashes": number; + "123invalidStart": boolean; + "property with spaces": string; + readonly [Symbol.species]: any; + [Symbol.asyncIterator](): AsyncIterator; + "@specialChar": number; + "constructor": string; // This should be valid + for: string; + Any: string; + as(): void; + "try"(): void; +} + +export function createArrayBuffer(): ArrayBufferLike; +export function createWeirdObject(): WeirdNaming; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts new file mode 100644 index 00000000..2b0474bb --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.d.ts @@ -0,0 +1,29 @@ +// 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 ArrayBufferLike { + slice(begin: number, end: number): ArrayBufferLike; + readonly byteLength: number; +} +export interface WeirdNaming { + as(): void; + normalProperty: string; + for: string; + Any: string; +} +export type Exports = { +} +export type Imports = { + createArrayBuffer(): ArrayBufferLike; + createWeirdObject(): WeirdNaming; +} +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/InvalidPropertyNames.Import.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js new file mode 100644 index 00000000..6b5211e0 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.Import.js @@ -0,0 +1,168 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +export async function createInstantiator(options, swift) { + let instance; + let memory; + let setException; + const textDecoder = new TextDecoder("utf-8"); + const textEncoder = new TextEncoder("utf-8"); + + let tmpRetString; + let tmpRetBytes; + let tmpRetException; + return { + /** + * @param {WebAssembly.Imports} importObject + */ + addImports: (importObject, importsContext) => { + const bjs = {}; + importObject["bjs"] = bjs; + const imports = options.getImports(importsContext); + bjs["swift_js_return_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + tmpRetString = textDecoder.decode(bytes); + } + bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { + const source = swift.memory.getObject(sourceId); + const bytes = new Uint8Array(memory.buffer, bytesPtr); + bytes.set(source); + } + bjs["swift_js_make_js_string"] = function(ptr, len) { + const bytes = new Uint8Array(memory.buffer, ptr, len); + return swift.memory.retain(textDecoder.decode(bytes)); + } + bjs["swift_js_init_memory_with_result"] = function(ptr, len) { + const target = new Uint8Array(memory.buffer, ptr, len); + target.set(tmpRetBytes); + tmpRetBytes = undefined; + } + bjs["swift_js_throw"] = function(id) { + tmpRetException = swift.memory.retainByRef(id); + } + bjs["swift_js_retain"] = function(id) { + return swift.memory.retainByRef(id); + } + bjs["swift_js_release"] = function(id) { + swift.memory.release(id); + } + + const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; + TestModule["bjs_createArrayBuffer"] = function bjs_createArrayBuffer() { + try { + let ret = imports.createArrayBuffer(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_createWeirdObject"] = function bjs_createWeirdObject() { + try { + let ret = imports.createWeirdObject(); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_ArrayBufferLike_byteLength_get"] = function bjs_ArrayBufferLike_byteLength_get(self) { + try { + let ret = swift.memory.getObject(self).byteLength; + return ret; + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_ArrayBufferLike_slice"] = function bjs_ArrayBufferLike_slice(self, begin, end) { + try { + let ret = swift.memory.getObject(self).slice(begin, end); + return swift.memory.retain(ret); + } catch (error) { + setException(error); + return 0 + } + } + TestModule["bjs_WeirdNaming_normalProperty_get"] = function bjs_WeirdNaming_normalProperty_get(self) { + try { + let ret = swift.memory.getObject(self).normalProperty; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_normalProperty_set"] = function bjs_WeirdNaming_normalProperty_set(self, newValue) { + try { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self).normalProperty = newValueObject; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_for_get"] = function bjs_WeirdNaming_for_get(self) { + try { + let ret = swift.memory.getObject(self).for; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_for_set"] = function bjs_WeirdNaming_for_set(self, newValue) { + try { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self).for = newValueObject; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_Any_get"] = function bjs_WeirdNaming_Any_get(self) { + try { + let ret = swift.memory.getObject(self).Any; + tmpRetBytes = textEncoder.encode(ret); + return tmpRetBytes.length; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_Any_set"] = function bjs_WeirdNaming_Any_set(self, newValue) { + try { + const newValueObject = swift.memory.getObject(newValue); + swift.memory.release(newValue); + swift.memory.getObject(self).Any = newValueObject; + } catch (error) { + setException(error); + } + } + TestModule["bjs_WeirdNaming_as"] = function bjs_WeirdNaming_as(self) { + try { + swift.memory.getObject(self).as(); + } catch (error) { + setException(error); + } + } + }, + setInstance: (i) => { + instance = i; + memory = instance.exports.memory; + setException = (error) => { + instance.exports._swift_js_exception.value = swift.memory.retain(error) + } + }, + /** @param {WebAssembly.Instance} instance */ + createExports: (instance) => { + const js = swift.memory.heap; + + return { + + }; + }, + } +} \ No newline at end of file diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift new file mode 100644 index 00000000..7e35f921 --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ImportTSTests/InvalidPropertyNames.swift @@ -0,0 +1,205 @@ +// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit, +// DO NOT EDIT. +// +// To update this file, just rebuild your project or run +// `swift package bridge-js`. + +@_spi(BridgeJS) import JavaScriptKit + +func createArrayBuffer() throws(JSException) -> ArrayBufferLike { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createArrayBuffer") + func bjs_createArrayBuffer() -> Int32 + #else + func bjs_createArrayBuffer() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createArrayBuffer() + if let error = _swift_js_take_exception() { + throw error + } + return ArrayBufferLike.bridgeJSLiftReturn(ret) +} + +func createWeirdObject() throws(JSException) -> WeirdNaming { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_createWeirdObject") + func bjs_createWeirdObject() -> Int32 + #else + func bjs_createWeirdObject() -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_createWeirdObject() + if let error = _swift_js_take_exception() { + throw error + } + return WeirdNaming.bridgeJSLiftReturn(ret) +} + +struct ArrayBufferLike: _JSBridgedClass { + let jsObject: JSObject + + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + var byteLength: Double { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ArrayBufferLike_byteLength_get") + func bjs_ArrayBufferLike_byteLength_get(_ self: Int32) -> Float64 + #else + func bjs_ArrayBufferLike_byteLength_get(_ self: Int32) -> Float64 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_ArrayBufferLike_byteLength_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return Double.bridgeJSLiftReturn(ret) + } + } + + func slice(_ begin: Double, _ end: Double) throws(JSException) -> ArrayBufferLike { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_ArrayBufferLike_slice") + func bjs_ArrayBufferLike_slice(_ self: Int32, _ begin: Float64, _ end: Float64) -> Int32 + #else + func bjs_ArrayBufferLike_slice(_ self: Int32, _ begin: Float64, _ end: Float64) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_ArrayBufferLike_slice(self.bridgeJSLowerParameter(), begin.bridgeJSLowerParameter(), end.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return ArrayBufferLike.bridgeJSLiftReturn(ret) + } + +} + +struct WeirdNaming: _JSBridgedClass { + let jsObject: JSObject + + init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + var normalProperty: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_normalProperty_get") + func bjs_WeirdNaming_normalProperty_get(_ self: Int32) -> Int32 + #else + func bjs_WeirdNaming_normalProperty_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_WeirdNaming_normalProperty_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func setNormalProperty(_ newValue: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_normalProperty_set") + func bjs_WeirdNaming_normalProperty_set(_ self: Int32, _ newValue: Int32) -> Void + #else + func bjs_WeirdNaming_normalProperty_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_WeirdNaming_normalProperty_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + + var `for`: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_for_get") + func bjs_WeirdNaming_for_get(_ self: Int32) -> Int32 + #else + func bjs_WeirdNaming_for_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_WeirdNaming_for_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func setFor(_ newValue: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_for_set") + func bjs_WeirdNaming_for_set(_ self: Int32, _ newValue: Int32) -> Void + #else + func bjs_WeirdNaming_for_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_WeirdNaming_for_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + + var `Any`: String { + get throws(JSException) { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_Any_get") + func bjs_WeirdNaming_Any_get(_ self: Int32) -> Int32 + #else + func bjs_WeirdNaming_Any_get(_ self: Int32) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + let ret = bjs_WeirdNaming_Any_get(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) + } + } + + func setAny(_ newValue: String) throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_Any_set") + func bjs_WeirdNaming_Any_set(_ self: Int32, _ newValue: Int32) -> Void + #else + func bjs_WeirdNaming_Any_set(_ self: Int32, _ newValue: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_WeirdNaming_Any_set(self.bridgeJSLowerParameter(), newValue.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + + func `as`() throws(JSException) -> Void { + #if arch(wasm32) + @_extern(wasm, module: "Check", name: "bjs_WeirdNaming_as") + func bjs_WeirdNaming_as(_ self: Int32) -> Void + #else + func bjs_WeirdNaming_as(_ self: Int32) -> Void { + fatalError("Only available on WebAssembly") + } + #endif + bjs_WeirdNaming_as(self.bridgeJSLowerParameter()) + if let error = _swift_js_take_exception() { + throw error + } + } + +} \ No newline at end of file From ef0c6cbbf9e0e996cfc718653d212f3f7c1b3b49 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 25 Aug 2025 15:33:24 +0900 Subject: [PATCH 58/61] BridgeJS: Fix build when using `@JS` with explicit access control * Ban `@JS private` and `@JS fileprivate` * Add explicit access control to synthesized `jsValue` property to match the class's access control. --- .../Sources/BridgeJSCore/ExportSwift.swift | 81 ++++++++++++++++++- .../BridgeJSSkeleton/BridgeJSSkeleton.swift | 6 ++ .../BridgeJSToolTests/Inputs/EnumCase.swift | 4 + .../BridgeJSToolTests/Inputs/SwiftClass.swift | 3 + .../BridgeJSLinkTests/EnumCase.Export.d.ts | 5 ++ .../BridgeJSLinkTests/EnumCase.Export.js | 4 + .../BridgeJSLinkTests/SwiftClass.Export.d.ts | 8 ++ .../BridgeJSLinkTests/SwiftClass.Export.js | 22 +++++ .../ExportSwiftTests/EnumCase.json | 14 ++++ .../ExportSwiftTests/EnumCase.swift | 31 +++++++ .../ExportSwiftTests/SwiftClass.json | 22 +++++ .../ExportSwiftTests/SwiftClass.swift | 40 +++++++++ .../BridgeJSRuntimeTests/ExportAPITests.swift | 4 + .../Generated/BridgeJS.ExportSwift.swift | 60 ++++++++++++++ .../JavaScript/BridgeJS.ExportSwift.json | 33 ++++++++ 15 files changed, 336 insertions(+), 1 deletion(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 76ce16d6..85642fc0 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -426,9 +426,14 @@ public class ExportSwift { let effectiveNamespace = computedNamespace ?? attributeNamespace let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: name) + let explicitAccessControl = computeExplicitAtLeastInternalAccessControl( + for: node, + message: "Class visibility must be at least internal" + ) let exportedClass = ExportedClass( name: name, swiftCallName: swiftCallName, + explicitAccessControl: explicitAccessControl, constructor: nil, methods: [], properties: [], @@ -520,9 +525,14 @@ public class ExportSwift { } let swiftCallName = ExportSwift.computeSwiftCallName(for: node, itemName: enumName) + let explicitAccessControl = computeExplicitAtLeastInternalAccessControl( + for: node, + message: "Enum visibility must be at least internal" + ) let exportedEnum = ExportedEnum( name: enumName, swiftCallName: swiftCallName, + explicitAccessControl: explicitAccessControl, cases: currentEnum.cases, rawType: currentEnum.rawType, namespace: effectiveNamespace, @@ -615,6 +625,25 @@ public class ExportSwift { return namespace.isEmpty ? nil : namespace } + + /// Requires the node to have at least internal access control. + private func computeExplicitAtLeastInternalAccessControl( + for node: some WithModifiersSyntax, + message: String + ) -> String? { + guard let accessControl = node.explicitAccessControl else { + return nil + } + guard accessControl.isAtLeastInternal else { + diagnose( + node: accessControl, + message: message, + hint: "Use `internal`, `package` or `public` access control" + ) + return nil + } + return accessControl.name.text + } } func parseSingleFile(_ sourceFile: SourceFileSyntax) throws -> [DiagnosticError] { @@ -1130,9 +1159,11 @@ public class ExportSwift { let wrapFunctionName = "_bjs_\(klass.name)_wrap" let externFunctionName = "bjs_\(klass.name)_wrap" + // If the class has an explicit access control, we need to add it to the extension declaration. + let accessControl = klass.explicitAccessControl.map { "\($0) " } ?? "" return """ extension \(raw: klass.swiftCallName): ConvertibleToJSValue, _BridgedSwiftHeapObject { - var jsValue: JSValue { + \(raw: accessControl)var jsValue: JSValue { #if arch(wasm32) @_extern(wasm, module: "\(raw: moduleName)", name: "\(raw: externFunctionName)") func \(raw: wrapFunctionName)(_: UnsafeMutableRawPointer) -> Int32 @@ -1309,3 +1340,51 @@ extension BridgeType { } } } + +extension DeclModifierSyntax { + var isAccessControl: Bool { + switch self.name.tokenKind { + case .keyword(.private), + .keyword(.fileprivate), + .keyword(.internal), + .keyword(.package), + .keyword(.public), + .keyword(.open): + return true + default: + return false + } + } + + var isAtLeastInternal: Bool { + switch self.name.tokenKind { + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): true + case .keyword(.package): true + case .keyword(.public): true + case .keyword(.open): true + default: false + } + } + + var isAtLeastPackage: Bool { + switch self.name.tokenKind { + case .keyword(.private): false + case .keyword(.fileprivate): false + case .keyword(.internal): true + case .keyword(.package): true + case .keyword(.public): true + case .keyword(.open): true + default: false + } + } +} + +extension WithModifiersSyntax { + var explicitAccessControl: DeclModifierSyntax? { + return self.modifiers.first { modifier in + modifier.isAccessControl + } + } +} diff --git a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift index 76385d41..5fa0acb9 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift @@ -104,6 +104,7 @@ public enum EnumEmitStyle: String, Codable { public struct ExportedEnum: Codable, Equatable { public let name: String public let swiftCallName: String + public let explicitAccessControl: String? public let cases: [EnumCase] public let rawType: String? public let namespace: [String]? @@ -121,6 +122,7 @@ public struct ExportedEnum: Codable, Equatable { public init( name: String, swiftCallName: String, + explicitAccessControl: String?, cases: [EnumCase], rawType: String?, namespace: [String]?, @@ -128,6 +130,7 @@ public struct ExportedEnum: Codable, Equatable { ) { self.name = name self.swiftCallName = swiftCallName + self.explicitAccessControl = explicitAccessControl self.cases = cases self.rawType = rawType self.namespace = namespace @@ -172,6 +175,7 @@ public struct ExportedFunction: Codable { public struct ExportedClass: Codable { public var name: String public var swiftCallName: String + public var explicitAccessControl: String? public var constructor: ExportedConstructor? public var methods: [ExportedFunction] public var properties: [ExportedProperty] @@ -180,6 +184,7 @@ public struct ExportedClass: Codable { public init( name: String, swiftCallName: String, + explicitAccessControl: String?, constructor: ExportedConstructor? = nil, methods: [ExportedFunction], properties: [ExportedProperty] = [], @@ -187,6 +192,7 @@ public struct ExportedClass: Codable { ) { self.name = name self.swiftCallName = swiftCallName + self.explicitAccessControl = explicitAccessControl self.constructor = constructor self.methods = methods self.properties = properties diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift index 6d5d6b55..15bdb9f4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/EnumCase.swift @@ -24,3 +24,7 @@ @JS func setTSDirection(_ direction: TSDirection) @JS func getTSDirection() -> TSDirection + +@JS public enum PublicStatus { + case success +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift index 08600d2a..116b0087 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/SwiftClass.swift @@ -15,3 +15,6 @@ @JS func takeGreeter(greeter: Greeter) { print(greeter.greet()) } + +@JS public class PublicGreeter {} +@JS package class PackageGreeter {} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts index 2d45e998..1375cc36 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.d.ts @@ -26,6 +26,11 @@ export enum TSDirection { West = 3, } +export const PublicStatus: { + readonly Success: 0; +}; +export type PublicStatus = typeof PublicStatus[keyof typeof PublicStatus]; + export type Exports = { setDirection(direction: Direction): void; getDirection(): Direction; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js index 42a98f62..b5bc1145 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.Export.js @@ -24,6 +24,10 @@ export const TSDirection = { West: 3, }; +export const PublicStatus = { + Success: 0, +}; + export async function createInstantiator(options, swift) { let instance; diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts index 7efbe7c3..8718463a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.d.ts @@ -16,10 +16,18 @@ export interface Greeter extends SwiftHeapObject { changeName(name: string): void; name: string; } +export interface PublicGreeter extends SwiftHeapObject { +} +export interface PackageGreeter extends SwiftHeapObject { +} export type Exports = { Greeter: { new(name: string): Greeter; } + PublicGreeter: { + } + PackageGreeter: { + } takeGreeter(greeter: Greeter): void; } export type Imports = { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js index 8ca2e4e5..fb995d24 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.Export.js @@ -57,6 +57,14 @@ export async function createInstantiator(options, swift) { const obj = Greeter.__construct(pointer); return swift.memory.retain(obj); }; + importObject["TestModule"]["bjs_PublicGreeter_wrap"] = function(pointer) { + const obj = PublicGreeter.__construct(pointer); + return swift.memory.retain(obj); + }; + importObject["TestModule"]["bjs_PackageGreeter_wrap"] = function(pointer) { + const obj = PackageGreeter.__construct(pointer); + return swift.memory.retain(obj); + }; }, setInstance: (i) => { @@ -126,8 +134,22 @@ export async function createInstantiator(options, swift) { swift.memory.release(valueId); } } + class PublicGreeter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PublicGreeter_deinit, PublicGreeter.prototype); + } + + } + class PackageGreeter extends SwiftHeapObject { + static __construct(ptr) { + return SwiftHeapObject.__wrap(ptr, instance.exports.bjs_PackageGreeter_deinit, PackageGreeter.prototype); + } + + } return { Greeter, + PublicGreeter, + PackageGreeter, takeGreeter: function bjs_takeGreeter(greeter) { instance.exports.bjs_takeGreeter(greeter.pointer); }, diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json index efb6e805..b90bd40b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.json @@ -89,6 +89,20 @@ "emitStyle" : "tsEnum", "name" : "TSDirection", "swiftCallName" : "TSDirection" + }, + { + "cases" : [ + { + "associatedValues" : [ + + ], + "name" : "success" + } + ], + "emitStyle" : "const", + "explicitAccessControl" : "public", + "name" : "PublicStatus", + "swiftCallName" : "PublicStatus" } ], "functions" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift index 8b087111..e222a2bb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/EnumCase.swift @@ -131,6 +131,37 @@ extension TSDirection { } } +extension PublicStatus { + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 { + return bridgeJSRawValue + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftReturn(_ value: Int32) -> PublicStatus { + return PublicStatus(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ value: Int32) -> PublicStatus { + return PublicStatus(bridgeJSRawValue: value)! + } + @_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() -> Int32 { + return bridgeJSRawValue + } + + private init?(bridgeJSRawValue: Int32) { + switch bridgeJSRawValue { + case 0: + self = .success + default: + return nil + } + } + + private var bridgeJSRawValue: Int32 { + switch self { + case .success: + return 0 + } + } +} + @_expose(wasm, "bjs_setDirection") @_cdecl("bjs_setDirection") public func _bjs_setDirection(direction: Int32) -> Void { diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json index 5266959a..ce506f51 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.json @@ -74,6 +74,28 @@ } ], "swiftCallName" : "Greeter" + }, + { + "explicitAccessControl" : "public", + "methods" : [ + + ], + "name" : "PublicGreeter", + "properties" : [ + + ], + "swiftCallName" : "PublicGreeter" + }, + { + "explicitAccessControl" : "package", + "methods" : [ + + ], + "name" : "PackageGreeter", + "properties" : [ + + ], + "swiftCallName" : "PackageGreeter" } ], "enums" : [ diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift index 5a354b43..81f5ccf4 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftClass.swift @@ -87,4 +87,44 @@ extension Greeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { #endif return .object(JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))) } +} + +@_expose(wasm, "bjs_PublicGreeter_deinit") +@_cdecl("bjs_PublicGreeter_deinit") +public func _bjs_PublicGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PublicGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + public var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_PublicGreeter_wrap") + func _bjs_PublicGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PublicGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PublicGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_PackageGreeter_deinit") +@_cdecl("bjs_PackageGreeter_deinit") +public func _bjs_PackageGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PackageGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + package var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "TestModule", name: "bjs_PackageGreeter_wrap") + func _bjs_PackageGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PackageGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PackageGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } } \ No newline at end of file diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index fc2d8d6a..5e68c951 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -102,6 +102,10 @@ struct TestError: Error { } } +@JS internal class InternalGreeter {} +@JS public class PublicGreeter {} +@JS package class PackageGreeter {} + @JS func createCalculator() -> Calculator { return Calculator() } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index e5999f4d..77d059a7 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -983,6 +983,66 @@ extension Calculator: ConvertibleToJSValue, _BridgedSwiftHeapObject { } } +@_expose(wasm, "bjs_InternalGreeter_deinit") +@_cdecl("bjs_InternalGreeter_deinit") +public func _bjs_InternalGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension InternalGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + internal var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_InternalGreeter_wrap") + func _bjs_InternalGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_InternalGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_InternalGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_PublicGreeter_deinit") +@_cdecl("bjs_PublicGreeter_deinit") +public func _bjs_PublicGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PublicGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + public var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_PublicGreeter_wrap") + func _bjs_PublicGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PublicGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PublicGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + +@_expose(wasm, "bjs_PackageGreeter_deinit") +@_cdecl("bjs_PackageGreeter_deinit") +public func _bjs_PackageGreeter_deinit(pointer: UnsafeMutableRawPointer) { + Unmanaged.fromOpaque(pointer).release() +} + +extension PackageGreeter: ConvertibleToJSValue, _BridgedSwiftHeapObject { + package var jsValue: JSValue { + #if arch(wasm32) + @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_PackageGreeter_wrap") + func _bjs_PackageGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 + #else + func _bjs_PackageGreeter_wrap(_: UnsafeMutableRawPointer) -> Int32 { + fatalError("Only available on WebAssembly") + } + #endif + return .object(JSObject(id: UInt32(bitPattern: _bjs_PackageGreeter_wrap(Unmanaged.passRetained(self).toOpaque())))) + } +} + @_expose(wasm, "bjs_Converter_init") @_cdecl("bjs_Converter_init") public func _bjs_Converter_init() -> UnsafeMutableRawPointer { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 30b0685f..03c61ada 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -150,6 +150,39 @@ ], "swiftCallName" : "Calculator" }, + { + "explicitAccessControl" : "internal", + "methods" : [ + + ], + "name" : "InternalGreeter", + "properties" : [ + + ], + "swiftCallName" : "InternalGreeter" + }, + { + "explicitAccessControl" : "public", + "methods" : [ + + ], + "name" : "PublicGreeter", + "properties" : [ + + ], + "swiftCallName" : "PublicGreeter" + }, + { + "explicitAccessControl" : "package", + "methods" : [ + + ], + "name" : "PackageGreeter", + "properties" : [ + + ], + "swiftCallName" : "PackageGreeter" + }, { "constructor" : { "abiName" : "bjs_Converter_init", From 99ece447c3161c9b3d92a8747c9e4b3b4483c426 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 03:47:44 +0000 Subject: [PATCH 59/61] Bump actions/upload-pages-artifact from 3 to 4 Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6da32aa..5375b3e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -125,7 +125,7 @@ jobs: SWIFT_SDK_ID_wasm32_unknown_wasi: ${{ steps.setup-wasm32-unknown-wasi.outputs.swift-sdk-id }} - name: Upload static files as artifact id: deployment - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: Examples/ deploy-examples: From 77272e73c94a53435da63f515d783417ef364e5a Mon Sep 17 00:00:00 2001 From: Simon Leeb <52261246+sliemeobn@users.noreply.github.com> Date: Tue, 26 Aug 2025 13:37:51 +0200 Subject: [PATCH 60/61] added `import _Concurrency` in JSPromise --- Sources/JavaScriptKit/BasicObjects/JSPromise.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index 201e8fa6..ee43f87f 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -1,3 +1,7 @@ +#if hasFeature(Embedded) && os(WASI) +import _Concurrency +#endif + /// A wrapper around [the JavaScript `Promise` class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise) public final class JSPromise: JSBridgedClass { /// The underlying JavaScript `Promise` object. From 739ea2e65831bca4409aef0889ea06a1e0142db4 Mon Sep 17 00:00:00 2001 From: Krzysztof Rodak Date: Tue, 26 Aug 2025 13:44:22 +0200 Subject: [PATCH 61/61] BridgeJS: Fix nested type resolution for MemberTypeSyntax (e.g., Networking.API.Method) --- .../Sources/BridgeJSCore/ExportSwift.swift | 49 +++---- .../BridgeJSCore/TypeDeclResolver.swift | 33 +++++ .../BridgeJSRuntimeTests/ExportAPITests.swift | 27 ++++ .../Generated/BridgeJS.ExportSwift.swift | 55 ++++++++ .../JavaScript/BridgeJS.ExportSwift.json | 126 ++++++++++++++++++ Tests/prelude.mjs | 9 ++ 6 files changed, 276 insertions(+), 23 deletions(-) diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 85642fc0..117158df 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -689,47 +689,50 @@ public class ExportSwift { if let primitive = BridgeType(swiftType: type.trimmedDescription) { return primitive } - guard let identifier = type.as(IdentifierTypeSyntax.self) else { - return nil - } - guard let typeDecl = typeDeclResolver.lookupType(for: identifier) else { + guard let typeDecl = typeDeclResolver.resolve(type) else { return nil } + if let enumDecl = typeDecl.as(EnumDeclSyntax.self) { - let enumName = enumDecl.name.text - if let existingEnum = exportedEnums.first(where: { $0.name == enumName }) { - switch existingEnum.enumType { - case .simple: - return .caseEnum(existingEnum.swiftCallName) - case .rawValue: - let rawType = SwiftEnumRawType.from(existingEnum.rawType!)! - return .rawValueEnum(existingEnum.swiftCallName, rawType) - case .associatedValue: - return .associatedValueEnum(existingEnum.swiftCallName) - case .namespace: - return .namespaceEnum(existingEnum.swiftCallName) - } - } let swiftCallName = ExportSwift.computeSwiftCallName(for: enumDecl, itemName: enumDecl.name.text) let rawTypeString = enumDecl.inheritanceClause?.inheritedTypes.first { inheritedType in let typeName = inheritedType.type.trimmedDescription return Constants.supportedRawTypes.contains(typeName) }?.type.trimmedDescription - if let rawTypeString = rawTypeString, - let rawType = SwiftEnumRawType.from(rawTypeString) - { + if let rawTypeString, let rawType = SwiftEnumRawType.from(rawTypeString) { return .rawValueEnum(swiftCallName, rawType) } else { - return .caseEnum(swiftCallName) + let hasAnyCases = enumDecl.memberBlock.members.contains { member in + member.decl.is(EnumCaseDeclSyntax.self) + } + if !hasAnyCases { + return .namespaceEnum(swiftCallName) + } + let hasAssociatedValues = + enumDecl.memberBlock.members.contains { member in + guard let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) else { return false } + return caseDecl.elements.contains { element in + if let params = element.parameterClause?.parameters { + return !params.isEmpty + } + return false + } + } + if hasAssociatedValues { + return .associatedValueEnum(swiftCallName) + } else { + return .caseEnum(swiftCallName) + } } } guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else { return nil } - return .swiftHeapObject(typeDecl.name.text) + let swiftCallName = ExportSwift.computeSwiftCallName(for: typeDecl, itemName: typeDecl.name.text) + return .swiftHeapObject(swiftCallName) } static let prelude: DeclSyntax = """ diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift index a7b183af..25200e13 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift @@ -109,4 +109,37 @@ class TypeDeclResolver { func lookupType(fullyQualified: QualifiedName) -> TypeDecl? { return typeDeclByQualifiedName[fullyQualified] } + + /// Resolves a type usage node to the corresponding nominal type declaration collected in this resolver. + /// + /// Supported inputs: + /// - IdentifierTypeSyntax (e.g. `Method`) — resolved relative to the lexical scope, preferring the innermost enclosing type. + /// - MemberTypeSyntax (e.g. `Networking.API.Method`) — resolved by recursively building the fully qualified name. + /// + /// Resolution strategy: + /// 1. If the node is IdentifierTypeSyntax, call `lookupType(for:)` which attempts scope-aware qualification via `tryQualify`. + /// 2. Otherwise, attempt to build a fully qualified name with `qualifiedComponents(from:)` and look it up with `lookupType(fullyQualified:)`. + /// + /// - Parameter type: The SwiftSyntax node representing a type appearance in source code. + /// - Returns: The nominal declaration (enum/class/actor/struct) if found, otherwise nil. + func resolve(_ type: TypeSyntax) -> TypeDecl? { + if let id = type.as(IdentifierTypeSyntax.self) { + return lookupType(for: id) + } + if let components = qualifiedComponents(from: type) { + return lookupType(fullyQualified: components) + } + return nil + } + + private func qualifiedComponents(from type: TypeSyntax) -> QualifiedName? { + if let m = type.as(MemberTypeSyntax.self) { + guard let base = qualifiedComponents(from: TypeSyntax(m.baseType)) else { return nil } + return base + [m.name.text] + } else if let id = type.as(IdentifierTypeSyntax.self) { + return [id.name.text] + } else { + return nil + } + } } diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 5e68c951..ff42f3c7 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -223,6 +223,8 @@ struct TestError: Error { return .light } +// MARK: - Namespace Enums + @JS enum Utils { @JS class Converter { @JS init() {} @@ -275,6 +277,31 @@ enum Internal { } } +@JS func echoNetworkingAPIMethod(_ method: Networking.API.Method) -> Networking.API.Method { + return method +} + +@JS func echoConfigurationLogLevel(_ level: Configuration.LogLevel) -> Configuration.LogLevel { + return level +} + +@JS func echoConfigurationPort(_ port: Configuration.Port) -> Configuration.Port { + return port +} + +@JS func processConfigurationLogLevel(_ level: Configuration.LogLevel) -> Configuration.Port { + switch level { + case .debug: return .development + case .info: return .http + case .warning: return .https + case .error: return .development + } +} + +@JS func echoInternalSupportedMethod(_ method: Internal.SupportedMethod) -> Internal.SupportedMethod { + return method +} + // MARK: - Property Tests // Simple class for SwiftHeapObject property testing diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 77d059a7..2c70c647 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -814,6 +814,61 @@ public func _bjs_getTSTheme() -> Void { #endif } +@_expose(wasm, "bjs_echoNetworkingAPIMethod") +@_cdecl("bjs_echoNetworkingAPIMethod") +public func _bjs_echoNetworkingAPIMethod(method: Int32) -> Int32 { + #if arch(wasm32) + let ret = echoNetworkingAPIMethod(_: Networking.API.Method.bridgeJSLiftParameter(method)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoConfigurationLogLevel") +@_cdecl("bjs_echoConfigurationLogLevel") +public func _bjs_echoConfigurationLogLevel(levelBytes: Int32, levelLength: Int32) -> Void { + #if arch(wasm32) + let ret = echoConfigurationLogLevel(_: Configuration.LogLevel.bridgeJSLiftParameter(levelBytes, levelLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoConfigurationPort") +@_cdecl("bjs_echoConfigurationPort") +public func _bjs_echoConfigurationPort(port: Int32) -> Int32 { + #if arch(wasm32) + let ret = echoConfigurationPort(_: Configuration.Port.bridgeJSLiftParameter(port)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processConfigurationLogLevel") +@_cdecl("bjs_processConfigurationLogLevel") +public func _bjs_processConfigurationLogLevel(levelBytes: Int32, levelLength: Int32) -> Int32 { + #if arch(wasm32) + let ret = processConfigurationLogLevel(_: Configuration.LogLevel.bridgeJSLiftParameter(levelBytes, levelLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoInternalSupportedMethod") +@_cdecl("bjs_echoInternalSupportedMethod") +public func _bjs_echoInternalSupportedMethod(method: Int32) -> Int32 { + #if arch(wasm32) + let ret = echoInternalSupportedMethod(_: Internal.SupportedMethod.bridgeJSLiftParameter(method)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_createPropertyHolder") @_cdecl("bjs_createPropertyHolder") public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 03c61ada..8e65a50d 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -1826,6 +1826,132 @@ } } }, + { + "abiName" : "bjs_echoNetworkingAPIMethod", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "echoNetworkingAPIMethod", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Networking.API.Method" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "Networking.API.Method" + } + } + }, + { + "abiName" : "bjs_echoConfigurationLogLevel", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "echoConfigurationLogLevel", + "parameters" : [ + { + "label" : "_", + "name" : "level", + "type" : { + "rawValueEnum" : { + "_0" : "Configuration.LogLevel", + "_1" : "String" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Configuration.LogLevel", + "_1" : "String" + } + } + }, + { + "abiName" : "bjs_echoConfigurationPort", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "echoConfigurationPort", + "parameters" : [ + { + "label" : "_", + "name" : "port", + "type" : { + "rawValueEnum" : { + "_0" : "Configuration.Port", + "_1" : "Int" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Configuration.Port", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_processConfigurationLogLevel", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "processConfigurationLogLevel", + "parameters" : [ + { + "label" : "_", + "name" : "level", + "type" : { + "rawValueEnum" : { + "_0" : "Configuration.LogLevel", + "_1" : "String" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Configuration.Port", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_echoInternalSupportedMethod", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "echoInternalSupportedMethod", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Internal.SupportedMethod" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "Internal.SupportedMethod" + } + } + }, { "abiName" : "bjs_createPropertyHolder", "effects" : { diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 1459f21a..8094a465 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -354,6 +354,15 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Get, 0); assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Post, 1); + assert.equal(exports.echoNetworkingAPIMethod(globalThis.Networking.API.Method.Get), globalThis.Networking.API.Method.Get); + assert.equal(exports.echoConfigurationLogLevel(globalThis.Configuration.LogLevel.Debug), globalThis.Configuration.LogLevel.Debug); + assert.equal(exports.echoConfigurationPort(globalThis.Configuration.Port.Http), globalThis.Configuration.Port.Http); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Debug), globalThis.Configuration.Port.Development); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Info), globalThis.Configuration.Port.Http); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Warning), globalThis.Configuration.Port.Https); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Error), globalThis.Configuration.Port.Development); + assert.equal(exports.echoInternalSupportedMethod(globalThis.Networking.APIV2.Internal.SupportedMethod.Get), globalThis.Networking.APIV2.Internal.SupportedMethod.Get); + const converter = new exports.Converter(); assert.equal(converter.toString(42), "42"); assert.equal(converter.toString(123), "123");