Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -720,5 +720,8 @@
}
}
],
"moduleName" : "Benchmarks"
"moduleName" : "Benchmarks",
"protocols" : [

]
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,8 @@
"functions" : [

],
"moduleName" : "PlayBridgeJS"
"moduleName" : "PlayBridgeJS",
"protocols" : [

]
}
422 changes: 343 additions & 79 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,8 @@ extension BridgeType {
case .void: return .void
case .swiftHeapObject:
throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures")
case .swiftProtocol:
throw BridgeJSCoreError("swiftProtocol is not supported in imported signatures")
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
case .optional:
Expand Down Expand Up @@ -465,6 +467,8 @@ extension BridgeType {
case .void: return .void
case .swiftHeapObject:
throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures")
case .swiftProtocol:
throw BridgeJSCoreError("swiftProtocol is not supported in imported signatures")
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
case .optional:
Expand Down
80 changes: 75 additions & 5 deletions Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ struct BridgeJSLink {
enumPropertyPrinter.write("},")

if !property.isReadonly {
let setterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false))
let setterThunkBuilder = ExportedThunkBuilder(
effects: Effects(isAsync: false, isThrows: false)
)
try setterThunkBuilder.lowerParameter(
param: Parameter(label: "value", name: "value", type: property.type)
)
Expand Down Expand Up @@ -300,6 +302,29 @@ struct BridgeJSLink {
data.importObjectBuilders.append(importObjectBuilder)
}

for skeleton in exportedSkeletons {
if !skeleton.protocols.isEmpty {
let importObjectBuilder: ImportObjectBuilder
if let existingBuilder = data.importObjectBuilders.first(where: { $0.moduleName == skeleton.moduleName }
) {
importObjectBuilder = existingBuilder
} else {
importObjectBuilder = ImportObjectBuilder(moduleName: skeleton.moduleName)
data.importObjectBuilders.append(importObjectBuilder)
}

for proto in skeleton.protocols {
for method in proto.methods {
try renderProtocolMethod(
importObjectBuilder: importObjectBuilder,
protocol: proto,
method: method
)
}
}
}
}

return data
}

Expand Down Expand Up @@ -572,6 +597,22 @@ struct BridgeJSLink {
"""
let printer = CodeFragmentPrinter(header: header)
printer.nextLine()

for skeleton in exportedSkeletons {
for proto in skeleton.protocols {
printer.write("export interface \(proto.name) {")
printer.indent {
for method in proto.methods {
printer.write(
"\(method.name)\(renderTSSignature(parameters: method.parameters, returnType: method.returnType, effects: method.effects));"
)
}
}
printer.write("}")
printer.nextLine()
}
}

printer.write(lines: data.topLevelDtsEnumLines)

// Generate Object types for const-style enums
Expand Down Expand Up @@ -1468,7 +1509,9 @@ extension BridgeJSLink {

if !property.isReadonly {
// Generate setter
let setterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false))
let setterThunkBuilder = ExportedThunkBuilder(
effects: Effects(isAsync: false, isThrows: false)
)
try setterThunkBuilder.lowerParameter(
param: Parameter(label: "value", name: "value", type: property.type)
)
Expand Down Expand Up @@ -1539,7 +1582,9 @@ extension BridgeJSLink {

if !property.isReadonly {
// Generate setter
let setterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false))
let setterThunkBuilder = ExportedThunkBuilder(
effects: Effects(isAsync: false, isThrows: false)
)
try setterThunkBuilder.lowerParameter(
param: Parameter(label: "value", name: "value", type: property.type)
)
Expand Down Expand Up @@ -1704,7 +1749,9 @@ extension BridgeJSLink {

// Generate static property setter if not readonly
if !property.isReadonly {
let setterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false))
let setterThunkBuilder = ExportedThunkBuilder(
effects: Effects(isAsync: false, isThrows: false)
)
try setterThunkBuilder.lowerParameter(
param: Parameter(label: "value", name: "value", type: property.type)
)
Expand Down Expand Up @@ -1752,7 +1799,9 @@ extension BridgeJSLink {

// Generate instance property setter if not readonly
if !property.isReadonly {
let setterThunkBuilder = ExportedThunkBuilder(effects: Effects(isAsync: false, isThrows: false))
let setterThunkBuilder = ExportedThunkBuilder(
effects: Effects(isAsync: false, isThrows: false)
)
setterThunkBuilder.lowerSelf()
try setterThunkBuilder.lowerParameter(
param: Parameter(label: "value", name: "value", type: property.type)
Expand Down Expand Up @@ -2367,6 +2416,25 @@ extension BridgeJSLink {
)
return (funcLines, [])
}

func renderProtocolMethod(
importObjectBuilder: ImportObjectBuilder,
protocol: ExportedProtocol,
method: ExportedFunction
) throws {
let thunkBuilder = ImportedThunkBuilder()
thunkBuilder.liftSelf()
for param in method.parameters {
try thunkBuilder.liftParameter(param: param)
}
let returnExpr = try thunkBuilder.callMethod(name: method.name, returnType: method.returnType)
let funcLines = thunkBuilder.renderFunction(
name: method.abiName,
returnExpr: returnExpr,
returnType: method.returnType
)
importObjectBuilder.assignToImportObject(name: method.abiName, function: funcLines)
}
}

struct BridgeJSLinkError: Error {
Expand Down Expand Up @@ -2402,6 +2470,8 @@ extension BridgeType {
return "\(name)Tag"
case .namespaceEnum(let name):
return name
case .swiftProtocol(let name):
return name
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,25 @@ struct IntrinsicJSFragment: Sendable {
switch wrappedType {
case .swiftHeapObject:
return ["+\(isSomeVar)", "\(isSomeVar) ? \(value).pointer : 0"]
case .swiftProtocol:
return [
"+\(isSomeVar)",
"\(isSomeVar) ? \(JSGlueVariableScope.reservedSwift).memory.retain(\(value)) : 0",
]
case .jsObject:
let idVar = scope.variable("id")
printer.write("let \(idVar);")
printer.write("if (\(isSomeVar)) {")
printer.indent {
printer.write("\(idVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(value));")
}
printer.write("}")
cleanupCode.write("if (\(idVar) !== undefined) {")
cleanupCode.indent {
cleanupCode.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(idVar));")
}
cleanupCode.write("}")
return ["+\(isSomeVar)", "\(isSomeVar) ? \(idVar) : 0"]
default:
return ["+\(isSomeVar)", "\(isSomeVar) ? \(value) : 0"]
}
Expand Down Expand Up @@ -326,6 +345,9 @@ struct IntrinsicJSFragment: Sendable {
case .string:
printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnString);")
printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;")
case .jsObject, .swiftProtocol:
printer.write("const \(resultVar) = \(JSGlueVariableScope.reservedStorageToReturnString);")
printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = undefined;")
case .swiftHeapObject(let className):
let pointerVar = scope.variable("pointer")
printer.write(
Expand Down Expand Up @@ -397,6 +419,7 @@ struct IntrinsicJSFragment: Sendable {
case .jsObject: return .jsObjectLowerParameter
case .swiftHeapObject:
return .swiftHeapObjectLowerParameter
case .swiftProtocol: return .jsObjectLowerParameter
case .void: return .void
case .optional(let wrappedType):
return try .optionalLowerParameter(wrappedType: wrappedType)
Expand All @@ -422,6 +445,7 @@ struct IntrinsicJSFragment: Sendable {
case .string: return .stringLiftReturn
case .jsObject: return .jsObjectLiftReturn
case .swiftHeapObject(let name): return .swiftHeapObjectLiftReturn(name)
case .swiftProtocol: return .jsObjectLiftReturn
case .void: return .void
case .optional(let wrappedType): return .optionalLiftReturn(wrappedType: wrappedType)
case .caseEnum: return .identity
Expand Down Expand Up @@ -455,6 +479,7 @@ struct IntrinsicJSFragment: Sendable {
message:
"Swift heap objects are not supported to be passed as parameters to imported JS functions: \(name)"
)
case .swiftProtocol: return .jsObjectLiftParameter
case .void:
throw BridgeJSLinkError(
message: "Void can't appear in parameters of imported JS functions"
Expand Down Expand Up @@ -494,6 +519,7 @@ struct IntrinsicJSFragment: Sendable {
throw BridgeJSLinkError(
message: "Swift heap objects are not supported to be returned from imported JS functions"
)
case .swiftProtocol: return .jsObjectLowerReturn
case .void: return .void
case .optional(let wrappedType):
throw BridgeJSLinkError(
Expand Down
26 changes: 25 additions & 1 deletion Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public enum BridgeType: Codable, Equatable, Sendable {
case rawValueEnum(String, SwiftEnumRawType)
case associatedValueEnum(String)
case namespaceEnum(String)
case swiftProtocol(String)
}

public enum WasmCoreType: String, Codable, Sendable {
Expand Down Expand Up @@ -269,6 +270,18 @@ public enum EnumType: String, Codable, Sendable {

// MARK: - Exported Skeleton

public struct ExportedProtocol: Codable, Equatable {
public let name: String
public let methods: [ExportedFunction]
public let namespace: [String]?

public init(name: String, methods: [ExportedFunction], namespace: [String]? = nil) {
self.name = name
self.methods = methods
self.namespace = namespace
}
}

public struct ExportedFunction: Codable, Equatable, Sendable {
public var name: String
public var abiName: String
Expand Down Expand Up @@ -407,12 +420,20 @@ public struct ExportedSkeleton: Codable {
public let functions: [ExportedFunction]
public let classes: [ExportedClass]
public let enums: [ExportedEnum]
public let protocols: [ExportedProtocol]

public init(moduleName: String, functions: [ExportedFunction], classes: [ExportedClass], enums: [ExportedEnum]) {
public init(
moduleName: String,
functions: [ExportedFunction],
classes: [ExportedClass],
enums: [ExportedEnum],
protocols: [ExportedProtocol] = []
) {
self.moduleName = moduleName
self.functions = functions
self.classes = classes
self.enums = enums
self.protocols = protocols
}
}

Expand Down Expand Up @@ -514,6 +535,9 @@ extension BridgeType {
return nil
case .namespaceEnum:
return nil
case .swiftProtocol:
// Protocols pass JSObject IDs as Int32
return .i32
}
}

Expand Down
41 changes: 41 additions & 0 deletions Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/Protocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import JavaScriptKit

@JS protocol MyViewControllerDelegate {
func onSomethingHappened()
func onValueChanged(_ value: String)
func onCountUpdated(count: Int) -> Bool
func onLabelUpdated(_ prefix: String, _ suffix: String)
func isCountEven() -> Bool
}

@JS class MyViewController {
@JS
var delegate: MyViewControllerDelegate

@JS
var secondDelegate: MyViewControllerDelegate?

@JS init(delegate: MyViewControllerDelegate) {
self.delegate = delegate
}

@JS func triggerEvent() {
delegate.onSomethingHappened()
}

@JS func updateValue(_ value: String) {
delegate.onValueChanged(value)
}

@JS func updateCount(_ count: Int) -> Bool {
return delegate.onCountUpdated(count: count)
}

@JS func updateLabel(_ prefix: String, _ suffix: String) {
delegate.onLabelUpdated(prefix, suffix)
}

@JS func checkEvenCount() -> Bool {
return delegate.isCountEven()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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 MyViewControllerDelegate {
onSomethingHappened(): void;
onValueChanged(value: string): void;
onCountUpdated(count: number): boolean;
onLabelUpdated(prefix: string, suffix: string): void;
isCountEven(): boolean;
}

/// 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 MyViewController extends SwiftHeapObject {
triggerEvent(): void;
updateValue(value: string): void;
updateCount(count: number): boolean;
updateLabel(prefix: string, suffix: string): void;
checkEvenCount(): boolean;
delegate: MyViewControllerDelegate;
secondDelegate: MyViewControllerDelegate | null;
}
export type Exports = {
MyViewController: {
new(delegate: MyViewControllerDelegate): MyViewController;
}
}
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;
}>;
Loading