Skip to content

Commit 854c78a

Browse files
committed
BridgeJS: Tests for generic imports
1 parent b3d6dcf commit 854c78a

12 files changed

Lines changed: 2135 additions & 1 deletion

File tree

Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSCodegenTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import Testing
2828
let exportSwift = ExportSwift(
2929
progress: .silent,
3030
moduleName: skeleton.moduleName,
31-
skeleton: exported
31+
skeleton: exported,
32+
imported: skeleton.imported
3233
)
3334
if let s = try exportSwift.finalize() {
3435
swiftParts.append(s)

Plugins/BridgeJS/Tests/BridgeJSToolTests/BridgeJSLinkTests.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,37 @@ import Testing
126126
try snapshot(bridgeJSLink: bridgeJSLink, name: "MixedModules")
127127
}
128128

129+
private func linkedJS(forFixture input: String) throws -> String {
130+
let url = Self.inputsDirectory.appendingPathComponent(input)
131+
let name = url.deletingPathExtension().lastPathComponent
132+
let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8))
133+
let importSwift = SwiftToSkeleton(
134+
progress: .silent,
135+
moduleName: "TestModule",
136+
exposeToGlobal: false,
137+
externalModuleIndex: .empty
138+
)
139+
importSwift.addSourceFile(sourceFile, inputFilePath: "\(name).swift")
140+
let importResult = try importSwift.finalize()
141+
var bridgeJSLink = BridgeJSLink(sharedMemory: false)
142+
let encoder = JSONEncoder()
143+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
144+
let unifiedData = try encoder.encode(importResult)
145+
try bridgeJSLink.addSkeletonFile(data: unifiedData)
146+
return try bridgeJSLink.link().0
147+
}
148+
149+
@Test
150+
func genericResolverIsGatedToGenericModules() throws {
151+
let genericJS = try linkedJS(forFixture: "GenericImports.swift")
152+
#expect(genericJS.contains("swift_js_resolve_type_id"))
153+
#expect(genericJS.contains("__bjs_codecs"))
154+
155+
let nonGenericJS = try linkedJS(forFixture: "SwiftStructImports.swift")
156+
#expect(!nonGenericJS.contains("swift_js_resolve_type_id"))
157+
#expect(!nonGenericJS.contains("__bjs_codecs"))
158+
}
159+
129160
@Test
130161
func perClassIdentityModeFromAnnotation() throws {
131162
let url = Self.inputsDirectory.appendingPathComponent("IdentityModeClass.swift")
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import Foundation
2+
import SwiftParser
3+
import SwiftSyntax
4+
import Testing
5+
6+
@testable import BridgeJSCore
7+
@testable import BridgeJSSkeleton
8+
9+
@Suite struct GenericImportDiagnosticsTests {
10+
// MARK: - Diagnostics
11+
12+
@Test
13+
func genericParameterRequiresBridgeableConstraint() throws {
14+
do {
15+
_ = try resolveApp(
16+
source: """
17+
@JSFunction func identity<T>(_ value: T) throws(JSException) -> T
18+
"""
19+
)
20+
Issue.record("Expected a generic-constraint diagnostic, but resolution succeeded")
21+
} catch let error as BridgeJSCoreDiagnosticError {
22+
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
23+
#expect(
24+
combined.contains("Generic parameter 'T' must be constrained to '_BridgedSwiftGenericBridgeable'")
25+
)
26+
}
27+
}
28+
29+
@Test
30+
func genericWhereClauseUnsupported() throws {
31+
do {
32+
_ = try resolveApp(
33+
source: """
34+
@JSFunction func identity<T: _BridgedSwiftGenericBridgeable>(_ value: T) throws(JSException) -> T where T: Sendable
35+
"""
36+
)
37+
Issue.record("Expected a where-clause diagnostic, but resolution succeeded")
38+
} catch let error as BridgeJSCoreDiagnosticError {
39+
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
40+
#expect(combined.contains("'where' clauses are not supported on @JSFunction"))
41+
}
42+
}
43+
44+
@Test
45+
func asyncGenericImportUnsupported() throws {
46+
do {
47+
_ = try resolveApp(
48+
source: """
49+
@JSFunction func identityAsync<T: _BridgedSwiftGenericBridgeable>(_ value: T) async throws(JSException) -> T
50+
"""
51+
)
52+
Issue.record("Expected an async-generic diagnostic, but resolution succeeded")
53+
} catch let error as BridgeJSCoreDiagnosticError {
54+
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
55+
#expect(combined.contains("Generic @JSFunction declarations cannot be 'async' yet."))
56+
}
57+
}
58+
59+
@Test
60+
func genericImportInsideJSClassUnsupported() throws {
61+
do {
62+
_ = try resolveApp(
63+
source: """
64+
@JSClass struct Box {
65+
@JSFunction func member<T: _BridgedSwiftGenericBridgeable>(_ value: T) throws(JSException) -> T
66+
}
67+
"""
68+
)
69+
Issue.record("Expected a @JSClass generic diagnostic, but resolution succeeded")
70+
} catch let error as BridgeJSCoreDiagnosticError {
71+
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
72+
#expect(
73+
combined.contains(
74+
"Generic @JSFunction declarations are only supported at the top level, not in @JSClass types."
75+
)
76+
)
77+
}
78+
}
79+
80+
@Test
81+
func wrappedGenericOptionalReturnUnsupported() throws {
82+
do {
83+
_ = try resolveApp(
84+
source: """
85+
@JSFunction func optionalReturn<T: _BridgedSwiftGenericBridgeable>(_ value: T) throws(JSException) -> T?
86+
"""
87+
)
88+
Issue.record("Expected a wrapped-generic diagnostic, but resolution succeeded")
89+
} catch let error as BridgeJSCoreDiagnosticError {
90+
let combined = error.diagnostics.map(\.diagnostic.message).joined(separator: "\n")
91+
#expect(
92+
combined.contains(
93+
"Generic parameter 'T' may only be used as a bare type; wrapping it (e.g. 'T?', '[T]') is not supported."
94+
)
95+
)
96+
}
97+
}
98+
99+
@Test
100+
func wrappedGenericArrayReturnUnsupported() throws {
101+
do {
102+
_ = try resolveApp(
103+
source: """
104+
@JSFunction func arrayReturn<T: _BridgedSwiftGenericBridgeable>(_ value: 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 (e.g. 'T?', '[T]') is not supported."
113+
)
114+
)
115+
}
116+
}
117+
118+
@Test
119+
func wrappedGenericArrayParameterUnsupported() throws {
120+
do {
121+
_ = try resolveApp(
122+
source: """
123+
@JSFunction func arrayParameter<T: _BridgedSwiftGenericBridgeable>(_ values: [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 (e.g. 'T?', '[T]') is not supported."
132+
)
133+
)
134+
}
135+
}
136+
137+
// MARK: - Positive control
138+
139+
// MARK: - Utilities
140+
141+
private func resolveApp(source appSource: String) throws -> BridgeJSSkeleton {
142+
let swiftAPI = SwiftToSkeleton(
143+
progress: .silent,
144+
moduleName: "App",
145+
exposeToGlobal: false,
146+
externalModuleIndex: ExternalModuleIndex(dependencies: [])
147+
)
148+
let sourceFile = Parser.parse(source: appSource)
149+
swiftAPI.addSourceFile(sourceFile, inputFilePath: "App.swift")
150+
return try swiftAPI.finalize()
151+
}
152+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@JS
2+
struct GenericPoint {
3+
var x: Int
4+
var y: Int
5+
}
6+
7+
@JS final class GenericImportBox {
8+
@JS var value: Int
9+
@JS init(value: Int) {
10+
self.value = value
11+
}
12+
@JS func get() -> Int {
13+
value
14+
}
15+
}
16+
17+
@JSFunction func genericRoundTrip<T: _BridgedSwiftGenericBridgeable>(_ value: T) throws(JSException) -> T
18+
19+
@JSFunction func importGenericIdentityClass<T: _BridgedSwiftGenericBridgeable>(_ value: T) throws(JSException) -> T
20+
21+
@JSFunction func genericParse<T: _BridgedSwiftGenericBridgeable>(_ json: String) throws(JSException) -> T
22+
23+
@JSFunction func importGenericPair<T: _BridgedSwiftGenericBridgeable>(_ a: T, _ b: T) throws(JSException) -> T
24+
25+
@JSFunction func importGenericMake<T: _BridgedSwiftGenericBridgeable>() throws(JSException) -> T
26+
27+
@JSFunction func importGenericCombine<T: _BridgedSwiftGenericBridgeable, U: _BridgedSwiftGenericBridgeable>(
28+
_ a: T,
29+
_ b: U
30+
) throws(JSException) -> U

0 commit comments

Comments
 (0)