From d0be6874f39eb3355f9a30a15cfe717cf460e304 Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 12 May 2026 16:50:41 +0900 Subject: [PATCH 1/9] Clone ImportedFunc for specialization --- Sources/JExtractSwiftLib/ImportedDecls.swift | 157 ++++++++---------- ...t2JavaGenerator+JavaBindingsPrinting.swift | 12 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 26 +-- .../JNI/JNISwift2JavaGenerator.swift | 9 - .../SwiftTypes/SwiftFunctionSignature.swift | 18 +- .../SwiftNominalTypeDeclaration.swift | 5 +- .../SwiftTypes/SwiftType.swift | 5 + .../SpecializationTests.swift | 6 +- 8 files changed, 99 insertions(+), 139 deletions(-) diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 12becf550..8dd0ab7c7 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -48,81 +48,12 @@ package final class ImportedNominalType: ImportedDecl { } // Backing storage for member collections - private var _initializers: [ImportedFunc] = [] - private var _methods: [ImportedFunc] = [] - private var _variables: [ImportedFunc] = [] - private var _cases: [ImportedEnumCase] = [] - private var _inheritedTypes: [SwiftType] - private var _parent: SwiftNominalTypeDeclaration? - - // Additional members from constrained extensions that only apply to this specialization - package var constrainedInitializers: [ImportedFunc] = [] - package var constrainedMethods: [ImportedFunc] = [] - package var constrainedVariables: [ImportedFunc] = [] - - package var initializers: [ImportedFunc] { - get { - if let specializationBaseType { specializationBaseType.initializers + constrainedInitializers } else { _initializers } - } - set { - if let specializationBaseType { - let baseSet = Set(specializationBaseType.initializers.map { ObjectIdentifier($0) }) - constrainedInitializers = newValue.filter { !baseSet.contains(ObjectIdentifier($0)) } - } else { - _initializers = newValue - } - } - } - package var methods: [ImportedFunc] { - get { - if let specializationBaseType { specializationBaseType.methods + constrainedMethods } else { _methods } - } - set { - if let specializationBaseType { - let baseSet = Set(specializationBaseType.methods.map { ObjectIdentifier($0) }) - constrainedMethods = newValue.filter { !baseSet.contains(ObjectIdentifier($0)) } - } else { - _methods = newValue - } - } - } - package var variables: [ImportedFunc] { - get { - if let specializationBaseType { specializationBaseType.variables + constrainedVariables } else { _variables } - } - set { - if let specializationBaseType { - let baseSet = Set(specializationBaseType.variables.map { ObjectIdentifier($0) }) - constrainedVariables = newValue.filter { !baseSet.contains(ObjectIdentifier($0)) } - } else { - _variables = newValue - } - } - } - package var cases: [ImportedEnumCase] { - get { - if let specializationBaseType { specializationBaseType.cases } else { _cases } - } - set { - if let specializationBaseType { specializationBaseType.cases = newValue } else { _cases = newValue } - } - } - var inheritedTypes: [SwiftType] { - get { - if let specializationBaseType { specializationBaseType.inheritedTypes } else { _inheritedTypes } - } - set { - if let specializationBaseType { specializationBaseType.inheritedTypes = newValue } else { _inheritedTypes = newValue } - } - } - package var parent: SwiftNominalTypeDeclaration? { - get { - if let specializationBaseType { specializationBaseType.parent } else { _parent } - } - set { - if let specializationBaseType { specializationBaseType.parent = newValue } else { _parent = newValue } - } - } + package var initializers: [ImportedFunc] = [] + package var methods: [ImportedFunc] = [] + package var variables: [ImportedFunc] = [] + package var cases: [ImportedEnumCase] = [] + var inheritedTypes: [SwiftType] + package var parent: SwiftNominalTypeDeclaration? /// The Swift base type name, e.g. "Box" — always the unparameterized name package var baseTypeName: String { swiftNominal.qualifiedName } @@ -150,25 +81,46 @@ package final class ImportedNominalType: ImportedDecl { init(swiftNominal: SwiftNominalTypeDeclaration, lookupContext: SwiftTypeLookupContext) throws { self.swiftNominal = swiftNominal self.specializationBaseType = nil - self._inheritedTypes = + self.inheritedTypes = swiftNominal.inheritanceTypes?.compactMap { try? SwiftType($0.type, lookupContext: lookupContext) } ?? [] - self._parent = swiftNominal.parent + self.parent = swiftNominal.parent + self.swiftType = swiftNominal.asSwiftType } /// Init for creating a specialization private init(base: ImportedNominalType, specializedTypeName: String, genericArguments: [String: String]) { self.swiftNominal = base.swiftNominal self.specializationBaseType = base + + let selfType = SwiftType.nominal( + .init( + parent: swiftNominal.parent?.asSwiftNominalType, + nominalTypeDecl: SwiftNominalTypeDeclaration( + sourceFilePath: swiftNominal.sourceFilePath, + moduleName: swiftNominal.moduleName, + parent: swiftNominal.parent, + node: swiftNominal.syntax!, + customName: specializedTypeName + ), + genericArguments: nil + ) + ) + self.initializers = base.initializers.map { $0.clone(for: selfType) } + self.methods = base.methods.map { $0.clone(for: selfType) } + self.variables = base.variables.map { $0.clone(for: selfType) } + self.cases = base.cases.map { $0.clone(for: selfType) } + self.inheritedTypes = base.inheritedTypes + self.parent = base.parent + self.specializedTypeName = specializedTypeName self.genericArguments = genericArguments - self._inheritedTypes = [] + self.inheritedTypes = [] + self.swiftType = selfType } - var swiftType: SwiftType { - swiftNominal.asSwiftType - } + let swiftType: SwiftType /// Structured Java-facing type name — "FishBox" for specialized, "Box" for base package var effectiveJavaTypeName: SwiftQualifiedTypeName { @@ -258,17 +210,17 @@ struct SpecializationError: Error { public final class ImportedEnumCase: ImportedDecl, CustomStringConvertible { /// The case name - public var name: String + public let name: String /// The enum parameters - var parameters: [SwiftEnumCaseParameter] + let parameters: [SwiftEnumCaseParameter] - var swiftDecl: any DeclSyntaxProtocol + let swiftDecl: any DeclSyntaxProtocol - var enumType: SwiftNominalType + let enumType: SwiftNominalType /// A function that represents the Swift static "initializer" for cases - var caseFunction: ImportedFunc + let caseFunction: ImportedFunc init( name: String, @@ -295,6 +247,16 @@ public final class ImportedEnumCase: ImportedDecl, CustomStringConvertible { } """ } + + func clone(for parent: SwiftType) -> ImportedEnumCase { + ImportedEnumCase( + name: name, + parameters: parameters, + swiftDecl: swiftDecl, + enumType: enumType, + caseFunction: caseFunction.clone(for: parent) + ) + } } extension ImportedEnumCase: Hashable { @@ -308,17 +270,17 @@ extension ImportedEnumCase: Hashable { public final class ImportedFunc: ImportedDecl, CustomStringConvertible { /// Swift module name (e.g. the target name where a type or function was declared) - public var module: String + public let module: String /// The function name. /// e.g., "init" for an initializer or "foo" for "foo(a:b:)". - public var name: String + public let name: String - public var swiftDecl: any DeclSyntaxProtocol + public let swiftDecl: any DeclSyntaxProtocol - package var apiKind: SwiftAPIKind + package let apiKind: SwiftAPIKind - var functionSignature: SwiftFunctionSignature + let functionSignature: SwiftFunctionSignature public var signatureString: String { self.swiftDecl.signatureString @@ -403,6 +365,19 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } """ } + + func clone(for parent: SwiftType) -> ImportedFunc { + var functionSignature = functionSignature + assert(functionSignature.selfParameter?.selfType != nil) + functionSignature.selfParameter?.selfType = parent + return ImportedFunc( + module: module, + swiftDecl: swiftDecl, + name: name, + apiKind: apiKind, + functionSignature: functionSignature + ) + } } extension ImportedFunc: Hashable { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 3455cc2fe..e9f136269 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -208,15 +208,6 @@ extension JNISwift2JavaGenerator { } private func printConcreteType(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { - let savedPrintingTypeName = self.currentPrintingTypeName - let savedPrintingType = self.currentPrintingType - self.currentPrintingTypeName = decl.effectiveJavaTypeName - self.currentPrintingType = decl - defer { - self.currentPrintingTypeName = savedPrintingTypeName - self.currentPrintingType = savedPrintingType - } - printNominal(&printer, decl) { printer in printer.print( """ @@ -837,9 +828,8 @@ extension JNISwift2JavaGenerator { //=== Part 3: Downcall. // TODO: If we always generate a native method and a "public" method, we can actually choose our own thunk names // using the registry? - let effectiveParentName = self.currentPrintingTypeName ?? translatedDecl.parentName let downcall = - "\(effectiveParentName.fullName).\(translatedDecl.nativeFunctionName)(\(arguments.joined(separator: ", ")))" + "\(translatedDecl.parentName).\(translatedDecl.nativeFunctionName)(\(arguments.joined(separator: ", ")))" //=== Part 4: Convert the return value. if translatedFunctionSignature.result.javaType.isVoid { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 4d92206e1..35722e25b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -302,15 +302,6 @@ extension JNISwift2JavaGenerator { } private func printConcreteTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - let savedPrintingTypeName = self.currentPrintingTypeName - let savedPrintingType = self.currentPrintingType - self.currentPrintingTypeName = type.effectiveJavaTypeName - self.currentPrintingType = type - defer { - self.currentPrintingTypeName = savedPrintingTypeName - self.currentPrintingType = savedPrintingType - } - // Specialized types are treated as concrete even if the underlying Swift type is generic let isEffectivelyGeneric = type.swiftNominal.isGeneric && !type.isSpecialization @@ -436,13 +427,8 @@ extension JNISwift2JavaGenerator { &printer, translatedDecl, ) { printer in - if let parent = decl.parentType?.asNominalType, parent.nominalTypeDecl.isGeneric { - if self.currentPrintingType?.isSpecialization == true { - // Specializations use direct calls with concrete type, not protocol opening - self.printFunctionDowncall(&printer, decl) - } else { - self.printFunctionOpenerCall(&printer, decl) - } + if let parent = decl.parentType?.asNominalType, parent.isGeneric { + self.printFunctionOpenerCall(&printer, decl) } else { self.printFunctionDowncall(&printer, decl) } @@ -598,13 +584,13 @@ extension JNISwift2JavaGenerator { // Callee let callee: String = switch decl.functionSignature.selfParameter { - case .instance: - if let specializedType = self.currentPrintingType, specializedType.isSpecialization { + case .instance(_, let swiftType): + if let nominal = swiftType.asNominalTypeDeclaration, nominal.name != nominal.syntax?.name.text { // For specializations, use the concrete Swift type for pointer casting // (the cached conversion uses the raw generic type name which won't compile) self.renderSpecializedSelfPointer( &printer, - concreteSwiftType: specializedType.effectiveSwiftTypeName, + concreteSwiftType: swiftType.description, ) } else { nativeSignature.selfParameter!.conversion.render( @@ -722,7 +708,7 @@ extension JNISwift2JavaGenerator { printCDecl( &printer, javaMethodName: translatedDecl.nativeFunctionName, - parentName: self.currentPrintingTypeName ?? translatedDecl.parentName, + parentName: translatedDecl.parentName, parameters: parameters, resultType: nativeSignature.result.javaType, ) { printer in diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift index e1006a8c7..adcef96fd 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift @@ -56,15 +56,6 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator { /// Duplicate identifier tracking for the current batch of methods being generated. var currentJavaIdentifiers: JavaIdentifierFactory = JavaIdentifierFactory() - /// 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: SwiftQualifiedTypeName? - - /// The type currently being printed (Java class or Swift thunks). - /// Used to determine specialization context for correct code generation - var currentPrintingType: ImportedNominalType? - /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, /// and write an empty file for those. /// diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 805f0b491..a957c0ea1 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -69,9 +69,21 @@ enum SwiftSelfParameter: Equatable { case initializer(SwiftType) var selfType: SwiftType { - switch self { - case .instance(_, let swiftType), .staticMethod(let swiftType), .initializer(let swiftType): - return swiftType + get { + switch self { + case .instance(_, let swiftType), .staticMethod(let swiftType), .initializer(let swiftType): + return swiftType + } + } + set { + switch self { + case .instance(let convention, _): + self = .instance(convention: convention, swiftType: newValue) + case .staticMethod: + self = .staticMethod(newValue) + case .initializer: + self = .initializer(newValue) + } } } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 21edbfd96..db00676e5 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -88,7 +88,8 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { sourceFilePath: String, moduleName: String, parent: SwiftNominalTypeDeclaration?, - node: NominalTypeDeclSyntaxNode + node: NominalTypeDeclSyntaxNode, + customName: String? = nil ) { self.parent = parent self.syntax = node @@ -106,7 +107,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { case .structDecl: self.kind = .struct default: fatalError("Not a nominal type declaration") } - super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: node.name.text) + super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: customName ?? node.name.text) } lazy var firstInheritanceType: TypeSyntax? = { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 69480ee27..56481d206 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -212,6 +212,7 @@ struct SwiftNominalType: Equatable { parent.map { .nominal($0) } ?? nominalTypeDecl.parent.map { .nominal(SwiftNominalType(nominalTypeDecl: $0)) } self.sugarName = sugarName self.nominalTypeDecl = nominalTypeDecl + assert(genericArguments == nil || !genericArguments!.isEmpty) self.genericArguments = genericArguments } @@ -228,6 +229,10 @@ struct SwiftNominalType: Equatable { SwiftKnownType(kind: $0, genericArguments: genericArguments) } } + + var isGeneric: Bool { + genericArguments != nil + } } extension SwiftNominalType: CustomStringConvertible { diff --git a/Tests/JExtractSwiftTests/SpecializationTests.swift b/Tests/JExtractSwiftTests/SpecializationTests.swift index 3ca1e6783..c715c3e90 100644 --- a/Tests/JExtractSwiftTests/SpecializationTests.swift +++ b/Tests/JExtractSwiftTests/SpecializationTests.swift @@ -271,7 +271,7 @@ struct SpecializationTests { input: multiSpecializationInput, .jni, .swift, - detectChunkByInitialLines: 1, + detectChunkByInitialLines: 4, expectedChunks: [ // FishBox constrained extension method: direct downcall with concrete type """ @@ -279,7 +279,7 @@ struct SpecializationTests { public func Java_com_example_swift_FishBox__00024observeTheFish__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, selfTypePointer: jlong) { assert(selfPointer != 0, "selfPointer memory address was null") let selfPointerBits$ = Int(Int64(fromJNI: selfPointer, in: environment)) - let selfPointer$ = UnsafeMutablePointer>(bitPattern: selfPointerBits$) + let selfPointer$ = UnsafeMutablePointer(bitPattern: selfPointerBits$) guard let selfPointer$ else { fatalError("selfPointer memory address was null in call to \\(#function)!") } @@ -292,7 +292,7 @@ struct SpecializationTests { public func Java_com_example_swift_FishBox__00024count__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, selfTypePointer: jlong) -> jlong { assert(selfPointer != 0, "selfPointer memory address was null") let selfPointerBits$ = Int(Int64(fromJNI: selfPointer, in: environment)) - let selfPointer$ = UnsafeMutablePointer>(bitPattern: selfPointerBits$) + let selfPointer$ = UnsafeMutablePointer(bitPattern: selfPointerBits$) guard let selfPointer$ else { fatalError("selfPointer memory address was null in call to \\(#function)!") } From 141cb81188a2edc1f5a85114e90437e275c08895 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 13 May 2026 10:41:34 +0900 Subject: [PATCH 2/9] Make syntax as non-optional --- Sources/JExtractSwiftLib/ImportedDecls.swift | 4 ++-- .../JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- Sources/JExtractSwiftLib/Swift2JavaTranslator.swift | 8 ++++---- .../SwiftTypes/SwiftNominalTypeDeclaration.swift | 13 ++++++------- .../SwiftParsedModuleSymbolTableBuilder.swift | 1 + Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift | 2 +- .../SwiftTypes/SwiftTypeLookupContext.swift | 1 + 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 8dd0ab7c7..69477dd41 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -98,11 +98,11 @@ package final class ImportedNominalType: ImportedDecl { .init( parent: swiftNominal.parent?.asSwiftNominalType, nominalTypeDecl: SwiftNominalTypeDeclaration( + name: specializedTypeName, sourceFilePath: swiftNominal.sourceFilePath, moduleName: swiftNominal.moduleName, parent: swiftNominal.parent, - node: swiftNominal.syntax!, - customName: specializedTypeName + node: swiftNominal.syntax ), genericArguments: nil ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 35722e25b..c78596b98 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -585,7 +585,7 @@ extension JNISwift2JavaGenerator { let callee: String = switch decl.functionSignature.selfParameter { case .instance(_, let swiftType): - if let nominal = swiftType.asNominalTypeDeclaration, nominal.name != nominal.syntax?.name.text { + if let nominal = swiftType.asNominalTypeDeclaration, nominal.name != nominal.syntax.name.text { // For specializations, use the concrete Swift type for pointer casting // (the cached conversion uses the raw generic type name which won't compile) self.renderSpecializedSelfPointer( diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index cb58e819e..b780eb709 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -138,12 +138,12 @@ extension Swift2JavaTranslator { let dataProtocolDecl = (self.symbolTable[.foundationDataProtocol] ?? self.symbolTable[.essentialsDataProtocol])! if self.isUsing(where: { $0 == dataDecl || $0 == dataProtocolDecl }) { visitor.visit( - nominalDecl: dataDecl.syntax!.asNominal!, + nominalDecl: dataDecl.syntax.asNominal!, in: nil, sourceFilePath: "Foundation/FAKE_FOUNDATION_DATA.swift", ) visitor.visit( - nominalDecl: dataProtocolDecl.syntax!.asNominal!, + nominalDecl: dataProtocolDecl.syntax.asNominal!, in: nil, sourceFilePath: "Foundation/FAKE_FOUNDATION_DATAPROTOCOL.swift", ) @@ -154,7 +154,7 @@ extension Swift2JavaTranslator { if let dateDecl = self.symbolTable[.foundationDate] ?? self.symbolTable[.essentialsDate] { if self.isUsing(where: { $0 == dateDecl }) { visitor.visit( - nominalDecl: dateDecl.syntax!.asNominal!, + nominalDecl: dateDecl.syntax.asNominal!, in: nil, sourceFilePath: "Foundation/FAKE_FOUNDATION_DATE.swift", ) @@ -278,7 +278,7 @@ extension Swift2JavaTranslator { return nil } - guard swiftNominalDecl.syntax!.shouldExtract(config: config, log: log, in: nil) else { + guard swiftNominalDecl.syntax.shouldExtract(config: config, log: log, in: nil) else { return nil } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index db00676e5..68bf51fc4 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -63,8 +63,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { } /// The syntax node this declaration is derived from. - /// Can be `nil` if this is loaded from a .swiftmodule. - let syntax: NominalTypeDeclSyntaxNode? + let syntax: NominalTypeDeclSyntaxNode /// The kind of nominal type. let kind: Kind @@ -85,11 +84,11 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { /// Create a nominal type declaration from the syntax node for a nominal type /// declaration. init( + name: String, sourceFilePath: String, moduleName: String, parent: SwiftNominalTypeDeclaration?, node: NominalTypeDeclSyntaxNode, - customName: String? = nil ) { self.parent = parent self.syntax = node @@ -107,11 +106,11 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { case .structDecl: self.kind = .struct default: fatalError("Not a nominal type declaration") } - super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: customName ?? node.name.text) + super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: name) } lazy var firstInheritanceType: TypeSyntax? = { - guard let firstInheritanceType = self.syntax?.inheritanceClause?.inheritedTypes.first else { + guard let firstInheritanceType = self.syntax.inheritanceClause?.inheritedTypes.first else { return nil } @@ -119,13 +118,13 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { }() var inheritanceTypes: InheritedTypeListSyntax? { - self.syntax?.inheritanceClause?.inheritedTypes + self.syntax.inheritanceClause?.inheritedTypes } /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". lazy var isSendable: Bool = { // Check if Sendable is in the inheritance list - guard let inheritanceClause = self.syntax?.inheritanceClause else { + guard let inheritanceClause = self.syntax.inheritanceClause else { return false } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift index 900f2c003..8691cdfa1 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftParsedModuleSymbolTableBuilder.swift @@ -124,6 +124,7 @@ extension SwiftParsedModuleSymbolTableBuilder { // Otherwise, create the nominal type declaration. let nominalTypeDecl = SwiftNominalTypeDeclaration( + name: node.name.text, sourceFilePath: sourceFilePath, moduleName: moduleName, parent: parent, diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 56481d206..72731d638 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -273,7 +273,7 @@ extension SwiftNominalType.Parent: CustomStringConvertible { extension SwiftNominalType { var isSwiftJavaWrapper: Bool { - nominalTypeDecl.syntax?.attributes.contains(where: \.isSwiftJavaMacro) ?? false + nominalTypeDecl.syntax.attributes.contains(where: \.isSwiftJavaMacro) } var isProtocol: Bool { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift index 408ef6fd8..ffc1154b8 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftTypeLookupContext.swift @@ -181,6 +181,7 @@ class SwiftTypeLookupContext { } return SwiftNominalTypeDeclaration( + name: node.name.text, sourceFilePath: sourceFilePath, moduleName: self.symbolTable.moduleName, parent: try parentTypeDecl(for: node), From 1efd7e2f0f4411cf31a210255bb3f28b5da8d136 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 13 May 2026 10:43:04 +0900 Subject: [PATCH 3/9] Fix class immutability --- .../SwiftTypes/SwiftNominalTypeDeclaration.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift index 68bf51fc4..6a9ab6cab 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftNominalTypeDeclaration.swift @@ -77,7 +77,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { /// Identify this nominal declaration as one of the known standard library /// types, like 'Swift.Int[. - lazy var knownTypeKind: SwiftKnownTypeDeclKind? = { + private(set) lazy var knownTypeKind: SwiftKnownTypeDeclKind? = { self.computeKnownStandardLibraryType() }() @@ -109,7 +109,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { super.init(sourceFilePath: sourceFilePath, moduleName: moduleName, name: name) } - lazy var firstInheritanceType: TypeSyntax? = { + private(set) lazy var firstInheritanceType: TypeSyntax? = { guard let firstInheritanceType = self.syntax.inheritanceClause?.inheritedTypes.first else { return nil } @@ -122,7 +122,7 @@ package class SwiftNominalTypeDeclaration: SwiftTypeDeclaration { } /// Returns true if this type conforms to `Sendable` and therefore is "threadsafe". - lazy var isSendable: Bool = { + private(set) lazy var isSendable: Bool = { // Check if Sendable is in the inheritance list guard let inheritanceClause = self.syntax.inheritanceClause else { return false From 4dd1d90e2c2c01c45b9597a7364a076b3466b70c Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 13 May 2026 10:45:52 +0900 Subject: [PATCH 4/9] Remove unused branch logic --- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index c78596b98..fb9a78c02 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -584,20 +584,11 @@ extension JNISwift2JavaGenerator { // Callee let callee: String = switch decl.functionSignature.selfParameter { - case .instance(_, let swiftType): - if let nominal = swiftType.asNominalTypeDeclaration, nominal.name != nominal.syntax.name.text { - // For specializations, use the concrete Swift type for pointer casting - // (the cached conversion uses the raw generic type name which won't compile) - self.renderSpecializedSelfPointer( - &printer, - concreteSwiftType: swiftType.description, - ) - } else { + case .instance: nativeSignature.selfParameter!.conversion.render( &printer, "selfPointer", ) - } case .staticMethod(let selfType), .initializer(let selfType): "\(selfType)" case .none: @@ -968,27 +959,6 @@ extension JNISwift2JavaGenerator { } } - /// Renders self pointer extraction for a specialized (concrete) type. - /// Used instead of the generic opener mechanism when we know the exact type at compile time. - /// - /// - Returns: name of the created "self" variable (e.g., "selfPointer$") - private func renderSpecializedSelfPointer( - _ printer: inout CodePrinter, - concreteSwiftType: String, - ) -> String { - printer.print( - """ - assert(selfPointer != 0, "selfPointer memory address was null") - let selfPointerBits$ = Int(Int64(fromJNI: selfPointer, in: environment)) - let selfPointer$ = UnsafeMutablePointer<\(concreteSwiftType)>(bitPattern: selfPointerBits$) - guard let selfPointer$ else { - fatalError("selfPointer memory address was null in call to \\(#function)!") - } - """ - ) - return "selfPointer$.pointee" - } - /// Print the necessary conversion logic to go from a `jlong` to a `UnsafeMutablePointer` /// /// - Returns: name of the created "self" variable From 42e9c5c3c92ec512681f993344b087384d306bab Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 13 May 2026 10:54:53 +0900 Subject: [PATCH 5/9] formatting --- Sources/JExtractSwiftLib/ImportedDecls.swift | 1 - .../JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 8 ++++---- Tests/JExtractSwiftTests/SpecializationTests.swift | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 69477dd41..c5c4058ba 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -116,7 +116,6 @@ package final class ImportedNominalType: ImportedDecl { self.specializedTypeName = specializedTypeName self.genericArguments = genericArguments - self.inheritedTypes = [] self.swiftType = selfType } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index fb9a78c02..b5a23fbc2 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -585,10 +585,10 @@ extension JNISwift2JavaGenerator { let callee: String = switch decl.functionSignature.selfParameter { case .instance: - nativeSignature.selfParameter!.conversion.render( - &printer, - "selfPointer", - ) + nativeSignature.selfParameter!.conversion.render( + &printer, + "selfPointer", + ) case .staticMethod(let selfType), .initializer(let selfType): "\(selfType)" case .none: diff --git a/Tests/JExtractSwiftTests/SpecializationTests.swift b/Tests/JExtractSwiftTests/SpecializationTests.swift index c715c3e90..4e40ad7b9 100644 --- a/Tests/JExtractSwiftTests/SpecializationTests.swift +++ b/Tests/JExtractSwiftTests/SpecializationTests.swift @@ -271,7 +271,7 @@ struct SpecializationTests { input: multiSpecializationInput, .jni, .swift, - detectChunkByInitialLines: 4, + detectChunkByInitialLines: 1, expectedChunks: [ // FishBox constrained extension method: direct downcall with concrete type """ From c8da15041ad2e2b67a1f2363cd9a2f2d248833a6 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 13 May 2026 11:31:52 +0900 Subject: [PATCH 6/9] Update generic parameter check of SwiftNominalType --- .../JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index b5a23fbc2..23269bc77 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -427,7 +427,7 @@ extension JNISwift2JavaGenerator { &printer, translatedDecl, ) { printer in - if let parent = decl.parentType?.asNominalType, parent.isGeneric { + if let parent = decl.parentType?.asNominalType, parent.hasGenericParameter { self.printFunctionOpenerCall(&printer, decl) } else { self.printFunctionDowncall(&printer, decl) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index 72731d638..a114d1e9b 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -230,8 +230,13 @@ struct SwiftNominalType: Equatable { } } - var isGeneric: Bool { - genericArguments != nil + var hasGenericParameter: Bool { + (genericArguments ?? []).contains { + if case .genericParameter = $0 { + return true + } + return false + } } } From 2b959c49beff4c1224e50cebc00b8c85a7222e19 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 13 May 2026 13:23:58 +0900 Subject: [PATCH 7/9] Add ImportedFunc check --- Tests/JExtractSwiftTests/SpecializationTests.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tests/JExtractSwiftTests/SpecializationTests.swift b/Tests/JExtractSwiftTests/SpecializationTests.swift index 4e40ad7b9..47e174d24 100644 --- a/Tests/JExtractSwiftTests/SpecializationTests.swift +++ b/Tests/JExtractSwiftTests/SpecializationTests.swift @@ -118,6 +118,14 @@ struct SpecializationTests { // Both wrappers delegate to the same base type #expect(fishBox.specializationBaseType === toolBox.specializationBaseType, "Both should wrap the same base Box type") #expect(fishBox.specializationBaseType === translator.importedTypes["Box"], "Base should be the original Box") + + // Both wrappers have owned method models + let baseCountFunc: ImportedFunc = try #require(baseBox.methods.first(where: { $0.name == "count" })) + let fishCountFunc: ImportedFunc = try #require(fishBox.methods.first(where: { $0.name == "count" })) + let toolCountFunc: ImportedFunc = try #require(toolBox.methods.first(where: { $0.name == "count" })) + #expect(baseCountFunc.parentType?.description == "Box") + #expect(fishCountFunc.parentType?.description == "FishBox") + #expect(toolCountFunc.parentType?.description == "ToolBox") } @Test("Specializations keyed by base type contain all entries") From fd9023743943da6a73637971e230f4414c4dee09 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 13 May 2026 13:25:45 +0900 Subject: [PATCH 8/9] Fix for rebasing --- Sources/JExtractSwiftLib/ImportedDecls.swift | 2 +- Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index c5c4058ba..a66a9ef2f 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -104,7 +104,7 @@ package final class ImportedNominalType: ImportedDecl { parent: swiftNominal.parent, node: swiftNominal.syntax ), - genericArguments: nil + genericArguments: [] ) ) self.initializers = base.initializers.map { $0.clone(for: selfType) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift index a114d1e9b..6c758acd5 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift @@ -212,7 +212,6 @@ struct SwiftNominalType: Equatable { parent.map { .nominal($0) } ?? nominalTypeDecl.parent.map { .nominal(SwiftNominalType(nominalTypeDecl: $0)) } self.sugarName = sugarName self.nominalTypeDecl = nominalTypeDecl - assert(genericArguments == nil || !genericArguments!.isEmpty) self.genericArguments = genericArguments } @@ -231,7 +230,7 @@ struct SwiftNominalType: Equatable { } var hasGenericParameter: Bool { - (genericArguments ?? []).contains { + genericArguments.contains { if case .genericParameter = $0 { return true } From 30f0835b8f9d309156376d4c7ef99980797cb125 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 13 May 2026 13:46:42 +0900 Subject: [PATCH 9/9] Fixed parent names would randomly change --- Sources/JExtractSwiftLib/ImportedDecls.swift | 2 +- .../JNISwift2JavaGenerator+JavaTranslation.swift | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index a66a9ef2f..1f6b80d28 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -95,7 +95,7 @@ package final class ImportedNominalType: ImportedDecl { self.specializationBaseType = base let selfType = SwiftType.nominal( - .init( + SwiftNominalType( parent: swiftNominal.parent?.asSwiftNominalType, nominalTypeDecl: SwiftNominalTypeDeclaration( name: specializedTypeName, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 1045bb3fc..1f97f13ee 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -162,14 +162,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: SwiftQualifiedTypeName - if let parentNominal = decl.parentType?.asNominalType?.nominalTypeDecl { - let importedParent = importedTypes.values.first { $0.swiftNominal === parentNominal } - parentName = importedParent?.effectiveJavaTypeName ?? parentNominal.qualifiedTypeName - } else { - parentName = SwiftQualifiedTypeName(swiftModuleName) - } + let parentName = + if let parent = decl.parentType?.asNominalTypeDeclaration { + parent.qualifiedTypeName + } else { + SwiftQualifiedTypeName(swiftModuleName) + } // Name. let javaName = javaIdentifiers.makeJavaMethodName(decl)