Skip to content

Commit cb337e7

Browse files
Merge pull request #433 from PassiveLogic/fix/nested-enum-methods-fix
2 parents d9c4d6d + 739ea2e commit cb337e7

File tree

6 files changed

+276
-23
lines changed

6 files changed

+276
-23
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -689,47 +689,50 @@ public class ExportSwift {
689689
if let primitive = BridgeType(swiftType: type.trimmedDescription) {
690690
return primitive
691691
}
692-
guard let identifier = type.as(IdentifierTypeSyntax.self) else {
693-
return nil
694-
}
695692

696-
guard let typeDecl = typeDeclResolver.lookupType(for: identifier) else {
693+
guard let typeDecl = typeDeclResolver.resolve(type) else {
697694
return nil
698695
}
696+
699697
if let enumDecl = typeDecl.as(EnumDeclSyntax.self) {
700-
let enumName = enumDecl.name.text
701-
if let existingEnum = exportedEnums.first(where: { $0.name == enumName }) {
702-
switch existingEnum.enumType {
703-
case .simple:
704-
return .caseEnum(existingEnum.swiftCallName)
705-
case .rawValue:
706-
let rawType = SwiftEnumRawType.from(existingEnum.rawType!)!
707-
return .rawValueEnum(existingEnum.swiftCallName, rawType)
708-
case .associatedValue:
709-
return .associatedValueEnum(existingEnum.swiftCallName)
710-
case .namespace:
711-
return .namespaceEnum(existingEnum.swiftCallName)
712-
}
713-
}
714698
let swiftCallName = ExportSwift.computeSwiftCallName(for: enumDecl, itemName: enumDecl.name.text)
715699
let rawTypeString = enumDecl.inheritanceClause?.inheritedTypes.first { inheritedType in
716700
let typeName = inheritedType.type.trimmedDescription
717701
return Constants.supportedRawTypes.contains(typeName)
718702
}?.type.trimmedDescription
719703

720-
if let rawTypeString = rawTypeString,
721-
let rawType = SwiftEnumRawType.from(rawTypeString)
722-
{
704+
if let rawTypeString, let rawType = SwiftEnumRawType.from(rawTypeString) {
723705
return .rawValueEnum(swiftCallName, rawType)
724706
} else {
725-
return .caseEnum(swiftCallName)
707+
let hasAnyCases = enumDecl.memberBlock.members.contains { member in
708+
member.decl.is(EnumCaseDeclSyntax.self)
709+
}
710+
if !hasAnyCases {
711+
return .namespaceEnum(swiftCallName)
712+
}
713+
let hasAssociatedValues =
714+
enumDecl.memberBlock.members.contains { member in
715+
guard let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) else { return false }
716+
return caseDecl.elements.contains { element in
717+
if let params = element.parameterClause?.parameters {
718+
return !params.isEmpty
719+
}
720+
return false
721+
}
722+
}
723+
if hasAssociatedValues {
724+
return .associatedValueEnum(swiftCallName)
725+
} else {
726+
return .caseEnum(swiftCallName)
727+
}
726728
}
727729
}
728730

729731
guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else {
730732
return nil
731733
}
732-
return .swiftHeapObject(typeDecl.name.text)
734+
let swiftCallName = ExportSwift.computeSwiftCallName(for: typeDecl, itemName: typeDecl.name.text)
735+
return .swiftHeapObject(swiftCallName)
733736
}
734737

735738
static let prelude: DeclSyntax = """

Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,37 @@ class TypeDeclResolver {
109109
func lookupType(fullyQualified: QualifiedName) -> TypeDecl? {
110110
return typeDeclByQualifiedName[fullyQualified]
111111
}
112+
113+
/// Resolves a type usage node to the corresponding nominal type declaration collected in this resolver.
114+
///
115+
/// Supported inputs:
116+
/// - IdentifierTypeSyntax (e.g. `Method`) — resolved relative to the lexical scope, preferring the innermost enclosing type.
117+
/// - MemberTypeSyntax (e.g. `Networking.API.Method`) — resolved by recursively building the fully qualified name.
118+
///
119+
/// Resolution strategy:
120+
/// 1. If the node is IdentifierTypeSyntax, call `lookupType(for:)` which attempts scope-aware qualification via `tryQualify`.
121+
/// 2. Otherwise, attempt to build a fully qualified name with `qualifiedComponents(from:)` and look it up with `lookupType(fullyQualified:)`.
122+
///
123+
/// - Parameter type: The SwiftSyntax node representing a type appearance in source code.
124+
/// - Returns: The nominal declaration (enum/class/actor/struct) if found, otherwise nil.
125+
func resolve(_ type: TypeSyntax) -> TypeDecl? {
126+
if let id = type.as(IdentifierTypeSyntax.self) {
127+
return lookupType(for: id)
128+
}
129+
if let components = qualifiedComponents(from: type) {
130+
return lookupType(fullyQualified: components)
131+
}
132+
return nil
133+
}
134+
135+
private func qualifiedComponents(from type: TypeSyntax) -> QualifiedName? {
136+
if let m = type.as(MemberTypeSyntax.self) {
137+
guard let base = qualifiedComponents(from: TypeSyntax(m.baseType)) else { return nil }
138+
return base + [m.name.text]
139+
} else if let id = type.as(IdentifierTypeSyntax.self) {
140+
return [id.name.text]
141+
} else {
142+
return nil
143+
}
144+
}
112145
}

Tests/BridgeJSRuntimeTests/ExportAPITests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ struct TestError: Error {
223223
return .light
224224
}
225225

226+
// MARK: - Namespace Enums
227+
226228
@JS enum Utils {
227229
@JS class Converter {
228230
@JS init() {}
@@ -275,6 +277,31 @@ enum Internal {
275277
}
276278
}
277279

280+
@JS func echoNetworkingAPIMethod(_ method: Networking.API.Method) -> Networking.API.Method {
281+
return method
282+
}
283+
284+
@JS func echoConfigurationLogLevel(_ level: Configuration.LogLevel) -> Configuration.LogLevel {
285+
return level
286+
}
287+
288+
@JS func echoConfigurationPort(_ port: Configuration.Port) -> Configuration.Port {
289+
return port
290+
}
291+
292+
@JS func processConfigurationLogLevel(_ level: Configuration.LogLevel) -> Configuration.Port {
293+
switch level {
294+
case .debug: return .development
295+
case .info: return .http
296+
case .warning: return .https
297+
case .error: return .development
298+
}
299+
}
300+
301+
@JS func echoInternalSupportedMethod(_ method: Internal.SupportedMethod) -> Internal.SupportedMethod {
302+
return method
303+
}
304+
278305
// MARK: - Property Tests
279306

280307
// Simple class for SwiftHeapObject property testing

Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,61 @@ public func _bjs_getTSTheme() -> Void {
814814
#endif
815815
}
816816

817+
@_expose(wasm, "bjs_echoNetworkingAPIMethod")
818+
@_cdecl("bjs_echoNetworkingAPIMethod")
819+
public func _bjs_echoNetworkingAPIMethod(method: Int32) -> Int32 {
820+
#if arch(wasm32)
821+
let ret = echoNetworkingAPIMethod(_: Networking.API.Method.bridgeJSLiftParameter(method))
822+
return ret.bridgeJSLowerReturn()
823+
#else
824+
fatalError("Only available on WebAssembly")
825+
#endif
826+
}
827+
828+
@_expose(wasm, "bjs_echoConfigurationLogLevel")
829+
@_cdecl("bjs_echoConfigurationLogLevel")
830+
public func _bjs_echoConfigurationLogLevel(levelBytes: Int32, levelLength: Int32) -> Void {
831+
#if arch(wasm32)
832+
let ret = echoConfigurationLogLevel(_: Configuration.LogLevel.bridgeJSLiftParameter(levelBytes, levelLength))
833+
return ret.bridgeJSLowerReturn()
834+
#else
835+
fatalError("Only available on WebAssembly")
836+
#endif
837+
}
838+
839+
@_expose(wasm, "bjs_echoConfigurationPort")
840+
@_cdecl("bjs_echoConfigurationPort")
841+
public func _bjs_echoConfigurationPort(port: Int32) -> Int32 {
842+
#if arch(wasm32)
843+
let ret = echoConfigurationPort(_: Configuration.Port.bridgeJSLiftParameter(port))
844+
return ret.bridgeJSLowerReturn()
845+
#else
846+
fatalError("Only available on WebAssembly")
847+
#endif
848+
}
849+
850+
@_expose(wasm, "bjs_processConfigurationLogLevel")
851+
@_cdecl("bjs_processConfigurationLogLevel")
852+
public func _bjs_processConfigurationLogLevel(levelBytes: Int32, levelLength: Int32) -> Int32 {
853+
#if arch(wasm32)
854+
let ret = processConfigurationLogLevel(_: Configuration.LogLevel.bridgeJSLiftParameter(levelBytes, levelLength))
855+
return ret.bridgeJSLowerReturn()
856+
#else
857+
fatalError("Only available on WebAssembly")
858+
#endif
859+
}
860+
861+
@_expose(wasm, "bjs_echoInternalSupportedMethod")
862+
@_cdecl("bjs_echoInternalSupportedMethod")
863+
public func _bjs_echoInternalSupportedMethod(method: Int32) -> Int32 {
864+
#if arch(wasm32)
865+
let ret = echoInternalSupportedMethod(_: Internal.SupportedMethod.bridgeJSLiftParameter(method))
866+
return ret.bridgeJSLowerReturn()
867+
#else
868+
fatalError("Only available on WebAssembly")
869+
#endif
870+
}
871+
817872
@_expose(wasm, "bjs_createPropertyHolder")
818873
@_cdecl("bjs_createPropertyHolder")
819874
public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32) -> UnsafeMutableRawPointer {

Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,132 @@
18261826
}
18271827
}
18281828
},
1829+
{
1830+
"abiName" : "bjs_echoNetworkingAPIMethod",
1831+
"effects" : {
1832+
"isAsync" : false,
1833+
"isThrows" : false
1834+
},
1835+
"name" : "echoNetworkingAPIMethod",
1836+
"parameters" : [
1837+
{
1838+
"label" : "_",
1839+
"name" : "method",
1840+
"type" : {
1841+
"caseEnum" : {
1842+
"_0" : "Networking.API.Method"
1843+
}
1844+
}
1845+
}
1846+
],
1847+
"returnType" : {
1848+
"caseEnum" : {
1849+
"_0" : "Networking.API.Method"
1850+
}
1851+
}
1852+
},
1853+
{
1854+
"abiName" : "bjs_echoConfigurationLogLevel",
1855+
"effects" : {
1856+
"isAsync" : false,
1857+
"isThrows" : false
1858+
},
1859+
"name" : "echoConfigurationLogLevel",
1860+
"parameters" : [
1861+
{
1862+
"label" : "_",
1863+
"name" : "level",
1864+
"type" : {
1865+
"rawValueEnum" : {
1866+
"_0" : "Configuration.LogLevel",
1867+
"_1" : "String"
1868+
}
1869+
}
1870+
}
1871+
],
1872+
"returnType" : {
1873+
"rawValueEnum" : {
1874+
"_0" : "Configuration.LogLevel",
1875+
"_1" : "String"
1876+
}
1877+
}
1878+
},
1879+
{
1880+
"abiName" : "bjs_echoConfigurationPort",
1881+
"effects" : {
1882+
"isAsync" : false,
1883+
"isThrows" : false
1884+
},
1885+
"name" : "echoConfigurationPort",
1886+
"parameters" : [
1887+
{
1888+
"label" : "_",
1889+
"name" : "port",
1890+
"type" : {
1891+
"rawValueEnum" : {
1892+
"_0" : "Configuration.Port",
1893+
"_1" : "Int"
1894+
}
1895+
}
1896+
}
1897+
],
1898+
"returnType" : {
1899+
"rawValueEnum" : {
1900+
"_0" : "Configuration.Port",
1901+
"_1" : "Int"
1902+
}
1903+
}
1904+
},
1905+
{
1906+
"abiName" : "bjs_processConfigurationLogLevel",
1907+
"effects" : {
1908+
"isAsync" : false,
1909+
"isThrows" : false
1910+
},
1911+
"name" : "processConfigurationLogLevel",
1912+
"parameters" : [
1913+
{
1914+
"label" : "_",
1915+
"name" : "level",
1916+
"type" : {
1917+
"rawValueEnum" : {
1918+
"_0" : "Configuration.LogLevel",
1919+
"_1" : "String"
1920+
}
1921+
}
1922+
}
1923+
],
1924+
"returnType" : {
1925+
"rawValueEnum" : {
1926+
"_0" : "Configuration.Port",
1927+
"_1" : "Int"
1928+
}
1929+
}
1930+
},
1931+
{
1932+
"abiName" : "bjs_echoInternalSupportedMethod",
1933+
"effects" : {
1934+
"isAsync" : false,
1935+
"isThrows" : false
1936+
},
1937+
"name" : "echoInternalSupportedMethod",
1938+
"parameters" : [
1939+
{
1940+
"label" : "_",
1941+
"name" : "method",
1942+
"type" : {
1943+
"caseEnum" : {
1944+
"_0" : "Internal.SupportedMethod"
1945+
}
1946+
}
1947+
}
1948+
],
1949+
"returnType" : {
1950+
"caseEnum" : {
1951+
"_0" : "Internal.SupportedMethod"
1952+
}
1953+
}
1954+
},
18291955
{
18301956
"abiName" : "bjs_createPropertyHolder",
18311957
"effects" : {

Tests/prelude.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,15 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) {
354354
assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Get, 0);
355355
assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Post, 1);
356356

357+
assert.equal(exports.echoNetworkingAPIMethod(globalThis.Networking.API.Method.Get), globalThis.Networking.API.Method.Get);
358+
assert.equal(exports.echoConfigurationLogLevel(globalThis.Configuration.LogLevel.Debug), globalThis.Configuration.LogLevel.Debug);
359+
assert.equal(exports.echoConfigurationPort(globalThis.Configuration.Port.Http), globalThis.Configuration.Port.Http);
360+
assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Debug), globalThis.Configuration.Port.Development);
361+
assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Info), globalThis.Configuration.Port.Http);
362+
assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Warning), globalThis.Configuration.Port.Https);
363+
assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Error), globalThis.Configuration.Port.Development);
364+
assert.equal(exports.echoInternalSupportedMethod(globalThis.Networking.APIV2.Internal.SupportedMethod.Get), globalThis.Networking.APIV2.Internal.SupportedMethod.Get);
365+
357366
const converter = new exports.Converter();
358367
assert.equal(converter.toString(42), "42");
359368
assert.equal(converter.toString(123), "123");

0 commit comments

Comments
 (0)