Skip to content

Commit 529291a

Browse files
committed
BridgeJS: Array import
1 parent e55d927 commit 529291a

20 files changed

Lines changed: 707 additions & 72 deletions

File tree

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public struct ImportTS {
8383
var abiReturnType: WasmCoreType?
8484
// Track destructured variable names for multiple lowered parameters
8585
var destructuredVarNames: [String] = []
86+
var stackLoweringStmts: [CodeBlockItemSyntax] = []
8687

8788
init(moduleName: String, abiName: String, context: BridgeContext = .importTS) {
8889
self.moduleName = moduleName
@@ -93,11 +94,6 @@ public struct ImportTS {
9394
func lowerParameter(param: Parameter) throws {
9495
let loweringInfo = try param.type.loweringParameterInfo(context: context)
9596

96-
// Generate destructured variable names for all lowered parameters
97-
let destructuredNames = loweringInfo.loweredParameters.map {
98-
"\(param.name)\($0.name.capitalizedFirstLetter)"
99-
}
100-
10197
let initializerExpr: ExprSyntax
10298
switch param.type {
10399
case .closure(let signature):
@@ -108,6 +104,33 @@ public struct ImportTS {
108104
initializerExpr = ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()")
109105
}
110106

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

168191
func call(returnType: BridgeType) throws {
169-
// Build function call expression
192+
let liftingInfo = try returnType.liftingReturnInfo(context: context)
193+
body.append(contentsOf: stackLoweringStmts)
194+
170195
let callExpr = FunctionCallExprSyntax(
171196
calledExpression: ExprSyntax("\(raw: abiName)"),
172197
leftParen: .leftParenToken(),
@@ -183,6 +208,8 @@ public struct ImportTS {
183208
} else if returnType.usesSideChannelForOptionalReturn() {
184209
// Side channel returns don't need "let ret ="
185210
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
211+
} else if liftingInfo.valueToLift == nil {
212+
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
186213
} else {
187214
body.append("let ret = \(raw: callExpr)")
188215
}
@@ -922,13 +949,8 @@ extension BridgeType {
922949
return LoweringParameterInfo(loweredParameters: [("value", .i32)])
923950
}
924951
case .rawValueEnum(_, let rawType):
925-
switch context {
926-
case .importTS:
927-
return LoweringParameterInfo(loweredParameters: [("value", rawType.wasmCoreType ?? .i32)])
928-
case .exportSwift:
929-
// For protocol export we return .i32 for String raw value type instead of nil
930-
return LoweringParameterInfo(loweredParameters: [("value", rawType.wasmCoreType ?? .i32)])
931-
}
952+
let wasmType = rawType.wasmCoreType ?? .i32
953+
return LoweringParameterInfo(loweredParameters: [("value", wasmType)])
932954
case .associatedValueEnum:
933955
switch context {
934956
case .importTS:
@@ -952,12 +974,7 @@ extension BridgeType {
952974
params.append(contentsOf: wrappedInfo.loweredParameters)
953975
return LoweringParameterInfo(loweredParameters: params)
954976
case .array:
955-
switch context {
956-
case .importTS:
957-
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
958-
case .exportSwift:
959-
return LoweringParameterInfo(loweredParameters: [])
960-
}
977+
return LoweringParameterInfo(loweredParameters: [])
961978
}
962979
}
963980

@@ -1011,13 +1028,8 @@ extension BridgeType {
10111028
return LiftingReturnInfo(valueToLift: .i32)
10121029
}
10131030
case .rawValueEnum(_, let rawType):
1014-
switch context {
1015-
case .importTS:
1016-
return LiftingReturnInfo(valueToLift: rawType.wasmCoreType ?? .i32)
1017-
case .exportSwift:
1018-
// For protocol export we return .i32 for String raw value type instead of nil
1019-
return LiftingReturnInfo(valueToLift: rawType.wasmCoreType ?? .i32)
1020-
}
1031+
let wasmType = rawType.wasmCoreType ?? .i32
1032+
return LiftingReturnInfo(valueToLift: wasmType)
10211033
case .associatedValueEnum:
10221034
switch context {
10231035
case .importTS:
@@ -1039,12 +1051,7 @@ extension BridgeType {
10391051
let wrappedInfo = try wrappedType.liftingReturnInfo(context: context)
10401052
return LiftingReturnInfo(valueToLift: wrappedInfo.valueToLift)
10411053
case .array:
1042-
switch context {
1043-
case .importTS:
1044-
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
1045-
case .exportSwift:
1046-
return LiftingReturnInfo(valueToLift: nil)
1047-
}
1054+
return LiftingReturnInfo(valueToLift: nil)
10481055
}
10491056
}
10501057
}

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

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

21482148
func liftParameter(param: Parameter) throws {
21492149
let liftingFragment = try IntrinsicJSFragment.liftParameter(type: param.type, context: context)
2150-
assert(
2151-
liftingFragment.parameters.count >= 1,
2152-
"Lifting fragment should have at least one parameter to lift"
2153-
)
21542150
let valuesToLift: [String]
2155-
if liftingFragment.parameters.count == 1 {
2151+
if liftingFragment.parameters.count == 0 {
2152+
valuesToLift = []
2153+
} else if liftingFragment.parameters.count == 1 {
21562154
parameterNames.append(param.name)
21572155
valuesToLift = [scope.variable(param.name)]
21582156
} else {

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,14 +1565,7 @@ struct IntrinsicJSFragment: Sendable {
15651565
"Namespace enums are not supported to be passed as parameters to imported JS functions: \(string)"
15661566
)
15671567
case .array(let elementType):
1568-
switch context {
1569-
case .importTS:
1570-
throw BridgeJSLinkError(
1571-
message: "Arrays are not yet supported to be passed as parameters to imported JS functions"
1572-
)
1573-
case .exportSwift:
1574-
return try arrayLift(elementType: elementType)
1575-
}
1568+
return try arrayLift(elementType: elementType)
15761569
}
15771570
}
15781571

@@ -1640,14 +1633,7 @@ struct IntrinsicJSFragment: Sendable {
16401633
message: "Namespace enums are not supported to be returned from imported JS functions: \(string)"
16411634
)
16421635
case .array(let elementType):
1643-
switch context {
1644-
case .importTS:
1645-
throw BridgeJSLinkError(
1646-
message: "Arrays are not yet supported to be returned from imported JS functions"
1647-
)
1648-
case .exportSwift:
1649-
return try arrayLower(elementType: elementType)
1650-
}
1636+
return try arrayLower(elementType: elementType)
16511637
}
16521638
}
16531639

@@ -2756,11 +2742,10 @@ struct IntrinsicJSFragment: Sendable {
27562742
// Lift return value if needed
27572743
if method.returnType != .void {
27582744
let liftFragment = try! IntrinsicJSFragment.liftReturn(type: method.returnType)
2759-
if !liftFragment.parameters.isEmpty {
2760-
let lifted = liftFragment.printCode(["ret"], methodScope, printer, methodCleanup)
2761-
if let liftedValue = lifted.first {
2762-
printer.write("return \(liftedValue);")
2763-
}
2745+
let liftArgs = liftFragment.parameters.isEmpty ? [] : ["ret"]
2746+
let lifted = liftFragment.printCode(liftArgs, methodScope, printer, methodCleanup)
2747+
if let liftedValue = lifted.first {
2748+
printer.write("return \(liftedValue);")
27642749
}
27652750
}
27662751
}
@@ -3090,7 +3075,6 @@ struct IntrinsicJSFragment: Sendable {
30903075
printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);")
30913076
cleanup.write("if (\(enumCleanupVar)) { \(enumCleanupVar)(); }")
30923077
default:
3093-
// For other types (nested structs, etc.), original logic applies
30943078
let wrappedFragment = structFieldLowerFragment(
30953079
field: ExportedProperty(
30963080
name: field.name,
@@ -3100,9 +3084,36 @@ struct IntrinsicJSFragment: Sendable {
31003084
),
31013085
allStructs: allStructs
31023086
)
3087+
let guardedPrinter = CodeFragmentPrinter()
3088+
let guardedCleanup = CodeFragmentPrinter()
3089+
_ = wrappedFragment.printCode([value], scope, guardedPrinter, guardedCleanup)
3090+
var loweredLines = guardedPrinter.lines
3091+
var hoistedCleanupVar: String?
3092+
if let first = loweredLines.first {
3093+
let trimmed = first.trimmingCharacters(in: .whitespaces)
3094+
if trimmed.hasPrefix("const "),
3095+
let namePart = trimmed.split(separator: " ").dropFirst().first,
3096+
trimmed.contains("= []")
3097+
{
3098+
hoistedCleanupVar = String(namePart)
3099+
loweredLines[0] = "\(hoistedCleanupVar!) = [];"
3100+
}
3101+
}
3102+
if let hoistedName = hoistedCleanupVar {
3103+
printer.write("let \(hoistedName);")
3104+
}
31033105
printer.write("if (\(isSomeVar)) {")
31043106
printer.indent {
3105-
_ = wrappedFragment.printCode([value], scope, printer, cleanup)
3107+
for line in loweredLines {
3108+
printer.write(line)
3109+
}
3110+
if !guardedCleanup.lines.isEmpty {
3111+
cleanup.write("if (\(isSomeVar)) {")
3112+
cleanup.indent {
3113+
cleanup.write(contentsOf: guardedCleanup)
3114+
}
3115+
cleanup.write("}")
3116+
}
31063117
}
31073118
printer.write("}")
31083119
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;
@@ -728,7 +737,7 @@ export class TypeProcessor {
728737
return this.renderTypeIdentifier(typeName);
729738
}
730739

731-
if (this.checker.isArrayType(type) || this.checker.isTupleType(type) || type.getCallSignatures().length > 0) {
740+
if (this.checker.isTupleType(type) || type.getCallSignatures().length > 0) {
732741
return "JSObject";
733742
}
734743
// "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)