Skip to content

Commit 3f55206

Browse files
committed
BridgeJS: Support optionals in associated value enum parameters + cleanup
1 parent 1e276d5 commit 3f55206

24 files changed

+1154
-859
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 95 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public class ExportSwift {
142142
hint: "Only primitive types and types defined in the same module are allowed"
143143
)
144144
}
145-
145+
146146
private func diagnoseNestedOptional(node: some SyntaxProtocol, type: String) {
147147
diagnose(
148148
node: node,
@@ -192,30 +192,30 @@ public class ExportSwift {
192192
var parameters: [Parameter] = []
193193
for param in node.signature.parameterClause.parameters {
194194
let resolvedType = self.parent.lookupType(for: param.type)
195-
195+
196196
if let type = resolvedType, case .optional(let wrappedType) = type, wrappedType.isOptional {
197197
diagnoseNestedOptional(node: param.type, type: param.type.trimmedDescription)
198198
continue
199199
}
200-
200+
201201
guard let type = resolvedType else {
202202
diagnoseUnsupportedType(node: param.type, type: param.type.trimmedDescription)
203203
continue
204204
}
205-
205+
206206
let name = param.secondName?.text ?? param.firstName.text
207207
let label = param.firstName.text
208208
parameters.append(Parameter(label: label, name: name, type: type))
209209
}
210210
let returnType: BridgeType
211211
if let returnClause = node.signature.returnClause {
212212
let resolvedType = self.parent.lookupType(for: returnClause.type)
213-
213+
214214
if let type = resolvedType, case .optional(let wrappedType) = type, wrappedType.isOptional {
215215
diagnoseNestedOptional(node: returnClause.type, type: returnClause.type.trimmedDescription)
216216
return nil
217217
}
218-
218+
219219
guard let type = resolvedType else {
220220
diagnoseUnsupportedType(node: returnClause.type, type: returnClause.type.trimmedDescription)
221221
return nil
@@ -561,12 +561,24 @@ public class ExportSwift {
561561
switch associatedValue.type {
562562
case .string, .int, .float, .double, .bool:
563563
break
564+
case .optional(let wrappedType):
565+
switch wrappedType {
566+
case .string, .int, .float, .double, .bool:
567+
break
568+
default:
569+
diagnose(
570+
node: node,
571+
message: "Unsupported associated value type: \(associatedValue.type.swiftType)",
572+
hint:
573+
"Only primitive types and optional primitives (String?, Int?, Float?, Double?, Bool?) are supported in associated-value enums"
574+
)
575+
}
564576
default:
565577
diagnose(
566578
node: node,
567579
message: "Unsupported associated value type: \(associatedValue.type.swiftType)",
568580
hint:
569-
"Only primitive types (String, Int, Float, Double, Bool) are supported in associated-value enums"
581+
"Only primitive types and optional primitives (String?, Int?, Float?, Double?, Bool?) are supported in associated-value enums"
570582
)
571583
}
572584
}
@@ -744,22 +756,24 @@ public class ExportSwift {
744756
}
745757
// Optional<T>
746758
if let identifierType = type.as(IdentifierTypeSyntax.self),
747-
identifierType.name.text == "Optional",
748-
let genericArgs = identifierType.genericArgumentClause?.arguments,
749-
genericArgs.count == 1,
750-
let argType = genericArgs.first?.argument {
759+
identifierType.name.text == "Optional",
760+
let genericArgs = identifierType.genericArgumentClause?.arguments,
761+
genericArgs.count == 1,
762+
let argType = genericArgs.first?.argument
763+
{
751764
if let baseType = lookupType(for: argType) {
752765
return .optional(baseType)
753766
}
754767
}
755768
// Swift.Optional<T>
756769
if let memberType = type.as(MemberTypeSyntax.self),
757-
let baseType = memberType.baseType.as(IdentifierTypeSyntax.self),
758-
baseType.name.text == "Swift",
759-
memberType.name.text == "Optional",
760-
let genericArgs = memberType.genericArgumentClause?.arguments,
761-
genericArgs.count == 1,
762-
let argType = genericArgs.first?.argument {
770+
let baseType = memberType.baseType.as(IdentifierTypeSyntax.self),
771+
baseType.name.text == "Swift",
772+
memberType.name.text == "Optional",
773+
let genericArgs = memberType.genericArgumentClause?.arguments,
774+
genericArgs.count == 1,
775+
let argType = genericArgs.first?.argument
776+
{
763777
if let wrappedType = lookupType(for: argType) {
764778
return .optional(wrappedType)
765779
}
@@ -771,7 +785,7 @@ public class ExportSwift {
771785
}
772786

773787
let typeName = type.trimmedDescription
774-
if let primitiveType = BridgeType.primitive(swiftType: typeName) {
788+
if let primitiveType = BridgeType(swiftType: typeName) {
775789
return primitiveType
776790
}
777791

@@ -890,15 +904,15 @@ public class ExportSwift {
890904
} else {
891905
argumentsToLift = liftingInfo.parameters.map { (name, _) in param.name + name.capitalizedFirstLetter }
892906
}
893-
907+
894908
let typeNameForIntrinsic: String
895909
switch param.type {
896910
case .optional(let wrappedType):
897911
typeNameForIntrinsic = "Optional<\(wrappedType.swiftType)>"
898912
default:
899913
typeNameForIntrinsic = param.type.swiftType
900914
}
901-
915+
902916
liftedParameterExprs.append(
903917
ExprSyntax(
904918
"\(raw: typeNameForIntrinsic).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
@@ -996,11 +1010,6 @@ public class ExportSwift {
9961010
return
9971011
}
9981012

999-
if case .optional(_) = returnType {
1000-
append("return ret.bridgeJSLowerReturn()")
1001-
return
1002-
}
1003-
10041013
append("return ret.bridgeJSLowerReturn()")
10051014
}
10061015

@@ -1158,8 +1167,26 @@ public class ExportSwift {
11581167
return "\(paramName)Float.bridgeJSLiftParameter(_swift_js_pop_param_f32())"
11591168
case .double:
11601169
return "\(paramName)Double.bridgeJSLiftParameter(_swift_js_pop_param_f64())"
1161-
case .optional(_):
1162-
return "nil"
1170+
case .optional(let wrappedType):
1171+
switch wrappedType {
1172+
case .string:
1173+
return
1174+
"\(paramName)Optional<String>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32(), _swift_js_pop_param_int32())"
1175+
case .int:
1176+
return
1177+
"\(paramName)Optional<Int>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())"
1178+
case .bool:
1179+
return
1180+
"\(paramName)Optional<Bool>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32())"
1181+
case .float:
1182+
return
1183+
"\(paramName)Optional<Float>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f32())"
1184+
case .double:
1185+
return
1186+
"\(paramName)Optional<Double>.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_f64())"
1187+
default:
1188+
return ""
1189+
}
11631190
default:
11641191
return "\(paramName)Int.bridgeJSLiftParameter(_swift_js_pop_param_int32())"
11651192
}
@@ -1196,6 +1223,30 @@ public class ExportSwift {
11961223
bodyLines.append("_swift_js_push_f32(\(paramName))")
11971224
case .double:
11981225
bodyLines.append("_swift_js_push_f64(\(paramName))")
1226+
case .optional(let wrappedType):
1227+
bodyLines.append("let __bjs_isSome_\(paramName) = \(paramName) != nil")
1228+
bodyLines.append("if let __bjs_unwrapped_\(paramName) = \(paramName) {")
1229+
switch wrappedType {
1230+
case .string:
1231+
bodyLines.append("var __bjs_str_\(paramName) = __bjs_unwrapped_\(paramName)")
1232+
bodyLines.append("__bjs_str_\(paramName).withUTF8 { ptr in")
1233+
bodyLines.append("_swift_js_push_string(ptr.baseAddress, Int32(ptr.count))")
1234+
bodyLines.append("}")
1235+
case .int:
1236+
bodyLines.append("_swift_js_push_int(Int32(__bjs_unwrapped_\(paramName)))")
1237+
case .bool:
1238+
bodyLines.append("_swift_js_push_int(__bjs_unwrapped_\(paramName) ? 1 : 0)")
1239+
case .float:
1240+
bodyLines.append("_swift_js_push_f32(__bjs_unwrapped_\(paramName))")
1241+
case .double:
1242+
bodyLines.append("_swift_js_push_f64(__bjs_unwrapped_\(paramName))")
1243+
default:
1244+
bodyLines.append(
1245+
"preconditionFailure(\"BridgeJS: unsupported optional wrapped type in generated code\")"
1246+
)
1247+
}
1248+
bodyLines.append("}")
1249+
bodyLines.append("_swift_js_push_int(__bjs_isSome_\(paramName) ? 1 : 0)")
11991250
default:
12001251
bodyLines.append(
12011252
"preconditionFailure(\"BridgeJS: unsupported associated value type in generated code\")"
@@ -1406,55 +1457,24 @@ extension AttributeListSyntax {
14061457
}
14071458

14081459
extension BridgeType {
1409-
// This initializer is primarily for string-based type descriptions,
1410-
// especially for internal and raw type lookups.
1411-
// For full SwiftSyntax based parsing, `ExportSwift.lookupType(for:)` should be used.
14121460
init?(swiftType: String) {
1413-
// Use SwiftSyntax to parse the string into a TypeSyntax for robust optional detection
1414-
let typeSyntax = TypeSyntax(stringLiteral: swiftType)
1415-
1416-
// 1. Handle T? syntax (OptionalTypeSyntax)
1417-
if let optionalType = typeSyntax.as(OptionalTypeSyntax.self) {
1418-
let wrappedTypeString = optionalType.wrappedType.trimmedDescription
1419-
if let baseType = BridgeType(swiftType: wrappedTypeString) {
1420-
self = .optional(baseType)
1421-
return
1422-
}
1423-
}
1424-
1425-
// 2. Handle Optional<T> syntax (IdentifierTypeSyntax with generic arguments)
1426-
if let identifierType = typeSyntax.as(IdentifierTypeSyntax.self),
1427-
identifierType.name.text == "Optional",
1428-
let genericArgs = identifierType.genericArgumentClause?.arguments,
1429-
genericArgs.count == 1,
1430-
let argType = genericArgs.first?.argument {
1431-
let innerTypeString = argType.trimmedDescription
1432-
if let baseType = BridgeType(swiftType: innerTypeString) {
1433-
self = .optional(baseType)
1434-
return
1435-
}
1436-
}
1437-
1438-
// Fallback to primitive type handling if not an optional
1439-
if let primitive = BridgeType.primitive(swiftType: swiftType) {
1440-
self = primitive
1441-
return
1442-
}
1443-
1444-
return nil
1445-
}
1446-
1447-
// Helper for direct primitive type mapping (used by init? and elsewhere)
1448-
fileprivate static func primitive(swiftType: String) -> BridgeType? {
14491461
switch swiftType {
1450-
case "Int": return .int
1451-
case "Float": return .float
1452-
case "Double": return .double
1453-
case "String": return .string
1454-
case "Bool": return .bool
1455-
case "Void": return .void
1456-
case "JSObject": return .jsObject(nil)
1457-
default: return nil
1462+
case "Int":
1463+
self = .int
1464+
case "Float":
1465+
self = .float
1466+
case "Double":
1467+
self = .double
1468+
case "String":
1469+
self = .string
1470+
case "Bool":
1471+
self = .bool
1472+
case "Void":
1473+
self = .void
1474+
case "JSObject":
1475+
self = .jsObject(nil)
1476+
default:
1477+
return nil
14581478
}
14591479
}
14601480
}
@@ -1507,7 +1527,6 @@ extension BridgeType {
15071527
("caseId", .i32)
15081528
])
15091529
}
1510-
15111530

15121531
func liftParameterInfo() throws -> LiftingIntrinsicInfo {
15131532
switch self {
@@ -1589,7 +1608,7 @@ extension BridgeType {
15891608
case .associatedValueEnum:
15901609
return .associatedValueEnum
15911610
case .namespaceEnum:
1592-
throw BridgeJSCoreError("Namespace enums are not supported as return types")
1611+
throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters")
15931612
}
15941613
}
15951614
}

Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class TypeDeclResolver {
6363
override func visitPost(_ node: EnumDeclSyntax) {
6464
visitPostNominalDecl()
6565
}
66-
66+
6767
override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind {
6868
let name = node.name.text
6969
let qualifiedName = scope.map(\.name.text) + [name]
@@ -139,7 +139,7 @@ class TypeDeclResolver {
139139
}
140140
return nil
141141
}
142-
142+
143143
/// Resolves a type usage node to a type alias declaration
144144
///
145145
/// - Parameter type: The SwiftSyntax node representing a type appearance in source code.

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,9 @@ struct BridgeJSLink {
365365
}
366366
printer.write("} else {")
367367
printer.indent {
368-
printer.write("\(JSGlueVariableScope.reservedStorageToReturnOptionalFloat) = Math.fround(value);")
368+
printer.write(
369+
"\(JSGlueVariableScope.reservedStorageToReturnOptionalFloat) = Math.fround(value);"
370+
)
369371
}
370372
printer.write("}")
371373
}
@@ -393,7 +395,7 @@ struct BridgeJSLink {
393395
printer.indent {
394396
printer.write(lines: [
395397
"const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr, len);",
396-
"\(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedTextDecoder).decode(bytes);"
398+
"\(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedTextDecoder).decode(bytes);",
397399
])
398400
}
399401
printer.write("}")
@@ -407,7 +409,9 @@ struct BridgeJSLink {
407409
}
408410
printer.write("} else {")
409411
printer.indent {
410-
printer.write("\(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedSwift).memory.getObject(objectId);")
412+
printer.write(
413+
"\(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedSwift).memory.getObject(objectId);"
414+
)
411415
}
412416
printer.write("}")
413417
}

0 commit comments

Comments
 (0)