Skip to content

Commit 3dba5df

Browse files
- Added JSValue array support end-to-end: JS glue now lowers/lifts optional JSValue arrays, using tmp stacks and JSValue helpers, and Swift intrinsics gained Optional<[JSValue]> bridging (Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift, Sources/JavaScriptKit/BridgeJSIntrinsics.swift).
- Expanded runtime coverage with JSValue array round-trips (including optionals) on both import and export paths plus JS prelude wiring and d.ts definitions (`Tests/BridgeJSRuntimeTests/ExportAPITests.swift`, `Tests/BridgeJSRuntimeTests/ImportAPITests.swift`, `Tests/BridgeJSRuntimeTests/bridge-js.d.ts`, `Tests/prelude.mjs`), and regenerated BridgeJS outputs (`Tests/BridgeJSRuntimeTests/Generated/*`, `Tests/BridgeJSGlobalTests/Generated/*`, plugin outputs). - Runtime tests now include JSValue array cases and all suites pass with the prescribed SDK. Tests: `make unittest SWIFT_SDK_ID=DEVELOPMENT-SNAPSHOT-2025-11-03-a-wasm32-unknown-wasip1`.
1 parent ffa6c06 commit 3dba5df

File tree

9 files changed

+313
-0
lines changed

9 files changed

+313
-0
lines changed

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,20 @@ struct IntrinsicJSFragment: Sendable {
10291029
printer.write("\(JSGlueVariableScope.reservedTmpRetF64s).push(\(payload2Var));")
10301030
}
10311031
printer.write("\(JSGlueVariableScope.reservedTmpRetInts).push(\(isSomeVar) ? 1 : 0);")
1032+
case .array(let elementType):
1033+
printer.write("if (\(isSomeVar)) {")
1034+
printer.indent {
1035+
let arrayLowerFragment = try! arrayLower(elementType: elementType)
1036+
let arrayCleanup = CodeFragmentPrinter()
1037+
let _ = arrayLowerFragment.printCode([value], scope, printer, arrayCleanup)
1038+
if !arrayCleanup.lines.isEmpty {
1039+
for line in arrayCleanup.lines {
1040+
printer.write(line)
1041+
}
1042+
}
1043+
}
1044+
printer.write("}")
1045+
printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);")
10321046
case .rawValueEnum(_, let rawType):
10331047
switch rawType {
10341048
case .string:

Sources/JavaScriptKit/BridgeJSIntrinsics.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,50 @@ extension Optional where Wrapped == JSValue {
13941394
}
13951395
}
13961396

1397+
extension Optional where Wrapped == [JSValue] {
1398+
// MARK: ExportSwift
1399+
1400+
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 {
1401+
switch consume self {
1402+
case .none:
1403+
return 0
1404+
case .some(let array):
1405+
array.bridgeJSLowerReturn()
1406+
return 1
1407+
}
1408+
}
1409+
1410+
@_spi(BridgeJS) public static func bridgeJSLiftParameter(_ isSome: Int32) -> [JSValue]? {
1411+
if isSome == 0 {
1412+
return nil
1413+
}
1414+
return [JSValue].bridgeJSLiftParameter()
1415+
}
1416+
1417+
@_spi(BridgeJS) public static func bridgeJSLiftParameter() -> [JSValue]? {
1418+
let isSome = _swift_js_pop_i32()
1419+
return bridgeJSLiftParameter(isSome)
1420+
}
1421+
1422+
@_spi(BridgeJS) public static func bridgeJSLiftReturn() -> [JSValue]? {
1423+
let isSome = _swift_js_pop_i32()
1424+
if isSome == 0 {
1425+
return nil
1426+
}
1427+
return [JSValue].bridgeJSLiftReturn()
1428+
}
1429+
1430+
@_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void {
1431+
switch consume self {
1432+
case .none:
1433+
_swift_js_push_i32(0)
1434+
case .some(let array):
1435+
array.bridgeJSLowerReturn()
1436+
_swift_js_push_i32(1)
1437+
}
1438+
}
1439+
}
1440+
13971441
extension Optional where Wrapped: _BridgedSwiftProtocolWrapper {
13981442
// MARK: ExportSwift
13991443

Tests/BridgeJSRuntimeTests/ExportAPITests.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ func runJsWorks() -> Void
6060
return v
6161
}
6262

