Skip to content

Commit 19e0b1b

Browse files
committed
BridgeJS: Support protocol refinement and constrained imports
A @js protocol may now refine other @js protocols declared in the same module, and an imported generic function may be constrained to a @js protocol: @js protocol NamedEntity: Identified, Named {} @js func describe(_ value: NamedEntity) -> String { ... } @js protocol Node: Identified, Named, BridgedSwiftGenericBridgeable {} @JSFunction func roundTrip<T: Node>(_ value: T) throws(JSException) -> T Refinement is resolved by Swift, not by the generator. The JS-backed implementations are emitted on the protocol itself, constrained to wrapper types, and the Any<P> wrapper becomes a trivial struct: extension Identified where Self: _BridgedSwiftProtocolWrapper { var id: String { ... } } struct AnyIdentified: Identified, _BridgedSwiftProtocolWrapper { let jsObject: JSObject; ... } AnyNamedEntity then satisfies Identified's and Named's requirements through their extensions. Every extern stays owned by the protocol that declares the member and is registered exactly once on the JS side, a member the refining protocol re-declares resolves to the more specific extension, and user conformers never see these defaults because they do not conform to _BridgedSwiftProtocolWrapper. The d.ts mirrors this with 'interface NamedEntity extends Identified, Named'. The skeleton records only each protocol's own members plus the refinement edge (inheritedJSProtocols). Refining a protocol from another module is diagnosed on the inheritance clause. Protocol wrapper extern thunks are now named by ABI name rather than '_extern_<method>', so two protocols declaring the same member no longer emit duplicate fileprivate declarations. Generic imports accept @js protocols in the constraint, either composed with BridgedSwiftGenericBridgeable or inheriting it through the protocol; qualified names such as GraphKit.Node are preserved in the generated Swift. A protocol that inherits BridgedSwiftGenericBridgeable gets a synthesized Any<P>: BridgedSwiftGenericBridgeable conformance and a registered type handle, so Any<P> can be used as a generic argument. The generated TypeScript signature carries 'T extends Node'. Skeletons written by earlier plugins still decode: a bare "T" generic parameter is read as an unconstrained GenericParameter. Runtime coverage drives a two-parent refinement with a re-declared member, an inherited settable property, and a member-less refiner from a single JS object, alongside the constrained-import round trips.
1 parent 53e8cc1 commit 19e0b1b

22 files changed

Lines changed: 1966 additions & 261 deletions

File tree

