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",