63+
@JS func roundTripJSValueArray(v: [JSValue]) -> [JSValue] {
64+
return v
65+
}
66+
67+
@JS func roundTripOptionalJSValueArray(v: [JSValue]?) -> [JSValue]? {
68+
return v
69+
}
70+
6371
@JSClass struct Foo {
6472
@JSGetter var value: String
6573
@JSFunction init(_ value: String) throws(JSException)

Tests/BridgeJSRuntimeTests/Generated/BridgeJS.Macros.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020

2121
@JSFunction func jsRoundTripJSValue(_ v: JSValue) throws(JSException) -> JSValue
2222

23+
@JSFunction func jsRoundTripJSValueArray(_ v: [JSValue]) throws(JSException) -> [JSValue]
24+
25+
@JSFunction func jsRoundTripOptionalJSValueArray(_ v: Optional<[JSValue]>) throws(JSException) -> Optional<[JSValue]>
26+
2327
@JSFunction func jsThrowOrVoid(_ shouldThrow: Bool) throws(JSException) -> Void
2428

2529
@JSFunction func jsThrowOrNumber(_ shouldThrow: Bool) throws(JSException) -> Double

Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3832,6 +3832,37 @@ public func _bjs_roundTripOptionalJSValue(_ vIsSome: Int32, _ vKind: Int32, _ vP
38323832
#endif
38333833
}
38343834

3835+
@_expose(wasm, "bjs_roundTripJSValueArray")
3836+
@_cdecl("bjs_roundTripJSValueArray")
3837+
public func _bjs_roundTripJSValueArray() -> Void {
3838+
#if arch(wasm32)
3839+
let ret = roundTripJSValueArray(v: [JSValue].bridgeJSLiftParameter())
3840+
ret.bridgeJSLowerReturn()
3841+
#else
3842+
fatalError("Only available on WebAssembly")
3843+
#endif
3844+
}
3845+
3846+
@_expose(wasm, "bjs_roundTripOptionalJSValueArray")
3847+
@_cdecl("bjs_roundTripOptionalJSValueArray")
3848+
public func _bjs_roundTripOptionalJSValueArray(_ v: Int32) -> Void {
3849+
#if arch(wasm32)
3850+
let ret = roundTripOptionalJSValueArray(v: {
3851+
if v == 0 {
3852+
return Optional<[JSValue]>.none
3853+
} else {
3854+
return [JSValue].bridgeJSLiftParameter()
3855+
}
3856+
}())
3857+
let __bjs_isSome_ret = ret != nil
3858+
if let __bjs_unwrapped_ret = ret {
3859+
__bjs_unwrapped_ret.bridgeJSLowerReturn()}
3860+
_swift_js_push_i32(__bjs_isSome_ret ? 1 : 0)
3861+
#else
3862+
fatalError("Only available on WebAssembly")
3863+
#endif
3864+
}
3865+
38353866
@_expose(wasm, "bjs_makeImportedFoo")
38363867
@_cdecl("bjs_makeImportedFoo")
38373868
public func _bjs_makeImportedFoo(_ valueBytes: Int32, _ valueLength: Int32) -> Int32 {
@@ -8583,6 +8614,42 @@ func _$jsRoundTripJSValue(_ v: JSValue) throws(JSException) -> JSValue {
85838614
return JSValue.bridgeJSLiftReturn()
85848615
}
85858616

8617+
#if arch(wasm32)
8618+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripJSValueArray")
8619+
fileprivate func bjs_jsRoundTripJSValueArray() -> Void
8620+
#else
8621+
fileprivate func bjs_jsRoundTripJSValueArray() -> Void {
8622+
fatalError("Only available on WebAssembly")
8623+
}
8624+
#endif
8625+
8626+
func _$jsRoundTripJSValueArray(_ v: [JSValue]) throws(JSException) -> [JSValue] {
8627+
let _ = v.bridgeJSLowerParameter()
8628+
bjs_jsRoundTripJSValueArray()
8629+
if let error = _swift_js_take_exception() {
8630+
throw error
8631+
}
8632+
return [JSValue].bridgeJSLiftReturn()
8633+
}
8634+
8635+
#if arch(wasm32)
8636+
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripOptionalJSValueArray")
8637+
fileprivate func bjs_jsRoundTripOptionalJSValueArray(_ v: Int32) -> Void
8638+
#else
8639+
fileprivate func bjs_jsRoundTripOptionalJSValueArray(_ v: Int32) -> Void {
8640+
fatalError("Only available on WebAssembly")
8641+
}
8642+
#endif
8643+
8644+
func _$jsRoundTripOptionalJSValueArray(_ v: Optional<[JSValue]>) throws(JSException) -> Optional<[JSValue]> {
8645+
let vIsSome = v.bridgeJSLowerParameter()
8646+
bjs_jsRoundTripOptionalJSValueArray(vIsSome)
8647+
if let error = _swift_js_take_exception() {
8648+
throw error
8649+
}
8650+
return Optional<[JSValue]>.bridgeJSLiftReturn()
8651+
}
8652+
85868653
#if arch(wasm32)
85878654
@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsThrowOrVoid")
85888655
fileprivate func bjs_jsThrowOrVoid(_ shouldThrow: Int32) -> Void

Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5637,6 +5637,82 @@
56375637
}
56385638
}
56395639
},
5640+
{
5641+
"abiName" : "bjs_roundTripJSValueArray",
5642+
"effects" : {
5643+
"isAsync" : false,
5644+
"isStatic" : false,
5645+
"isThrows" : false
5646+
},
5647+
"name" : "roundTripJSValueArray",
5648+
"parameters" : [
5649+
{
5650+
"label" : "v",
5651+
"name" : "v",
5652+
"type" : {
5653+
"array" : {
5654+
"_0" : {
5655+
"jsValue" : {
5656+
5657+
}
5658+
}
5659+
}
5660+
}
5661+
}
5662+
],
5663+
"returnType" : {
5664+
"array" : {
5665+
"_0" : {
5666+
"jsValue" : {
5667+
5668+
}
5669+
}
5670+
}
5671+
}
5672+
},
5673+
{
5674+
"abiName" : "bjs_roundTripOptionalJSValueArray",
5675+
"effects" : {
5676+
"isAsync" : false,
5677+
"isStatic" : false,
5678+
"isThrows" : false
5679+
},
5680+
"name" : "roundTripOptionalJSValueArray",
5681+
"parameters" : [
5682+
{
5683+
"label" : "v",
5684+
"name" : "v",
5685+
"type" : {
5686+
"nullable" : {
5687+
"_0" : {
5688+
"array" : {
5689+
"_0" : {
5690+
"jsValue" : {
5691+
5692+
}
5693+
}
5694+
}
5695+
},
5696+
"_1" : "null"
5697+
}
5698+
}
5699+
}
5700+
],
5701+
"returnType" : {
5702+
"nullable" : {
5703+
"_0" : {
5704+
"array" : {
5705+
"_0" : {
5706+
"jsValue" : {
5707+
5708+
}
5709+
}
5710+
}
5711+
},
5712+
"_1" : "null"
5713+
}
5714+
}
5715+
},
56405716
{
56415717
"abiName" : "bjs_makeImportedFoo",
56425718
"effects" : {
@@ -12944,6 +13020,68 @@
1294413020
}
1294513021
}
1294613022
},
13023+
{
13024+
"name" : "jsRoundTripJSValueArray",
13025+
"parameters" : [
13026+
{
13027+
"name" : "v",
13028+
"type" : {
13029+
"array" : {
13030+
"_0" : {
13031+
"jsValue" : {
13032+
13033+
}
13034+
}
13035+
}
13036+
}
13037+
}
13038+
],
13039+
"returnType" : {
13040+
"array" : {
13041+
"_0" : {
13042+
"jsValue" : {
13043+
13044+
}
13045+
}
13046+
}
13047+
}
13048+
},
13049+
{
13050+
"name" : "jsRoundTripOptionalJSValueArray",
13051+
"parameters" : [
13052+
{
13053+
"name" : "v",
13054+
"type" : {
13055+
"nullable" : {
13056+
"_0" : {
13057+
"array" : {
13058+
"_0" : {
13059+
"jsValue" : {
13060+
13061+
}
13062+
}
13063+
}
13064+
},
13065+
"_1" : "null"
13066+
}
13067+
}
13068+
}
13069+
],
13070+
"returnType" : {
13071+
"nullable" : {
13072+
"_0" : {
13073+
"array" : {
13074+
"_0" : {
13075+
"jsValue" : {
13076+
13077+
}
13078+
}
13079+
}
13080+
},
13081+
"_1" : "null"
13082+
}
13083+
}
13084+
},
1294713085
{
1294813086
"name" : "jsThrowOrVoid",
1294913087
"parameters" : [

Tests/BridgeJSRuntimeTests/ImportAPITests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,32 @@ class ImportAPITests: XCTestCase {
7373
}
7474
}
7575

76+
func testRoundTripJSValueArray() throws {
77+
let object = JSObject.global
78+
let symbol = JSSymbol("array")
79+
let bigInt = JSBigInt(_slowBridge: Int64(42))
80+
let values: [JSValue] = [
81+
.boolean(false),
82+
.number(123.5),
83+
.string(JSString("hello")),
84+
.object(object),
85+
.null,
86+
.undefined,
87+
.symbol(symbol),
88+
.bigInt(bigInt),
89+
]
90+
let roundTripped = try jsRoundTripJSValueArray(values)
91+
XCTAssertEqual(roundTripped, values)
92+
XCTAssertEqual(try jsRoundTripJSValueArray([]), [])
93+
}
94+
95+
func testRoundTripOptionalJSValueArray() throws {
96+
XCTAssertNil(try jsRoundTripOptionalJSValueArray(nil))
97+
let values: [JSValue] = [.number(1), .undefined, .null]
98+
let result = try jsRoundTripOptionalJSValueArray(values)
99+
XCTAssertEqual(result, values)
100+
}
101+
76102
func testRoundTripFeatureFlag() throws {
77103
for v in [FeatureFlag.foo, .bar] {
78104
try XCTAssertEqual(jsRoundTripFeatureFlag(v), v)

Tests/BridgeJSRuntimeTests/bridge-js.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export function jsRoundTripOptionalNumberNull(v: number | null): number | null
66
export function jsRoundTripOptionalNumberUndefined(v: number | undefined): number | undefined
77
export type JSValue = any
88
export function jsRoundTripJSValue(v: JSValue): JSValue
9+
export function jsRoundTripJSValueArray(v: JSValue[]): JSValue[]
10+
export function jsRoundTripOptionalJSValueArray(v: JSValue[] | null): JSValue[] | null
911
export function jsThrowOrVoid(shouldThrow: boolean): void
1012
export function jsThrowOrNumber(shouldThrow: boolean): number
1113
export function jsThrowOrBool(shouldThrow: boolean): boolean

Tests/prelude.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ export async function setupOptions(options, context) {
7171
"jsRoundTripJSValue": (v) => {
7272
return v;
7373
},
74+
"jsRoundTripJSValueArray": (values) => {
75+
return values;
76+
},
77+
"jsRoundTripOptionalJSValueArray": (values) => {
78+
return values ?? null;
79+
},
7480
"jsRoundTripIntArray": (items) => {
7581
return items;
7682
},
@@ -273,6 +279,10 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) {
273279
assert.deepEqual(arrayStructRoundTrip.optStrings, ["a", "b"]);
274280
assert.equal(exports.arrayMembersSum(arrayStruct, [10, 20]), 30);
275281
assert.equal(exports.arrayMembersFirst(arrayStruct, ["x", "y"]), "x");
282+
const jsValueArray = [true, 42, "ok", { nested: 1 }, null, undefined];
283+
assert.deepEqual(exports.roundTripJSValueArray(jsValueArray), jsValueArray);
284+
assert.deepEqual(exports.roundTripOptionalJSValueArray(jsValueArray), jsValueArray);
285+
assert.equal(exports.roundTripOptionalJSValueArray(null), null);
276286

277287
for (const p of [1, 4, 1024, 65536, 2147483647]) {
278288
assert.equal(exports.roundTripUnsafeRawPointer(p), p);

0 commit comments

Comments
 (0)