‎Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift‎

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,7 +1425,7 @@ struct ProtocolCodegen {
14251425
for method in proto.methods {
14261426
let builder = try ImportTS.CallJSEmission(
14271427
moduleName: moduleName,
1428-
abiName: "_extern_\(method.name)",
1428+
abiName: method.abiName,
14291429
effects: method.effects,
14301430
returnType: method.returnType,
14311431
context: .exportSwift
@@ -1450,7 +1450,7 @@ struct ProtocolCodegen {
14501450
printer: externDeclPrinter,
14511451
moduleName: moduleName,
14521452
abiName: method.abiName,
1453-
functionName: "_extern_\(method.name)",
1453+
functionName: method.abiName,
14541454
abiParameters: builder.abiParameterSignatures,
14551455
returnType: builder.abiReturnType
14561456
)
@@ -1478,32 +1478,42 @@ struct ProtocolCodegen {
14781478
externDecls.append(contentsOf: propertyExternDecls)
14791479
}
14801480

1481-
let structDeclPrinter = CodeFragmentPrinter()
1482-
structDeclPrinter.write("struct \(wrapperName): \(protocolName), _BridgedSwiftProtocolWrapper {")
1483-
structDeclPrinter.indent {
1484-
structDeclPrinter.write("let jsObject: JSObject")
1485-
structDeclPrinter.nextLine()
1486-
1487-
for methodDecl in methodDecls {
1488-
structDeclPrinter.write(lines: methodDecl.lines)
1489-
structDeclPrinter.nextLine()
1490-
}
1491-
1492-
for decl in propertyDecls {
1493-
structDeclPrinter.write(lines: decl.lines)
1494-
structDeclPrinter.nextLine()
1481+
// The JS-backed implementations live on the protocol itself, constrained
1482+
// to wrapper types. A refining protocol's wrapper (`AnyC` for
1483+
// `C: A, B`) then satisfies A's and B's requirements through their
1484+
// extensions: Swift's conformance resolution does the "flattening",
1485+
// every extern stays owned by the protocol that declares the member,
1486+
// and a member the refining protocol re-declares resolves to the more
1487+
// specific extension. User conformers never see these defaults - they
1488+
// do not conform to `_BridgedSwiftProtocolWrapper`.
1489+
var decls: [DeclSyntax] = []
1490+
if !methodDecls.isEmpty || !propertyDecls.isEmpty {
1491+
let extensionPrinter = CodeFragmentPrinter()
1492+
extensionPrinter.write("extension \(protocolName) where Self: _BridgedSwiftProtocolWrapper {")
1493+
extensionPrinter.indent {
1494+
var first = true
1495+
for decl in methodDecls + propertyDecls {
1496+
if !first { extensionPrinter.nextLine() }
1497+
first = false
1498+
extensionPrinter.write(lines: decl.lines)
1499+
}
14951500
}
1496-
structDeclPrinter.write(
1497-
multilineString: """
1498-
static func bridgeJSLiftParameter(_ value: Int32) -> Self {
1499-
return \(wrapperName)(jsObject: JSObject(id: UInt32(bitPattern: value)))
1500-
}
1501-
"""
1502-
)
1501+
extensionPrinter.write("}")
1502+
decls.append("\(raw: extensionPrinter.lines.joined(separator: "\n"))")
15031503
}
1504-
structDeclPrinter.write("}")
15051504

1506-
return ["\(raw: structDeclPrinter.lines.joined(separator: "\n"))"] + externDecls
1505+
decls.append(
1506+
"""
1507+
struct \(raw: wrapperName): \(raw: protocolName), _BridgedSwiftProtocolWrapper {
1508+
let jsObject: JSObject
1509+
1510+
static func bridgeJSLiftParameter(_ value: Int32) -> Self {
1511+
return \(raw: wrapperName)(jsObject: JSObject(id: UInt32(bitPattern: value)))
1512+
}
1513+
}
1514+
"""
1515+
)
1516+
return decls + externDecls
15071517
}
15081518

15091519
private func renderProtocolProperty(

‎Plugins/BridgeJS/Sources/BridgeJSCore/ExternalModuleIndex.swift‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public struct ExternalModuleIndex {
77
public struct ExternalType: Equatable {
88
public let moduleName: String
99
public let bridgeType: BridgeType
10+
public var isGenericBridgeableProtocol = false
1011
}
1112

1213
public enum LookupResult: Equatable {
@@ -31,8 +32,12 @@ public struct ExternalModuleIndex {
3132
guard let exported = skeleton.exported else { continue }
3233
var moduleEntries = entriesByModule[moduleName] ?? [:]
3334

34-
func register(dotPath: String, bridgeType: BridgeType) {
35-
let externalType = ExternalType(moduleName: moduleName, bridgeType: bridgeType)
35+
func register(dotPath: String, bridgeType: BridgeType, isGenericBridgeableProtocol: Bool = false) {
36+
let externalType = ExternalType(
37+
moduleName: moduleName,
38+
bridgeType: bridgeType,
39+
isGenericBridgeableProtocol: isGenericBridgeableProtocol
40+
)
3641
if moduleEntries[dotPath] == nil {
3742
moduleEntries[dotPath] = externalType
3843
entriesByDotPath[dotPath, default: []].append(externalType)
@@ -61,7 +66,11 @@ public struct ExternalModuleIndex {
6166
register(dotPath: enumDef.swiftCallName, bridgeType: bridgeType)
6267
}
6368
for proto in exported.protocols {
64-
register(dotPath: proto.name, bridgeType: .swiftProtocol(proto.name))
69+
register(
70+
dotPath: proto.name,
71+
bridgeType: .swiftProtocol(proto.name),
72+
isGenericBridgeableProtocol: proto.isGenericBridgeable == true
73+
)
6574
}
6675
for alias in exported.aliases {
6776
register(

‎Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ public struct ImportTS {
381381
parameters: [Parameter],
382382
returnType: BridgeType,
383383
effects: Effects,
384-
genericParameters: [String] = []
384+
genericParameters: [GenericParameter] = []
385385
) -> DeclSyntax {
386386
let printer = CodeFragmentPrinter()
387387
let signature = SwiftSignatureBuilder.buildFunctionSignature(
@@ -393,7 +393,13 @@ public struct ImportTS {
393393
let genericClause =
394394
genericParameters.isEmpty
395395
? ""
396-
: "<" + genericParameters.map { "\($0): BridgedSwiftGenericBridgeable" }.joined(separator: ", ")
396+
: "<"
397+
+ genericParameters.map { genericParameter in
398+
let constraints =
399+
["BridgedSwiftGenericBridgeable"]
400+
+ (genericParameter.swiftConstraints ?? genericParameter.constraints)
401+
return "\(genericParameter.name): \(constraints.joined(separator: " & "))"
402+
}.joined(separator: ", ")
397403
+ ">"
398404
printer.write("func \(name.backtickIfNeeded())\(genericClause)\(signature) {")
399405
printer.indent {
@@ -465,7 +471,7 @@ public struct ImportTS {
465471
parameters: function.parameters,
466472
returnType: function.returnType,
467473
effects: function.effects,
468-
genericParameters: function.genericParameterNames
474+
genericParameters: function.genericParameters ?? []
469475
)
470476
.with(\.leadingTrivia, Self.renderDocumentation(documentation: function.documentation))
471477
]
@@ -496,7 +502,7 @@ public struct ImportTS {
496502
parameters: [selfParameter] + method.parameters,
497503
returnType: method.returnType,
498504
effects: method.effects,
499-
genericParameters: method.genericParameterNames
505+
genericParameters: method.genericParameters ?? []
500506
)
501507
]
502508
}
@@ -522,7 +528,7 @@ public struct ImportTS {
522528
parameters: method.parameters,
523529
returnType: method.returnType,
524530
effects: method.effects,
525-
genericParameters: method.genericParameterNames
531+
genericParameters: method.genericParameters ?? []
526532
)
527533
]
528534
}
@@ -548,7 +554,7 @@ public struct ImportTS {
548554
parameters: constructor.parameters,
549555
returnType: .jsObject(nil),
550556
effects: effects,
551-
genericParameters: constructor.genericParameterNames
557+
genericParameters: constructor.genericParameters ?? []
552558
)
553559
]
554560
}

0 commit comments

Comments
 (0)