From 7ed99461f5b35f86a51bb5fa5347de23f591410e Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 9 Apr 2026 23:39:48 +0900 Subject: [PATCH] Refactor how we pass swift names into SwiftQualifiedTypeName In many places we need to either _ connect the names or $ etc. This also helps to properly support deeply nested type names, since we can have multiple parent names, and we can later on add generic parameters if we needed to e.g. Name.Something.self etc. We now call "flat name" to do snake case for c decls, and jni escaped names replacing the . with $ Overall this should give us more consistency with dealing with qualified Swift names --- ...MSwift2JavaGenerator+JavaTranslation.swift | 4 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 10 ++- Sources/JExtractSwiftLib/JNI/JNICaching.swift | 8 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 28 +++--- ...wift2JavaGenerator+NativeTranslation.swift | 10 +-- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 20 ++--- .../JNI/JNISwift2JavaGenerator.swift | 2 +- .../JExtractSwiftLib/SwiftKit+Printing.swift | 2 +- .../SwiftNominalTypeDeclaration.swift | 17 +++- .../SwiftTypes/SwiftQualifiedTypeName.swift | 50 +++++++++++ .../JExtractSwiftLib/ThunkNameRegistry.swift | 13 +-- .../NestedTypeThunkTests.swift | 85 +++++++++++++++++++ 13 files changed, 204 insertions(+), 49 deletions(-) create mode 100644 Sources/JExtractSwiftLib/SwiftTypes/SwiftQualifiedTypeName.swift create mode 100644 Tests/JExtractSwiftTests/NestedTypeThunkTests.swift diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index e3a61dc75..c7347d53b 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -913,10 +913,10 @@ extension FFMSwift2JavaGenerator { func translate( swiftType: SwiftType ) throws -> JavaType { - guard let nominalName = swiftType.asNominalTypeDeclaration?.name else { + guard let nominalDecl = swiftType.asNominalTypeDeclaration else { throw JavaTranslationError.unhandledType(swiftType) } - return .class(package: nil, name: nominalName) + return .class(package: nil, name: nominalDecl.qualifiedName) } } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index e736f9a9b..33c4e7933 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -170,9 +170,17 @@ package final class ImportedNominalType: ImportedDecl { .nominal(.init(nominalTypeDecl: swiftNominal)) } + /// Structured Java-facing type name — "FishBox" for specialized, "Box" for base + package var effectiveJavaTypeName: SwiftQualifiedTypeName { + if let specializedTypeName { + return SwiftQualifiedTypeName(specializedTypeName) + } + return swiftNominal.qualifiedTypeName + } + /// The effective Java-facing name — "FishBox" for specialized, "Box" for base var effectiveJavaName: String { - specializedTypeName ?? swiftNominal.qualifiedName + effectiveJavaTypeName.fullName } /// The simple Java class name (no qualification) for file naming purposes diff --git a/Sources/JExtractSwiftLib/JNI/JNICaching.swift b/Sources/JExtractSwiftLib/JNI/JNICaching.swift index 07f56876a..f91d7b7d4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNICaching.swift +++ b/Sources/JExtractSwiftLib/JNI/JNICaching.swift @@ -14,15 +14,15 @@ enum JNICaching { static func cacheName(for type: ImportedNominalType) -> String { - cacheName(for: type.effectiveJavaName) + cacheName(for: type.effectiveJavaTypeName) } static func cacheName(for type: SwiftNominalType) -> String { - cacheName(for: type.nominalTypeDecl.qualifiedName) + cacheName(for: type.nominalTypeDecl.qualifiedTypeName) } - private static func cacheName(for qualifiedName: String) -> String { - "_JNI_\(qualifiedName.replacingOccurrences(of: ".", with: "_"))" + private static func cacheName(for typeName: SwiftQualifiedTypeName) -> String { + "_JNI_\(typeName.fullFlatName)" } static func cacheMemberName(for enumCase: ImportedEnumCase) -> String { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 42c4ce381..8fcb074ed 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -190,7 +190,7 @@ extension JNISwift2JavaGenerator { private func printConcreteType(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { let savedPrintingTypeName = self.currentPrintingTypeName let savedPrintingType = self.currentPrintingType - self.currentPrintingTypeName = decl.effectiveJavaName + self.currentPrintingTypeName = decl.effectiveJavaTypeName self.currentPrintingType = decl defer { self.currentPrintingTypeName = savedPrintingTypeName @@ -757,7 +757,7 @@ extension JNISwift2JavaGenerator { // using the registry? let effectiveParentName = self.currentPrintingTypeName ?? translatedDecl.parentName let downcall = - "\(effectiveParentName).\(translatedDecl.nativeFunctionName)(\(arguments.joined(separator: ", ")))" + "\(effectiveParentName.fullName).\(translatedDecl.nativeFunctionName)(\(arguments.joined(separator: ", ")))" //=== Part 4: Convert the return value. if translatedFunctionSignature.resultType.javaType.isVoid { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 53db4d40d..06c430627 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -102,7 +102,7 @@ extension JNISwift2JavaGenerator { ) let methodName = "" // TODO: Used for closures, replace with better name? - let parentName = "" // TODO: Used for closures, replace with better name? + let parentName = SwiftQualifiedTypeName("") // TODO: Used for closures, replace with better name? let translatedValues = try self.translateParameters( enumCase.parameters.map { ($0.name, $0.type) }, @@ -153,7 +153,7 @@ extension JNISwift2JavaGenerator { isThrowing: false, isAsync: false, nativeFunctionName: "$\(getAsCaseName)", - parentName: enumName, + parentName: SwiftQualifiedTypeName(enumName), functionTypes: [], translatedFunctionSignature: TranslatedFunctionSignature( selfParameter: TranslatedParameter( @@ -230,12 +230,12 @@ extension JNISwift2JavaGenerator { // Types with no parent will be outputted inside a "module" class. // For specialized types, use the Java-facing name as the parent scope - let parentName: String + let parentName: SwiftQualifiedTypeName if let parentNominal = decl.parentType?.asNominalType?.nominalTypeDecl { let importedParent = importedTypes.values.first { $0.swiftNominal === parentNominal } - parentName = importedParent?.effectiveJavaName ?? parentNominal.qualifiedName + parentName = importedParent?.effectiveJavaTypeName ?? parentNominal.qualifiedTypeName } else { - parentName = swiftModuleName + parentName = SwiftQualifiedTypeName(swiftModuleName) } // Name. @@ -300,7 +300,7 @@ extension JNISwift2JavaGenerator { func translateFunctionType( name: String, swiftType: SwiftFunctionType, - parentName: String, + parentName: SwiftQualifiedTypeName, ) throws -> TranslatedFunctionType { var translatedParams: [TranslatedParameter] = [] @@ -332,7 +332,7 @@ extension JNISwift2JavaGenerator { func translate( functionSignature: SwiftFunctionSignature, methodName: String, - parentName: String, + parentName: SwiftQualifiedTypeName, ) throws -> TranslatedFunctionSignature { let parameters = try translateParameters( functionSignature.parameters.map { ($0.parameterName, $0.type) }, @@ -384,7 +384,7 @@ extension JNISwift2JavaGenerator { func translateParameters( _ parameters: [(name: String?, type: SwiftType)], methodName: String, - parentName: String, + parentName: SwiftQualifiedTypeName, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement], ) throws -> [TranslatedParameter] { @@ -405,7 +405,7 @@ extension JNISwift2JavaGenerator { func translateSelfParameter( _ selfParameter: SwiftSelfParameter?, methodName: String, - parentName: String, + parentName: SwiftQualifiedTypeName, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement], ) throws -> TranslatedParameter? { @@ -428,7 +428,7 @@ extension JNISwift2JavaGenerator { func translateSelfTypeParameter( _ selfParameter: SwiftSelfParameter?, methodName: String, - parentName: String, + parentName: SwiftQualifiedTypeName, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement], ) throws -> TranslatedParameter? { @@ -456,7 +456,7 @@ extension JNISwift2JavaGenerator { swiftType: SwiftType, parameterName: String, methodName: String, - parentName: String, + parentName: SwiftQualifiedTypeName, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement], parameterPosition: Int?, @@ -577,7 +577,7 @@ extension JNISwift2JavaGenerator { return TranslatedParameter( parameter: JavaParameter( name: parameterName, - type: .class(package: javaPackage, name: "\(parentName).\(methodName).\(parameterName)"), + type: .class(package: javaPackage, name: "\(parentName.fullName).\(methodName).\(parameterName)"), annotations: parameterAnnotations, ), conversion: .placeholder, @@ -640,7 +640,7 @@ extension JNISwift2JavaGenerator { elements: [SwiftTupleElement], parameterName: String, methodName: String, - parentName: String, + parentName: SwiftQualifiedTypeName, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement], parameterPosition: Int?, @@ -1668,7 +1668,7 @@ extension JNISwift2JavaGenerator { let nativeFunctionName: String /// The name of the Java parent scope this function is declared in - let parentName: String + let parentName: SwiftQualifiedTypeName /// Functional interfaces required for the Java method. let functionTypes: [TranslatedFunctionType] diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 2351ea474..954c865a5 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -31,7 +31,7 @@ extension JNISwift2JavaGenerator { functionSignature: SwiftFunctionSignature, translatedFunctionSignature: TranslatedFunctionSignature, methodName: String, - parentName: String + parentName: SwiftQualifiedTypeName ) throws -> NativeFunctionSignature { let parameters = try zip(translatedFunctionSignature.parameters, functionSignature.parameters).map { translatedParameter, @@ -93,7 +93,7 @@ extension JNISwift2JavaGenerator { type: SwiftType, parameterName: String, methodName: String, - parentName: String, + parentName: SwiftQualifiedTypeName, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] ) throws -> NativeParameter { @@ -343,7 +343,7 @@ extension JNISwift2JavaGenerator { elements: [SwiftTupleElement], parameterName: String, methodName: String, - parentName: String, + parentName: SwiftQualifiedTypeName, genericParameters: [SwiftGenericParameterDeclaration], genericRequirements: [SwiftGenericRequirement] ) throws -> NativeParameter { @@ -376,7 +376,7 @@ extension JNISwift2JavaGenerator { protocolType: SwiftType, methodName: String, parameterName: String, - parentName: String? + parentName: SwiftQualifiedTypeName? ) throws -> NativeParameter { switch protocolType { case .nominal(let nominalType): @@ -411,7 +411,7 @@ extension JNISwift2JavaGenerator { protocolTypes: [SwiftNominalType], methodName: String, parameterName: String, - parentName: String? + parentName: SwiftQualifiedTypeName? ) throws -> NativeParameter { // We allow Java implementations if we are able to generate the needed // Swift wrappers for all the protocol types. diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 4f0fb6c39..65eec3afd 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -322,7 +322,7 @@ extension JNISwift2JavaGenerator { private func printConcreteTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { let savedPrintingTypeName = self.currentPrintingTypeName let savedPrintingType = self.currentPrintingType - self.currentPrintingTypeName = type.effectiveJavaName + self.currentPrintingTypeName = type.effectiveJavaTypeName self.currentPrintingType = type defer { self.currentPrintingTypeName = savedPrintingTypeName @@ -558,7 +558,7 @@ extension JNISwift2JavaGenerator { let swiftClassName = JNISwift2JavaGenerator.protocolParameterWrapperClassName( methodName: decl.name, parameterName: parameterName, - parentName: decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName, + parentName: decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedTypeName ?? SwiftQualifiedTypeName(swiftModuleName), ) let implementingProtocols = protocolWrappers.map(\.wrapperName).joined(separator: ", ") @@ -791,7 +791,7 @@ extension JNISwift2JavaGenerator { private func printCDecl( _ printer: inout CodePrinter, javaMethodName: String, - parentName: String, + parentName: SwiftQualifiedTypeName, parameters: [JavaParameter], resultType: JavaType, _ body: (inout CodePrinter) -> Void, @@ -803,7 +803,7 @@ extension JNISwift2JavaGenerator { let cName = "Java_" + self.javaPackage.replacingOccurrences(of: ".", with: "_") - + "_\(parentName.replacingOccurrences(of: ".", with: "$").escapedJNIIdentifier)_" + + "_\(parentName.jniEscapedName.escapedJNIIdentifier)_" + javaMethodName.escapedJNIIdentifier + "__" + jniSignature.escapedJNIIdentifier @@ -860,7 +860,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: "$typeMetadataAddressDowncall", - parentName: type.effectiveJavaName, + parentName: type.effectiveJavaTypeName, parameters: [], resultType: .long, ) { printer in @@ -896,7 +896,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: "$toByteArray", - parentName: type.effectiveJavaName, + parentName: type.effectiveJavaTypeName, parameters: [ selfPointerParam ], @@ -917,7 +917,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: "$toByteArrayIndirectCopy", - parentName: type.effectiveJavaName, + parentName: type.effectiveJavaTypeName, parameters: [ selfPointerParam ], @@ -1081,11 +1081,11 @@ extension JNISwift2JavaGenerator { static func protocolParameterWrapperClassName( methodName: String, parameterName: String, - parentName: String?, + parentName: SwiftQualifiedTypeName?, ) -> String { let parent = if let parentName { - "\(parentName)_" + "\(parentName.fullFlatName)_" } else { "" } @@ -1095,7 +1095,7 @@ extension JNISwift2JavaGenerator { extension SwiftNominalTypeDeclaration { private var safeProtocolName: String { - self.qualifiedName.replacingOccurrences(of: ".", with: "_") + self.flatName } /// The name of the corresponding `@JavaInterface` of this type. diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index beffe9fb0..76825c7b5 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -55,7 +55,7 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { /// The Java-facing name of the type currently being printed. /// Used to override cached parentName in translations (needed for specializations /// where the same ImportedFunc is shared between base and specialized types) - var currentPrintingTypeName: String? + var currentPrintingTypeName: SwiftQualifiedTypeName? /// The type currently being printed (Java class or Swift thunks). /// Used to determine specialization context for correct code generation diff --git a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift index b8ceb6047..f5ac6a4a4 100644 --- a/Sources/JExtractSwiftLib/SwiftKit+Printing.swift +++ b/Sources/JExtractSwiftLib/SwiftKit+Printing.swift @@ -52,7 +52,7 @@ extension SwiftKitPrinting { extension SwiftKitPrinting.Names { static func getType(module: String, nominal: ImportedNominalType) -> String { - "swiftjava_getType_\(module)_\(nominal.swiftNominal.qualifiedName)" + "swiftjava_getType_\(module)_\(nominal.swiftNominal.qualifiedTypeName.fullFlatName)" } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index d44149715..ba70a578c 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -147,14 +147,25 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { return SwiftKnownTypeDeclKind(rawValue: "\(moduleName).\(name)") } - package var qualifiedName: String { + /// Structured qualified type name built from the parent chain + package var qualifiedTypeName: SwiftQualifiedTypeName { if let parent = self.parent { - return parent.qualifiedName + "." + name + return SwiftQualifiedTypeName(parent.qualifiedTypeName.components + [name]) } else { - return name + return SwiftQualifiedTypeName(name) } } + package var qualifiedName: String { + qualifiedTypeName.fullName + } + + /// Like `qualifiedName` but with dots replaced by underscores, suitable for + /// use in C symbol names and Java identifiers + package var flatName: String { + qualifiedTypeName.fullFlatName + } + var isReferenceType: Bool { switch kind { case .actor, .class: diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftQualifiedTypeName.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftQualifiedTypeName.swift new file mode 100644 index 000000000..2c170ddb7 --- /dev/null +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftQualifiedTypeName.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +/// A structured representation of a Swift qualified type name such as +/// `Logger.Message`, providing self-documenting conversions to the various +/// string forms needed across the codebase: +/// - **qualifiedName** (`Logger.Message`) - for Swift source +/// - **flatName** (`Logger_Message`) - for C symbols / `@_cdecl` and Java identifiers +/// - **leafName** (`Message`) - innermost component only +package struct SwiftQualifiedTypeName: Hashable, Sendable, CustomStringConvertible { + /// Name components from outermost to innermost, e.g. ["Logger", "Message"] + let components: [String] + + init(_ components: [String]) { + precondition(!components.isEmpty) + self.components = components + } + + init(_ leafName: String) { + self.components = [leafName] + } + + /// Leaf name (innermost), e.g. "Message" + var leafName: String { components.last! } + + /// Dot-separated for Swift source, e.g. "Logger.Message" + var fullName: String { components.joined(separator: ".") } + + /// Underscore-separated for C symbols and Java identifiers, e.g. "Logger_Message" + var fullFlatName: String { components.joined(separator: "_") } + + /// Dollar-separated for JNI C symbol parent names, e.g. "Logger$Message" + var jniEscapedName: String { components.joined(separator: "$") } + + /// CustomStringConvertible - uses fullName + package var description: String { fullName } + + var isNested: Bool { components.count > 1 } +} diff --git a/Sources/JExtractSwiftLib/ThunkNameRegistry.swift b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift index 03e25aa54..ac783f5c8 100644 --- a/Sources/JExtractSwiftLib/ThunkNameRegistry.swift +++ b/Sources/JExtractSwiftLib/ThunkNameRegistry.swift @@ -43,12 +43,13 @@ package struct ThunkNameRegistry { .joined() } - let name = - if let parent = decl.parentType { - "swiftjava_\(decl.module)_\(parent)_\(decl.name)\(suffix)" - } else { - "swiftjava_\(decl.module)_\(decl.name)\(suffix)" - } + let name: String + if let parent = decl.parentType, let nominalDecl = parent.asNominalTypeDeclaration { + let parentName = nominalDecl.flatName + name = "swiftjava_\(decl.module)_\(parentName)_\(decl.name)\(suffix)" + } else { + name = "swiftjava_\(decl.module)_\(decl.name)\(suffix)" + } let emittedCount = self.duplicateNames[name, default: 0] defer { self.duplicateNames[name] = emittedCount + 1 } diff --git a/Tests/JExtractSwiftTests/NestedTypeThunkTests.swift b/Tests/JExtractSwiftTests/NestedTypeThunkTests.swift new file mode 100644 index 000000000..cddba3687 --- /dev/null +++ b/Tests/JExtractSwiftTests/NestedTypeThunkTests.swift @@ -0,0 +1,85 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +final class NestedTypeThunkTests { + let input = + """ + import Swift + + public class Outer { + public class Inner { + public var value: Int + public init(value: Int) {} + public func describe() -> String { "" } + } + } + """ + + @Test("Nested type thunks: dots replaced with underscores in cdecl names") + func thunk_nestedType_swift() throws { + try assertOutput( + input: input, + .ffm, + .swift, + swiftModuleName: "FakeModule", + detectChunkByInitialLines: 1, + expectedChunks: [ + // The getType thunk should use Outer_Inner, not Outer.Inner + """ + @_cdecl("swiftjava_getType_FakeModule_Outer_Inner") + public func swiftjava_getType_FakeModule_Outer_Inner() -> UnsafeMutableRawPointer /* Any.Type */ { + return unsafeBitCast(Outer.Inner.self, to: UnsafeMutableRawPointer.self) + } + """, + // Member thunks should also use Outer_Inner + """ + @_cdecl("swiftjava_FakeModule_Outer_Inner_init_value") + public func swiftjava_FakeModule_Outer_Inner_init_value(_ value: Int, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: Outer.Inner.self).initialize(to: Outer.Inner(value: value)) + } + """, + """ + @_cdecl("swiftjava_FakeModule_Outer_Inner_value$get") + public func swiftjava_FakeModule_Outer_Inner_value$get(_ self: UnsafeRawPointer) -> Int { + return self.assumingMemoryBound(to: Outer.Inner.self).pointee.value + } + """, + ] + ) + } + + @Test("Nested type Java bindings: class names use underscores not dots") + func thunk_nestedType_java() throws { + try assertOutput( + input: input, + .ffm, + .java, + swiftModuleName: "FakeModule", + detectChunkByInitialLines: 1, + expectedChunks: [ + // Java class name for the inner class descriptor should not contain dots + "swiftjava_FakeModule_Outer_Inner_init_value", + "swiftjava_FakeModule_Outer_Inner_value$get", + "swiftjava_FakeModule_Outer_Inner_value$set", + ], + // Must NOT contain the dotted version + notExpectedChunks: [ + "swiftjava_FakeModule_Outer.Inner" + ] + ) + } +}