From 3e597995d3742843a8aec3c7d4959c618c861c96 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 18 May 2026 15:01:44 +0900 Subject: [PATCH 1/5] Import static method in generic type --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 8 ++++++ ...ISwift2JavaGenerator+JavaTranslation.swift | 27 ++++++++++--------- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 10 ------- .../SpecializationTests.swift | 19 +++++++++++-- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index e9f13626..c848b1d1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -349,11 +349,19 @@ extension JNISwift2JavaGenerator { } for method in decl.methods { + if isEffectivelyGeneric && method.isStatic { + self.logger.debug("Skipping static method '\(method.name)' on unspecialized generic type '\(decl.effectiveJavaName)'") + continue + } printFunctionDowncallMethods(&printer, method) printer.println() } for variable in decl.variables { + if isEffectivelyGeneric && variable.isStatic { + self.logger.debug("Skipping static property '\(variable.name)' on unspecialized generic type '\(decl.effectiveJavaName)'") + continue + } printFunctionDowncallMethods(&printer, variable) printer.println() } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index e294f2ef..8272eed8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -366,20 +366,23 @@ extension JNISwift2JavaGenerator { guard let selfParameter else { return nil } + let selfType = selfParameter.selfType + let isGeneric = selfType.asNominalTypeDeclaration?.isGeneric == true + guard isGeneric else { + return nil + } - let isGeneric = selfParameter.selfType.asNominalTypeDeclaration?.isGeneric == true - if isGeneric { - return try self.translateParameter( - swiftType: .metatype(selfParameter.selfType), - parameterName: "selfTypePointer", - methodName: methodName, - parentName: parentName, - genericParameters: genericParameters, - genericRequirements: genericRequirements, - parameterPosition: nil, + switch selfParameter { + case .instance: + return TranslatedParameter( + parameter: JavaParameter(name: "selfTypePointer", type: .long), + conversion: .typeMetadataAddress(.placeholder), + ) + case .staticMethod, .initializer: + return TranslatedParameter( + parameter: JavaParameter(name: "selfTypePointer", type: .long), + conversion: .call(.constant(""), function: "$typeMetadataAddressDowncall"), ) - } else { - return nil } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 668760c7..ca2c3c61 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -213,11 +213,6 @@ final class Swift2JavaVisitor { functionSignature: signature, ) - if typeContext?.swiftNominal.isGeneric == true && typeContext?.isSpecialization != true && imported.isStatic { - log.debug("Skip importing static function in generic type: '\(node.qualifiedNameForDebug)'") - return - } - log.debug("Record imported method \(node.qualifiedNameForDebug)") if let typeContext { typeContext.methods.append(imported) @@ -451,11 +446,6 @@ final class Swift2JavaVisitor { functionSignature: signature, ) - if typeContext?.swiftNominal.isGeneric == true && typeContext?.isSpecialization != true && imported.isStatic { - log.debug("Skip importing static accessor in generic type: '\(node.qualifiedNameForDebug)'") - return - } - log.debug( "Record imported variable accessor \(kind == .getter || kind == .subscriptGetter ? "getter" : "setter"):\(node.qualifiedNameForDebug)" ) diff --git a/Tests/JExtractSwiftTests/SpecializationTests.swift b/Tests/JExtractSwiftTests/SpecializationTests.swift index 47e174d2..c30c0132 100644 --- a/Tests/JExtractSwiftTests/SpecializationTests.swift +++ b/Tests/JExtractSwiftTests/SpecializationTests.swift @@ -213,6 +213,10 @@ struct SpecializationTests { public func count() -> Int { return items.count } + + public static func elementDescription() -> String { + return "\(Element.self)" + } } public struct Fish { @@ -226,10 +230,21 @@ struct SpecializationTests { input: input, .jni, .java, - detectChunkByInitialLines: 1, + detectChunkByInitialLines: 2, expectedChunks: [ "public final class FishBox implements JNISwiftInstance {", - "public long count()", + """ + public long count() { + return FishBox.$count(this.$memoryAddress(), this.$typeMetadataAddress()); + } + private static native long $count(long selfPointer, long selfTypePointer); + """, + """ + public static java.lang.String elementDescription() { + return FishBox.$elementDescription($typeMetadataAddressDowncall()); + } + private static native java.lang.String $elementDescription(long selfTypePointer); + """ ], ) } From f4ca3a0f24074fd3dba9bc0b7062d5ec043eb373 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 18 May 2026 15:23:24 +0900 Subject: [PATCH 2/5] Remove skipping static member in opener --- .../MySwiftLibrary/BoxSpecialization.swift | 4 +++ .../example/swift/BoxSpecializationTest.java | 5 +++ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 -- .../JNI/JNIGenericTypeTests.swift | 33 +++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/BoxSpecialization.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/BoxSpecialization.swift index f6bf8fb7..f4164c8b 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/BoxSpecialization.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/BoxSpecialization.swift @@ -18,6 +18,10 @@ public struct Box: Hashable { public init(count: Int64) { self.count = count } + + public static func describeElement() -> String { + String(describing: Element.self) + } } public struct Fish: Hashable { diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java index c5aca8eb..741c0f50 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java @@ -36,6 +36,11 @@ void fishBoxHasExpectedMethods() throws Exception { Method setCount = fishBoxClass.getMethod("setCount", long.class); assertNotNull(setCount); + // Static method + Method describeElement = fishBoxClass.getMethod("describeElement"); + assertNotNull(describeElement); + assertEquals(String.class, describeElement.getReturnType()); + // Constrained extension method (only on FishBox, not on Box) Method describeFish = fishBoxClass.getMethod("describeFish"); assertNotNull(describeFish); diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 43391270..70162f8d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -1039,12 +1039,10 @@ extension JNISwift2JavaGenerator { printer.println() printer.printBraceBlock("extension \(type.swiftNominal.qualifiedName): \(protocolName)") { printer in for variable in type.variables { - if variable.isStatic { continue } printFunctionDecl(&printer, decl: variable, skipMethodBody: false) } for method in type.methods { - if method.isStatic { continue } printFunctionDecl(&printer, decl: method, skipMethodBody: false) } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 5f82b3bd..9c9b89e9 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -214,6 +214,39 @@ struct JNIGenericTypeTests { ) } + @Test + func generateOpenerForGenericStaticMethod() throws { + let input = + #""" + public struct Box { + public static func describeElement() -> String { + "\(Element.self)" + } + } + """# + + try assertOutput( + input: input, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + protocol _SwiftModule_Box_opener { + static func _describeElement(environment: UnsafeMutablePointer!, thisClass: jclass) -> jstring? + } + """, + #""" + extension Box: _SwiftModule_Box_opener { + static func _describeElement(environment: UnsafeMutablePointer!, thisClass: jclass) -> jstring? { + return Box.describeElement().getJNILocalRefValue(in: environment) + } + } + """#, + ] + ) + } + @Test func genericValueInEnumCase() throws { let input = From 660ac43b5a1acbebdf8583363ff002c0ba1826c9 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 18 May 2026 15:24:00 +0900 Subject: [PATCH 3/5] swift format --- Tests/JExtractSwiftTests/SpecializationTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/JExtractSwiftTests/SpecializationTests.swift b/Tests/JExtractSwiftTests/SpecializationTests.swift index c30c0132..f2303d45 100644 --- a/Tests/JExtractSwiftTests/SpecializationTests.swift +++ b/Tests/JExtractSwiftTests/SpecializationTests.swift @@ -213,7 +213,7 @@ struct SpecializationTests { public func count() -> Int { return items.count } - + public static func elementDescription() -> String { return "\(Element.self)" } @@ -244,7 +244,7 @@ struct SpecializationTests { return FishBox.$elementDescription($typeMetadataAddressDowncall()); } private static native java.lang.String $elementDescription(long selfTypePointer); - """ + """, ], ) } From 7a60c66bcd03748286abb92693ed1383a610eefd Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 18 May 2026 15:27:43 +0900 Subject: [PATCH 4/5] Use more simplified expression --- .../JNI/JNISwift2JavaGenerator+JavaTranslation.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 8272eed8..2d7762be 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -366,8 +366,7 @@ extension JNISwift2JavaGenerator { guard let selfParameter else { return nil } - let selfType = selfParameter.selfType - let isGeneric = selfType.asNominalTypeDeclaration?.isGeneric == true + let isGeneric = selfParameter.selfType.asNominalTypeDeclaration?.isGeneric == true guard isGeneric else { return nil } @@ -381,7 +380,7 @@ extension JNISwift2JavaGenerator { case .staticMethod, .initializer: return TranslatedParameter( parameter: JavaParameter(name: "selfTypePointer", type: .long), - conversion: .call(.constant(""), function: "$typeMetadataAddressDowncall"), + conversion: .constant("$typeMetadataAddressDowncall()"), ) } } From cfd76c695443a9ff9fe5a5ef39c1a5f36c2c5ee5 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 18 May 2026 15:31:23 +0900 Subject: [PATCH 5/5] Add call static function test --- .../test/java/com/example/swift/BoxSpecializationTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java index 741c0f50..73134d97 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/BoxSpecializationTest.java @@ -61,4 +61,9 @@ void boxHasGenericTypeParameter() { "Box should have one generic type parameter"); assertEquals("Element", Box.class.getTypeParameters()[0].getName()); } + + @Test + void callFishBoxStaticMethod() { + assertEquals("Fish", FishBox.describeElement()); + } }