Skip to content

Commit 4ad8122

Browse files
committed
BridgeJS: Generic function review fixes and simplifications
1 parent ef81857 commit 4ad8122

26 files changed

Lines changed: 1226 additions & 914 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 291 additions & 278 deletions
Large diffs are not rendered by default.

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ public struct ImportTS {
246246
abiParameterForwardings.insert(contentsOf: ["resolveRef", "rejectRef"], at: 0)
247247
}
248248

249-
func appendTypeIDParameter(genericParameterName: String) {
250-
let abiParamName = "\(genericParameterName.lowercased())TypeId"
249+
func appendTypeIDParameter(index: Int, genericParameterName: String) {
250+
let abiParamName = "_generic\(index)TypeId"
251251
abiParameterSignatures.append((abiParamName, .i32))
252252
abiParameterForwardings.append("\(genericParameterName).bridgeJSTypeID")
253253
}
@@ -453,8 +453,8 @@ public struct ImportTS {
453453
for param in function.parameters {
454454
try builder.lowerParameter(param: param)
455455
}
456-
for genericParam in function.genericParameters ?? [] {
457-
builder.appendTypeIDParameter(genericParameterName: genericParam)
456+
for (index, genericParam) in (function.genericParameters ?? []).enumerated() {
457+
builder.appendTypeIDParameter(index: index, genericParameterName: genericParam)
458458
}
459459
try builder.call()
460460
try builder.liftReturnValue()

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,13 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
12991299
)
13001300
return nil
13011301
}
1302+
if node.signature.effectSpecifiers?.throwsClause != nil {
1303+
diagnose(
1304+
node: node,
1305+
message: "Generic @JS functions cannot be 'throws' yet."
1306+
)
1307+
return nil
1308+
}
13021309
}
13031310

