From 29f3d18ba2a1660c297425c8262210bb0128ba8a Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 12 Jul 2025 12:49:02 +0200 Subject: [PATCH 1/6] add support for variables --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 19 +++++++ .../MySwiftLibrary/MySwiftLibrary.swift | 2 + .../Sources/MySwiftLibrary/swift-java.config | 2 +- .../com/example/swift/MySwiftClassTest.java | 51 +++++++++++++++++++ .../com/example/swift/MySwiftLibraryTest.java | 7 +++ Sources/JExtractSwiftLib/ImportedDecls.swift | 2 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 12 +++++ ...ISwift2JavaGenerator+JavaTranslation.swift | 9 +++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 41 ++++++++++++--- .../SwiftTypes/SwiftFunctionSignature.swift | 2 +- 10 files changed, 136 insertions(+), 11 deletions(-) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 82958464..3cbcf5c5 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -16,6 +16,25 @@ public class MySwiftClass { let x: Int64 let y: Int64 + public let constant: Int64 = 100 + public var mutable: Int64 = 0 + public var product: Int64 { + return x * y + } + public var throwingVariable: Int64 { + get throws { + throw MySwiftClassError.swiftError + } + } + public var mutableDividedByTwo: Int64 { + get { + return mutable / 2 + } + set { + mutable = newValue * 2 + } + } + public static func method() { p("Hello from static method in a class!") } diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index be04bc08..1dd84547 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -23,6 +23,8 @@ import Darwin.C #endif +public var globalVariable: Int64 = 0 + public func helloWorld() { p("\(#function)") } diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config index 46bf1f1c..bb637f34 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/swift-java.config @@ -1,4 +1,4 @@ { "javaPackage": "com.example.swift", - "mode": "jni" + "mode": "jni", } diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 3982b480..6a8fdf5d 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -61,4 +61,55 @@ void throwingFunction() { assertEquals("swiftError", exception.getMessage()); } } + + @Test + void constant() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(100, c.getConstant()); + } + } + + @Test + void mutable() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(0, c.getMutable()); + c.setMutable(42); + assertEquals(42, c.getMutable()); + } + } + + @Test + void product() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(200, c.getProduct()); + } + } + + @Test + void throwingVariable() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + + Exception exception = assertThrows(Exception.class, () -> c.getThrowingVariable()); + + assertEquals("swiftError", exception.getMessage()); + } + } + + @Test + void mutableDividedByTwo() { + try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertEquals(0, c.getMutableDividedByTwo()); + c.setMutable(20); + assertEquals(10, c.getMutableDividedByTwo()); + c.setMutableDividedByTwo(5); + assertEquals(10, c.getMutable()); + } + } + + } \ No newline at end of file diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index f1cefd19..5c9c2358 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -54,4 +54,11 @@ void call_writeString_jextract() { assertEquals(string.length(), reply); } + + @Test + void globalVariable() { + assertEquals(0, MySwiftLibrary.getGlobalVariable()); + MySwiftLibrary.setGlobalVariable(100); + assertEquals(100, MySwiftLibrary.getGlobalVariable()); + } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 76d53d88..c8ffdd61 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -91,7 +91,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } return false } - + /// If this function/method is member of a class/struct/protocol, /// this will contain that declaration's imported name. /// diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index dce399a9..ec0fa9c4 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -79,6 +79,11 @@ extension JNISwift2JavaGenerator { printFunctionBinding(&printer, decl) printer.println() } + + for decl in analysis.importedGlobalVariables { + printFunctionBinding(&printer, decl) + printer.println() + } } } @@ -113,10 +118,17 @@ extension JNISwift2JavaGenerator { for initializer in decl.initializers { printInitializerBindings(&printer, initializer, type: decl) + printer.println() } for method in decl.methods { printFunctionBinding(&printer, method) + printer.println() + } + + for variable in decl.variables { + printFunctionBinding(&printer, variable) + printer.println() } printDestroyFunction(&printer, decl) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index f006b4a8..d1be7983 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -37,8 +37,15 @@ extension JNISwift2JavaGenerator { // Types with no parent will be outputted inside a "module" class. let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName + // Name. + let javaName = switch decl.apiKind { + case .getter: "get\(decl.name.toCamelCase)" + case .setter: "set\(decl.name.toCamelCase)" + case .function, .initializer: decl.name + } + return TranslatedFunctionDecl( - name: decl.name, + name: javaName, parentName: parentName, translatedFunctionSignature: translatedFunctionSignature ) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 24532ca1..3fe09509 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -87,6 +87,11 @@ extension JNISwift2JavaGenerator { printSwiftFunctionThunk(&printer, decl) printer.println() } + + for decl in analysis.importedGlobalVariables { + printSwiftFunctionThunk(&printer, decl) + printer.println() + } } private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { @@ -102,6 +107,11 @@ extension JNISwift2JavaGenerator { printer.println() } + for variable in type.variables { + printSwiftFunctionThunk(&printer, variable) + printer.println() + } + printDestroyFunctionThunk(&printer, type) } @@ -192,19 +202,32 @@ extension JNISwift2JavaGenerator { let translatedDecl = self.translatedDecl(for: decl) let swiftReturnType = decl.functionSignature.result.type - let downcallParameters = renderDowncallArguments( - swiftFunctionSignature: decl.functionSignature, - translatedFunctionSignature: translatedDecl.translatedFunctionSignature - ) let tryClause: String = decl.isThrowing ? "try " : "" - let functionDowncall = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + + let result: String + switch decl.apiKind { + case .function, .initializer: + let downcallParameters = renderDowncallArguments( + swiftFunctionSignature: decl.functionSignature, + translatedFunctionSignature: translatedDecl.translatedFunctionSignature + ) + result = "\(tryClause)\(calleeName).\(decl.name)(\(downcallParameters))" + + case .getter: + result = "\(tryClause)\(calleeName).\(decl.name)" + + case .setter: + assert(decl.functionSignature.parameters.count == 1) + let newValueParameter = decl.functionSignature.parameters[0] + result = "\(calleeName).\(decl.name) = \(renderJNIToSwiftConversion("newValue", type: newValueParameter.type))" + } let returnStatement = if swiftReturnType.isVoid { - functionDowncall + result } else { """ - let result = \(functionDowncall) + let result = \(result) return result.getJNIValue(in: environment) """ } @@ -320,6 +343,10 @@ extension JNISwift2JavaGenerator { } .joined(separator: ", ") } + + private func renderJNIToSwiftConversion(_ variableName: String, type: SwiftType) -> String { + "\(type)(fromJNI: \(variableName), in: environment!)" + } } extension String { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index 8f0b3128..d18ad5c9 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -254,7 +254,7 @@ extension VariableDeclSyntax { /// - Parameters: /// - binding the pattern binding in this declaration. func supportedAccessorKinds(binding: PatternBindingSyntax) -> SupportedAccessorKinds { - if self.bindingSpecifier == .keyword(.let) { + if self.bindingSpecifier.tokenKind == .keyword(.let) { return [.get] } From e6acb169b1a0afa0194c19c6dccbfd0c68bb57d7 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 12 Jul 2025 13:14:51 +0200 Subject: [PATCH 2/6] prefix boolean variables with `is` to match Java conventions --- .../Convenience/String+Extensions.swift | 30 ++++++++++++++++++- ...MSwift2JavaGenerator+JavaTranslation.swift | 5 ++-- Sources/JExtractSwiftLib/ImportedDecls.swift | 2 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 5 ++-- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 53781cef..f88980bb 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -22,4 +22,32 @@ extension String { return "\(f.uppercased())\(String(dropFirst()))" } -} \ No newline at end of file + + /// Returns whether the string is of the format `isX` + private var hasJavaBooleanNamingConvention: Bool { + guard self.hasPrefix("is"), self.count > 2 else { + return false + } + + let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) + return self[thirdCharacterIndex].isUppercase + } + + func javaGetterName(isBoolean: Bool) -> String { + if !isBoolean { + return "get\(self.toCamelCase)" + } else if !hasJavaBooleanNamingConvention { + return "is\(self.toCamelCase)" + } else { + return self.toCamelCase + } + } + + func javaSetterName(isBoolean: Bool) -> String { + if !isBoolean || !hasJavaBooleanNamingConvention { + return "set\(self.toCamelCase)" + } else { + return "setIs\(self.toCamelCase)" + } + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index db706a91..de8551bd 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -126,9 +126,10 @@ extension FFMSwift2JavaGenerator { let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. + let returnsBoolean = decl.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool let javaName = switch decl.apiKind { - case .getter: "get\(decl.name.toCamelCase)" - case .setter: "set\(decl.name.toCamelCase)" + case .getter: decl.name.javaGetterName(isBoolean: returnsBoolean) + case .setter: decl.name.javaSetterName(isBoolean: returnsBoolean) case .function, .initializer: decl.name } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index c8ffdd61..76d53d88 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -91,7 +91,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible { } return false } - + /// If this function/method is member of a class/struct/protocol, /// this will contain that declaration's imported name. /// diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index d1be7983..8badc844 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -38,9 +38,10 @@ extension JNISwift2JavaGenerator { let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName // Name. + let returnsBoolean = translatedFunctionSignature.resultType == .boolean let javaName = switch decl.apiKind { - case .getter: "get\(decl.name.toCamelCase)" - case .setter: "set\(decl.name.toCamelCase)" + case .getter: decl.name.javaGetterName(isBoolean: returnsBoolean) + case .setter: decl.name.javaSetterName(isBoolean: returnsBoolean) case .function, .initializer: decl.name } From f506eceae274cc231130d2f8f185a34a4c1e19e6 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 12 Jul 2025 13:15:29 +0200 Subject: [PATCH 3/6] add confined arena constructor with current thread --- .../com/example/swift/HelloJava2SwiftJNI.java | 2 +- .../com/example/swift/MySwiftClassTest.java | 20 +++++++++---------- .../core/ConfinedSwiftMemorySession.java | 4 ++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 575859ec..80bf1a40 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -41,7 +41,7 @@ static void examples() { MySwiftClass.method(); - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass myClass = MySwiftClass.init(10, 5, arena); MySwiftClass myClass2 = MySwiftClass.init(arena); diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 6a8fdf5d..00dc7142 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -22,7 +22,7 @@ public class MySwiftClassTest { @Test void init_noParameters() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(arena); assertNotNull(c); } @@ -30,7 +30,7 @@ void init_noParameters() { @Test void init_withParameters() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(1337, 42, arena); assertNotNull(c); } @@ -38,7 +38,7 @@ void init_withParameters() { @Test void sum() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(30, c.sum()); } @@ -46,7 +46,7 @@ void sum() { @Test void xMultiplied() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(200, c.xMultiplied(10)); } @@ -54,7 +54,7 @@ void xMultiplied() { @Test void throwingFunction() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); Exception exception = assertThrows(Exception.class, () -> c.throwingFunction()); @@ -64,7 +64,7 @@ void throwingFunction() { @Test void constant() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(100, c.getConstant()); } @@ -72,7 +72,7 @@ void constant() { @Test void mutable() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(0, c.getMutable()); c.setMutable(42); @@ -82,7 +82,7 @@ void mutable() { @Test void product() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(200, c.getProduct()); } @@ -90,7 +90,7 @@ void product() { @Test void throwingVariable() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); Exception exception = assertThrows(Exception.class, () -> c.getThrowingVariable()); @@ -101,7 +101,7 @@ void throwingVariable() { @Test void mutableDividedByTwo() { - try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) { + try (var arena = new ConfinedSwiftMemorySession()) { MySwiftClass c = MySwiftClass.init(20, 10, arena); assertEquals(0, c.getMutableDividedByTwo()); c.setMutable(20); diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java index cd8f2dd1..1b6821ca 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/ConfinedSwiftMemorySession.java @@ -28,6 +28,10 @@ public class ConfinedSwiftMemorySession implements ClosableSwiftArena { final ConfinedResourceList resources; + public ConfinedSwiftMemorySession() { + this(Thread.currentThread()); + } + public ConfinedSwiftMemorySession(Thread owner) { this.owner = owner; this.state = new AtomicInteger(ACTIVE); From a6d3887185419d0eecbf869c72dbe3fd6cf8b501 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 12 Jul 2025 14:06:22 +0200 Subject: [PATCH 4/6] add variable tests --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 1 + .../com/example/swift/HelloJava2SwiftJNI.java | 2 + .../Convenience/String+Extensions.swift | 20 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 5 +- Sources/JExtractSwiftLib/ImportedDecls.swift | 24 ++ ...ISwift2JavaGenerator+JavaTranslation.swift | 5 +- .../JNI/JNIVariablesTests.swift | 386 ++++++++++++++++++ 7 files changed, 418 insertions(+), 25 deletions(-) create mode 100644 Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 3cbcf5c5..b68748c9 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -34,6 +34,7 @@ public class MySwiftClass { mutable = newValue * 2 } } + public let warm: Bool = false public static func method() { p("Hello from static method in a class!") diff --git a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java index 80bf1a40..8e7ca284 100644 --- a/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java +++ b/Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java @@ -45,6 +45,8 @@ static void examples() { MySwiftClass myClass = MySwiftClass.init(10, 5, arena); MySwiftClass myClass2 = MySwiftClass.init(arena); + System.out.println("myClass.isWarm: " + myClass.isWarm()); + try { myClass.throwingFunction(); } catch (Exception e) { diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index f88980bb..1878ba2f 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -24,7 +24,7 @@ extension String { } /// Returns whether the string is of the format `isX` - private var hasJavaBooleanNamingConvention: Bool { + var hasJavaBooleanNamingConvention: Bool { guard self.hasPrefix("is"), self.count > 2 else { return false } @@ -32,22 +32,4 @@ extension String { let thirdCharacterIndex = self.index(self.startIndex, offsetBy: 2) return self[thirdCharacterIndex].isUppercase } - - func javaGetterName(isBoolean: Bool) -> String { - if !isBoolean { - return "get\(self.toCamelCase)" - } else if !hasJavaBooleanNamingConvention { - return "is\(self.toCamelCase)" - } else { - return self.toCamelCase - } - } - - func javaSetterName(isBoolean: Bool) -> String { - if !isBoolean || !hasJavaBooleanNamingConvention { - return "set\(self.toCamelCase)" - } else { - return "setIs\(self.toCamelCase)" - } - } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index de8551bd..e548db5c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -126,10 +126,9 @@ extension FFMSwift2JavaGenerator { let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. - let returnsBoolean = decl.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool let javaName = switch decl.apiKind { - case .getter: decl.name.javaGetterName(isBoolean: returnsBoolean) - case .setter: decl.name.javaSetterName(isBoolean: returnsBoolean) + case .getter: decl.javaGetterName + case .setter: decl.javaSetterName case .function, .initializer: decl.name } diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 76d53d88..57ed6f94 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -154,3 +154,27 @@ extension ImportedFunc: Hashable { return lhs === rhs } } + +extension ImportedFunc { + var javaGetterName: String { + let returnsBoolean = self.functionSignature.result.type.asNominalTypeDeclaration?.knownTypeKind == .bool + + if !returnsBoolean { + return "get\(self.name.toCamelCase)" + } else if !self.name.hasJavaBooleanNamingConvention { + return "is\(self.name.toCamelCase)" + } else { + return self.name.toCamelCase + } + } + + var javaSetterName: String { + let firstParameterIsBoolean = self.functionSignature.parameters.first?.type.asNominalTypeDeclaration?.knownTypeKind == .bool + + if !firstParameterIsBoolean || self.name.hasJavaBooleanNamingConvention { + return "set\(self.name.toCamelCase)" + } else { + return "setIs\(self.name.toCamelCase)" + } + } +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 8badc844..179991df 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -38,10 +38,9 @@ extension JNISwift2JavaGenerator { let parentName = decl.parentType?.asNominalType?.nominalTypeDecl.qualifiedName ?? swiftModuleName // Name. - let returnsBoolean = translatedFunctionSignature.resultType == .boolean let javaName = switch decl.apiKind { - case .getter: decl.name.javaGetterName(isBoolean: returnsBoolean) - case .setter: decl.name.javaSetterName(isBoolean: returnsBoolean) + case .getter: decl.javaGetterName + case .setter: decl.javaSetterName case .function, .initializer: decl.name } diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift new file mode 100644 index 00000000..236a227f --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -0,0 +1,386 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIVariablesTests { + let membersSource = """ + public class MyClass { + public let constant: Int64 + public var mutable: Int64 + public var computed: Int64 { + return 0 + } + public var computedThrowing: Int64 { + get throws { return 0 } + } + public var getterAndSetter: Int64 { + get { return 0 } + set { } + } + public var someBoolean: Bool + public let isBoolean: Bool + } + """ + + @Test + func constant_javaBindings() throws { + try assertOutput(input: membersSource, .jni, .java, expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public let constant: Int64 + * } + */ + public long getConstant() { + long selfPointer = this.pointer(); + return MyClass.$getConstant(selfPointer); + } + """, + """ + private static native long $getConstant(long selfPointer); + """ + ]) + } + + @Test + func constant_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getConstant__J") + func Java_com_example_swift_MyClass__00024getConstant__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.constant + return result.getJNIValue(in: environment) + } + """ + ] + ) + } + + @Test + func mutable_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var mutable: Int64 + * } + */ + public long getMutable() { + long selfPointer = this.pointer(); + return MyClass.$getMutable(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var mutable: Int64 + * } + */ + public void setMutable(long newValue) { + long selfPointer = this.pointer(); + MyClass.$setMutable(newValue, selfPointer); + } + """, + """ + private static native long $getMutable(long selfPointer); + """, + """ + private static native void $setMutable(long newValue, long selfPointer); + """ + ] + ) + } + + @Test + func mutable_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getMutable__J") + func Java_com_example_swift_MyClass__00024getMutable__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.mutable + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setMutable__JJ") + func Java_com_example_swift_MyClass__00024setMutable__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.mutable = Int64(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } + + @Test + func computed_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var computed: Int64 + * } + */ + public long getComputed() { + long selfPointer = this.pointer(); + return MyClass.$getComputed(selfPointer); + } + """, + """ + private static native long $getComputed(long selfPointer); + """, + ] + ) + } + + @Test + func computed_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getComputed__J") + func Java_com_example_swift_MyClass__00024getComputed__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.computed + return result.getJNIValue(in: environment) + } + """, + ] + ) + } + + @Test + func computedThrowing_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var computedThrowing: Int64 + * } + */ + public long getComputedThrowing() throws Exception { + long selfPointer = this.pointer(); + return MyClass.$getComputedThrowing(selfPointer); + } + """, + """ + private static native long $getComputedThrowing(long selfPointer); + """, + ] + ) + } + + @Test + func computedThrowing_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getComputedThrowing__J") + func Java_com_example_swift_MyClass__00024getComputedThrowing__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + do { + let result = try self$.pointee.computedThrowing + return result.getJNIValue(in: environment) + } catch { + environment.throwAsException(error) + return Int64.jniPlaceholderValue + } + } + """, + ] + ) + } + + @Test + func getterAndSetter_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var getterAndSetter: Int64 + * } + */ + public long getGetterAndSetter() { + long selfPointer = this.pointer(); + return MyClass.$getGetterAndSetter(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var getterAndSetter: Int64 + * } + */ + public void setGetterAndSetter(long newValue) { + long selfPointer = this.pointer(); + MyClass.$setGetterAndSetter(newValue, selfPointer); + } + """, + """ + private static native long $getGetterAndSetter(long selfPointer); + """, + """ + private static native void $setGetterAndSetter(long newValue, long selfPointer); + """ + ] + ) + } + + @Test + func getterAndSetter_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024getGetterAndSetter__J") + func Java_com_example_swift_MyClass__00024getGetterAndSetter__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jlong { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.getterAndSetter + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ") + func Java_com_example_swift_MyClass__00024setGetterAndSetter__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.getterAndSetter = Int64(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } + + @Test + func someBoolean_javaBindings() throws { + try assertOutput( + input: membersSource, + .jni, + .java, + detectChunkByInitialLines: 8, + expectedChunks: [ + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var someBoolean: Bool + * } + */ + public boolean isSomeBoolean() { + long selfPointer = this.pointer(); + return MyClass.$isSomeBoolean(selfPointer); + } + """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public var someBoolean: Bool + * } + */ + public void setIsSomeBoolean(boolean newValue) { + long selfPointer = this.pointer(); + MyClass.$setIsSomeBoolean(newValue, selfPointer); + } + """, + """ + private static native boolean $isSomeBoolean(long selfPointer); + """, + """ + private static native void $setIsSomeBoolean(boolean newValue, long selfPointer); + """ + ] + ) + } + + @Test + func boolean_swiftThunks() throws { + try assertOutput( + input: membersSource, + .jni, + .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_MyClass__00024isSomeBoolean__J") + func Java_com_example_swift_MyClass__00024isSomeBoolean__J(environment: UnsafeMutablePointer!, thisClass: jclass, selfPointer: jlong) -> jboolean { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + let result = self$.pointee.someBoolean + return result.getJNIValue(in: environment) + } + """, + """ + @_cdecl("Java_com_example_swift_MyClass__00024setIsSomeBoolean__ZJ") + func Java_com_example_swift_MyClass__00024setIsSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { + let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! + self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) + } + """ + ] + ) + } + +} From 6c036246fdc390ba209e69c6a661f9c5b328a855 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 13 Jul 2025 08:17:27 +0200 Subject: [PATCH 5/6] PR feedback --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 5 +++ Sources/JExtractSwiftLib/ImportedDecls.swift | 8 +--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 6 ++- .../SwiftTypes/SwiftFunctionSignature.swift | 9 +++-- .../JNI/JNIVariablesTests.swift | 40 +++++++++---------- 5 files changed, 36 insertions(+), 32 deletions(-) diff --git a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index b68748c9..386a7227 100644 --- a/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/JExtractJNISampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -35,6 +35,11 @@ public class MySwiftClass { } } public let warm: Bool = false + public var getAsync: Int64 { + get async { + return 42 + } + } public static func method() { p("Hello from static method in a class!") diff --git a/Sources/JExtractSwiftLib/ImportedDecls.swift b/Sources/JExtractSwiftLib/ImportedDecls.swift index 57ed6f94..4b8a478a 100644 --- a/Sources/JExtractSwiftLib/ImportedDecls.swift +++ b/Sources/JExtractSwiftLib/ImportedDecls.swift @@ -169,12 +169,6 @@ extension ImportedFunc { } var javaSetterName: String { - let firstParameterIsBoolean = self.functionSignature.parameters.first?.type.asNominalTypeDeclaration?.knownTypeKind == .bool - - if !firstParameterIsBoolean || self.name.hasJavaBooleanNamingConvention { - return "set\(self.name.toCamelCase)" - } else { - return "setIs\(self.name.toCamelCase)" - } + "set\(self.name.toCamelCase)" } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 3fe09509..c32e0e27 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -217,8 +217,10 @@ extension JNISwift2JavaGenerator { result = "\(tryClause)\(calleeName).\(decl.name)" case .setter: - assert(decl.functionSignature.parameters.count == 1) - let newValueParameter = decl.functionSignature.parameters[0] + guard let newValueParameter = decl.functionSignature.parameters.first else { + fatalError("Setter did not contain newValue parameter: \(decl)") + } + result = "\(calleeName).\(decl.name) = \(renderJNIToSwiftConversion("newValue", type: newValueParameter.type))" } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift index d18ad5c9..5cca79bf 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftFunctionSignature.swift @@ -211,11 +211,11 @@ extension SwiftFunctionSignature { switch binding.accessorBlock?.accessors { case .getter(let getter): if let getter = getter.as(AccessorDeclSyntax.self) { - effectSpecifiers = Self.effectSpecifiers(from: getter) + effectSpecifiers = try Self.effectSpecifiers(from: getter) } case .accessors(let accessors): if let getter = accessors.first(where: { $0.accessorSpecifier.tokenKind == .keyword(.get) }) { - effectSpecifiers = Self.effectSpecifiers(from: getter) + effectSpecifiers = try Self.effectSpecifiers(from: getter) } default: break @@ -232,11 +232,14 @@ extension SwiftFunctionSignature { } } - private static func effectSpecifiers(from decl: AccessorDeclSyntax) -> [SwiftEffectSpecifier] { + private static func effectSpecifiers(from decl: AccessorDeclSyntax) throws -> [SwiftEffectSpecifier] { var effectSpecifiers = [SwiftEffectSpecifier]() if decl.effectSpecifiers?.throwsClause != nil { effectSpecifiers.append(.throws) } + if let asyncSpecifier = decl.effectSpecifiers?.asyncSpecifier { + throw SwiftFunctionTranslationError.async(asyncSpecifier) + } return effectSpecifiers } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift index 236a227f..f09789d4 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIVariablesTests.swift @@ -17,24 +17,25 @@ import Testing @Suite struct JNIVariablesTests { - let membersSource = """ + let membersSource = + """ public class MyClass { - public let constant: Int64 - public var mutable: Int64 - public var computed: Int64 { - return 0 - } - public var computedThrowing: Int64 { - get throws { return 0 } - } - public var getterAndSetter: Int64 { - get { return 0 } - set { } - } + public let constant: Int64 + public var mutable: Int64 + public var computed: Int64 { + return 0 + } + public var computedThrowing: Int64 { + get throws { return 0 } + } + public var getterAndSetter: Int64 { + get { return 0 } + set { } + } public var someBoolean: Bool public let isBoolean: Bool } - """ + """ @Test func constant_javaBindings() throws { @@ -341,16 +342,16 @@ struct JNIVariablesTests { * public var someBoolean: Bool * } */ - public void setIsSomeBoolean(boolean newValue) { + public void setSomeBoolean(boolean newValue) { long selfPointer = this.pointer(); - MyClass.$setIsSomeBoolean(newValue, selfPointer); + MyClass.$setSomeBoolean(newValue, selfPointer); } """, """ private static native boolean $isSomeBoolean(long selfPointer); """, """ - private static native void $setIsSomeBoolean(boolean newValue, long selfPointer); + private static native void $setSomeBoolean(boolean newValue, long selfPointer); """ ] ) @@ -373,8 +374,8 @@ struct JNIVariablesTests { } """, """ - @_cdecl("Java_com_example_swift_MyClass__00024setIsSomeBoolean__ZJ") - func Java_com_example_swift_MyClass__00024setIsSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { + @_cdecl("Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ") + func Java_com_example_swift_MyClass__00024setSomeBoolean__ZJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jboolean, selfPointer: jlong) { let self$ = UnsafeMutablePointer(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))! self$.pointee.someBoolean = Bool(fromJNI: newValue, in: environment!) } @@ -382,5 +383,4 @@ struct JNIVariablesTests { ] ) } - } From 590757473783260ba9ed7bd8e7c992d020c520a4 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 13 Jul 2025 08:40:55 +0200 Subject: [PATCH 6/6] add test for isWarm --- .../src/test/java/com/example/swift/MySwiftClassTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java index 00dc7142..bf244416 100644 --- a/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java +++ b/Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java @@ -111,5 +111,11 @@ void mutableDividedByTwo() { } } - + @Test + void isWarm() { + try (var arena = new ConfinedSwiftMemorySession()) { + MySwiftClass c = MySwiftClass.init(20, 10, arena); + assertFalse(c.isWarm()); + } + } } \ No newline at end of file