diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 85642fc0..117158df 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -689,47 +689,50 @@ public class ExportSwift { if let primitive = BridgeType(swiftType: type.trimmedDescription) { return primitive } - guard let identifier = type.as(IdentifierTypeSyntax.self) else { - return nil - } - guard let typeDecl = typeDeclResolver.lookupType(for: identifier) else { + guard let typeDecl = typeDeclResolver.resolve(type) else { return nil } + if let enumDecl = typeDecl.as(EnumDeclSyntax.self) { - let enumName = enumDecl.name.text - if let existingEnum = exportedEnums.first(where: { $0.name == enumName }) { - switch existingEnum.enumType { - case .simple: - return .caseEnum(existingEnum.swiftCallName) - case .rawValue: - let rawType = SwiftEnumRawType.from(existingEnum.rawType!)! - return .rawValueEnum(existingEnum.swiftCallName, rawType) - case .associatedValue: - return .associatedValueEnum(existingEnum.swiftCallName) - case .namespace: - return .namespaceEnum(existingEnum.swiftCallName) - } - } let swiftCallName = ExportSwift.computeSwiftCallName(for: enumDecl, itemName: enumDecl.name.text) let rawTypeString = enumDecl.inheritanceClause?.inheritedTypes.first { inheritedType in let typeName = inheritedType.type.trimmedDescription return Constants.supportedRawTypes.contains(typeName) }?.type.trimmedDescription - if let rawTypeString = rawTypeString, - let rawType = SwiftEnumRawType.from(rawTypeString) - { + if let rawTypeString, let rawType = SwiftEnumRawType.from(rawTypeString) { return .rawValueEnum(swiftCallName, rawType) } else { - return .caseEnum(swiftCallName) + let hasAnyCases = enumDecl.memberBlock.members.contains { member in + member.decl.is(EnumCaseDeclSyntax.self) + } + if !hasAnyCases { + return .namespaceEnum(swiftCallName) + } + let hasAssociatedValues = + enumDecl.memberBlock.members.contains { member in + guard let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) else { return false } + return caseDecl.elements.contains { element in + if let params = element.parameterClause?.parameters { + return !params.isEmpty + } + return false + } + } + if hasAssociatedValues { + return .associatedValueEnum(swiftCallName) + } else { + return .caseEnum(swiftCallName) + } } } guard typeDecl.is(ClassDeclSyntax.self) || typeDecl.is(ActorDeclSyntax.self) else { return nil } - return .swiftHeapObject(typeDecl.name.text) + let swiftCallName = ExportSwift.computeSwiftCallName(for: typeDecl, itemName: typeDecl.name.text) + return .swiftHeapObject(swiftCallName) } static let prelude: DeclSyntax = """ diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift index a7b183af..25200e13 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift @@ -109,4 +109,37 @@ class TypeDeclResolver { func lookupType(fullyQualified: QualifiedName) -> TypeDecl? { return typeDeclByQualifiedName[fullyQualified] } + + /// Resolves a type usage node to the corresponding nominal type declaration collected in this resolver. + /// + /// Supported inputs: + /// - IdentifierTypeSyntax (e.g. `Method`) — resolved relative to the lexical scope, preferring the innermost enclosing type. + /// - MemberTypeSyntax (e.g. `Networking.API.Method`) — resolved by recursively building the fully qualified name. + /// + /// Resolution strategy: + /// 1. If the node is IdentifierTypeSyntax, call `lookupType(for:)` which attempts scope-aware qualification via `tryQualify`. + /// 2. Otherwise, attempt to build a fully qualified name with `qualifiedComponents(from:)` and look it up with `lookupType(fullyQualified:)`. + /// + /// - Parameter type: The SwiftSyntax node representing a type appearance in source code. + /// - Returns: The nominal declaration (enum/class/actor/struct) if found, otherwise nil. + func resolve(_ type: TypeSyntax) -> TypeDecl? { + if let id = type.as(IdentifierTypeSyntax.self) { + return lookupType(for: id) + } + if let components = qualifiedComponents(from: type) { + return lookupType(fullyQualified: components) + } + return nil + } + + private func qualifiedComponents(from type: TypeSyntax) -> QualifiedName? { + if let m = type.as(MemberTypeSyntax.self) { + guard let base = qualifiedComponents(from: TypeSyntax(m.baseType)) else { return nil } + return base + [m.name.text] + } else if let id = type.as(IdentifierTypeSyntax.self) { + return [id.name.text] + } else { + return nil + } + } } diff --git a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift index 5e68c951..ff42f3c7 100644 --- a/Tests/BridgeJSRuntimeTests/ExportAPITests.swift +++ b/Tests/BridgeJSRuntimeTests/ExportAPITests.swift @@ -223,6 +223,8 @@ struct TestError: Error { return .light } +// MARK: - Namespace Enums + @JS enum Utils { @JS class Converter { @JS init() {} @@ -275,6 +277,31 @@ enum Internal { } } +@JS func echoNetworkingAPIMethod(_ method: Networking.API.Method) -> Networking.API.Method { + return method +} + +@JS func echoConfigurationLogLevel(_ level: Configuration.LogLevel) -> Configuration.LogLevel { + return level +} + +@JS func echoConfigurationPort(_ port: Configuration.Port) -> Configuration.Port { + return port +} + +@JS func processConfigurationLogLevel(_ level: Configuration.LogLevel) -> Configuration.Port { + switch level { + case .debug: return .development + case .info: return .http + case .warning: return .https + case .error: return .development + } +} + +@JS func echoInternalSupportedMethod(_ method: Internal.SupportedMethod) -> Internal.SupportedMethod { + return method +} + // MARK: - Property Tests // Simple class for SwiftHeapObject property testing diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift index 77d059a7..2c70c647 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.ExportSwift.swift @@ -814,6 +814,61 @@ public func _bjs_getTSTheme() -> Void { #endif } +@_expose(wasm, "bjs_echoNetworkingAPIMethod") +@_cdecl("bjs_echoNetworkingAPIMethod") +public func _bjs_echoNetworkingAPIMethod(method: Int32) -> Int32 { + #if arch(wasm32) + let ret = echoNetworkingAPIMethod(_: Networking.API.Method.bridgeJSLiftParameter(method)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoConfigurationLogLevel") +@_cdecl("bjs_echoConfigurationLogLevel") +public func _bjs_echoConfigurationLogLevel(levelBytes: Int32, levelLength: Int32) -> Void { + #if arch(wasm32) + let ret = echoConfigurationLogLevel(_: Configuration.LogLevel.bridgeJSLiftParameter(levelBytes, levelLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoConfigurationPort") +@_cdecl("bjs_echoConfigurationPort") +public func _bjs_echoConfigurationPort(port: Int32) -> Int32 { + #if arch(wasm32) + let ret = echoConfigurationPort(_: Configuration.Port.bridgeJSLiftParameter(port)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_processConfigurationLogLevel") +@_cdecl("bjs_processConfigurationLogLevel") +public func _bjs_processConfigurationLogLevel(levelBytes: Int32, levelLength: Int32) -> Int32 { + #if arch(wasm32) + let ret = processConfigurationLogLevel(_: Configuration.LogLevel.bridgeJSLiftParameter(levelBytes, levelLength)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_echoInternalSupportedMethod") +@_cdecl("bjs_echoInternalSupportedMethod") +public func _bjs_echoInternalSupportedMethod(method: Int32) -> Int32 { + #if arch(wasm32) + let ret = echoInternalSupportedMethod(_: Internal.SupportedMethod.bridgeJSLiftParameter(method)) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + @_expose(wasm, "bjs_createPropertyHolder") @_cdecl("bjs_createPropertyHolder") public func _bjs_createPropertyHolder(intValue: Int32, floatValue: Float32, doubleValue: Float64, boolValue: Int32, stringValueBytes: Int32, stringValueLength: Int32, jsObject: Int32) -> UnsafeMutableRawPointer { diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json index 03c61ada..8e65a50d 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.ExportSwift.json @@ -1826,6 +1826,132 @@ } } }, + { + "abiName" : "bjs_echoNetworkingAPIMethod", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "echoNetworkingAPIMethod", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Networking.API.Method" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "Networking.API.Method" + } + } + }, + { + "abiName" : "bjs_echoConfigurationLogLevel", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "echoConfigurationLogLevel", + "parameters" : [ + { + "label" : "_", + "name" : "level", + "type" : { + "rawValueEnum" : { + "_0" : "Configuration.LogLevel", + "_1" : "String" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Configuration.LogLevel", + "_1" : "String" + } + } + }, + { + "abiName" : "bjs_echoConfigurationPort", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "echoConfigurationPort", + "parameters" : [ + { + "label" : "_", + "name" : "port", + "type" : { + "rawValueEnum" : { + "_0" : "Configuration.Port", + "_1" : "Int" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Configuration.Port", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_processConfigurationLogLevel", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "processConfigurationLogLevel", + "parameters" : [ + { + "label" : "_", + "name" : "level", + "type" : { + "rawValueEnum" : { + "_0" : "Configuration.LogLevel", + "_1" : "String" + } + } + } + ], + "returnType" : { + "rawValueEnum" : { + "_0" : "Configuration.Port", + "_1" : "Int" + } + } + }, + { + "abiName" : "bjs_echoInternalSupportedMethod", + "effects" : { + "isAsync" : false, + "isThrows" : false + }, + "name" : "echoInternalSupportedMethod", + "parameters" : [ + { + "label" : "_", + "name" : "method", + "type" : { + "caseEnum" : { + "_0" : "Internal.SupportedMethod" + } + } + } + ], + "returnType" : { + "caseEnum" : { + "_0" : "Internal.SupportedMethod" + } + } + }, { "abiName" : "bjs_createPropertyHolder", "effects" : { diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 1459f21a..8094a465 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -354,6 +354,15 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Get, 0); assert.equal(globalThis.Networking.APIV2.Internal.SupportedMethod.Post, 1); + assert.equal(exports.echoNetworkingAPIMethod(globalThis.Networking.API.Method.Get), globalThis.Networking.API.Method.Get); + assert.equal(exports.echoConfigurationLogLevel(globalThis.Configuration.LogLevel.Debug), globalThis.Configuration.LogLevel.Debug); + assert.equal(exports.echoConfigurationPort(globalThis.Configuration.Port.Http), globalThis.Configuration.Port.Http); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Debug), globalThis.Configuration.Port.Development); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Info), globalThis.Configuration.Port.Http); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Warning), globalThis.Configuration.Port.Https); + assert.equal(exports.processConfigurationLogLevel(globalThis.Configuration.LogLevel.Error), globalThis.Configuration.Port.Development); + assert.equal(exports.echoInternalSupportedMethod(globalThis.Networking.APIV2.Internal.SupportedMethod.Get), globalThis.Networking.APIV2.Internal.SupportedMethod.Get); + const converter = new exports.Converter(); assert.equal(converter.toString(42), "42"); assert.equal(converter.toString(123), "123");