Skip to content

Commit 55ee129

Browse files
committed
BridgeJS: WIP: Swift Optional support Swift -> TS
1 parent 250af4d commit 55ee129

File tree

52 files changed

+5311
-57
lines changed

Some content is hidden

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

52 files changed

+5311
-57
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 148 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ public class ExportSwift {
142142
hint: "Only primitive types and types defined in the same module are allowed"
143143
)
144144
}
145+
146+
private func diagnoseNestedOptional(node: some SyntaxProtocol, type: String) {
147+
diagnose(
148+
node: node,
149+
message: "Nested optional types are not supported: \(type)",
150+
hint: "Use a single optional like String? instead of String?? or Optional<Optional<T>>"
151+
)
152+
}
145153

146154
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
147155
switch state {
@@ -183,17 +191,32 @@ public class ExportSwift {
183191

184192
var parameters: [Parameter] = []
185193
for param in node.signature.parameterClause.parameters {
186-
guard let type = self.parent.lookupType(for: param.type) else {
194+
let resolvedType = self.parent.lookupType(for: param.type)
195+
196+
if let type = resolvedType, case .optional(let wrappedType) = type, wrappedType.isOptional {
197+
diagnoseNestedOptional(node: param.type, type: param.type.trimmedDescription)
198+
continue
199+
}
200+
201+
guard let type = resolvedType else {
187202
diagnoseUnsupportedType(node: param.type, type: param.type.trimmedDescription)
188203
continue
189204
}
205+
190206
let name = param.secondName?.text ?? param.firstName.text
191207
let label = param.firstName.text
192208
parameters.append(Parameter(label: label, name: name, type: type))
193209
}
194210
let returnType: BridgeType
195211
if let returnClause = node.signature.returnClause {
196-
guard let type = self.parent.lookupType(for: returnClause.type) else {
212+
let resolvedType = self.parent.lookupType(for: returnClause.type)
213+
214+
if let type = resolvedType, case .optional(let wrappedType) = type, wrappedType.isOptional {
215+
diagnoseNestedOptional(node: returnClause.type, type: returnClause.type.trimmedDescription)
216+
return nil
217+
}
218+
219+
guard let type = resolvedType else {
197220
diagnoseUnsupportedType(node: returnClause.type, type: returnClause.type.trimmedDescription)
198221
return nil
199222
}
@@ -712,10 +735,53 @@ public class ExportSwift {
712735
}
713736

714737
func lookupType(for type: TypeSyntax) -> BridgeType? {
715-
if let primitive = BridgeType(swiftType: type.trimmedDescription) {
716-
return primitive
738+
// 1. Handle T? syntax (OptionalTypeSyntax)
739+
if let optionalType = type.as(OptionalTypeSyntax.self) {
740+
let wrappedType = optionalType.wrappedType
741+
if let baseType = lookupType(for: wrappedType) {
742+
return .optional(baseType)
743+
}
744+
}
745+
746+
// 2. Handle Optional<T> syntax (IdentifierTypeSyntax with generic arguments)
747+
if let identifierType = type.as(IdentifierTypeSyntax.self),
748+
identifierType.name.text == "Optional",
749+
let genericArgs = identifierType.genericArgumentClause?.arguments,
750+
genericArgs.count == 1,
751+
let argType = genericArgs.first?.argument {
752+
if let baseType = lookupType(for: argType) {
753+
return .optional(baseType)
754+
}
755+
}
756+
757+
// 3. Handle Swift.Optional<T> syntax (MemberTypeSyntax)
758+
if let memberType = type.as(MemberTypeSyntax.self),
759+
let baseType = memberType.baseType.as(IdentifierTypeSyntax.self),
760+
baseType.name.text == "Swift",
761+
memberType.name.text == "Optional",
762+
let genericArgs = memberType.genericArgumentClause?.arguments,
763+
genericArgs.count == 1,
764+
let argType = genericArgs.first?.argument {
765+
if let wrappedType = lookupType(for: argType) {
766+
return .optional(wrappedType)
767+
}
768+
}
769+
770+
// 4. Handle type aliases - try to resolve through type declaration resolver first
771+
if let aliasDecl = typeDeclResolver.resolveTypeAlias(type) {
772+
// Recursively lookup the aliased type
773+
if let resolvedType = lookupType(for: aliasDecl.initializer.value) {
774+
return resolvedType
775+
}
717776
}
718777

778+
// 5. Handle primitive types before falling back to other type declarations
779+
let typeName = type.trimmedDescription
780+
if let primitiveType = BridgeType.primitive(swiftType: typeName) {
781+
return primitiveType
782+
}
783+
784+
// 6. Try to resolve other type declarations
719785
guard let typeDecl = typeDeclResolver.resolve(type) else {
720786
return nil
721787
}
@@ -831,9 +897,19 @@ public class ExportSwift {
831897
} else {
832898
argumentsToLift = liftingInfo.parameters.map { (name, _) in param.name + name.capitalizedFirstLetter }
833899
}
900+
901+
// For optional types, use Optional<WrappedType> syntax instead of WrappedType?
902+
let typeNameForIntrinsic: String
903+
switch param.type {
904+
case .optional(let wrappedType):
905+
typeNameForIntrinsic = "Optional<\(wrappedType.swiftType)>"
906+
default:
907+
typeNameForIntrinsic = param.type.swiftType
908+
}
909+
834910
liftedParameterExprs.append(
835911
ExprSyntax(
836-
"\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
912+
"\(raw: typeNameForIntrinsic).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
837913
)
838914
)
839915
for (name, type) in zip(argumentsToLift, liftingInfo.parameters.map { $0.type }) {
@@ -928,6 +1004,12 @@ public class ExportSwift {
9281004
return
9291005
}
9301006

1007+
// Handle optional types that use special storage functions and return Void
1008+
if case .optional(_) = returnType {
1009+
append("return ret.bridgeJSLowerReturn()")
1010+
return
1011+
}
1012+
9311013
append("return ret.bridgeJSLowerReturn()")
9321014
}
9331015

@@ -1085,6 +1167,8 @@ public class ExportSwift {
10851167
return "\(paramName)Float.bridgeJSLiftParameter(_swift_js_pop_param_f32())"
10861168
case .double:
10871169
return "\(paramName)Double.bridgeJSLiftParameter(_swift_js_pop_param_f64())"
1170+
case .optional(_):
1171+
return "/* Optional associated value lifting not yet implemented */nil" // Placeholder
10881172
default:
10891173
return "\(paramName)Int.bridgeJSLiftParameter(_swift_js_pop_param_int32())"
10901174
}
@@ -1121,6 +1205,8 @@ public class ExportSwift {
11211205
bodyLines.append("_swift_js_push_f32(\(paramName))")
11221206
case .double:
11231207
bodyLines.append("_swift_js_push_f64(\(paramName))")
1208+
case .optional(_):
1209+
bodyLines.append("// Optional associated value lowering not yet implemented") // Placeholder
11241210
default:
11251211
bodyLines.append(
11261212
"preconditionFailure(\"BridgeJS: unsupported associated value type in generated code\")"
@@ -1331,24 +1417,55 @@ extension AttributeListSyntax {
13311417
}
13321418

13331419
extension BridgeType {
1420+
// This initializer is primarily for string-based type descriptions,
1421+
// especially for internal and raw type lookups.
1422+
// For full SwiftSyntax based parsing, `ExportSwift.lookupType(for:)` should be used.
13341423
init?(swiftType: String) {
1424+
// Use SwiftSyntax to parse the string into a TypeSyntax for robust optional detection
1425+
let typeSyntax = TypeSyntax(stringLiteral: swiftType)
1426+
1427+
// 1. Handle T? syntax (OptionalTypeSyntax)
1428+
if let optionalType = typeSyntax.as(OptionalTypeSyntax.self) {
1429+
let wrappedTypeString = optionalType.wrappedType.trimmedDescription
1430+
if let baseType = BridgeType(swiftType: wrappedTypeString) {
1431+
self = .optional(baseType)
1432+
return
1433+
}
1434+
}
1435+
1436+
// 2. Handle Optional<T> syntax (IdentifierTypeSyntax with generic arguments)
1437+
if let identifierType = typeSyntax.as(IdentifierTypeSyntax.self),
1438+
identifierType.name.text == "Optional",
1439+
let genericArgs = identifierType.genericArgumentClause?.arguments,
1440+
genericArgs.count == 1,
1441+
let argType = genericArgs.first?.argument {
1442+
let innerTypeString = argType.trimmedDescription
1443+
if let baseType = BridgeType(swiftType: innerTypeString) {
1444+
self = .optional(baseType)
1445+
return
1446+
}
1447+
}
1448+
1449+
// Fallback to primitive type handling if not an optional
1450+
if let primitive = BridgeType.primitive(swiftType: swiftType) {
1451+
self = primitive
1452+
return
1453+
}
1454+
1455+
return nil
1456+
}
1457+
1458+
// Helper for direct primitive type mapping (used by init? and elsewhere)
1459+
fileprivate static func primitive(swiftType: String) -> BridgeType? {
13351460
switch swiftType {
1336-
case "Int":
1337-
self = .int
1338-
case "Float":
1339-
self = .float
1340-
case "Double":
1341-
self = .double
1342-
case "String":
1343-
self = .string
1344-
case "Bool":
1345-
self = .bool
1346-
case "Void":
1347-
self = .void
1348-
case "JSObject":
1349-
self = .jsObject(nil)
1350-
default:
1351-
return nil
1461+
case "Int": return .int
1462+
case "Float": return .float
1463+
case "Double": return .double
1464+
case "String": return .string
1465+
case "Bool": return .bool
1466+
case "Void": return .void
1467+
case "JSObject": return .jsObject(nil)
1468+
default: return nil
13521469
}
13531470
}
13541471
}
@@ -1377,6 +1494,7 @@ extension BridgeType {
13771494
case .jsObject(let name?): return name
13781495
case .swiftHeapObject(let name): return name
13791496
case .void: return "Void"
1497+
case .optional(let wrappedType): return "\(wrappedType.swiftType)?"
13801498
case .caseEnum(let name): return name
13811499
case .rawValueEnum(let name, _): return name
13821500
case .associatedValueEnum(let name): return name
@@ -1400,6 +1518,7 @@ extension BridgeType {
14001518
("caseId", .i32)
14011519
])
14021520
}
1521+
14031522

14041523
func liftParameterInfo() throws -> LiftingIntrinsicInfo {
14051524
switch self {
@@ -1411,6 +1530,11 @@ extension BridgeType {
14111530
case .jsObject: return .jsObject
14121531
case .swiftHeapObject: return .swiftHeapObject
14131532
case .void: return .void
1533+
case .optional(let wrappedType):
1534+
// Optional parameters include optionality flag plus wrapped type parameters
1535+
var optionalParams: [(name: String, type: WasmCoreType)] = [("isSome", .i32)]
1536+
optionalParams.append(contentsOf: try wrappedType.liftParameterInfo().parameters)
1537+
return LiftingIntrinsicInfo(parameters: optionalParams)
14141538
case .caseEnum: return .caseEnum
14151539
case .rawValueEnum(_, let rawType):
14161540
switch rawType {
@@ -1458,6 +1582,9 @@ extension BridgeType {
14581582
case .jsObject: return .jsObject
14591583
case .swiftHeapObject: return .swiftHeapObject
14601584
case .void: return .void
1585+
case .optional(_):
1586+
// Optional return values use special storage functions and return void
1587+
return LoweringIntrinsicInfo(returnType: nil)
14611588
case .caseEnum: return .caseEnum
14621589
case .rawValueEnum(_, let rawType):
14631590
switch rawType {

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,51 @@ public struct ImportTS {
7272

7373
func lowerParameter(param: Parameter) throws {
7474
let loweringInfo = try param.type.loweringParameterInfo()
75-
assert(
76-
loweringInfo.loweredParameters.count == 1,
77-
"For now, we require a single parameter to be lowered to a single Wasm core type"
78-
)
79-
let (_, type) = loweringInfo.loweredParameters[0]
80-
abiParameterForwardings.append(
81-
LabeledExprSyntax(
82-
label: param.label,
83-
expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()")
75+
76+
// Handle void parameters (empty loweredParameters array)
77+
if loweringInfo.loweredParameters.isEmpty {
78+
// Void parameters don't generate any ABI parameters
79+
return
80+
} else if loweringInfo.loweredParameters.count == 1 {
81+
// Simple case: single parameter
82+
let (_, type) = loweringInfo.loweredParameters[0]
83+
abiParameterForwardings.append(
84+
LabeledExprSyntax(
85+
label: param.label,
86+
expression: ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()")
87+
)
8488
)
85-
)
86-
abiParameterSignatures.append((param.name, type))
89+
abiParameterSignatures.append((param.name, type))
90+
} else {
91+
// Complex case: multiple parameters (e.g., optionals with isSome flag + wrapped parameters)
92+
// For optional types, use Optional<WrappedType> syntax for the lowering call
93+
let typeNameForLowering: String
94+
switch param.type {
95+
case .optional(let wrappedType):
96+
typeNameForLowering = "Optional<\(wrappedType.swiftType)>"
97+
default:
98+
typeNameForLowering = param.type.swiftType
99+
}
100+
101+
let paramNames = loweringInfo.loweredParameters.enumerated().map { index, paramInfo in
102+
if index == 0 {
103+
return param.name + paramInfo.name.capitalizedFirstLetter
104+
} else {
105+
return param.name + paramInfo.name.capitalizedFirstLetter
106+
}
107+
}
108+
109+
abiParameterForwardings.append(
110+
LabeledExprSyntax(
111+
label: param.label,
112+
expression: ExprSyntax("\(raw: typeNameForLowering).bridgeJSLowerParameter(\(raw: param.name))")
113+
)
114+
)
115+
116+
for (paramName, (_, type)) in zip(paramNames, loweringInfo.loweredParameters) {
117+
abiParameterSignatures.append((paramName, type))
118+
}
119+
}
87120
}
88121

89122
func call(returnType: BridgeType) {
@@ -103,6 +136,13 @@ public struct ImportTS {
103136
if returnType == .void {
104137
return
105138
}
139+
140+
// Handle optional return values that use special storage functions
141+
if case .optional(_) = returnType {
142+
body.append("return \(raw: returnType.swiftType).bridgeJSLiftReturn()")
143+
return
144+
}
145+
106146
body.append("return \(raw: returnType.swiftType).bridgeJSLiftReturn(ret)")
107147
}
108148

@@ -433,6 +473,12 @@ extension BridgeType {
433473
case .string: return .string
434474
case .jsObject: return .jsObject
435475
case .void: return .void
476+
case .optional(let wrappedType):
477+
// Optional parameters include optionality flag plus wrapped type parameters
478+
let wrappedInfo = try wrappedType.loweringParameterInfo()
479+
var optionalParams: [(name: String, type: WasmCoreType)] = [("isSome", .i32)]
480+
optionalParams.append(contentsOf: wrappedInfo.loweredParameters)
481+
return LoweringParameterInfo(loweredParameters: optionalParams)
436482
case .swiftHeapObject:
437483
throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures")
438484
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
@@ -461,6 +507,9 @@ extension BridgeType {
461507
case .string: return .string
462508
case .jsObject: return .jsObject
463509
case .void: return .void
510+
case .optional(_):
511+
// Optional return values use special storage functions and return void
512+
return LiftingReturnInfo(valueToLift: nil)
464513
case .swiftHeapObject:
465514
throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures")
466515
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:

0 commit comments

Comments
 (0)