Skip to content

Commit 79a0a4e

Browse files
Merge pull request swiftwasm#444 from PassiveLogic/feat/optionals-support
2 parents 250af4d + 29064b9 commit 79a0a4e

File tree

65 files changed

+7225
-128
lines changed

Some content is hidden

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

65 files changed

+7225
-128
lines changed

Benchmarks/Sources/Generated/BridgeJS.ExportSwift.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
@_spi(BridgeJS) import JavaScriptKit
88

9-
private extension APIResult {
10-
static func bridgeJSLiftParameter(_ caseId: Int32) -> APIResult {
9+
extension APIResult: _BridgedSwiftAssociatedValueEnum {
10+
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ caseId: Int32) -> APIResult {
1111
switch caseId {
1212
case 0:
1313
return .success(String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()))
@@ -26,7 +26,7 @@ private extension APIResult {
2626
}
2727
}
2828

29-
func bridgeJSLowerReturn() {
29+
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() {
3030
switch self {
3131
case .success(let param0):
3232
_swift_js_push_tag(Int32(0))
@@ -52,8 +52,8 @@ private extension APIResult {
5252
}
5353
}
5454

55-
private extension ComplexResult {
56-
static func bridgeJSLiftParameter(_ caseId: Int32) -> ComplexResult {
55+
extension ComplexResult: _BridgedSwiftAssociatedValueEnum {
56+
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ caseId: Int32) -> ComplexResult {
5757
switch caseId {
5858
case 0:
5959
return .success(String.bridgeJSLiftParameter(_swift_js_pop_param_int32(), _swift_js_pop_param_int32()))
@@ -74,7 +74,7 @@ private extension ComplexResult {
7474
}
7575
}
7676

77-
func bridgeJSLowerReturn() {
77+
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() {
7878
switch self {
7979
case .success(let param0):
8080
_swift_js_push_tag(Int32(0))

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 143 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ public class ExportSwift {
143143
)
144144
}
145145

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+
}
153+
146154
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
147155
switch state {
148156
case .topLevel:
@@ -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
}
@@ -538,12 +561,24 @@ public class ExportSwift {
538561
switch associatedValue.type {
539562
case .string, .int, .float, .double, .bool:
540563
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+
}
541576
default:
542577
diagnose(
543578
node: node,
544579
message: "Unsupported associated value type: \(associatedValue.type.swiftType)",
545580
hint:
546-
"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"
547582
)
548583
}
549584
}
@@ -712,8 +747,46 @@ public class ExportSwift {
712747
}
713748

714749
func lookupType(for type: TypeSyntax) -> BridgeType? {
715-
if let primitive = BridgeType(swiftType: type.trimmedDescription) {
716-
return primitive
750+
// T?
751+
if let optionalType = type.as(OptionalTypeSyntax.self) {
752+
let wrappedType = optionalType.wrappedType
753+
if let baseType = lookupType(for: wrappedType) {
754+
return .optional(baseType)
755+
}
756+
}
757+
// Optional<T>
758+
if let identifierType = type.as(IdentifierTypeSyntax.self),
759+
identifierType.name.text == "Optional",
760+
let genericArgs = identifierType.genericArgumentClause?.arguments,
761+
genericArgs.count == 1,
762+
let argType = genericArgs.first?.argument
763+
{
764+
if let baseType = lookupType(for: argType) {
765+
return .optional(baseType)
766+
}
767+
}
768+
// Swift.Optional<T>
769+
if let memberType = type.as(MemberTypeSyntax.self),
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+
{
777+
if let wrappedType = lookupType(for: argType) {
778+
return .optional(wrappedType)
779+
}
780+
}
781+
if let aliasDecl = typeDeclResolver.resolveTypeAlias(type) {
782+
if let resolvedType = lookupType(for: aliasDecl.initializer.value) {
783+
return resolvedType
784+
}
785+
}
786+
787+
let typeName = type.trimmedDescription
788+
if let primitiveType = BridgeType(swiftType: typeName) {
789+
return primitiveType
717790
}
718791

719792
guard let typeDecl = typeDeclResolver.resolve(type) else {
@@ -831,9 +904,18 @@ public class ExportSwift {
831904
} else {
832905
argumentsToLift = liftingInfo.parameters.map { (name, _) in param.name + name.capitalizedFirstLetter }
833906
}
907+
908+
let typeNameForIntrinsic: String
909+
switch param.type {
910+
case .optional(let wrappedType):
911+
typeNameForIntrinsic = "Optional<\(wrappedType.swiftType)>"
912+
default:
913+
typeNameForIntrinsic = param.type.swiftType
914+
}
915+
834916
liftedParameterExprs.append(
835917
ExprSyntax(
836-
"\(raw: param.type.swiftType).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
918+
"\(raw: typeNameForIntrinsic).bridgeJSLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
837919
)
838920
)
839921
for (name, type) in zip(argumentsToLift, liftingInfo.parameters.map { $0.type }) {
@@ -1013,7 +1095,7 @@ public class ExportSwift {
10131095
let valueSwitch = (["switch self {"] + valueCases + ["}"]).joined(separator: "\n")
10141096

10151097
return """
1016-
extension \(raw: typeName) {
1098+
extension \(raw: typeName): _BridgedSwiftCaseEnum {
10171099
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerParameter() -> Int32 {
10181100
return bridgeJSRawValue
10191101
}
@@ -1041,15 +1123,15 @@ public class ExportSwift {
10411123
func renderAssociatedValueEnumHelpers(_ enumDef: ExportedEnum) -> DeclSyntax {
10421124
let typeName = enumDef.swiftCallName
10431125
return """
1044-
private extension \(raw: typeName) {
1045-
static func bridgeJSLiftParameter(_ caseId: Int32) -> \(raw: typeName) {
1126+
extension \(raw: typeName): _BridgedSwiftAssociatedValueEnum {
1127+
@_spi(BridgeJS) @_transparent public static func bridgeJSLiftParameter(_ caseId: Int32) -> \(raw: typeName) {
10461128
switch caseId {
10471129
\(raw: generateStackLiftSwitchCases(enumDef: enumDef).joined(separator: "\n"))
10481130
default: fatalError("Unknown \(raw: typeName) case ID: \\(caseId)")
10491131
}
10501132
}
10511133
1052-
func bridgeJSLowerReturn() {
1134+
@_spi(BridgeJS) @_transparent public consuming func bridgeJSLowerReturn() {
10531135
switch self {
10541136
\(raw: generateReturnSwitchCases(enumDef: enumDef).joined(separator: "\n"))
10551137
}
@@ -1085,6 +1167,26 @@ 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(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+
}
10881190
default:
10891191
return "\(paramName)Int.bridgeJSLiftParameter(_swift_js_pop_param_int32())"
10901192
}
@@ -1121,6 +1223,30 @@ public class ExportSwift {
11211223
bodyLines.append("_swift_js_push_f32(\(paramName))")
11221224
case .double:
11231225
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)")
11241250
default:
11251251
bodyLines.append(
11261252
"preconditionFailure(\"BridgeJS: unsupported associated value type in generated code\")"
@@ -1377,6 +1503,7 @@ extension BridgeType {
13771503
case .jsObject(let name?): return name
13781504
case .swiftHeapObject(let name): return name
13791505
case .void: return "Void"
1506+
case .optional(let wrappedType): return "Optional<\(wrappedType.swiftType)>"
13801507
case .caseEnum(let name): return name
13811508
case .rawValueEnum(let name, _): return name
13821509
case .associatedValueEnum(let name): return name
@@ -1411,6 +1538,10 @@ extension BridgeType {
14111538
case .jsObject: return .jsObject
14121539
case .swiftHeapObject: return .swiftHeapObject
14131540
case .void: return .void
1541+
case .optional(let wrappedType):
1542+
var optionalParams: [(name: String, type: WasmCoreType)] = [("isSome", .i32)]
1543+
optionalParams.append(contentsOf: try wrappedType.liftParameterInfo().parameters)
1544+
return LiftingIntrinsicInfo(parameters: optionalParams)
14141545
case .caseEnum: return .caseEnum
14151546
case .rawValueEnum(_, let rawType):
14161547
switch rawType {
@@ -1446,6 +1577,7 @@ extension BridgeType {
14461577
static let caseEnum = LoweringIntrinsicInfo(returnType: .i32)
14471578
static let rawValueEnum = LoweringIntrinsicInfo(returnType: .i32)
14481579
static let associatedValueEnum = LoweringIntrinsicInfo(returnType: nil)
1580+
static let optional = LoweringIntrinsicInfo(returnType: nil)
14491581
}
14501582

14511583
func loweringReturnInfo() throws -> LoweringIntrinsicInfo {
@@ -1458,6 +1590,7 @@ extension BridgeType {
14581590
case .jsObject: return .jsObject
14591591
case .swiftHeapObject: return .swiftHeapObject
14601592
case .void: return .void
1593+
case .optional: return .optional
14611594
case .caseEnum: return .caseEnum
14621595
case .rawValueEnum(_, let rawType):
14631596
switch rawType {

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ extension BridgeType {
437437
throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures")
438438
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
439439
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
440+
case .optional:
441+
throw BridgeJSCoreError("Optional types are not yet supported in TypeScript imports")
440442
}
441443
}
442444

@@ -465,6 +467,8 @@ extension BridgeType {
465467
throw BridgeJSCoreError("swiftHeapObject is not supported in imported signatures")
466468
case .caseEnum, .rawValueEnum, .associatedValueEnum, .namespaceEnum:
467469
throw BridgeJSCoreError("Enum types are not yet supported in TypeScript imports")
470+
case .optional:
471+
throw BridgeJSCoreError("Optional types are not yet supported in TypeScript imports")
468472
}
469473
}
470474
}

Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class TypeDeclResolver {
88
/// `Outer.Inner` type declaration is represented as ["Outer", "Inner"]
99
typealias QualifiedName = [String]
1010
private var typeDeclByQualifiedName: [QualifiedName: TypeDecl] = [:]
11+
private var typeAliasByQualifiedName: [QualifiedName: TypeAliasDeclSyntax] = [:]
1112

1213
enum Error: Swift.Error {
1314
case typeNotFound(QualifiedName)
@@ -62,6 +63,13 @@ class TypeDeclResolver {
6263
override func visitPost(_ node: EnumDeclSyntax) {
6364
visitPostNominalDecl()
6465
}
66+
67+
override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind {
68+
let name = node.name.text
69+
let qualifiedName = scope.map(\.name.text) + [name]
70+
resolver.typeAliasByQualifiedName[qualifiedName] = node
71+
return .skipChildren
72+
}
6573
}
6674

6775
/// Collects type declarations from a parsed Swift source file
@@ -132,6 +140,21 @@ class TypeDeclResolver {
132140
return nil
133141
}
134142

143+
/// Resolves a type usage node to a type alias declaration
144+
///
145+
/// - Parameter type: The SwiftSyntax node representing a type appearance in source code.
146+
/// - Returns: The type alias declaration if found, otherwise nil.
147+
func resolveTypeAlias(_ type: TypeSyntax) -> TypeAliasDeclSyntax? {
148+
if let id = type.as(IdentifierTypeSyntax.self) {
149+
let qualifiedName = tryQualify(type: id)
150+
return typeAliasByQualifiedName[qualifiedName]
151+
}
152+
if let components = qualifiedComponents(from: type) {
153+
return typeAliasByQualifiedName[components]
154+
}
155+
return nil
156+
}
157+
135158
private func qualifiedComponents(from type: TypeSyntax) -> QualifiedName? {
136159
if let m = type.as(MemberTypeSyntax.self) {
137160
guard let base = qualifiedComponents(from: TypeSyntax(m.baseType)) else { return nil }

0 commit comments

Comments
 (0)