Skip to content

Commit 16a3894

Browse files
committed
BridgeJS: Array import
1 parent 16e3f44 commit 16a3894

File tree

19 files changed

+710
-83
lines changed

19 files changed

+710
-83
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ public struct ImportTS {
8383
var abiReturnType: WasmCoreType?
8484
// Track destructured variable names for multiple lowered parameters
8585
var destructuredVarNames: [String] = []
86+
// Stack-lowered parameters should be evaluated in reverse order to match LIFO stacks
87+
var stackLoweringStmts: [CodeBlockItemSyntax] = []
8688

8789
init(moduleName: String, abiName: String, context: BridgeContext = .importTS) {
8890
self.moduleName = moduleName
@@ -93,11 +95,6 @@ public struct ImportTS {
9395
func lowerParameter(param: Parameter) throws {
9496
let loweringInfo = try param.type.loweringParameterInfo(context: context)
9597

96-
// Generate destructured variable names for all lowered parameters
97-
let destructuredNames = loweringInfo.loweredParameters.map {
98-
"\(param.name)\($0.name.capitalizedFirstLetter)"
99-
}
100-
10198
let initializerExpr: ExprSyntax
10299
switch param.type {
103100
case .closure(let signature):
@@ -108,6 +105,33 @@ public struct ImportTS {
108105
initializerExpr = ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()")
109106
}
110107

108+
if loweringInfo.loweredParameters.isEmpty {
109+
let stmt = CodeBlockItemSyntax(
110+
item: .decl(
111+
DeclSyntax(
112+
VariableDeclSyntax(
113+
bindingSpecifier: .keyword(.let),
114+
bindings: PatternBindingListSyntax {
115+
PatternBindingSyntax(
116+
pattern: PatternSyntax(
117+
IdentifierPatternSyntax(identifier: .wildcardToken())
118+
),
119+
initializer: InitializerClauseSyntax(value: initializerExpr)
120+
)
121+
}
122+
)
123+
)
124+
)
125+
)
126+
stackLoweringStmts.insert(stmt, at: 0)
127+
return
128+
}
129+
130+
// Generate destructured variable names for all lowered parameters
131+
let destructuredNames = loweringInfo.loweredParameters.map {
132+
"\(param.name)\($0.name.capitalizedFirstLetter)"
133+
}
134+
111135
// Always add destructuring statement to body (unified for single and multiple)
112136
let pattern: PatternSyntax
113137
if destructuredNames.count == 1 {
@@ -166,7 +190,9 @@ public struct ImportTS {
166190
}
167191

168192
func call(returnType: BridgeType) throws {
169-
// Build function call expression
193+
let liftingInfo = try returnType.liftingReturnInfo(context: context)
194+
body.append(contentsOf: stackLoweringStmts)
195+
170196
let callExpr = FunctionCallExprSyntax(
171197
calledExpression: ExprSyntax("\(raw: abiName)"),
172198
leftParen: .leftParenToken(),
@@ -178,19 +204,15 @@ public struct ImportTS {
178204
rightParen: .rightParenToken()
179205
)
180206

181-
let needsRetBinding: Bool
182-
let liftingInfo = try returnType.liftingReturnInfo(context: context)
183-
if liftingInfo.valueToLift == nil || returnType.usesSideChannelForOptionalReturn() {
184-
// Void and side-channel returns don't need "let ret ="
185-
needsRetBinding = false
207+
if returnType == .void {
208+
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
209+
} else if returnType.usesSideChannelForOptionalReturn() {
210+
// Side channel returns don't need "let ret ="
211+
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
212+
} else if liftingInfo.valueToLift == nil {
213+
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
186214
} else {
187-
needsRetBinding = true
188-
}
189-
190-
if needsRetBinding {
191215
body.append("let ret = \(raw: callExpr)")
192-
} else {
193-
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
194216
}
195217

196218
// Add exception check for ImportTS context
@@ -934,13 +956,8 @@ extension BridgeType {
934956
return LoweringParameterInfo(loweredParameters: [("value", .i32)])
935957
}
936958
case .rawValueEnum(_, let rawType):
937-
switch context {
938-
case .importTS:
939-
return LoweringParameterInfo(loweredParameters: [("value", rawType.wasmCoreType ?? .i32)])
940-
case .exportSwift:
941-
// For protocol export we return .i32 for String raw value type instead of nil
942-
return LoweringParameterInfo(loweredParameters: [("value", rawType.wasmCoreType ?? .i32)])
943-
}
959+
let wasmType = rawType.wasmCoreType ?? .i32
960+
return LoweringParameterInfo(loweredParameters: [("value", wasmType)])
944961
case .associatedValueEnum:
945962
switch context {
946963
case .importTS:
@@ -964,12 +981,7 @@ extension BridgeType {
964981
params.append(contentsOf: wrappedInfo.loweredParameters)
965982
return LoweringParameterInfo(loweredParameters: params)
966983
case .array:
967-
switch context {
968-
case .importTS:
969-
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
970-
case .exportSwift:
971-
return LoweringParameterInfo(loweredParameters: [])
972-
}
984+
return LoweringParameterInfo(loweredParameters: [])
973985
}
974986
}
975987

@@ -1025,13 +1037,8 @@ extension BridgeType {
10251037
return LiftingReturnInfo(valueToLift: .i32)
10261038
}
10271039
case .rawValueEnum(_, let rawType):
1028-
switch context {
1029-
case .importTS:
1030-
return LiftingReturnInfo(valueToLift: rawType.wasmCoreType ?? .i32)
1031-
case .exportSwift:
1032-
// For protocol export we return .i32 for String raw value type instead of nil
1033-
return LiftingReturnInfo(valueToLift: rawType.wasmCoreType ?? .i32)
1034-
}
1040+
let wasmType = rawType.wasmCoreType ?? .i32
1041+
return LiftingReturnInfo(valueToLift: wasmType)
10351042
case .associatedValueEnum:
10361043
switch context {
10371044
case .importTS:
@@ -1053,12 +1060,7 @@ extension BridgeType {
10531060
let wrappedInfo = try wrappedType.liftingReturnInfo(context: context)
10541061
return LiftingReturnInfo(valueToLift: wrappedInfo.valueToLift)
10551062
case .array:
1056-
switch context {
1057-
case .importTS:
1058-
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
1059-
case .exportSwift:
1060-
return LiftingReturnInfo(valueToLift: nil)
1061-
}
1063+
return LiftingReturnInfo(valueToLift: nil)
10621064
}
10631065
}
10641066
}

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,12 +2201,10 @@ extension BridgeJSLink {
22012201

22022202
func liftParameter(param: Parameter) throws {
22032203
let liftingFragment = try IntrinsicJSFragment.liftParameter(type: param.type, context: context)
2204-
assert(
2205-
liftingFragment.parameters.count >= 1,
2206-
"Lifting fragment should have at least one parameter to lift"
2207-
)
22082204
let valuesToLift: [String]
2209-
if liftingFragment.parameters.count == 1 {
2205+
if liftingFragment.parameters.count == 0 {
2206+
valuesToLift = []
2207+
} else if liftingFragment.parameters.count == 1 {
22102208
parameterNames.append(param.name)
22112209
valuesToLift = [scope.variable(param.name)]
22122210
} else {

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,14 +1894,7 @@ struct IntrinsicJSFragment: Sendable {
18941894
"Namespace enums are not supported to be passed as parameters to imported JS functions: \(string)"
18951895
)
18961896
case .array(let elementType):
1897-
switch context {
1898-
case .importTS:
1899-
throw BridgeJSLinkError(
1900-
message: "Arrays are not yet supported to be passed as parameters to imported JS functions"
1901-
)
1902-
case .exportSwift:
1903-
return try arrayLift(elementType: elementType)
1904-
}
1897+
return try arrayLift(elementType: elementType)
19051898
}
19061899
}
19071900

@@ -1970,14 +1963,7 @@ struct IntrinsicJSFragment: Sendable {
19701963
message: "Namespace enums are not supported to be returned from imported JS functions: \(string)"
19711964
)
19721965
case .array(let elementType):
1973-
switch context {
1974-
case .importTS:
1975-
throw BridgeJSLinkError(
1976-
message: "Arrays are not yet supported to be returned from imported JS functions"
1977-
)
1978-
case .exportSwift:
1979-
return try arrayLower(elementType: elementType)
1980-
}
1966+
return try arrayLower(elementType: elementType)
19811967
}
19821968
}
19831969

@@ -3094,11 +3080,10 @@ struct IntrinsicJSFragment: Sendable {
30943080
// Lift return value if needed
30953081
if method.returnType != .void {
30963082
let liftFragment = try! IntrinsicJSFragment.liftReturn(type: method.returnType)
3097-
if !liftFragment.parameters.isEmpty {
3098-
let lifted = liftFragment.printCode(["ret"], methodScope, printer, methodCleanup)
3099-
if let liftedValue = lifted.first {
3100-
printer.write("return \(liftedValue);")
3101-
}
3083+
let liftArgs = liftFragment.parameters.isEmpty ? [] : ["ret"]
3084+
let lifted = liftFragment.printCode(liftArgs, methodScope, printer, methodCleanup)
3085+
if let liftedValue = lifted.first {
3086+
printer.write("return \(liftedValue);")
31023087
}
31033088
}
31043089
}
@@ -3430,7 +3415,6 @@ struct IntrinsicJSFragment: Sendable {
34303415
printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);")
34313416
cleanup.write("if (\(enumCleanupVar)) { \(enumCleanupVar)(); }")
34323417
default:
3433-
// For other types (nested structs, etc.), original logic applies
34343418
let wrappedFragment = structFieldLowerFragment(
34353419
field: ExportedProperty(
34363420
name: field.name,
@@ -3440,9 +3424,36 @@ struct IntrinsicJSFragment: Sendable {
34403424
),
34413425
allStructs: allStructs
34423426
)
3427+
let guardedPrinter = CodeFragmentPrinter()
3428+
let guardedCleanup = CodeFragmentPrinter()
3429+
_ = wrappedFragment.printCode([value], scope, guardedPrinter, guardedCleanup)
3430+
var loweredLines = guardedPrinter.lines
3431+
var hoistedCleanupVar: String?
3432+
if let first = loweredLines.first {
3433+
let trimmed = first.trimmingCharacters(in: .whitespaces)
3434+
if trimmed.hasPrefix("const "),
3435+
let namePart = trimmed.split(separator: " ").dropFirst().first,
3436+
trimmed.contains("= []")
3437+
{
3438+
hoistedCleanupVar = String(namePart)
3439+
loweredLines[0] = "\(hoistedCleanupVar!) = [];"
3440+
}
3441+
}
3442+
if let hoistedName = hoistedCleanupVar {
3443+
printer.write("let \(hoistedName);")
3444+
}
34433445
printer.write("if (\(isSomeVar)) {")
34443446
printer.indent {
3445-
_ = wrappedFragment.printCode([value], scope, printer, cleanup)
3447+
for line in loweredLines {
3448+
printer.write(line)
3449+
}
3450+
if !guardedCleanup.lines.isEmpty {
3451+
cleanup.write("if (\(isSomeVar)) {")
3452+
cleanup.indent {
3453+
cleanup.write(contentsOf: guardedCleanup)
3454+
}
3455+
cleanup.write("}")
3456+
}
34463457
}
34473458
printer.write("}")
34483459
printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);")

Plugins/BridgeJS/Sources/TS2Swift/JavaScript/src/processor.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,15 @@ export class TypeProcessor {
663663
* @private
664664
*/
665665
visitType(type, node) {
666+
if (this.checker.isArrayType(type)) {
667+
const typeArgs = this.checker.getTypeArguments(type);
668+
if (typeArgs && typeArgs.length > 0) {
669+
const elementType = this.visitType(typeArgs[0], node);
670+
return `[${elementType}]`;
671+
}
672+
return "[JSObject]";
673+
}
674+
666675
// Treat A<B> and A<C> as the same type
667676
if (isTypeReference(type)) {
668677
type = type.target;
@@ -727,7 +736,7 @@ export class TypeProcessor {
727736
return this.renderTypeIdentifier(typeName);
728737
}
729738

730-
if (this.checker.isArrayType(type) || this.checker.isTupleType(type) || type.getCallSignatures().length > 0) {
739+
if (this.checker.isTupleType(type) || type.getCallSignatures().length > 0) {
731740
return "JSObject";
732741
}
733742
// "a" | "b" -> string

Plugins/BridgeJS/Sources/TS2Swift/JavaScript/test/__snapshots__/ts2swift.test.js.snap

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ exports[`ts2swift > snapshots Swift output for ArrayParameter.d.ts > ArrayParame
99
1010
@_spi(Experimental) @_spi(BridgeJS) import JavaScriptKit
1111
12-
@JSFunction func checkArray(_ a: JSObject) throws(JSException) -> Void
12+
@JSFunction func processNumbers(_ values: [Double]) throws(JSException) -> Void
1313
14-
@JSFunction func checkArrayWithLength(_ a: JSObject, _ b: Double) throws(JSException) -> Void
14+
@JSFunction func getNumbers() throws(JSException) -> [Double]
1515
16-
@JSFunction func checkArray(_ a: JSObject) throws(JSException) -> Void
16+
@JSFunction func transformNumbers(_ values: [Double]) throws(JSException) -> [Double]
17+
18+
@JSFunction func processStrings(_ values: [String]) throws(JSException) -> [String]
19+
20+
@JSFunction func processBooleans(_ values: [Bool]) throws(JSException) -> [Bool]
21+
22+
@JSFunction func processArraySyntax(_ values: [Double]) throws(JSException) -> [Double]
1723
"
1824
`;
1925

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1-
export function checkArray(a: number[]): void;
2-
export function checkArrayWithLength(a: number[], b: number): void;
3-
export function checkArray(a: Array<number>): void;
1+
// Array as parameter
2+
export function processNumbers(values: number[]): void;
3+
4+
// Array as return value
5+
export function getNumbers(): number[];
6+
7+
// Array as both parameter and return value
8+
export function transformNumbers(values: number[]): number[];
9+
10+
// String arrays
11+
export function processStrings(values: string[]): string[];
12+
13+
// Boolean arrays
14+
export function processBooleans(values: boolean[]): boolean[];
15+
16+
// Using Array<T> syntax
17+
export function processArraySyntax(values: Array<number>): Array<number>;

Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ArrayTypes.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,9 @@
6161

6262
@JSFunction func checkArray(_ a: JSObject) throws(JSException) -> Void
6363
@JSFunction func checkArrayWithLength(_ a: JSObject, _ b: Double) throws(JSException) -> Void
64+
65+
@JSFunction func importProcessNumbers(_ values: [Double]) throws(JSException) -> Void
66+
@JSFunction func importGetNumbers() throws(JSException) -> [Double]
67+
@JSFunction func importTransformNumbers(_ values: [Double]) throws(JSException) -> [Double]
68+
@JSFunction func importProcessStrings(_ values: [String]) throws(JSException) -> [String]
69+
@JSFunction func importProcessBooleans(_ values: [Bool]) throws(JSException) -> [Bool]

0 commit comments

Comments
 (0)