13041311
let name = node.name.text

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 52 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,15 @@ public struct BridgeJSLink {
424424
private func genericWrapperHelperDeclarations() -> [String] {
425425
let i32 = JSGlueVariableScope.reservedI32Stack
426426
let codecs = JSGlueVariableScope.reservedCodecs
427+
let codecsById = JSGlueVariableScope.reservedCodecsById
427428
return [
429+
"function __bjs_resolveGenericType(token) {",
430+
" const codec = \(codecs)[token];",
431+
" if (!codec) { throw new Error(\"BridgeJS: no generic codec registered for type '\" + token + \"'\"); }",
432+
" let id = \(codecsById).indexOf(codec);",
433+
" if (id === -1) { id = \(codecsById).push(codec) - 1; }",
434+
" return id;",
435+
"}",
428436
"function __bjs_lowerArrayGeneric(value, codec) {",
429437
" for (let i = 0; i < value.length; i++) { codec.lower(value[i]); }",
430438
" \(i32).push(value.length);",
@@ -475,23 +483,7 @@ public struct BridgeJSLink {
475483
allClasses: [ExportedClass],
476484
allEnums: [ExportedEnum]
477485
) -> [(token: String, type: BridgeType)] {
478-
var entries: [(token: String, type: BridgeType)] = [
479-
("Bool", .bool),
480-
("Int", .integer(.int)),
481-
("Int8", .integer(.int8)),
482-
("UInt8", .integer(.uint8)),
483-
("Int16", .integer(.int16)),
484-
("UInt16", .integer(.uint16)),
485-
("Int32", .integer(.int32)),
486-
("UInt32", .integer(.uint32)),
487-
("UInt", .integer(.uint)),
488-
("Int64", .integer(.int64)),
489-
("UInt64", .integer(.uint64)),
490-
("Float", .float),
491-
("Double", .double),
492-
("String", .string),
493-
("JSValue", .jsValue),
494-
]
486+
var entries = BridgeType.genericBridgeablePrimitives
495487
for structDef in allStructs {
496488
entries.append((structDef.abiName, .swiftStruct(structDef.abiName)))
497489
}
@@ -550,57 +542,39 @@ public struct BridgeJSLink {
550542
printer.write("};")
551543
printer.write("bjs[\"swift_js_resolve_type_id\"] = function(ptr, len) {")
552544
printer.indent {
553-
printer.write("const name = \(decodeString)(ptr, len);")
554-
printer.write("const codec = \(JSGlueVariableScope.reservedCodecs)[name];")
555-
printer.write("if (!codec) {")
556-
printer.indent {
557-
printer.write(
558-
"throw new Error(\"BridgeJS: no generic codec registered for type '\" + name + \"'\");"
559-
)
560-
}
561-
printer.write("}")
562-
printer.write("let id = \(JSGlueVariableScope.reservedCodecsById).indexOf(codec);")
563-
printer.write("if (id === -1) {")
564-
printer.indent {
565-
printer.write("id = \(JSGlueVariableScope.reservedCodecsById).push(codec) - 1;")
566-
}
567-
printer.write("}")
568-
printer.write("return id;")
545+
printer.write("return __bjs_resolveGenericType(\(decodeString)(ptr, len));")
569546
}
570547
printer.write("}")
571548
}
572549

573550
private func generateBridgeTypeTokens() -> (js: [String], dts: [String]) {
574-
let primitives: [(token: String, tsType: String)] = [
575-
("Bool", BridgeType.bool.tsType),
576-
("Int", BridgeType.integer(.int).tsType),
577-
("Int8", BridgeType.integer(.int8).tsType),
578-
("UInt8", BridgeType.integer(.uint8).tsType),
579-
("Int16", BridgeType.integer(.int16).tsType),
580-
("UInt16", BridgeType.integer(.uint16).tsType),
581-
("Int32", BridgeType.integer(.int32).tsType),
582-
("UInt32", BridgeType.integer(.uint32).tsType),
583-
("UInt", BridgeType.integer(.uint).tsType),
584-
("Int64", BridgeType.integer(.int64).tsType),
585-
("UInt64", BridgeType.integer(.uint64).tsType),
586-
("Float", BridgeType.float.tsType),
587-
("Double", BridgeType.double.tsType),
588-
("String", BridgeType.string.tsType),
589-
("JSValue", BridgeType.jsValue.tsType),
590-
]
551+
let primitives: [(token: String, tsType: String)] = BridgeType.genericBridgeablePrimitives.map {
552+
(token: $0.token, tsType: $0.type.tsType)
553+
}
554+
func tsQualifiedName(name: String, namespace: [String]?) -> String {
555+
((namespace ?? []) + [name]).joined(separator: ".")
556+
}
591557
let structs = skeletons.compactMap { $0.exported?.structs }.flatMap { $0 }
592558
let classes = skeletons.compactMap { $0.exported?.classes }.flatMap { $0 }
593559
let enums = skeletons.compactMap { $0.exported?.enums }.flatMap { $0 }
594560
var tokens: [(token: String, tsType: String)] = primitives
595561
for structDef in structs {
596-
tokens.append((token: structDef.abiName, tsType: structDef.name))
562+
tokens.append(
563+
(
564+
token: structDef.abiName,
565+
tsType: tsQualifiedName(name: structDef.name, namespace: structDef.namespace)
566+
)
567+
)
597568
}
598569
for klass in classes where klass.isFinal == true {
599-
tokens.append((token: klass.abiName, tsType: klass.name))
570+
tokens.append(
571+
(token: klass.abiName, tsType: tsQualifiedName(name: klass.name, namespace: klass.namespace))
572+
)
600573
}
601574
for enumDef in enums {
602-
guard let bridgeType = genericEnumBridgeType(enumDef) else { continue }
603-
tokens.append((token: enumDef.abiName, tsType: bridgeType.tsType))
575+
guard genericEnumBridgeType(enumDef) != nil else { continue }
576+
let enumTypeName = enumDef.emitStyle == .tsEnum ? enumDef.tsFullPath : "\(enumDef.tsFullPath)Tag"
577+
tokens.append((token: enumDef.abiName, tsType: enumTypeName))
604578
}
605579

606580
let jsEntries = tokens.map { "\($0.token): \"\($0.token)\"" }
@@ -2168,7 +2142,10 @@ extension BridgeJSLink {
21682142
let genericNames = function.genericParameters ?? []
21692143

21702144
func genericNameOf(_ param: Parameter) -> String {
2171-
param.type.referencedGenericName ?? genericNames[0]
2145+
guard let name = param.type.referencedGenericName else {
2146+
preconditionFailure("Generic value parameter '\(param.name)' has no referenced generic name")
2147+
}
2148+
return name
21722149
}
21732150

21742151
let genericValueParameters = function.parameters.filter { $0.type.usesBareGeneric }
@@ -2188,9 +2165,20 @@ extension BridgeJSLink {
21882165
}
21892166
let concreteForwardings = thunkBuilder.parameterForwardings
21902167

2191-
func tokenName(_ genericName: String) -> String { "type\(genericName)" }
2192-
func codecVariable(_ genericName: String) -> String { "codec\(genericName)" }
2193-
func typeIdVariable(_ genericName: String) -> String { "\(genericName.lowercased())TypeId" }
2168+
let nameScope = JSGlueVariableScope(intrinsicRegistry: intrinsicRegistry)
2169+
for parameter in function.parameters {
2170+
_ = nameScope.variable(parameter.name)
2171+
}
2172+
let tokenNames = Dictionary(uniqueKeysWithValues: genericNames.map { ($0, nameScope.variable("type\($0)")) })
2173+
let codecNames = Dictionary(uniqueKeysWithValues: genericNames.map { ($0, nameScope.variable("codec\($0)")) })
2174+
let typeIdNames = Dictionary(
2175+
uniqueKeysWithValues: genericNames.map { ($0, nameScope.variable("\($0.lowercased())TypeId")) }
2176+
)
2177+
func tokenName(_ genericName: String) -> String { tokenNames[genericName] ?? "type\(genericName)" }
2178+
func codecVariable(_ genericName: String) -> String { codecNames[genericName] ?? "codec\(genericName)" }
2179+
func typeIdVariable(_ genericName: String) -> String {
2180+
typeIdNames[genericName] ?? "\(genericName.lowercased())TypeId"
2181+
}
21942182

21952183
let returnGenericName = function.returnType.referencedGenericName
21962184

@@ -2200,16 +2188,10 @@ extension BridgeJSLink {
22002188
printer.indent {
22012189
for genericName in genericNames {
22022190
printer.write(
2203-
"const \(codecVariable(genericName)) = \(JSGlueVariableScope.reservedCodecs)[\(tokenName(genericName))];"
2204-
)
2205-
printer.write(
2206-
"if (!\(codecVariable(genericName))) { throw new Error(\"BridgeJS: no generic codec registered for type '\" + \(tokenName(genericName)) + \"'\"); }"
2207-
)
2208-
printer.write(
2209-
"let \(typeIdVariable(genericName)) = \(JSGlueVariableScope.reservedCodecsById).indexOf(\(codecVariable(genericName)));"
2191+
"const \(typeIdVariable(genericName)) = __bjs_resolveGenericType(\(tokenName(genericName)));"
22102192
)
22112193
printer.write(
2212-
"if (\(typeIdVariable(genericName)) === -1) { \(typeIdVariable(genericName)) = \(JSGlueVariableScope.reservedCodecsById).push(\(codecVariable(genericName))) - 1; }"
2194+
"const \(codecVariable(genericName)) = \(JSGlueVariableScope.reservedCodecsById)[\(typeIdVariable(genericName))];"
22132195
)
22142196
}
22152197
printer.write(contentsOf: thunkBuilder.body)
@@ -2652,6 +2634,7 @@ extension BridgeJSLink {
26522634
var parameterForwardings: [String] = []
26532635
var returnExpr: String?
26542636
var genericCodecVariables: [String: String] = [:]
2637+
var genericTypeIdParameters: [String: String] = [:]
26552638
let printContext: IntrinsicJSFragment.PrintCodeContext
26562639

26572640
init(
@@ -2679,10 +2662,11 @@ extension BridgeJSLink {
26792662

26802663
func declareGenericCodecs(genericParameters: [String]) {
26812664
for genericParam in genericParameters {
2682-
let typeIdParam = "\(genericParam.lowercased())TypeId"
2665+
let typeIdParam = scope.variable("\(genericParam.lowercased())TypeId")
26832666
let codecVar = scope.variable("codec\(genericParam)")
26842667
body.write("const \(codecVar) = \(JSGlueVariableScope.reservedCodecsById)[\(typeIdParam)];")
26852668
genericCodecVariables[genericParam] = codecVar
2669+
genericTypeIdParameters[genericParam] = typeIdParam
26862670
}
26872671
}
26882672

@@ -3729,7 +3713,7 @@ extension BridgeJSLink {
37293713
try thunkBuilder.liftParameter(param: param)
37303714
}
37313715
for genericParam in genericParameters {
3732-
thunkBuilder.parameterNames.append("\(genericParam.lowercased())TypeId")
3716+
thunkBuilder.parameterNames.append(thunkBuilder.genericTypeIdParameters[genericParam] ?? genericParam)
37333717
}
37343718
let jsName = function.jsName ?? function.name
37353719
let importRootExpr = function.from == .global ? "globalThis" : "imports"

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2809,15 +2809,10 @@ private extension BridgeType {
28092809
switch self {
28102810
case .bool, .rawValueEnum(_, .bool): return "$0 !== 0"
28112811
case .integer(let t) where !t.is64Bit && !t.isSigned: return "$0 >>> 0"
2812-
case .integer(let t) where t.is64Bit && !t.isSigned: return "BigInt.asUintN(64, $0)"
28132812
case .rawValueEnum(_, let rawType)
28142813
where rawType.integerType?.is64Bit == false
28152814
&& rawType.integerType?.isSigned == false:
28162815
return "$0 >>> 0"
2817-
case .rawValueEnum(_, let rawType)
2818-
where rawType.integerType?.is64Bit == true
2819-
&& rawType.integerType?.isSigned == false:
2820-
return "BigInt.asUintN(64, $0)"
28212816
default: return nil
28222817
}
28232818
}

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,24 @@ extension BridgeType {
289289
}
290290

291291
public var usesBareGeneric: Bool { referencedGenericName != nil }
292+
293+
public static let genericBridgeablePrimitives: [(token: String, type: BridgeType)] = [
294+
("Bool", .bool),
295+
("Int", .integer(.int)),
296+
("Int8", .integer(.int8)),
297+
("UInt8", .integer(.uint8)),
298+
("Int16", .integer(.int16)),
299+
("UInt16", .integer(.uint16)),
300+
("Int32", .integer(.int32)),
301+
("UInt32", .integer(.uint32)),
302+
("UInt", .integer(.uint)),
303+
("Int64", .integer(.int64)),
304+
("UInt64", .integer(.uint64)),
305+
("Float", .float),
306+
("Double", .double),
307+
("String", .string),
308+
("JSValue", .jsValue),
309+
]
292310
}
293311

294312
public enum WasmCoreType: String, Codable, Sendable {
@@ -1079,7 +1097,7 @@ public struct ExportedSkeleton: Codable {
10791097
}
10801098

10811099
private var asyncClosureResolveReturnTypes: [BridgeType] {
1082-
var collector = AsyncClosureReturnTypeCollector()
1100+
let collector = AsyncClosureReturnTypeCollector()
10831101
var walker = BridgeSkeletonWalker(visitor: collector)
10841102
walker.walk(self)
10851103
return walker.visitor.returnTypes

Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericExportDiagnosticsTests.swift

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,41 +57,26 @@ import Testing
5757
}
5858

5959
@Test
60-
func nestedWrappedGenericReturnUnsupported() throws {
60+
func throwsGenericExportUnsupported() throws {
6161
do {
6262
_ = try resolveApp(
6363
source: """
64-
@JS public func f<T: _BridgedSwiftGenericBridgeable>(_ v: T) -> [[T]] { [[v]] }
64+
@JS public func f<T: _BridgedSwiftGenericBridgeable>(_ v: T) throws(JSException) -> T { v }
6565
"""
6666
)
67-
Issue.record("Expected a wrapped-generic diagnostic, but resolution succeeded")
67+
Issue.record("Expected a throws-generic diagnostic, but resolution succeeded")
6868
} catch let error as BridgeJSCoreDiagnosticError {
6969
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
70-
#expect(combined.contains("may only be used as a bare type"))
70+
#expect(combined.contains("Generic @JS functions cannot be 'throws' yet."))
7171
}
7272
}
7373

7474
@Test
75-
func optionalArrayElementGenericParameterUnsupported() throws {
76-
do {
77-
_ = try resolveApp(
78-
source: """
79-
@JS public func f<T: _BridgedSwiftGenericBridgeable>(_ values: [T?]) -> T { values[0]! }
80-
"""
81-
)
82-
Issue.record("Expected a wrapped-generic diagnostic, but resolution succeeded")
83-
} catch let error as BridgeJSCoreDiagnosticError {
84-
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
85-
#expect(combined.contains("may only be used as a bare type"))
86-
}
87-
}
88-
89-
@Test
90-
func nonStringDictionaryGenericParameterUnsupported() throws {
75+
func nestedWrappedGenericReturnUnsupported() throws {
9176
do {
9277
_ = try resolveApp(
9378
source: """
94-
@JS public func f<T: _BridgedSwiftGenericBridgeable>(_ values: [Int: T]) -> T { values[0]! }
79+
@JS public func f<T: _BridgedSwiftGenericBridgeable>(_ v: T) -> [[T]] { [[v]] }
9580
"""
9681
)
9782
Issue.record("Expected a wrapped-generic diagnostic, but resolution succeeded")

Plugins/BridgeJS/Tests/BridgeJSToolTests/GenericImportDiagnosticsTests.swift

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -96,44 +96,6 @@ import Testing
9696
}
9797
}
9898

99-
@Test
100-
func optionalArrayElementGenericParameterUnsupported() throws {
101-
do {
102-
_ = try resolveApp(
103-
source: """
104-
@JSFunction func optionalElements<T: _BridgedSwiftGenericBridgeable>(_ values: [T?]) throws(JSException) -> T
105-
"""
106-
)
107-
Issue.record("Expected a wrapped-generic diagnostic, but resolution succeeded")
108-
} catch let error as BridgeJSCoreDiagnosticError {
109-
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
110-
#expect(
111-
combined.contains(
112-
"Generic parameter 'T' may only be used as a bare type; wrapping it beyond 'T?', '[T]' and '[String: T]' is not supported."
113-
)
114-
)
115-
}
116-
}
117-
118-
@Test
119-
func nonStringDictionaryGenericParameterUnsupported() throws {
120-
do {
121-
_ = try resolveApp(
122-
source: """
123-
@JSFunction func intKeyed<T: _BridgedSwiftGenericBridgeable>(_ values: [Int: T]) throws(JSException) -> T
124-
"""
125-
)
126-
Issue.record("Expected a wrapped-generic diagnostic, but resolution succeeded")
127-
} catch let error as BridgeJSCoreDiagnosticError {
128-
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
129-
#expect(
130-
combined.contains(
131-
"Generic parameter 'T' may only be used as a bare type; wrapping it beyond 'T?', '[T]' and '[String: T]' is not supported."
132-
)
133-
)
134-
}
135-
}
136-
13799
// MARK: - Positive control
138100

139101
// MARK: - Utilities

0 commit comments

Comments
 (0)