From 3ff03290ffa3ab753f528b839bb035badf7c943d Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 27 Apr 2026 12:46:36 +0900 Subject: [PATCH 01/17] Fix code generation for single element tuple --- .../Sources/MySwiftLibrary/Tuples.swift | 4 ++ .../java/com/example/swift/TupleTest.java | 7 +++ ...ISwift2JavaGenerator+JavaTranslation.swift | 20 ++++++++ ...wift2JavaGenerator+NativeTranslation.swift | 13 ++++++ .../org/swift/swiftkit/core/tuple/Tuple1.java | 46 ------------------- .../JNI/JNITupleTests.swift | 35 ++++++++++++++ 6 files changed, 79 insertions(+), 46 deletions(-) delete mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/tuple/Tuple1.java diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift index 728d7ce01..ff5bce585 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Tuples.swift @@ -24,6 +24,10 @@ public func labeledTuple() -> (x: Int32, y: Int32) { (x: 10, y: 20) } +public func echoSingleTuple(input: (String)) -> (String) { + input +} + public func echoTriple(triple: (Bool, Double, Int64)) -> (Bool, Double, Int64) { triple } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java index 3eb36209f..b58e12ab8 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/TupleTest.java @@ -56,6 +56,13 @@ void labeledTuple() { Tuple2 check = result; } + @Test + void echoSingleTuple() { + var input = "Swift"; + String result = MySwiftLibrary.echoSingleTuple(input); + assertEquals(input, result); + } + @Test void echoTriple() { Tuple3 input = new Tuple3<>(true, 3.14, 100L); diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 667c3d4fa..ce669f730 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -630,6 +630,17 @@ extension JNISwift2JavaGenerator { conversion: .typeMetadataAddress(.placeholder), ) + case .tuple(let elements) where elements.count == 1: + return try translateParameter( + swiftType: elements[0].type, + parameterName: parameterName, + methodName: methodName, + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + parameterPosition: parameterPosition, + ) + case .tuple(let elements) where !elements.isEmpty: return try translateTupleParameter( elements: elements, @@ -1023,6 +1034,15 @@ extension JNISwift2JavaGenerator { case .tuple([]): return TranslatedResult(javaType: .void, nativeJavaType: .void, outParameters: [], conversion: .placeholder) + case .tuple(let elements) where elements.count == 1: + return try translateResult( + swiftType: elements[0].type, + methodName: methodName, + resultName: resultName, + genericParameters: genericParameters, + genericRequirements: genericRequirements, + ) + case .tuple(let elements) where !elements.isEmpty: return try translateTupleResult( methodName: methodName, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 8bd5eb6d5..5335b81b1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -332,6 +332,16 @@ extension JNISwift2JavaGenerator { conversionCheck: nil ) + case .tuple(let elements) where elements.count == 1: + return try translateParameter( + type: elements[0].type, + parameterName: parameterName, + methodName: methodName, + parentName: parentName, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + case .tuple(let elements) where !elements.isEmpty: return try translateTupleParameter( elements: elements, @@ -777,6 +787,9 @@ extension JNISwift2JavaGenerator { outParameters: [] ) + case .tuple(let elements) where elements.count == 1: + return try translateResult(swiftType: elements[0].type, methodName: methodName, resultName: resultName) + case .tuple(let elements) where !elements.isEmpty: return try translateTupleResult(methodName: methodName, elements: elements, resultName: resultName) diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/tuple/Tuple1.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/tuple/Tuple1.java deleted file mode 100644 index db1baa8e4..000000000 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/tuple/Tuple1.java +++ /dev/null @@ -1,46 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -package org.swift.swiftkit.core.tuple; - -/** - * Corresponds to Swift's built-in 1-element tuple type (T0). - * Elements are accessed via public final fields $0, $1, etc. - * @param the type of element 0 - */ -public class Tuple1 { - public final T0 $0; - - public Tuple1(T0 $0) { - this.$0 = $0; - } - - @Override - public boolean equals(Object other) { - if (this == other) return true; - if (!(other instanceof Tuple1)) return false; - Tuple1 o = (Tuple1) other; - return java.util.Objects.equals(this.$0, o.$0); - } - - @Override - public int hashCode() { - return java.util.Objects.hash($0); - } - - @Override - public String toString() { - return "Tuple1(" + $0 + ")"; - } -} diff --git a/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift b/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift index ef90053fa..24e9ebfde 100644 --- a/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift @@ -174,4 +174,39 @@ struct JNITupleTests { ] ) } + + @Test + func singleTuple() throws { + let input = """ + public func singleTuple(input: (String)) -> (String) { + input + } + """ + + try assertOutput( + input: input, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + private static native java.lang.String $singleTuple(java.lang.String input); + """ + ] + ) + + try assertOutput( + input: input, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public func Java_com_example_swift_SwiftModule__00024singleTuple__Ljava_lang_String_2(environment: UnsafeMutablePointer!, thisClass: jclass, input: jstring?) -> jstring? { + return SwiftModule.singleTuple(input: String(fromJNI: input, in: environment)).getJNILocalRefValue(in: environment) + } + """ + ] + ) + } } From 8e6b736e733923c86a9c429523bd867a465c8e62 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 27 Apr 2026 14:33:04 +0900 Subject: [PATCH 02/17] Fix in generic type parameter --- ...ISwift2JavaGenerator+JavaTranslation.swift | 7 ++++++ .../JNI/JNITupleTests.swift | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index ce669f730..e25b8ff98 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -1168,6 +1168,13 @@ extension JNISwift2JavaGenerator { } return .class(package: nil, name: generic.name) + case .tuple(let elements) where elements.count == 1: + return try translateGenericTypeParameter( + elements[0].type, + genericParameters: genericParameters, + genericRequirements: genericRequirements + ) + case .tuple(let elements): let elementJavaTypes = try elements.map { element in try translateGenericTypeParameter( diff --git a/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift b/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift index 24e9ebfde..3e49daf08 100644 --- a/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNITupleTests.swift @@ -209,4 +209,26 @@ struct JNITupleTests { ] ) } + + @Test + func singleTupleInGeneric() throws { + let input = """ + public struct Box {} + public var singleTupleInGeneric: Box<(String)> { + "input" + } + """ + + try assertOutput( + input: input, + .jni, + .java, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + public static Box getSingleTupleInGeneric(SwiftArena swiftArena) + """ + ] + ) + } } From a4127008e7adf8fcc95b58777d8ebda66a6bd6fd Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 20 Apr 2026 17:16:33 +0900 Subject: [PATCH 03/17] Add test case for generic value in enum case --- .../JNI/JNIGenericTypeTests.swift | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 7c97ad398..f6915e3e7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -213,4 +213,73 @@ struct JNIGenericTypeTests { ] ) } + + @Test + func genericValueInEnumCase() throws { + let input = + #""" + public struct MyID {} + + public enum MyEnum { + case foo(MyID) + } + """# + + try assertOutput( + input: input, + .jni, + .java, + detectChunkByInitialLines: 2, + expectedChunks: [ + #""" + public sealed interface Case { + record Foo(MyID arg0) implements Case { + record _NativeParameters(org.swift.swiftkit.core._OutSwiftGenericInstance arg0) {} + } + } + """# + ] + ) + + + try assertOutput( + input: input, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + #""" + @_cdecl("Java_com_example_swift_MyEnum__00024getAsFoo__JLorg_swift_swiftkit_core__1OutSwiftGenericInstance_2") + public func Java_com_example_swift_MyEnum__00024getAsFoo__JLorg_swift_swiftkit_core__1OutSwiftGenericInstance_2(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, arg0Out: jobject?) -> jobject? { + assert(selfPointer != 0, "selfPointer memory address was null") + let selfPointerBits$ = Int(Int64(fromJNI: selfPointer, in: environment)) + let selfPointer$ = UnsafeMutablePointer(bitPattern: selfPointerBits$) + guard let selfPointer$ else { + fatalError("selfPointer memory address was null in call to \(#function)!") + } + guard case .foo(let _0) = selfPointer$.pointee else { + fatalError("Expected enum case 'foo', but was '\(selfPointer$.pointee)'!") + } + let cache$ = _JNI_MyEnum.myEnumFooCache + let class$ = cache$.javaClass + let method$ = _JNIMethodIDCache.Method(name: "", signature: "(V)V") + let constructorID$ = cache$[method$] + + let _0$ = UnsafeMutablePointer>.allocate(capacity: 1) + _0$.initialize(to: _0) + let _0Bits$ = Int64(Int(bitPattern: _0$)) + do { + environment.interface.SetLongField(environment, arg0Out, _JNIMethodIDCache._OutSwiftGenericInstance.selfPointer, _0Bits$.getJNIValue(in: environment)) + let metadataPointer = unsafeBitCast(MyID.self, to: UnsafeRawPointer.self) + let metadataPointerBits$ = Int64(Int(bitPattern: metadataPointer)) + environment.interface.SetLongField(environment, arg0Out, _JNIMethodIDCache._OutSwiftGenericInstance.selfTypePointer, metadataPointerBits$.getJNIValue(in: environment)) + } + + let newObjectArgs$: [jvalue] = [] + return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) + } + """# + ] + ) + } } From 9f95bcf3f39d8ae0861fe81c9946cbded00069ec Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 20 Apr 2026 18:02:39 +0900 Subject: [PATCH 04/17] Add test variation for jextract wrapped class # Conflicts: # Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift --- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index c202c95ae..1f43a169f 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -20,11 +20,13 @@ import Testing @Suite struct JNIEnumTests { let source = """ - public enum MyEnum { - case first - case second(String) - case third(x: Int64, y: Int32) - } + public struct MyValue {} + + public enum MyEnum { + case first + case second(String) + case third(x: Int64, y: Int32, MyValue) + } """ @Test @@ -170,17 +172,17 @@ struct JNIEnumTests { record Second(java.lang.String arg0) implements Case { record _NativeParameters(java.lang.String arg0) {} } - record Third(long x, int y) implements Case { - record _NativeParameters(long x, int y) {} + record Third(long x, int y, MyValue arg2) implements Case { + record _NativeParameters(long x, int y, long arg2) {} } } """, """ - public Case getCase() { + public Case getCase(SwiftArena swiftArena) { return switch (this.getDiscriminator()) { case FIRST -> this.getAsFirst().orElseThrow(); case SECOND -> this.getAsSecond().orElseThrow(); - case THIRD -> this.getAsThird().orElseThrow(); + case THIRD -> this.getAsThird(swiftArena).orElseThrow(); } } """, @@ -207,8 +209,8 @@ struct JNIEnumTests { } """, """ - public static MyEnum third(long x, int y, SwiftArena swiftArena) { - return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$third(x, y), swiftArena); + public static MyEnum third(long x, int y, MyValue arg2, SwiftArena swiftArena) { + return MyEnum.wrapMemoryAddressUnsafe(MyEnum.$third(x, y, arg2.$memoryAddress()), swiftArena); } """, ] @@ -242,10 +244,16 @@ struct JNIEnumTests { } """, """ - @_cdecl("Java_com_example_swift_MyEnum__00024third__JI") - public func Java_com_example_swift_MyEnum__00024third__JI(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint) -> jlong { + @_cdecl("Java_com_example_swift_MyEnum__00024third__JIJ") + public func Java_com_example_swift_MyEnum__00024third__JIJ(environment: UnsafeMutablePointer!, thisClass: jclass, x: jlong, y: jint, arg2: jlong) -> jlong { + assert(arg2 != 0, "arg2 memory address was null") + let arg2Bits$ = Int(Int64(fromJNI: arg2, in: environment)) + let arg2$ = UnsafeMutablePointer(bitPattern: arg2Bits$) + guard let arg2$ else { + fatalError("arg2 memory address was null in call to \\(#function)!") + } let result$ = UnsafeMutablePointer.allocate(capacity: 1) - result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment), y: Int32(fromJNI: y, in: environment))) + result$.initialize(to: MyEnum.third(x: Int64(fromJNI: x, in: environment), y: Int32(fromJNI: y, in: environment), arg2$.pointee)) let resultBits$ = Int64(Int(bitPattern: result$)) return resultBits$.getJNILocalRefValue(in: environment) } @@ -280,12 +288,12 @@ struct JNIEnumTests { } """, """ - public java.util.Optional getAsThird() { + public java.util.Optional getAsThird(SwiftArena swiftArena) { if (getDiscriminator() != Discriminator.THIRD) { return Optional.empty(); } Case.Third._NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress()); - return Optional.of(new Case.Third($nativeParameters.x, $nativeParameters.y)); + return Optional.of(new Case.Third($nativeParameters.x, $nativeParameters.y, MyValue.wrapMemoryAddressUnsafe($nativeParameters.arg2, swiftArena))); } """, ] @@ -303,7 +311,7 @@ struct JNIEnumTests { """ enum _JNI_MyEnum { static let myEnumSecondCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Case$Second$_NativeParameters", methods: [.init(name: "", signature: "(Ljava/lang/String;)V")]) - static let myEnumThirdCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Case$Third$_NativeParameters", methods: [.init(name: "", signature: "(JI)V")]) + static let myEnumThirdCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Case$Third$_NativeParameters", methods: [.init(name: "", signature: "(JIJ)V")]) } """, """ @@ -325,14 +333,17 @@ struct JNIEnumTests { @_cdecl("Java_com_example_swift_MyEnum__00024getAsThird__J") public func Java_com_example_swift_MyEnum__00024getAsThird__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jobject? { ... - guard case .third(let x, let y) = selfPointer$.pointee else { + guard case .third(let x, let y, let _2) = selfPointer$.pointee else { fatalError("Expected enum case 'third', but was '\\(selfPointer$.pointee)'!") } let cache$ = _JNI_MyEnum.myEnumThirdCache let class$ = cache$.javaClass - let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JI)V") + let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JIJ)V") let constructorID$ = cache$[method$] - let newObjectArgs$: [jvalue] = [jvalue(j: x.getJNILocalRefValue(in: environment)), jvalue(i: y.getJNILocalRefValue(in: environment))] + let arg2$ = UnsafeMutablePointer.allocate(capacity: 1) + arg2$.initialize(to: _2) + let arg2Bits$ = Int64(Int(bitPattern: arg2$)) + let newObjectArgs$: [jvalue] = [jvalue(j: x.getJNILocalRefValue(in: environment)), jvalue(i: y.getJNILocalRefValue(in: environment)), jvalue(j: arg2Bits$.getJNILocalRefValue(in: environment))] return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) } """, From 02f95e47e8bf6d3a7b238a55744663cd3fb106bf Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 22 Apr 2026 11:49:04 +0900 Subject: [PATCH 05/17] _casenameValues getter --- ...ISwift2JavaGenerator+JavaTranslation.swift | 4 --- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 25 ++++++++++++++++--- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 16 ++++++++++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index e25b8ff98..e3739593d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -222,7 +222,6 @@ extension JNISwift2JavaGenerator { return TranslatedEnumCase( name: enumCase.name.firstCharacterUppercased, - enumName: enumCase.enumType.nominalTypeDecl.name, original: enumCase, translatedValues: translatedValues, parameterConversions: conversions, @@ -1650,9 +1649,6 @@ extension JNISwift2JavaGenerator { /// The corresponding Java case class (CamelCased) let name: String - /// The name of the translated enum - let enumName: String - /// The oringinal enum case. let original: ImportedEnumCase diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 3ad676be2..3fc772f61 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -348,7 +348,7 @@ extension JNISwift2JavaGenerator { if !isEffectivelyGeneric { for enumCase in type.cases { - printEnumCase(&printer, enumCase) + printEnumCase(&printer, type, enumCase) printer.println() } } @@ -393,7 +393,7 @@ extension JNISwift2JavaGenerator { } } - private func printEnumCase(_ printer: inout CodePrinter, _ enumCase: ImportedEnumCase) { + private func printEnumCase(_ printer: inout CodePrinter, _ enumType: ImportedNominalType, _ enumCase: ImportedEnumCase) { guard let translatedCase = self.translatedEnumCase(for: enumCase) else { return } @@ -404,12 +404,13 @@ extension JNISwift2JavaGenerator { // Print getAsCase method if !translatedCase.translatedValues.isEmpty { - printEnumGetAsCaseThunk(&printer, translatedCase) + printEnumGetAsCaseThunk(&printer, enumType, translatedCase) } } private func renderEnumCaseCacheInit(_ enumCase: TranslatedEnumCase) -> String { - let nativeParametersClassName = "\(enumCase.enumName)$Case$\(enumCase.name)$_NativeParameters" + let enumName = enumCase.original.enumType.nominalTypeDecl.name + let nativeParametersClassName = "\(enumName)$Case$\(enumCase.name)$_NativeParameters" let methodSignature = MethodSignature( resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType), @@ -429,8 +430,24 @@ extension JNISwift2JavaGenerator { private func printEnumGetAsCaseThunk( _ printer: inout CodePrinter, + _ enumType: ImportedNominalType, _ enumCase: TranslatedEnumCase, ) { + printer.printBraceBlock("extension \(enumType.effectiveSwiftTypeName)") { printer in + let associatedValueTypes = enumCase.original.parameters.map { param in + param.type.description + }.joined(separator: ", ") + printer.printBraceBlock("private var _\(enumCase.original.name)Values: (\(associatedValueTypes))?") { printer in + let params = enumCase.original.parameters.enumerated().map { i, param in + param.name ?? "_\(i)" + }.joined(separator: ", ") + printer.printBraceBlock("if case let .\(enumCase.original.name)(\(params)) = self") { printer in + printer.print("return (\(params))") + } + printer.print("return nil") + } + } + printCDecl( &printer, enumCase.getAsCaseFunction, diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 0f625d24e..f9fc3abc0 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -245,6 +245,8 @@ final class Swift2JavaVisitor { ) typeContext.cases.append(importedCase) + + self.synthesizeGetAssociatedValue(enumType: typeContext, enumCase: importedCase) } } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") @@ -477,6 +479,20 @@ final class Swift2JavaVisitor { } } + private func synthesizeGetAssociatedValue( + enumType: ImportedNominalType, + enumCase: ImportedEnumCase, + ) { + if enumCase.parameters.isEmpty { + return + } + let associatedValueTypes = enumCase.parameters.map { param in + param.type.description + }.joined(separator: ", ") + let decl: DeclSyntax = "@JavaExport private var _\(raw: enumCase.name)Values: (\(raw: associatedValueTypes))? { get }" + self.visit(decl: decl, in: enumType, sourceFilePath: enumType.sourceFilePath) + } + // ==== ----------------------------------------------------------------------- // MARK: Typealias declarations From 9f2748078b6d5a2768e086dbcdf20fbc3ea0e3bd Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 27 Apr 2026 11:07:23 +0900 Subject: [PATCH 06/17] Use generated getter method --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 77 ++++++++++++++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 42 +--------- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 2 +- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 2 +- .../JNI/JNIGenericTypeTests.swift | 2 +- 5 files changed, 70 insertions(+), 55 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 21d303d31..0c21487a0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -32,6 +32,8 @@ extension JNISwift2JavaGenerator { // NonNull, Unsigned and friends "org.swift.swiftkit.core.annotations.*", ] + + private static let globalArenaName = "SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA" } // MARK: Printing @@ -506,10 +508,9 @@ extension JNISwift2JavaGenerator { let members = translatedCase.translatedValues.map { $0.parameter.renderParameter() } - let caseName = enumCase.name.firstCharacterUppercased // Print record - printer.printBraceBlock("record \(caseName)(\(members.joined(separator: ", "))) implements Case") { + printer.printBraceBlock("record \(translatedCase.name)(\(members.joined(separator: ", "))) implements Case") { printer in let nativeParameters = zip(translatedCase.translatedValues, translatedCase.parameterConversions).map { value, @@ -533,10 +534,16 @@ extension JNISwift2JavaGenerator { guard let translatedCase = self.translatedEnumCase(for: enumCase) else { continue } - let arenaArgument = translatedCase.requiresSwiftArena ? "swiftArena" : "" - printer.print( - "case \(enumCase.name.uppercased()) -> this.getAs\(enumCase.name.firstCharacterUppercased)(\(arenaArgument)).orElseThrow();" - ) + if enumCase.parameters.isEmpty { + printer.print( + "case \(enumCase.name.uppercased()) -> new Case.\(translatedCase.name)();" + ) + } else { + let arenaArgument = translatedCase.requiresSwiftArena ? "swiftArena" : "" + printer.print( + "case \(enumCase.name.uppercased()) -> this.getAs\(translatedCase.name)(\(arenaArgument)).orElseThrow();" + ) + } } } } @@ -557,10 +564,59 @@ extension JNISwift2JavaGenerator { private func printEnumCases(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { for enumCase in decl.cases { guard let translatedCase = self.translatedEnumCase(for: enumCase) else { - return + continue + } + + let resultType = translatedCase.getAsCaseFunction.translatedFunctionSignature.result.javaType + if enumCase.parameters.isEmpty { + printer.print( + """ + public \(resultType) \(translatedCase.getAsCaseFunction.name)() { + if (getDiscriminator() == Discriminator.\(enumCase.name.uppercased())) { + return java.util.Optional.of(new Case.\(translatedCase.name)()); + } + return java.util.Optional.empty(); + } + """ + ) + } else { + let shouldGenerateGlobalArenaVariation = config.effectiveMemoryManagementMode.requiresGlobalArena && translatedCase.requiresSwiftArena + + let caseClassName = "Case.\(enumCase.name.firstCharacterUppercased)" + let args = + if enumCase.parameters.count == 1 { + "t" + } else { + (0.. + new \(caseClassName)(\(args)) + ); + } + """ + ) } - self.printJavaBindingWrapperMethod(&printer, translatedCase.getAsCaseFunction, skipMethodBody: false) printer.println() } } @@ -711,10 +767,9 @@ extension JNISwift2JavaGenerator { printer.printBraceBlock( "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" ) { printer in - let globalArenaName = "SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA" - let arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.name) + [globalArenaName] + let arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.name) + [Self.globalArenaName] let call = "\(translatedDecl.name)(\(arguments.joined(separator: ", ")))" - if translatedDecl.translatedFunctionSignature.result.javaType.isVoid { + if translatedSignature.result.javaType.isVoid { printer.print("\(call);") } else { printer.print("return \(call);") diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 3fc772f61..a4dc82f0b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -437,7 +437,7 @@ extension JNISwift2JavaGenerator { let associatedValueTypes = enumCase.original.parameters.map { param in param.type.description }.joined(separator: ", ") - printer.printBraceBlock("private var _\(enumCase.original.name)Values: (\(associatedValueTypes))?") { printer in + printer.printBraceBlock("fileprivate var _\(enumCase.original.name)Values: (\(associatedValueTypes))?") { printer in let params = enumCase.original.parameters.enumerated().map { i, param in param.name ?? "_\(i)" }.joined(separator: ", ") @@ -447,46 +447,6 @@ extension JNISwift2JavaGenerator { printer.print("return nil") } } - - printCDecl( - &printer, - enumCase.getAsCaseFunction, - ) { printer in - let selfPointer = enumCase.getAsCaseFunction.nativeFunctionSignature.selfParameter!.conversion.render( - &printer, - "selfPointer", - ) - let caseNames = enumCase.original.parameters.enumerated().map { idx, parameter in - parameter.name ?? "_\(idx)" - } - let caseNamesWithLet = caseNames.map { "let \($0)" } - let methodSignature = MethodSignature( - resultType: .void, - parameterTypes: enumCase.parameterConversions.map(\.native.javaType), - ) - printer.print( - """ - guard case .\(enumCase.original.name)(\(caseNamesWithLet.joined(separator: ", "))) = \(selfPointer).pointee else { - fatalError("Expected enum case '\(enumCase.original.name)', but was '\\(\(selfPointer).pointee)'!") - } - let cache$ = \(JNICaching.cacheName(for: enumCase.original.enumType)).\(JNICaching.cacheMemberName(for: enumCase.original)) - let class$ = cache$.javaClass - let method$ = _JNIMethodIDCache.Method(name: "", signature: "\(methodSignature.mangledName)") - let constructorID$ = cache$[method$] - """ - ) - let upcallArguments = zip(enumCase.parameterConversions, caseNames).map { conversion, caseName in - let nullConversion = !conversion.native.javaType.isPrimitive ? " ?? nil" : "" - let result = conversion.native.conversion.render(&printer, caseName) - return "jvalue(\(conversion.native.javaType.jniFieldName): \(result)\(nullConversion))" - } - printer.print( - """ - let newObjectArgs$: [jvalue] = [\(upcallArguments.joined(separator: ", "))] - return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) - """ - ) - } } private func printSwiftFunctionThunk( diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index f9fc3abc0..1a5f4701f 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -489,7 +489,7 @@ final class Swift2JavaVisitor { let associatedValueTypes = enumCase.parameters.map { param in param.type.description }.joined(separator: ", ") - let decl: DeclSyntax = "@JavaExport private var _\(raw: enumCase.name)Values: (\(raw: associatedValueTypes))? { get }" + let decl: DeclSyntax = "@JavaExport fileprivate var _\(raw: enumCase.name)Values: (\(raw: associatedValueTypes))? { get }" self.visit(decl: decl, in: enumType, sourceFilePath: enumType.sourceFilePath) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 1f43a169f..636370251 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -21,7 +21,7 @@ import Testing struct JNIEnumTests { let source = """ public struct MyValue {} - + public enum MyEnum { case first case second(String) diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index f6915e3e7..f06a1889d 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -219,7 +219,7 @@ struct JNIGenericTypeTests { let input = #""" public struct MyID {} - + public enum MyEnum { case foo(MyID) } From 8cdaa9240477b14faa4b25933e9a36ee1165f7e2 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 27 Apr 2026 16:30:32 +0900 Subject: [PATCH 07/17] Add JavaAccessModifier --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 4 ++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 5 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 5 ++ .../JavaTypes/JavaAccessModifier.swift | 46 +++++++++++++++++++ 5 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index afd32d7fe..8792aad6e 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -364,7 +364,7 @@ extension FFMSwift2JavaGenerator { let translated = self.translatedDecl(for: decl)! let methodName = translated.name - var modifiers = "public" + var modifiers: String = translated.acccessModifier?.description ?? "" switch decl.functionSignature.selfParameter { case .staticMethod, .initializer, nil: modifiers.append(" static") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index c7347d53b..92767c441 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -111,6 +111,9 @@ extension FFMSwift2JavaGenerator { /// Java function name. let name: String + /// Access level in Java + let acccessModifier: JavaAccessModifier? + /// Functional interfaces required for the Java method. let functionTypes: [TranslatedFunctionType] @@ -208,6 +211,7 @@ extension FFMSwift2JavaGenerator { return TranslatedFunctionDecl( name: javaName, + acccessModifier: JavaAccessModifier(decl.swiftDecl), functionTypes: funcTypes, translatedSignature: translatedSignature, loweredSignature: loweredSignature diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 0c21487a0..3a2dfeea9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -710,7 +710,10 @@ extension JNISwift2JavaGenerator { importedFunc: ImportedFunc? = nil, skipMethodBody: Bool, ) { - var modifiers = ["public"] + var modifiers: [String] = [] + if let acccessModifier = translatedDecl.acccessModifier { + modifiers.append(acccessModifier.description) + } if translatedDecl.isStatic { modifiers.append("static") } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index e3739593d..4221ecac0 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -160,6 +160,7 @@ extension JNISwift2JavaGenerator { let getAsCaseFunction = TranslatedFunctionDecl( name: getAsCaseName, + acccessModifier: .public, isStatic: false, isThrowing: false, isAsync: false, @@ -296,6 +297,7 @@ extension JNISwift2JavaGenerator { return TranslatedFunctionDecl( name: javaName, + acccessModifier: JavaAccessModifier(decl.swiftDecl), isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer, isThrowing: decl.isThrowing, isAsync: decl.isAsync, @@ -1670,6 +1672,9 @@ extension JNISwift2JavaGenerator { /// Java function name let name: String + /// Access level in Java + let acccessModifier: JavaAccessModifier? + let isStatic: Bool let isThrowing: Bool diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift new file mode 100644 index 000000000..f655f8412 --- /dev/null +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 SwiftSyntax + +enum JavaAccessModifier: String, CustomStringConvertible { + case `public` + case `private` + case `protected` + + var description: String { + rawValue + } +} + +extension JavaAccessModifier { + init?(_ syntax: some DeclSyntaxProtocol) { + guard let syntax = syntax.asProtocol(WithModifiersSyntax.self) else { + self = .private + return nil + } + for modifier in syntax.modifiers { + switch modifier.name.tokenKind { + case .keyword(.private), .keyword(.fileprivate), .keyword(.internal): + self = .private + return + case .keyword(.package), .keyword(.public), .keyword(.open): + self = .public + return + default: break + } + } + return nil + } +} From a329b396087c3621e6b61b402305b3e5702ff304 Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 27 Apr 2026 17:18:58 +0900 Subject: [PATCH 08/17] Remove unused generation logics --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 45 ++++----- ...ISwift2JavaGenerator+JavaTranslation.swift | 92 +------------------ ...ift2JavaGenerator+SwiftThunkPrinting.swift | 31 +------ .../JExtractSwiftLib/Swift2JavaVisitor.swift | 2 +- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 12 +-- .../JNI/JNIGenericTypeTests.swift | 4 +- 6 files changed, 27 insertions(+), 159 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 3a2dfeea9..2beec6dc8 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -510,16 +510,7 @@ extension JNISwift2JavaGenerator { } // Print record - printer.printBraceBlock("record \(translatedCase.name)(\(members.joined(separator: ", "))) implements Case") { - printer in - let nativeParameters = zip(translatedCase.translatedValues, translatedCase.parameterConversions).map { - value, - conversion in - "\(conversion.native.javaType) \(value.parameter.name)" - } - - printer.print("record _NativeParameters(\(nativeParameters.joined(separator: ", "))) {}") - } + printer.print("record \(translatedCase.name)(\(members.joined(separator: ", "))) implements Case {}") } } printer.println() @@ -567,11 +558,12 @@ extension JNISwift2JavaGenerator { continue } - let resultType = translatedCase.getAsCaseFunction.translatedFunctionSignature.result.javaType + let resultType = JavaType.optional(.class(package: nil, name: "Case.\(translatedCase.name)")) + let getAsCaseName = "getAs\(translatedCase.name)" if enumCase.parameters.isEmpty { printer.print( """ - public \(resultType) \(translatedCase.getAsCaseFunction.name)() { + public \(resultType) \(getAsCaseName)() { if (getDiscriminator() == Discriminator.\(enumCase.name.uppercased())) { return java.util.Optional.of(new Case.\(translatedCase.name)()); } @@ -580,7 +572,18 @@ extension JNISwift2JavaGenerator { """ ) } else { - let shouldGenerateGlobalArenaVariation = config.effectiveMemoryManagementMode.requiresGlobalArena && translatedCase.requiresSwiftArena + let requiresSwiftArena = translatedCase.requiresSwiftArena + let shouldGenerateGlobalArenaVariation = config.effectiveMemoryManagementMode.requiresGlobalArena && requiresSwiftArena + + if shouldGenerateGlobalArenaVariation { + printer.print( + """ + public \(resultType) \(getAsCaseName)() { + return \(getAsCaseName)(\(Self.globalArenaName)); + } + """ + ) + } let caseClassName = "Case.\(enumCase.name.firstCharacterUppercased)" let args = @@ -592,24 +595,14 @@ extension JNISwift2JavaGenerator { }.joined(separator: ", ") } var parameters: [String] = [] - if translatedCase.requiresSwiftArena { + if requiresSwiftArena { parameters.append("SwiftArena swiftArena") } - if shouldGenerateGlobalArenaVariation { - printer.print( - """ - public \(resultType) \(translatedCase.getAsCaseFunction.name)() { - return \(translatedCase.getAsCaseFunction.name)(\(Self.globalArenaName)); - } - """ - ) - } - printer.print( """ - public \(resultType) \(translatedCase.getAsCaseFunction.name)(\(parameters.joined(separator: ", "))) { - return get_\(translatedCase.original.name)Values(\(translatedCase.requiresSwiftArena ? "swiftArena" : "")).map(t -> + public \(resultType) \(getAsCaseName)(\(parameters.joined(separator: ", "))) { + return _get\(translatedCase.name)Values(\(requiresSwiftArena ? "swiftArena" : "")).map(t -> new \(caseClassName)(\(args)) ); } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 4221ecac0..00c510804 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -135,98 +135,12 @@ extension JNISwift2JavaGenerator { } let caseName = enumCase.name.firstCharacterUppercased - let enumName = enumCase.enumType.nominalTypeDecl.name - let caseType = JavaType.class(package: nil, name: "Case.\(caseName)") - let nativeParametersType = JavaType.class(package: nil, name: "Case.\(caseName)._NativeParameters") - let getAsCaseName = "getAs\(caseName)" - // If the case has no parameters, we can skip the native call. - let constructRecordConversion = JavaNativeConversionStep.method( - .constant("Optional"), - function: "of", - arguments: [ - .constructJavaClass( - .commaSeparated(conversions.map(\.translated.conversion)), - caseType, - ) - ], - ) - var exceptions: [JavaExceptionType] = [] - - if enumCase.parameters.contains(where: \.type.isArchDependingInteger) { - exceptions.append(.integerOverflow) - } - - let isGenericParent = enumCase.caseFunction.parentType?.asNominalTypeDeclaration?.isGeneric == true - - let getAsCaseFunction = TranslatedFunctionDecl( - name: getAsCaseName, - acccessModifier: .public, - isStatic: false, - isThrowing: false, - isAsync: false, - nativeFunctionName: "$\(getAsCaseName)", - parentName: SwiftQualifiedTypeName(enumName), - functionTypes: [], - translatedFunctionSignature: TranslatedFunctionSignature( - selfParameter: TranslatedParameter( - parameter: JavaParameter(name: "selfPointer", type: .long), - conversion: .aggregate( - [ - .ifStatement( - .constant("getDiscriminator() != Discriminator.\(caseName.uppercased())"), - thenExp: .constant("return Optional.empty();"), - ), - .valueMemoryAddress(.placeholder), - ] - ), - ), - selfTypeParameter: !isGenericParent - ? nil - : .init( - parameter: JavaParameter(name: "selfTypePointer", type: .long), - conversion: .typeMetadataAddress(.placeholder), - ), - parameters: [], - result: TranslatedResult( - javaType: .optional(caseType), - nativeJavaType: nativeParametersType, - outParameters: conversions.flatMap(\.translated.outParameters), - conversion: enumCase.parameters.isEmpty - ? constructRecordConversion - : .aggregate(variable: ("$nativeParameters", nativeParametersType), [constructRecordConversion]), - ), - exceptions: exceptions, - ), - nativeFunctionSignature: NativeFunctionSignature( - selfParameter: NativeParameter( - parameters: [JavaParameter(name: "selfPointer", type: .long)], - conversion: .extractSwiftValue(.placeholder, swiftType: .nominal(enumCase.enumType), allowNil: false), - indirectConversion: nil, - conversionCheck: nil, - ), - selfTypeParameter: !isGenericParent - ? nil - : .init( - parameters: [JavaParameter(name: "selfTypePointer", type: .long)], - conversion: .extractMetatypeValue(.placeholder), - indirectConversion: nil, - conversionCheck: nil, - ), - parameters: [], - result: NativeResult( - javaType: nativeParametersType, - conversion: .placeholder, - outParameters: conversions.flatMap(\.native.outParameters), - ), - ), - ) return TranslatedEnumCase( - name: enumCase.name.firstCharacterUppercased, + name: caseName, original: enumCase, translatedValues: translatedValues, - parameterConversions: conversions, - getAsCaseFunction: getAsCaseFunction, + parameterConversions: conversions ) } @@ -1660,8 +1574,6 @@ extension JNISwift2JavaGenerator { /// A list of parameter conversions let parameterConversions: [(translated: TranslatedResult, native: NativeResult)] - let getAsCaseFunction: TranslatedFunctionDecl - /// Returns whether the parameters require an arena var requiresSwiftArena: Bool { parameterConversions.contains(where: \.translated.conversion.requiresSwiftArena) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index a4dc82f0b..6ba919794 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -173,23 +173,6 @@ extension JNISwift2JavaGenerator { logger.info("[swift-java] Generated linker export list (\(generatedCDeclSymbolNames.count) symbols): \(outputPath)") } - private func printJNICache(_ printer: inout CodePrinter, _ type: ImportedNominalType) { - let targetCases = type.cases - .compactMap(translatedEnumCase(for:)) - .filter { !$0.translatedValues.isEmpty } - if targetCases.isEmpty { - return - } - - printer.printBraceBlock("enum \(JNICaching.cacheName(for: type))") { printer in - for translatedCase in targetCases { - printer.print( - "static let \(JNICaching.cacheMemberName(for: translatedCase)) = \(renderEnumCaseCacheInit(translatedCase))" - ) - } - } - } - /// Prints the extension needed to make allow upcalls from Swift to Java for protocols private func printSwiftInterfaceWrapper( _ printer: inout CodePrinter, @@ -308,7 +291,6 @@ extension JNISwift2JavaGenerator { private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { printHeader(&printer) - printJNICache(&printer, type) printer.println() switch type.swiftNominal.kind { @@ -408,17 +390,6 @@ extension JNISwift2JavaGenerator { } } - private func renderEnumCaseCacheInit(_ enumCase: TranslatedEnumCase) -> String { - let enumName = enumCase.original.enumType.nominalTypeDecl.name - let nativeParametersClassName = "\(enumName)$Case$\(enumCase.name)$_NativeParameters" - let methodSignature = MethodSignature( - resultType: .void, - parameterTypes: enumCase.parameterConversions.map(\.native.javaType), - ) - - return renderJNICacheInit(className: nativeParametersClassName, methods: [("", methodSignature)]) - } - private func renderJNICacheInit(className: String, methods: [(String, MethodSignature)]) -> String { let fullClassName = "\(javaPackagePath)/\(className)" let methods = methods.map { name, signature in @@ -437,7 +408,7 @@ extension JNISwift2JavaGenerator { let associatedValueTypes = enumCase.original.parameters.map { param in param.type.description }.joined(separator: ", ") - printer.printBraceBlock("fileprivate var _\(enumCase.original.name)Values: (\(associatedValueTypes))?") { printer in + printer.printBraceBlock("fileprivate func _get\(enumCase.name)Values() -> (\(associatedValueTypes))?") { printer in let params = enumCase.original.parameters.enumerated().map { i, param in param.name ?? "_\(i)" }.joined(separator: ", ") diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index 1a5f4701f..a517a3f70 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -489,7 +489,7 @@ final class Swift2JavaVisitor { let associatedValueTypes = enumCase.parameters.map { param in param.type.description }.joined(separator: ", ") - let decl: DeclSyntax = "@JavaExport fileprivate var _\(raw: enumCase.name)Values: (\(raw: associatedValueTypes))? { get }" + let decl: DeclSyntax = "@JavaExport fileprivate func _get\(raw: enumCase.name.firstCharacterUppercased)Values() -> (\(raw: associatedValueTypes))?" self.visit(decl: decl, in: enumType, sourceFilePath: enumType.sourceFilePath) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 636370251..fddcea5b1 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -166,15 +166,9 @@ struct JNIEnumTests { expectedChunks: [ """ public sealed interface Case { - record First() implements Case { - record _NativeParameters() {} - } - record Second(java.lang.String arg0) implements Case { - record _NativeParameters(java.lang.String arg0) {} - } - record Third(long x, int y, MyValue arg2) implements Case { - record _NativeParameters(long x, int y, long arg2) {} - } + record First() implements Case {} + record Second(java.lang.String arg0) implements Case {} + record Third(long x, int y, MyValue arg2) implements Case {} } """, """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index f06a1889d..9dba22dc4 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -233,9 +233,7 @@ struct JNIGenericTypeTests { expectedChunks: [ #""" public sealed interface Case { - record Foo(MyID arg0) implements Case { - record _NativeParameters(org.swift.swiftkit.core._OutSwiftGenericInstance arg0) {} - } + record Foo(MyID arg0) implements Case {} } """# ] From 50765ac955c1c9e7f15384766f75554cf05ae0bf Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 27 Apr 2026 17:49:15 +0900 Subject: [PATCH 09/17] Fix enum case initializer does not marked as `public` --- Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift index f655f8412..feffbce4e 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift @@ -41,6 +41,10 @@ extension JavaAccessModifier { default: break } } + if syntax.is(EnumCaseDeclSyntax.self) { + self = .public + return + } return nil } } From 2cb01d52c501fdc4a2f417825b591a1c6ec1b7de Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 27 Apr 2026 17:50:10 +0900 Subject: [PATCH 10/17] More reduce unused logics --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 4 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 53 ++++--------------- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 7 ++- 3 files changed, 16 insertions(+), 48 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 2beec6dc8..2a4ba428b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -505,8 +505,8 @@ extension JNISwift2JavaGenerator { continue } - let members = translatedCase.translatedValues.map { - $0.parameter.renderParameter() + let members = translatedCase.parameters.map { + $0.renderParameter() } // Print record diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 00c510804..63f71817e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -95,52 +95,26 @@ extension JNISwift2JavaGenerator { let importedTypes: [String: ImportedNominalType] func translate(enumCase: ImportedEnumCase) throws -> TranslatedEnumCase { - let nativeTranslation = NativeJavaTranslation( - config: self.config, - javaPackage: self.javaPackage, - javaClassLookupTable: self.javaClassLookupTable, - knownTypes: self.knownTypes, - protocolWrappers: self.protocolWrappers, - logger: self.logger, - ) - let methodName = "" // 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) }, - methodName: methodName, - parentName: parentName, - genericParameters: [], - genericRequirements: [], - ) - - let conversions = try enumCase.parameters.enumerated().map { idx, parameter in + let parameterResults = try enumCase.parameters.enumerated().map { idx, parameter in let resultName = parameter.name ?? "arg\(idx)" - var translatedResult = try self.translateResult( + let translatedResult = try self.translateResult( swiftType: parameter.type, methodName: methodName, resultName: resultName ) - translatedResult.conversion = .replacingPlaceholder( - translatedResult.conversion, - placeholder: "$nativeParameters.\(resultName)", - ) - let nativeResult = try nativeTranslation.translateResult( - swiftType: parameter.type, - methodName: methodName, - resultName: resultName + return ( + parameter: JavaParameter(name: resultName, type: translatedResult.javaType), + requiresSwiftArena: translatedResult.conversion.requiresSwiftArena ) - return (translated: translatedResult, native: nativeResult) } - let caseName = enumCase.name.firstCharacterUppercased - return TranslatedEnumCase( - name: caseName, + name: enumCase.name.firstCharacterUppercased, original: enumCase, - translatedValues: translatedValues, - parameterConversions: conversions + parameters: parameterResults.map(\.parameter), + requiresSwiftArena: parameterResults.contains(where: \.requiresSwiftArena) ) } @@ -1569,15 +1543,10 @@ extension JNISwift2JavaGenerator { let original: ImportedEnumCase /// A list of the translated associated values - let translatedValues: [TranslatedParameter] - - /// A list of parameter conversions - let parameterConversions: [(translated: TranslatedResult, native: NativeResult)] + let parameters: [JavaParameter] - /// Returns whether the parameters require an arena - var requiresSwiftArena: Bool { - parameterConversions.contains(where: \.translated.conversion.requiresSwiftArena) - } + /// Returns whether the associated values require an arena + let requiresSwiftArena: Bool } struct TranslatedFunctionDecl { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 6ba919794..0b96e374f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -384,9 +384,8 @@ extension JNISwift2JavaGenerator { printSwiftFunctionThunk(&printer, enumCase.caseFunction) printer.println() - // Print getAsCase method - if !translatedCase.translatedValues.isEmpty { - printEnumGetAsCaseThunk(&printer, enumType, translatedCase) + if !translatedCase.parameters.isEmpty { + printEnumGetAssociatedValueThunk(&printer, enumType, translatedCase) } } @@ -399,7 +398,7 @@ extension JNISwift2JavaGenerator { return #"_JNIMethodIDCache(className: "\#(fullClassName)", methods: [\#(methods)])"# } - private func printEnumGetAsCaseThunk( + private func printEnumGetAssociatedValueThunk( _ printer: inout CodePrinter, _ enumType: ImportedNominalType, _ enumCase: TranslatedEnumCase, From 3330cc403e113c616dfd498bcafca4a7b8cf682f Mon Sep 17 00:00:00 2001 From: Iceman Date: Mon, 27 Apr 2026 18:06:53 +0900 Subject: [PATCH 11/17] Fix test cases --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 6 +- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 81 ++++++++----------- .../JNI/JNIGenericTypeTests.swift | 32 +------- .../JExtractSwiftTests/JavaKeywordTests.swift | 5 +- 4 files changed, 43 insertions(+), 81 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 2a4ba428b..732ceef75 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -564,10 +564,10 @@ extension JNISwift2JavaGenerator { printer.print( """ public \(resultType) \(getAsCaseName)() { - if (getDiscriminator() == Discriminator.\(enumCase.name.uppercased())) { - return java.util.Optional.of(new Case.\(translatedCase.name)()); + if (getDiscriminator() != Discriminator.\(enumCase.name.uppercased())) { + return java.util.Optional.empty(); } - return java.util.Optional.empty(); + return java.util.Optional.of(new Case.\(translatedCase.name)()); } """ ) diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index fddcea5b1..336026b45 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -174,7 +174,7 @@ struct JNIEnumTests { """ public Case getCase(SwiftArena swiftArena) { return switch (this.getDiscriminator()) { - case FIRST -> this.getAsFirst().orElseThrow(); + case FIRST -> new Case.First(); case SECOND -> this.getAsSecond().orElseThrow(); case THIRD -> this.getAsThird(swiftArena).orElseThrow(); } @@ -262,34 +262,36 @@ struct JNIEnumTests { input: source, .jni, .java, - detectChunkByInitialLines: 1, + detectChunkByInitialLines: 2, expectedChunks: [ """ public java.util.Optional getAsFirst() { if (getDiscriminator() != Discriminator.FIRST) { - return Optional.empty(); + return java.util.Optional.empty(); } - return Optional.of(new Case.First()); + return java.util.Optional.of(new Case.First()); } """, """ public java.util.Optional getAsSecond() { - if (getDiscriminator() != Discriminator.SECOND) { - return Optional.empty(); - } - Case.Second._NativeParameters $nativeParameters = MyEnum.$getAsSecond(this.$memoryAddress()); - return Optional.of(new Case.Second($nativeParameters.arg0)); + return _getSecondValues().map(t -> + new Case.Second(t) + ); } """, """ + private java.util.Optional _getSecondValues() { + """, + """ public java.util.Optional getAsThird(SwiftArena swiftArena) { - if (getDiscriminator() != Discriminator.THIRD) { - return Optional.empty(); - } - Case.Third._NativeParameters $nativeParameters = MyEnum.$getAsThird(this.$memoryAddress()); - return Optional.of(new Case.Third($nativeParameters.x, $nativeParameters.y, MyValue.wrapMemoryAddressUnsafe($nativeParameters.arg2, swiftArena))); + return _getThirdValues(swiftArena).map(t -> + new Case.Third(t.$0, t.$1, t.$2) + ); } """, + """ + private java.util.Optional> _getThirdValues(SwiftArena swiftArena) { + """ ] ) } @@ -300,50 +302,37 @@ struct JNIEnumTests { input: source, .jni, .swift, - detectChunkByInitialLines: 1, + detectChunkByInitialLines: 2, expectedChunks: [ """ - enum _JNI_MyEnum { - static let myEnumSecondCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Case$Second$_NativeParameters", methods: [.init(name: "", signature: "(Ljava/lang/String;)V")]) - static let myEnumThirdCache = _JNIMethodIDCache(className: "com/example/swift/MyEnum$Case$Third$_NativeParameters", methods: [.init(name: "", signature: "(JIJ)V")]) + extension MyEnum { + fileprivate func _getSecondValues() -> (String)? { + if case let .second(_0) = self { + return (_0) + } + return nil + } } """, """ - @_cdecl("Java_com_example_swift_MyEnum__00024getAsSecond__J") - public func Java_com_example_swift_MyEnum__00024getAsSecond__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jobject? { - ... - guard case .second(let _0) = selfPointer$.pointee else { - fatalError("Expected enum case 'second', but was '\\(selfPointer$.pointee)'!") + extension MyEnum { + fileprivate func _getThirdValues() -> (Int64, Int32, MyValue)? { + if case let .third(x, y, _2) = self { + return (x, y, _2) + } + return nil } - let cache$ = _JNI_MyEnum.myEnumSecondCache - let class$ = cache$.javaClass - let method$ = _JNIMethodIDCache.Method(name: "", signature: "(Ljava/lang/String;)V") - let constructorID$ = cache$[method$] - let newObjectArgs$: [jvalue] = [jvalue(l: _0.getJNILocalRefValue(in: environment) ?? nil)] - return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) } """, """ - @_cdecl("Java_com_example_swift_MyEnum__00024getAsThird__J") - public func Java_com_example_swift_MyEnum__00024getAsThird__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jobject? { - ... - guard case .third(let x, let y, let _2) = selfPointer$.pointee else { - fatalError("Expected enum case 'third', but was '\\(selfPointer$.pointee)'!") - } - let cache$ = _JNI_MyEnum.myEnumThirdCache - let class$ = cache$.javaClass - let method$ = _JNIMethodIDCache.Method(name: "", signature: "(JIJ)V") - let constructorID$ = cache$[method$] - let arg2$ = UnsafeMutablePointer.allocate(capacity: 1) - arg2$.initialize(to: _2) - let arg2Bits$ = Int64(Int(bitPattern: arg2$)) - let newObjectArgs$: [jvalue] = [jvalue(j: x.getJNILocalRefValue(in: environment)), jvalue(i: y.getJNILocalRefValue(in: environment)), jvalue(j: arg2Bits$.getJNILocalRefValue(in: environment))] - return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) - } + public func Java_com_example_swift_MyEnum__00024_1getSecondValues + """, + """ + public func Java_com_example_swift_MyEnum__00024_1getThirdValues """, ], notExpectedChunks: [ - "public func Java_com_example_swift_MyEnum__00024getAsFirst__J(" + "fileprivate func _getFirstValues(" ] ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index 9dba22dc4..bd98cb3c7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -244,38 +244,10 @@ struct JNIGenericTypeTests { input: input, .jni, .swift, - detectChunkByInitialLines: 2, + detectChunkByInitialLines: 1, expectedChunks: [ #""" - @_cdecl("Java_com_example_swift_MyEnum__00024getAsFoo__JLorg_swift_swiftkit_core__1OutSwiftGenericInstance_2") - public func Java_com_example_swift_MyEnum__00024getAsFoo__JLorg_swift_swiftkit_core__1OutSwiftGenericInstance_2(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, arg0Out: jobject?) -> jobject? { - assert(selfPointer != 0, "selfPointer memory address was null") - let selfPointerBits$ = Int(Int64(fromJNI: selfPointer, in: environment)) - let selfPointer$ = UnsafeMutablePointer(bitPattern: selfPointerBits$) - guard let selfPointer$ else { - fatalError("selfPointer memory address was null in call to \(#function)!") - } - guard case .foo(let _0) = selfPointer$.pointee else { - fatalError("Expected enum case 'foo', but was '\(selfPointer$.pointee)'!") - } - let cache$ = _JNI_MyEnum.myEnumFooCache - let class$ = cache$.javaClass - let method$ = _JNIMethodIDCache.Method(name: "", signature: "(V)V") - let constructorID$ = cache$[method$] - - let _0$ = UnsafeMutablePointer>.allocate(capacity: 1) - _0$.initialize(to: _0) - let _0Bits$ = Int64(Int(bitPattern: _0$)) - do { - environment.interface.SetLongField(environment, arg0Out, _JNIMethodIDCache._OutSwiftGenericInstance.selfPointer, _0Bits$.getJNIValue(in: environment)) - let metadataPointer = unsafeBitCast(MyID.self, to: UnsafeRawPointer.self) - let metadataPointerBits$ = Int64(Int(bitPattern: metadataPointer)) - environment.interface.SetLongField(environment, arg0Out, _JNIMethodIDCache._OutSwiftGenericInstance.selfTypePointer, metadataPointerBits$.getJNIValue(in: environment)) - } - - let newObjectArgs$: [jvalue] = [] - return environment.interface.NewObjectA(environment, class$, constructorID$, newObjectArgs$) - } + public func Java_com_example_swift_MyEnum__00024_1getFooValues__J_3BLorg_swift_swiftkit_core__1OutSwiftGenericInstance_2(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, result_discriminator$: jbyteArray?, resultWrappedOut: jobject?) { """# ] ) diff --git a/Tests/JExtractSwiftTests/JavaKeywordTests.swift b/Tests/JExtractSwiftTests/JavaKeywordTests.swift index 135eda574..12bbce2f5 100644 --- a/Tests/JExtractSwiftTests/JavaKeywordTests.swift +++ b/Tests/JExtractSwiftTests/JavaKeywordTests.swift @@ -98,7 +98,7 @@ struct JavaKeywordTests { record Instanceof(java.lang.String arg0) implements Case { """, """ - private static native Case.Instanceof._NativeParameters $getAsInstanceof(long selfPointer); + public java.util.Optional getAsInstanceof() { """, ] ) @@ -109,7 +109,8 @@ struct JavaKeywordTests { .swift, expectedChunks: [ """ - @_cdecl("Java_com_example_swift_MyEnumWithValue__00024getAsInstanceof__J") + extension MyEnumWithValue { + fileprivate func _getInstanceofValues() -> (String)? { """ ] ) From 39ce10fb881efb044f2d62c99fc617f29fb3be1b Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 28 Apr 2026 10:58:02 +0900 Subject: [PATCH 12/17] Convert associated values in single function --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 62 +++++++---------- ...ISwift2JavaGenerator+JavaTranslation.swift | 69 ++++++++++++++----- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 6 +- .../JExtractSwiftLib/Swift2JavaVisitor.swift | 16 ----- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 2 +- 5 files changed, 84 insertions(+), 71 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 732ceef75..a4c13d007 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -558,34 +558,11 @@ extension JNISwift2JavaGenerator { continue } - let resultType = JavaType.optional(.class(package: nil, name: "Case.\(translatedCase.name)")) - let getAsCaseName = "getAs\(translatedCase.name)" - if enumCase.parameters.isEmpty { - printer.print( - """ - public \(resultType) \(getAsCaseName)() { - if (getDiscriminator() != Discriminator.\(enumCase.name.uppercased())) { - return java.util.Optional.empty(); - } - return java.util.Optional.of(new Case.\(translatedCase.name)()); - } - """ - ) - } else { - let requiresSwiftArena = translatedCase.requiresSwiftArena - let shouldGenerateGlobalArenaVariation = config.effectiveMemoryManagementMode.requiresGlobalArena && requiresSwiftArena - - if shouldGenerateGlobalArenaVariation { - printer.print( - """ - public \(resultType) \(getAsCaseName)() { - return \(getAsCaseName)(\(Self.globalArenaName)); - } - """ - ) - } - - let caseClassName = "Case.\(enumCase.name.firstCharacterUppercased)" + let caseType = JavaType.class(package: nil, name: "Case.\(translatedCase.name)") + let resultType = JavaType.optional(caseType) + if let getAsCaseFunctionDecl = translatedCase.getAsCaseFunction, + var getAsCaseFunction = self.translatedDecl(for: getAsCaseFunctionDecl) + { let args = if enumCase.parameters.count == 1 { "t" @@ -594,17 +571,28 @@ extension JNISwift2JavaGenerator { "t.$\(i)" }.joined(separator: ", ") } - var parameters: [String] = [] - if requiresSwiftArena { - parameters.append("SwiftArena swiftArena") - } - + let translatedResult = getAsCaseFunction.translatedFunctionSignature.result + getAsCaseFunction.translatedFunctionSignature.result.conversion = .method( + .placeToVar(translatedResult.conversion, name: "associatedValues$", type: translatedResult.javaType), + function: "map", + arguments: [ + .lambda( + args: ["t"], + body: .constructJavaClass(.constant(args), caseType) + ) + ] + ) + getAsCaseFunction.translatedFunctionSignature.result.javaType = resultType + printJavaBindingWrapperMethod(&printer, getAsCaseFunction, skipMethodBody: false) + } else { + let getAsCaseName = "getAs\(translatedCase.name)" printer.print( """ - public \(resultType) \(getAsCaseName)(\(parameters.joined(separator: ", "))) { - return _get\(translatedCase.name)Values(\(requiresSwiftArena ? "swiftArena" : "")).map(t -> - new \(caseClassName)(\(args)) - ); + public \(resultType) \(getAsCaseName)() { + if (getDiscriminator() != Discriminator.\(enumCase.name.uppercased())) { + return java.util.Optional.empty(); + } + return java.util.Optional.of(new Case.\(translatedCase.name)()); } """ ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 63f71817e..a645fb97b 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -15,6 +15,7 @@ import CodePrinting import SwiftJavaConfigurationShared import SwiftJavaJNICore +import SwiftSyntax extension JNISwift2JavaGenerator { var javaTranslator: JavaTranslation { @@ -110,10 +111,44 @@ extension JNISwift2JavaGenerator { ) } + let associatedValueTypes = enumCase.parameters.map { param in + param.type.description + }.joined(separator: ", ") + let javaCaseClassName = enumCase.name.firstCharacterUppercased + let resultType = knownTypes.optionalSugar( + .tuple( + enumCase.parameters.map { + SwiftTupleElement(label: nil, type: $0.type) + } + ) + ) + let getAsCaseFunction: ImportedFunc? = + if !enumCase.parameters.isEmpty { + // try translate( + ImportedFunc( + module: enumCase.enumType.nominalTypeDecl.moduleName, + swiftDecl: DeclSyntax("func getAs\(raw: javaCaseClassName)() -> (\(raw: associatedValueTypes))?"), + name: "getAs\(javaCaseClassName)", + apiKind: .function, + functionSignature: .init( + selfParameter: .instance(convention: .byValue, swiftType: .nominal(enumCase.enumType)), + parameters: [], + result: .init(convention: .direct, type: resultType), + effectSpecifiers: [], + genericParameters: [], + genericRequirements: [] + ) + ) + // ) + } else { + nil + } + return TranslatedEnumCase( - name: enumCase.name.firstCharacterUppercased, + name: javaCaseClassName, original: enumCase, parameters: parameterResults.map(\.parameter), + getAsCaseFunction: getAsCaseFunction, requiresSwiftArena: parameterResults.contains(where: \.requiresSwiftArena) ) } @@ -1141,7 +1176,6 @@ extension JNISwift2JavaGenerator { let javaNativeConversionStep: JavaNativeConversionStep = .tupleFromOutParams( - // try!-safe, because we know the result type is a class here (a Tuple of some form) tupleClassName: "\(javaResultType)", elements: tupleElements ) @@ -1545,37 +1579,39 @@ extension JNISwift2JavaGenerator { /// A list of the translated associated values let parameters: [JavaParameter] + let getAsCaseFunction: ImportedFunc? + /// Returns whether the associated values require an arena let requiresSwiftArena: Bool } struct TranslatedFunctionDecl { /// Java function name - let name: String + var name: String /// Access level in Java - let acccessModifier: JavaAccessModifier? + var acccessModifier: JavaAccessModifier? - let isStatic: Bool + var isStatic: Bool - let isThrowing: Bool + var isThrowing: Bool - let isAsync: Bool + var isAsync: Bool /// The name of the native function - let nativeFunctionName: String + var nativeFunctionName: String /// The name of the Java parent scope this function is declared in - let parentName: SwiftQualifiedTypeName + var parentName: SwiftQualifiedTypeName /// Functional interfaces required for the Java method. - let functionTypes: [TranslatedFunctionType] + var functionTypes: [TranslatedFunctionType] /// Function signature of the Java function the user will call - let translatedFunctionSignature: TranslatedFunctionSignature + var translatedFunctionSignature: TranslatedFunctionSignature /// Function signature of the native function that will be implemented by Swift - let nativeFunctionSignature: NativeFunctionSignature + var nativeFunctionSignature: NativeFunctionSignature /// Annotations to include on the Java function declaration var annotations: [JavaAnnotation] { @@ -1774,7 +1810,7 @@ extension JNISwift2JavaGenerator { /// E.g. `new Tuple2<>(result_0$[0], result_1$[0])` case tupleFromOutParams(tupleClassName: String, elements: [(outParamName: String, elementConversion: JavaNativeConversionStep)]) - indirect case placeToVar(JavaNativeConversionStep, name: String) + indirect case placeToVar(JavaNativeConversionStep, name: String, type: JavaType? = nil) /// `Arrays.stream(args)` static func arraysStream(_ argument: JavaNativeConversionStep) -> JavaNativeConversionStep { @@ -1944,9 +1980,10 @@ extension JNISwift2JavaGenerator { } return "new \(tupleClassName)(\(args.joined(separator: ", ")))" - case .placeToVar(let inner, let name): + case .placeToVar(let inner, let name, let type): let inner = inner.render(&printer, placeholder) - printer.print("var \(name) = \(inner);") + let type = type?.description ?? "var" + printer.print("\(type) \(name) = \(inner);") return name } } @@ -2014,7 +2051,7 @@ extension JNISwift2JavaGenerator { case .tupleFromOutParams(_, let elements): return elements.contains(where: { $0.elementConversion.requiresSwiftArena }) - case .placeToVar(let inner, _): + case .placeToVar(let inner, _, _): return inner.requiresSwiftArena } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 0b96e374f..bb7969d34 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -407,7 +407,7 @@ extension JNISwift2JavaGenerator { let associatedValueTypes = enumCase.original.parameters.map { param in param.type.description }.joined(separator: ", ") - printer.printBraceBlock("fileprivate func _get\(enumCase.name)Values() -> (\(associatedValueTypes))?") { printer in + printer.printBraceBlock("fileprivate func getAs\(enumCase.name)() -> (\(associatedValueTypes))?") { printer in let params = enumCase.original.parameters.enumerated().map { i, param in param.name ?? "_\(i)" }.joined(separator: ", ") @@ -417,6 +417,10 @@ extension JNISwift2JavaGenerator { printer.print("return nil") } } + + if let getAsCaseFunction = enumCase.getAsCaseFunction { + printSwiftFunctionThunk(&printer, getAsCaseFunction) + } } private func printSwiftFunctionThunk( diff --git a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift index a517a3f70..0f625d24e 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaVisitor.swift @@ -245,8 +245,6 @@ final class Swift2JavaVisitor { ) typeContext.cases.append(importedCase) - - self.synthesizeGetAssociatedValue(enumType: typeContext, enumCase: importedCase) } } catch { self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)") @@ -479,20 +477,6 @@ final class Swift2JavaVisitor { } } - private func synthesizeGetAssociatedValue( - enumType: ImportedNominalType, - enumCase: ImportedEnumCase, - ) { - if enumCase.parameters.isEmpty { - return - } - let associatedValueTypes = enumCase.parameters.map { param in - param.type.description - }.joined(separator: ", ") - let decl: DeclSyntax = "@JavaExport fileprivate func _get\(raw: enumCase.name.firstCharacterUppercased)Values() -> (\(raw: associatedValueTypes))?" - self.visit(decl: decl, in: enumType, sourceFilePath: enumType.sourceFilePath) - } - // ==== ----------------------------------------------------------------------- // MARK: Typealias declarations diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index 336026b45..ed5299999 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -291,7 +291,7 @@ struct JNIEnumTests { """, """ private java.util.Optional> _getThirdValues(SwiftArena swiftArena) { - """ + """, ] ) } From 02e0d6c8ac5804f2cb0b419993313cc0c2cf55d6 Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 28 Apr 2026 12:31:51 +0900 Subject: [PATCH 13/17] Revert "Add JavaAccessModifier" This reverts commit 7102952ac9d56b9cf6415382b8e6dbb4b960118d. # Conflicts: # Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift # Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 4 -- ...t2JavaGenerator+JavaBindingsPrinting.swift | 5 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 6 --- .../JavaTypes/JavaAccessModifier.swift | 50 ------------------- 5 files changed, 2 insertions(+), 65 deletions(-) delete mode 100644 Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 8792aad6e..afd32d7fe 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -364,7 +364,7 @@ extension FFMSwift2JavaGenerator { let translated = self.translatedDecl(for: decl)! let methodName = translated.name - var modifiers: String = translated.acccessModifier?.description ?? "" + var modifiers = "public" switch decl.functionSignature.selfParameter { case .staticMethod, .initializer, nil: modifiers.append(" static") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 92767c441..c7347d53b 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -111,9 +111,6 @@ extension FFMSwift2JavaGenerator { /// Java function name. let name: String - /// Access level in Java - let acccessModifier: JavaAccessModifier? - /// Functional interfaces required for the Java method. let functionTypes: [TranslatedFunctionType] @@ -211,7 +208,6 @@ extension FFMSwift2JavaGenerator { return TranslatedFunctionDecl( name: javaName, - acccessModifier: JavaAccessModifier(decl.swiftDecl), functionTypes: funcTypes, translatedSignature: translatedSignature, loweredSignature: loweredSignature diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index a4c13d007..18ff52ede 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -691,10 +691,7 @@ extension JNISwift2JavaGenerator { importedFunc: ImportedFunc? = nil, skipMethodBody: Bool, ) { - var modifiers: [String] = [] - if let acccessModifier = translatedDecl.acccessModifier { - modifiers.append(acccessModifier.description) - } + var modifiers = ["public"] if translatedDecl.isStatic { modifiers.append("static") } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index a645fb97b..3cff487af 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -124,7 +124,6 @@ extension JNISwift2JavaGenerator { ) let getAsCaseFunction: ImportedFunc? = if !enumCase.parameters.isEmpty { - // try translate( ImportedFunc( module: enumCase.enumType.nominalTypeDecl.moduleName, swiftDecl: DeclSyntax("func getAs\(raw: javaCaseClassName)() -> (\(raw: associatedValueTypes))?"), @@ -139,7 +138,6 @@ extension JNISwift2JavaGenerator { genericRequirements: [] ) ) - // ) } else { nil } @@ -220,7 +218,6 @@ extension JNISwift2JavaGenerator { return TranslatedFunctionDecl( name: javaName, - acccessModifier: JavaAccessModifier(decl.swiftDecl), isStatic: decl.isStatic || !decl.hasParent || decl.isInitializer, isThrowing: decl.isThrowing, isAsync: decl.isAsync, @@ -1589,9 +1586,6 @@ extension JNISwift2JavaGenerator { /// Java function name var name: String - /// Access level in Java - var acccessModifier: JavaAccessModifier? - var isStatic: Bool var isThrowing: Bool diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift deleted file mode 100644 index feffbce4e..000000000 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaAccessModifier.swift +++ /dev/null @@ -1,50 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2026 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 SwiftSyntax - -enum JavaAccessModifier: String, CustomStringConvertible { - case `public` - case `private` - case `protected` - - var description: String { - rawValue - } -} - -extension JavaAccessModifier { - init?(_ syntax: some DeclSyntaxProtocol) { - guard let syntax = syntax.asProtocol(WithModifiersSyntax.self) else { - self = .private - return nil - } - for modifier in syntax.modifiers { - switch modifier.name.tokenKind { - case .keyword(.private), .keyword(.fileprivate), .keyword(.internal): - self = .private - return - case .keyword(.package), .keyword(.public), .keyword(.open): - self = .public - return - default: break - } - } - if syntax.is(EnumCaseDeclSyntax.self) { - self = .public - return - } - return nil - } -} From 43a3d75c41fa4e0bbb031090092a4eb46b5e01ea Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 28 Apr 2026 12:35:33 +0900 Subject: [PATCH 14/17] Undo extra changes --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 7 ++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 30 +++++++++---------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 18ff52ede..3455cc2fe 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -32,8 +32,6 @@ extension JNISwift2JavaGenerator { // NonNull, Unsigned and friends "org.swift.swiftkit.core.annotations.*", ] - - private static let globalArenaName = "SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA" } // MARK: Printing @@ -748,9 +746,10 @@ extension JNISwift2JavaGenerator { printer.printBraceBlock( "\(annotationsStr)\(modifiers.joined(separator: " ")) \(resultType) \(translatedDecl.name)(\(parametersStr))\(throwsClause)" ) { printer in - let arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.name) + [Self.globalArenaName] + let globalArenaName = "SwiftMemoryManagement.DEFAULT_SWIFT_JAVA_AUTO_ARENA" + let arguments = translatedDecl.translatedFunctionSignature.parameters.map(\.parameter.name) + [globalArenaName] let call = "\(translatedDecl.name)(\(arguments.joined(separator: ", ")))" - if translatedSignature.result.javaType.isVoid { + if translatedDecl.translatedFunctionSignature.result.javaType.isVoid { printer.print("\(call);") } else { printer.print("return \(call);") diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index bb7969d34..0d5246dc1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -384,9 +384,7 @@ extension JNISwift2JavaGenerator { printSwiftFunctionThunk(&printer, enumCase.caseFunction) printer.println() - if !translatedCase.parameters.isEmpty { - printEnumGetAssociatedValueThunk(&printer, enumType, translatedCase) - } + printEnumGetAsCaseThunk(&printer, enumType, translatedCase) } private func renderJNICacheInit(className: String, methods: [(String, MethodSignature)]) -> String { @@ -398,27 +396,27 @@ extension JNISwift2JavaGenerator { return #"_JNIMethodIDCache(className: "\#(fullClassName)", methods: [\#(methods)])"# } - private func printEnumGetAssociatedValueThunk( + private func printEnumGetAsCaseThunk( _ printer: inout CodePrinter, _ enumType: ImportedNominalType, _ enumCase: TranslatedEnumCase, ) { - printer.printBraceBlock("extension \(enumType.effectiveSwiftTypeName)") { printer in - let associatedValueTypes = enumCase.original.parameters.map { param in - param.type.description - }.joined(separator: ", ") - printer.printBraceBlock("fileprivate func getAs\(enumCase.name)() -> (\(associatedValueTypes))?") { printer in - let params = enumCase.original.parameters.enumerated().map { i, param in - param.name ?? "_\(i)" + if let getAsCaseFunction = enumCase.getAsCaseFunction { + printer.printBraceBlock("extension \(enumType.effectiveSwiftTypeName)") { printer in + let associatedValueTypes = enumCase.original.parameters.map { param in + param.type.description }.joined(separator: ", ") - printer.printBraceBlock("if case let .\(enumCase.original.name)(\(params)) = self") { printer in - printer.print("return (\(params))") + printer.printBraceBlock("fileprivate func getAs\(enumCase.name)() -> (\(associatedValueTypes))?") { printer in + let params = enumCase.original.parameters.enumerated().map { i, param in + param.name ?? "_\(i)" + }.joined(separator: ", ") + printer.printBraceBlock("if case let .\(enumCase.original.name)(\(params)) = self") { printer in + printer.print("return (\(params))") + } + printer.print("return nil") } - printer.print("return nil") } - } - if let getAsCaseFunction = enumCase.getAsCaseFunction { printSwiftFunctionThunk(&printer, getAsCaseFunction) } } From 6ea50f5b7ee21f1ff2c42fac24d356ae674b8141 Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 28 Apr 2026 14:10:31 +0900 Subject: [PATCH 15/17] Fix test cases --- .../JExtractSwiftTests/JNI/JNIEnumTests.swift | 30 +++++++++---------- .../JNI/JNIGenericTypeTests.swift | 2 +- .../JExtractSwiftTests/JavaKeywordTests.swift | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift index ed5299999..ddb22bae8 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIEnumTests.swift @@ -262,7 +262,7 @@ struct JNIEnumTests { input: source, .jni, .java, - detectChunkByInitialLines: 2, + detectChunkByInitialLines: 1, expectedChunks: [ """ public java.util.Optional getAsFirst() { @@ -274,24 +274,22 @@ struct JNIEnumTests { """, """ public java.util.Optional getAsSecond() { - return _getSecondValues().map(t -> - new Case.Second(t) + ... + return associatedValues$.map((t) -> { + return new Case.Second(t); + } ); } """, """ - private java.util.Optional _getSecondValues() { - """, - """ public java.util.Optional getAsThird(SwiftArena swiftArena) { - return _getThirdValues(swiftArena).map(t -> - new Case.Third(t.$0, t.$1, t.$2) + ... + return associatedValues$.map((t) -> { + return new Case.Third(t.$0, t.$1, t.$2); + } ); } """, - """ - private java.util.Optional> _getThirdValues(SwiftArena swiftArena) { - """, ] ) } @@ -306,7 +304,7 @@ struct JNIEnumTests { expectedChunks: [ """ extension MyEnum { - fileprivate func _getSecondValues() -> (String)? { + fileprivate func getAsSecond() -> (String)? { if case let .second(_0) = self { return (_0) } @@ -316,7 +314,7 @@ struct JNIEnumTests { """, """ extension MyEnum { - fileprivate func _getThirdValues() -> (Int64, Int32, MyValue)? { + fileprivate func getAsThird() -> (Int64, Int32, MyValue)? { if case let .third(x, y, _2) = self { return (x, y, _2) } @@ -325,14 +323,14 @@ struct JNIEnumTests { } """, """ - public func Java_com_example_swift_MyEnum__00024_1getSecondValues + public func Java_com_example_swift_MyEnum__00024getAsSecond__J_3B """, """ - public func Java_com_example_swift_MyEnum__00024_1getThirdValues + public func Java_com_example_swift_MyEnum__00024getAsThird__J_3B_3J_3I_3J """, ], notExpectedChunks: [ - "fileprivate func _getFirstValues(" + "fileprivate func getAsFirst(" ] ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift index bd98cb3c7..e0c148d10 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIGenericTypeTests.swift @@ -247,7 +247,7 @@ struct JNIGenericTypeTests { detectChunkByInitialLines: 1, expectedChunks: [ #""" - public func Java_com_example_swift_MyEnum__00024_1getFooValues__J_3BLorg_swift_swiftkit_core__1OutSwiftGenericInstance_2(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, result_discriminator$: jbyteArray?, resultWrappedOut: jobject?) { + public func Java_com_example_swift_MyEnum__00024getAsFoo__J_3BLorg_swift_swiftkit_core__1OutSwiftGenericInstance_2(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong, result_discriminator$: jbyteArray?, resultWrappedOut: jobject?) { """# ] ) diff --git a/Tests/JExtractSwiftTests/JavaKeywordTests.swift b/Tests/JExtractSwiftTests/JavaKeywordTests.swift index 12bbce2f5..81588be3b 100644 --- a/Tests/JExtractSwiftTests/JavaKeywordTests.swift +++ b/Tests/JExtractSwiftTests/JavaKeywordTests.swift @@ -110,7 +110,7 @@ struct JavaKeywordTests { expectedChunks: [ """ extension MyEnumWithValue { - fileprivate func _getInstanceofValues() -> (String)? { + fileprivate func getAsInstanceof() -> (String)? { """ ] ) From de7ad26116f0365fb69eada636c16c3c51b3ba72 Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 30 Apr 2026 11:18:55 +0900 Subject: [PATCH 16/17] Add complex enum type to sample app --- .../Sources/MySwiftLibrary/Enums.swift | 6 +++ .../Sources/MySwiftLibrary/GenericType.swift | 2 + .../test/java/com/example/swift/EnumTest.java | 48 ++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift index 349062ef1..a97fe9497 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift @@ -31,3 +31,9 @@ public enum EnumWithCaseNameValue { public var message: String } } + +public enum ComplexAssociatedValues { + case generic(MyID, GenericEnum) + case typealiasedGeneric(id: MyIntID) + case array([String]) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift index 0968489eb..ff4d579ed 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/GenericType.swift @@ -22,6 +22,8 @@ public struct MyID { } } +public typealias MyIntID = MyID + public enum MyIDs { public static func makeIntID(_ value: Int) -> MyID { MyID(value) diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java index e9e01891a..52ca26aeb 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java @@ -19,6 +19,9 @@ import static org.junit.jupiter.api.Assertions.*; +import java.util.List; +import java.util.Optional; + public class EnumTest { @Test void enumWithValueCases() { @@ -42,11 +45,54 @@ void enumWithCaseNameValue() { try (var arena = SwiftArena.ofConfined()) { var success = EnumWithCaseNameValue.Success.init("ok", arena); EnumWithCaseNameValue e = EnumWithCaseNameValue.success(success, arena); - + switch (e.getCase(arena)) { case EnumWithCaseNameValue.Case.Success(var s): assertEquals("ok", s.getMessage()); } } } + + @Test + void complexAssociatedValues_generic() { + try (var arena = SwiftArena.ofConfined()) { + var e = ComplexAssociatedValues.generic( + MyIDs.makeIntID(42L, arena), + MySwiftLibrary.makeIntGenericEnum(arena), + arena + ); + assertEquals( + Optional.of("42"), + e.getAsGeneric(arena).map(v -> v.arg0().getDescription()) + ); + } + } + + @Test + void complexAssociatedValues_typealiasedGeneric() { + try (var arena = SwiftArena.ofConfined()) { + var e = ComplexAssociatedValues.typealiasedGeneric( + MyIDs.makeIntID(42L, arena), + arena + ); + assertEquals( + Optional.of("42"), + e.getAsTypealiasedGeneric(arena).map(v -> v.id().getDescription()) + ); + } + } + + @Test + void complexAssociatedValues_array() { + try (var arena = SwiftArena.ofConfined()) { + var e = ComplexAssociatedValues.array( + List.of("Hello", "World").toArray(String[]::new), + arena + ); + assertDoesNotThrow(() -> { + var value = e.getAsArray().orElseThrow().arg0(); + assertArrayEquals(new String[]{"Hello", "World"}, value); + }); + } + } } From c45645988912cbd74b596091b2a03fd87d5a35df Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 30 Apr 2026 11:47:45 +0900 Subject: [PATCH 17/17] Add optional associated value to example --- .../Sources/MySwiftLibrary/Enums.swift | 2 +- .../test/java/com/example/swift/EnumTest.java | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift index a97fe9497..be8f7445c 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Enums.swift @@ -34,6 +34,6 @@ public enum EnumWithCaseNameValue { public enum ComplexAssociatedValues { case generic(MyID, GenericEnum) - case typealiasedGeneric(id: MyIntID) + case optionalTypealiasedGeneric(id: MyIntID?) case array([String]) } diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java index 52ca26aeb..275963981 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumTest.java @@ -69,16 +69,16 @@ void complexAssociatedValues_generic() { } @Test - void complexAssociatedValues_typealiasedGeneric() { + void complexAssociatedValues_optionalTypealiasedGeneric() { try (var arena = SwiftArena.ofConfined()) { - var e = ComplexAssociatedValues.typealiasedGeneric( - MyIDs.makeIntID(42L, arena), + var e = ComplexAssociatedValues.optionalTypealiasedGeneric( + Optional.of(MyIDs.makeIntID(42L, arena)), arena ); - assertEquals( - Optional.of("42"), - e.getAsTypealiasedGeneric(arena).map(v -> v.id().getDescription()) - ); + assertDoesNotThrow(() -> { + var id = e.getAsOptionalTypealiasedGeneric(arena).orElseThrow().id(); + assertEquals(Optional.of("42"), id.map(MyID::getDescription)); + }); } } @@ -86,7 +86,7 @@ void complexAssociatedValues_typealiasedGeneric() { void complexAssociatedValues_array() { try (var arena = SwiftArena.ofConfined()) { var e = ComplexAssociatedValues.array( - List.of("Hello", "World").toArray(String[]::new), + new String[]{"Hello", "World"}, arena ); assertDoesNotThrow(() -> {