Skip to content

Commit 8784a38

Browse files
authored
BridgeJS: Swift Array support (#542)
1 parent 515b6d6 commit 8784a38

File tree

76 files changed

+7293
-14
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+7293
-14
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,43 @@ public class ExportSwift {
130130
case .swiftStruct(let structName):
131131
typeNameForIntrinsic = structName
132132
liftingExpr = ExprSyntax("\(raw: structName).bridgeJSLiftParameter()")
133+
case .array:
134+
typeNameForIntrinsic = param.type.swiftType
135+
liftingExpr = StackCodegen().liftExpression(for: param.type)
133136
case .optional(let wrappedType):
134-
typeNameForIntrinsic = "Optional<\(wrappedType.swiftType)>"
137+
if case .array(let elementType) = wrappedType {
138+
let arrayLift = StackCodegen().liftArrayExpression(elementType: elementType)
139+
let isSomeParam = argumentsToLift[0]
140+
let swiftTypeName = elementType.swiftType
141+
typeNameForIntrinsic = "Optional<[\(swiftTypeName)]>"
142+
liftingExpr = ExprSyntax(
143+
"""
144+
{
145+
if \(raw: isSomeParam) == 0 {
146+
return Optional<[\(raw: swiftTypeName)]>.none
147+
} else {
148+
return \(arrayLift)
149+
}
150+
}()
151+
"""
152+
)
153+
} else if case .swiftProtocol(let protocolName) = wrappedType {
154+
let wrapperName = "Any\(protocolName)"
155+
typeNameForIntrinsic = "Optional<\(wrapperName)>"
156+
liftingExpr = ExprSyntax(
157+
"\(raw: typeNameForIntrinsic).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
158+
)
159+
} else {
160+
typeNameForIntrinsic = "Optional<\(wrappedType.swiftType)>"
161+
liftingExpr = ExprSyntax(
162+
"\(raw: typeNameForIntrinsic).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
163+
)
164+
}
165+
case .swiftProtocol(let protocolName):
166+
let wrapperName = "Any\(protocolName)"
167+
typeNameForIntrinsic = wrapperName
135168
liftingExpr = ExprSyntax(
136-
"\(raw: typeNameForIntrinsic).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
169+
"\(raw: wrapperName).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
137170
)
138171
default:
139172
typeNameForIntrinsic = param.type.swiftType
@@ -246,7 +279,8 @@ public class ExportSwift {
246279
let stackParamIndices = parameters.enumerated().compactMap { index, param -> Int? in
247280
switch param.type {
248281
case .swiftStruct, .optional(.swiftStruct),
249-
.associatedValueEnum, .optional(.associatedValueEnum):
282+
.associatedValueEnum, .optional(.associatedValueEnum),
283+
.array:
250284
return index
251285
default:
252286
return nil
@@ -319,9 +353,15 @@ public class ExportSwift {
319353
return
320354
}
321355

322-
if case .closure(let signature) = returnType {
356+
switch returnType {
357+
case .closure(let signature):
323358
append("return _BJS_Closure_\(raw: signature.mangleName).bridgeJSLower(ret)")
324-
} else {
359+
case .array, .optional(.array):
360+
let stackCodegen = StackCodegen()
361+
for stmt in stackCodegen.lowerStatements(for: returnType, accessor: "ret", varPrefix: "ret") {
362+
append(stmt)
363+
}
364+
default:
325365
append("return ret.bridgeJSLowerReturn()")
326366
}
327367
}
@@ -774,9 +814,10 @@ struct StackCodegen {
774814
return "\(raw: className).bridgeJSLiftParameter(_swift_js_pop_param_pointer())"
775815
case .unsafePointer:
776816
return "\(raw: type.swiftType).bridgeJSLiftParameter(_swift_js_pop_param_pointer())"
777-
case .swiftProtocol:
778-
// Protocols are handled via JSObject
779-
return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())"
817+
case .swiftProtocol(let protocolName):
818+
// Protocols use their Any wrapper type for lifting
819+
let wrapperName = "Any\(protocolName)"
820+
return "\(raw: wrapperName).bridgeJSLiftParameter(_swift_js_pop_param_int32())"
780821
case .caseEnum(let enumName):
781822
return "\(raw: enumName).bridgeJSLiftParameter(_swift_js_pop_param_int32())"
782823
case .rawValueEnum(let enumName, let rawType):
@@ -805,9 +846,28 @@ struct StackCodegen {
805846
return "()"
806847
case .closure:
807848
return "JSObject.bridgeJSLiftParameter(_swift_js_pop_param_int32())"
849+
case .array(let elementType):
850+
return liftArrayExpression(elementType: elementType)
808851
}
809852
}
810853

854+
func liftArrayExpression(elementType: BridgeType) -> ExprSyntax {
855+
let elementLift = liftExpression(for: elementType)
856+
let swiftTypeName = elementType.swiftType
857+
return """
858+
{
859+
let __count = Int(_swift_js_pop_param_array_length())
860+
var __result: [\(raw: swiftTypeName)] = []
861+
__result.reserveCapacity(__count)
862+
for _ in 0..<__count {
863+
__result.append(\(elementLift))
864+
}
865+
__result.reverse()
866+
return __result
867+
}()
868+
"""
869+
}
870+
811871
private func liftOptionalExpression(wrappedType: BridgeType) -> ExprSyntax {
812872
switch wrappedType {
813873
case .string:
@@ -849,6 +909,19 @@ struct StackCodegen {
849909
"Optional<\(raw: enumName)>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())"
850910
case .jsObject:
851911
return "Optional<JSObject>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())"
912+
case .array(let elementType):
913+
let arrayLift = liftArrayExpression(elementType: elementType)
914+
let swiftTypeName = elementType.swiftType
915+
return """
916+
{
917+
let __isSome = _swift_js_pop_param_int32()
918+
if __isSome == 0 {
919+
return Optional<[\(raw: swiftTypeName)]>.none
920+
} else {
921+
return \(arrayLift)
922+
}
923+
}()
924+
"""
852925
default:
853926
// Fallback for other optional types
854927
return "Optional<Int>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())"
@@ -886,8 +959,9 @@ struct StackCodegen {
886959
return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"]
887960
case .unsafePointer:
888961
return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"]
889-
case .swiftProtocol:
890-
return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerParameter())"]
962+
case .swiftProtocol(let protocolName):
963+
let wrapperName = "Any\(protocolName)"
964+
return ["_swift_js_push_int((\(raw: accessor) as! \(raw: wrapperName)).bridgeJSLowerReturn())"]
891965
case .caseEnum:
892966
return ["_swift_js_push_int(Int32(\(raw: accessor).bridgeJSLowerParameter()))"]
893967
case .rawValueEnum(_, let rawType):
@@ -916,9 +990,34 @@ struct StackCodegen {
916990
return []
917991
case .closure:
918992
return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"]
993+
case .array(let elementType):
994+
return lowerArrayStatements(elementType: elementType, accessor: accessor, varPrefix: varPrefix)
919995
}
920996
}
921997

998+
private func lowerArrayStatements(
999+
elementType: BridgeType,
1000+
accessor: String,
1001+
varPrefix: String
1002+
) -> [CodeBlockItemSyntax] {
1003+
var statements: [CodeBlockItemSyntax] = []
1004+
let elementVarName = "__bjs_elem_\(varPrefix)"
1005+
statements.append("for \(raw: elementVarName) in \(raw: accessor) {")
1006+
1007+
let elementStatements = lowerStatements(
1008+
for: elementType,
1009+
accessor: elementVarName,
1010+
varPrefix: "\(varPrefix)_elem"
1011+
)
1012+
for stmt in elementStatements {
1013+
statements.append(stmt)
1014+
}
1015+
1016+
statements.append("}")
1017+
statements.append("_swift_js_push_array_length(Int32(\(raw: accessor).count))")
1018+
return statements
1019+
}
1020+
9221021
private func lowerOptionalStatements(
9231022
wrappedType: BridgeType,
9241023
accessor: String,
@@ -985,6 +1084,8 @@ struct StackCodegen {
9851084
return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"]
9861085
case .jsObject:
9871086
return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerReturn())"]
1087+
case .array(let elementType):
1088+
return lowerArrayStatements(elementType: elementType, accessor: unwrappedVar, varPrefix: varPrefix)
9881089
default:
9891090
return ["preconditionFailure(\"BridgeJS: unsupported optional wrapped type\")"]
9901091
}
@@ -1552,6 +1653,7 @@ extension BridgeType {
15521653
case .swiftProtocol(let name): return "Any\(name)"
15531654
case .void: return "Void"
15541655
case .optional(let wrappedType): return "Optional<\(wrappedType.swiftType)>"
1656+
case .array(let elementType): return "[\(elementType.swiftType)]"
15551657
case .caseEnum(let name): return name
15561658
case .rawValueEnum(let name, _): return name
15571659
case .associatedValueEnum(let name): return name
@@ -1609,6 +1711,8 @@ extension BridgeType {
16091711
throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters")
16101712
case .closure:
16111713
return LiftingIntrinsicInfo(parameters: [("callbackId", .i32)])
1714+
case .array:
1715+
return LiftingIntrinsicInfo(parameters: [])
16121716
}
16131717
}
16141718

@@ -1629,6 +1733,7 @@ extension BridgeType {
16291733
static let associatedValueEnum = LoweringIntrinsicInfo(returnType: nil)
16301734
static let swiftStruct = LoweringIntrinsicInfo(returnType: nil)
16311735
static let optional = LoweringIntrinsicInfo(returnType: nil)
1736+
static let array = LoweringIntrinsicInfo(returnType: nil)
16321737
}
16331738

16341739
func loweringReturnInfo() throws -> LoweringIntrinsicInfo {
@@ -1655,6 +1760,8 @@ extension BridgeType {
16551760
throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters")
16561761
case .closure:
16571762
return .swiftHeapObject
1763+
case .array:
1764+
return .array
16581765
}
16591766
}
16601767
}

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,13 @@ extension BridgeType {
933933
params.append(contentsOf: wrappedInfo.loweredParameters)
934934
return LoweringParameterInfo(loweredParameters: params)
935935
}
936+
case .array:
937+
switch context {
938+
case .importTS:
939+
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
940+
case .exportSwift:
941+
return LoweringParameterInfo(loweredParameters: [])
942+
}
936943
}
937944
}
938945

@@ -1018,6 +1025,13 @@ extension BridgeType {
10181025
let wrappedInfo = try wrappedType.liftingReturnInfo(context: context)
10191026
return LiftingReturnInfo(valueToLift: wrappedInfo.valueToLift)
10201027
}
1028+
case .array:
1029+
switch context {
1030+
case .importTS:
1031+
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
1032+
case .exportSwift:
1033+
return LiftingReturnInfo(valueToLift: nil)
1034+
}
10211035
}
10221036
}
10231037
}

Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,36 @@ public final class SwiftToSkeleton {
157157
return .optional(wrappedType)
158158
}
159159
}
160+
// [T]
161+
if let arrayType = type.as(ArrayTypeSyntax.self) {
162+
if let elementType = lookupType(for: arrayType.element, errors: &errors) {
163+
return .array(elementType)
164+
}
165+
}
166+
// Array<T>
167+
if let identifierType = type.as(IdentifierTypeSyntax.self),
168+
identifierType.name.text == "Array",
169+
let genericArgs = identifierType.genericArgumentClause?.arguments,
170+
genericArgs.count == 1,
171+
let argType = TypeSyntax(genericArgs.first?.argument)
172+
{
173+
if let elementType = lookupType(for: argType, errors: &errors) {
174+
return .array(elementType)
175+
}
176+
}
177+
// Swift.Array<T>
178+
if let memberType = type.as(MemberTypeSyntax.self),
179+
let baseType = memberType.baseType.as(IdentifierTypeSyntax.self),
180+
baseType.name.text == "Swift",
181+
memberType.name.text == "Array",
182+
let genericArgs = memberType.genericArgumentClause?.arguments,
183+
genericArgs.count == 1,
184+
let argType = TypeSyntax(genericArgs.first?.argument)
185+
{
186+
if let elementType = lookupType(for: argType, errors: &errors) {
187+
return .array(elementType)
188+
}
189+
}
160190
if let aliasDecl = typeDeclResolver.resolveTypeAlias(type) {
161191
if let resolvedType = lookupType(for: aliasDecl.initializer.value, errors: &errors) {
162192
return resolvedType
@@ -479,7 +509,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
479509
let expression = initClause.value
480510

481511
// Function calls are checked later in extractDefaultValue (as constructors are allowed)
482-
if expression.is(ArrayExprSyntax.self) { return false }
512+
// Array literals are allowed but checked in extractArrayDefaultValue
483513
if expression.is(DictionaryExprSyntax.self) { return false }
484514
if expression.is(BinaryOperatorExprSyntax.self) { return false }
485515
if expression.is(ClosureExprSyntax.self) { return false }
@@ -575,6 +605,10 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
575605
return extractConstructorDefaultValue(from: funcCall, type: type)
576606
}
577607

608+
if let arrayExpr = expr.as(ArrayExprSyntax.self) {
609+
return extractArrayDefaultValue(from: arrayExpr, type: type)
610+
}
611+
578612
if let literalValue = extractLiteralValue(from: expr, type: type) {
579613
return literalValue
580614
}
@@ -735,6 +769,43 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {
735769
return nil
736770
}
737771

772+
/// Extracts default value from an array literal expression
773+
private func extractArrayDefaultValue(
774+
from arrayExpr: ArrayExprSyntax,
775+
type: BridgeType
776+
) -> DefaultValue? {
777+
// Verify the type is an array type
778+
let elementType: BridgeType?
779+
switch type {
780+
case .array(let element):
781+
elementType = element
782+
case .optional(.array(let element)):
783+
elementType = element
784+
default:
785+
diagnose(
786+
node: arrayExpr,
787+
message: "Array literal is only valid for array parameters",
788+
hint: "Parameter type should be an array like [Int] or [String]"
789+
)
790+
return nil
791+
}
792+
793+
var elements: [DefaultValue] = []
794+
for element in arrayExpr.elements {
795+
guard let elementValue = extractLiteralValue(from: element.expression, type: elementType) else {
796+
diagnose(
797+
node: element.expression,
798+
message: "Array element must be a literal value",
799+
hint: "Use simple literals like \"text\", 42, true, false in array elements"
800+
)
801+
return nil
802+
}
803+
elements.append(elementValue)
804+
}
805+
806+
return .array(elements)
807+
}
808+
738809
/// Shared parameter parsing logic used by functions, initializers, and protocol methods
739810
private func parseParameters(
740811
from parameterClause: FunctionParameterClauseSyntax,

0 commit comments

Comments
 (0)