From 5019f5d654c10bc4d7c226d030051bd2dc35a13f Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 26 Dec 2025 23:59:53 +0100 Subject: [PATCH 1/7] add support for date --- .../Sources/MySwiftLibrary/Date.swift | 28 +++++ .../test/java/com/example/swift/DateTest.java | 36 ++++++ .../FFM/CDeclLowering/CRepresentation.swift | 2 +- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 58 +-------- .../JNI/JNIJavaTypeTranslator.swift | 2 +- ...ISwift2JavaGenerator+JavaTranslation.swift | 35 +++++- ...wift2JavaGenerator+NativeTranslation.swift | 23 +++- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 + .../JavaTypes/JavaType+JDK.swift | 4 + .../SwiftTypes/SwiftKnownModules.swift | 2 + .../SwiftTypes/SwiftKnownTypeDecls.swift | 2 + .../SwiftTypes/SwiftSymbolTable.swift | 56 +++++++++ Tests/JExtractSwiftTests/DateTests.swift | 111 ++++++++++++++++++ .../FoundationImportTests.swift | 63 ++++++++++ 14 files changed, 364 insertions(+), 60 deletions(-) create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Date.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java create mode 100644 Tests/JExtractSwiftTests/DateTests.swift create mode 100644 Tests/JExtractSwiftTests/FoundationImportTests.swift diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Date.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Date.swift new file mode 100644 index 000000000..35f78cbce --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/Date.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif +import SwiftJava + +public func compareDates(date1: Date, date2: Date) -> Bool { + return date1 == date2 +} + +public func dateFromSeconds(_ seconds: Double) -> Date { + return Date(timeIntervalSince1970: seconds) +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java new file mode 100644 index 000000000..5a844046e --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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 com.example.swift; + +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.*; + +public class DateTest { + @Test + void date() { + assertEquals(Instant.ofEpochSecond(1000), MySwiftLibrary.dateFromSeconds(1000.0)); + assertEquals(Instant.ofEpochSecond(1000, 500_000_000), MySwiftLibrary.dateFromSeconds(1000.50)); + assertTrue(MySwiftLibrary.compareDates(Instant.ofEpochSecond(5000), Instant.ofEpochSecond(5000))); + assertFalse(MySwiftLibrary.compareDates(Instant.ofEpochSecond(4999, 500_000_000), Instant.ofEpochSecond(5000))); + assertTrue(MySwiftLibrary.compareDates(MySwiftLibrary.dateFromSeconds(1000.5), Instant.ofEpochSecond(1000, 500_000_000))); + + var date = MySwiftLibrary.dateFromSeconds(50000.5); + assertEquals(50_000, date.getEpochSecond()); + assertEquals(500_000_000, date.getNano()); + } +} \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift index 6b0e33991..3c0a00267 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift @@ -129,7 +129,7 @@ extension SwiftKnownTypeDeclKind { case .void: .void case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol, - .essentialsData, .essentialsDataProtocol, .optional: + .essentialsData, .essentialsDataProtocol, .optional, .foundationDate, .essentialsDate: nil } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 09de17782..21b3fa679 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -111,7 +111,7 @@ extension FFMSwift2JavaGenerator { """) - printSwiftThunkImports(&printer) + self.lookupContext.symbolTable.printImportedModules(&printer) for thunk in stt.renderGlobalThunks() { printer.print(thunk) @@ -140,67 +140,13 @@ extension FFMSwift2JavaGenerator { """ ) - printSwiftThunkImports(&printer) + self.lookupContext.symbolTable.printImportedModules(&printer) for thunk in stt.renderThunks(forType: ty) { printer.print("\(thunk)") printer.print("") } } - - func printSwiftThunkImports(_ printer: inout CodePrinter) { - let mainSymbolSourceModules = Set( - self.lookupContext.symbolTable.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName) - ) - - for module in self.lookupContext.symbolTable.importedModules.keys.sorted() { - guard module != "Swift" else { - continue - } - - guard let alternativeModules = self.lookupContext.symbolTable.importedModules[module]?.alternativeModules else { - printer.print("import \(module)") - continue - } - - // Try to print only on main module from relation chain as it has every other module. - guard !mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) || alternativeModules.isMainSourceOfSymbols else { - if !alternativeModules.isMainSourceOfSymbols { - printer.print("import \(module)") - } - continue - } - - var importGroups: [String: [String]] = [:] - for name in alternativeModules.moduleNames { - guard let otherModule = self.lookupContext.symbolTable.importedModules[name] else { continue } - - let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName - importGroups[groupKey, default: []].append(otherModule.moduleName) - } - - for (index, group) in importGroups.keys.sorted().enumerated() { - if index > 0 && importGroups.keys.count > 1 { - printer.print("#elseif canImport(\(group))") - } else { - printer.print("#if canImport(\(group))") - } - - for groupModule in importGroups[group] ?? [] { - printer.print("import \(groupModule)") - } - } - - if (importGroups.keys.isEmpty) { - printer.print("import \(module)") - } else { - printer.print("#else") - printer.print("import \(module)") - printer.print("#endif") - } - } - printer.println() - } } struct SwiftThunkTranslator { diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index 491331bce..0f95f4b6f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -44,7 +44,7 @@ enum JNIJavaTypeTranslator { .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, - .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, .array: + .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, .array, .foundationDate, .essentialsDate: return nil } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index c6f9713ba..6fa54a242 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -389,6 +389,12 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) + case .foundationDate, .essentialsDate: + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .javaTimeInstant, annotations: parameterAnnotations), + conversion: .instantToDouble + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -671,6 +677,14 @@ extension JNISwift2JavaGenerator { elementType: elementType ) + case .foundationDate, .essentialsDate: + return TranslatedResult( + javaType: .javaTimeInstant, + annotations: resultAnnotations, + outParameters: [], + conversion: .doubleToInstant + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -1109,6 +1123,12 @@ extension JNISwift2JavaGenerator { .method(.constant("Arrays"), function: "stream", arguments: [argument]) } + /// Convert a `java.time.Instant` to a seconds double + case instantToDouble + + /// Convert a double to `java.time.Instant` + case doubleToInstant + /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -1242,13 +1262,26 @@ extension JNISwift2JavaGenerator { case .requireNonNull(let inner, let message): let inner = inner.render(&printer, placeholder) return #"Objects.requireNonNull(\#(inner), "\#(message)")"# + + case .instantToDouble: + return "(\(placeholder).getEpochSecond() + (\(placeholder).getNano() / 1_000_000_000.0))" + + case .doubleToInstant: + printer.print( + """ + double $instant = \(placeholder); + long $seconds = (long) $instant; + long $nanos = (long) (($instant - $seconds) * 1_000_000_000); + """ + ) + return "java.time.Instant.ofEpochSecond($seconds, $nanos)" } } /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .constant, .isOptionalPresent, .combinedName: + case .placeholder, .constant, .isOptionalPresent, .combinedName, .instantToDouble, .doubleToInstant: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index d8b0a1d1b..48d00bddb 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -98,6 +98,20 @@ extension JNISwift2JavaGenerator { } return try translateArrayParameter(elementType: elementType, parameterName: parameterName) + case .foundationDate, .essentialsDate: + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .double) + ], + conversion: .method( + .constant("Date"), + function: "init", + arguments: [ + ("timeIntervalSince1970", .initFromJNI(.placeholder, swiftType: self.knownTypes.double)) + ] + ) + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { @@ -484,6 +498,13 @@ extension JNISwift2JavaGenerator { } return try translateArrayResult(elementType: elementType, resultName: resultName) + case .foundationDate, .essentialsDate: + return NativeResult( + javaType: .double, + conversion: .getJNIValue(.member(.placeholder, member: "timeIntervalSince1970")), + outParameters: [] + ) + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) @@ -1111,4 +1132,4 @@ extension JNISwift2JavaGenerator { } } } -} \ No newline at end of file +} diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 8005485d2..89d1ec07d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -710,6 +710,8 @@ extension JNISwift2JavaGenerator { """ ) + + self.lookupContext.symbolTable.printImportedModules(&printer) } private func printTypeMetadataAddressThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index e1aabd7fd..84657b6b5 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -55,4 +55,8 @@ extension JavaType { static func future(_ T: JavaType) -> JavaType { .class(package: "java.util.concurrent", name: "Future", typeParameters: [T.boxedType]) } + + static var javaTimeInstant: JavaType { + .class(package: "java.time", name: "Instant") + } } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 1be8071c4..9a111993f 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -113,6 +113,8 @@ private let foundationEssentialsSourceFile: SourceFileSyntax = """ public var count: Int { get } public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) } + + public struct Date {} """ private var foundationSourceFile: SourceFileSyntax { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 5401f0fab..16240449a 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -47,6 +47,8 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case essentialsDataProtocol = "FoundationEssentials.DataProtocol" case foundationData = "Foundation.Data" case essentialsData = "FoundationEssentials.Data" + case foundationDate = "Foundation.Date" + case essentialsDate = "FoundationEssentials.Date" var moduleAndName: (module: String, name: String) { let qualified = self.rawValue diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift index ef299bab3..166aeb6f2 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftSymbolTable.swift @@ -147,3 +147,59 @@ extension SwiftSymbolTable { return found } } + +extension SwiftSymbolTable { + func printImportedModules(_ printer: inout CodePrinter) { + let mainSymbolSourceModules = Set( + self.importedModules.values.filter { $0.alternativeModules?.isMainSourceOfSymbols ?? false }.map(\.moduleName) + ) + + for module in self.importedModules.keys.sorted() { + guard module != "Swift" else { + continue + } + + guard let alternativeModules = self.importedModules[module]?.alternativeModules else { + printer.print("import \(module)") + continue + } + + // Try to print only on main module from relation chain as it has every other module. + guard !mainSymbolSourceModules.isDisjoint(with: alternativeModules.moduleNames) || alternativeModules.isMainSourceOfSymbols else { + if !alternativeModules.isMainSourceOfSymbols { + printer.print("import \(module)") + } + continue + } + + var importGroups: [String: [String]] = [:] + for name in alternativeModules.moduleNames { + guard let otherModule = self.importedModules[name] else { continue } + + let groupKey = otherModule.requiredAvailablityOfModuleWithName ?? otherModule.moduleName + importGroups[groupKey, default: []].append(otherModule.moduleName) + } + + for (index, group) in importGroups.keys.sorted().enumerated() { + if index > 0 && importGroups.keys.count > 1 { + printer.print("#elseif canImport(\(group))") + } else { + printer.print("#if canImport(\(group))") + } + + for groupModule in importGroups[group] ?? [] { + printer.print("import \(groupModule)") + } + } + + if (importGroups.keys.isEmpty) { + printer.print("import \(module)") + } else { + printer.print("#else") + printer.print("import \(module)") + printer.print("#endif") + } + } + printer.println() + } +} diff --git a/Tests/JExtractSwiftTests/DateTests.swift b/Tests/JExtractSwiftTests/DateTests.swift new file mode 100644 index 000000000..2e88a6b3b --- /dev/null +++ b/Tests/JExtractSwiftTests/DateTests.swift @@ -0,0 +1,111 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftJavaConfigurationShared +import Testing + +struct DateTests { + @Test( + "Import: accept Date", + arguments: [ + ( + JExtractGenerationMode.jni, + /* expected Java chunks */ + [ + """ + public static void acceptDate(java.time.Instant date) { + SwiftModule.$acceptDate((date.getEpochSecond() + (date.getNano() / 1_000_000_000.0))); + } + """, + ], + /* expected Swift chunks */ + [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024acceptDate__D") + func Java_com_example_swift_SwiftModule__00024acceptDate__D(environment: UnsafeMutablePointer!, thisClass: jclass, date: jdouble) { + SwiftModule.acceptDate(date: Date.init(timeIntervalSince1970: Double(fromJNI: date, in: environment))) + } + """ + ], + ) + ] + ) + func func_accept_date(mode: JExtractGenerationMode, expectedJavaChunks: [String], expectedSwiftChunks: [String]) throws { + let text = + """ + import Foundation + + public func acceptDate(date: Date) + """ + + try assertOutput( + input: text, + mode, .java, + detectChunkByInitialLines: 1, + expectedChunks: expectedJavaChunks) + + try assertOutput( + input: text, + mode, .swift, + detectChunkByInitialLines: 1, + expectedChunks: expectedSwiftChunks) + } + + @Test( + "Import: return Date", + arguments: [ + ( + JExtractGenerationMode.jni, + /* expected Java chunks */ + [ + """ + public static java.time.Instant returnDate() { + double $instant = SwiftModule.$returnDate(); + long $seconds = (long) $instant; + long $nanos = (long) (($instant - $seconds) * 1_000_000_000); + return java.time.Instant.ofEpochSecond($seconds, $nanos); + } + """ + ], + /* expected Swift chunks */ + [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024returnDate__") + func Java_com_example_swift_SwiftModule__00024returnDate__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jdouble { + return SwiftModule.returnDate().timeIntervalSince1970.getJNIValue(in: environment) + } + """ + ] + ) + ] + ) + func func_return_Date(mode: JExtractGenerationMode, expectedJavaChunks: [String], expectedSwiftChunks: [String]) throws { + let text = + """ + import Foundation + public func returnDate() -> Date + """ + + try assertOutput( + input: text, + mode, .java, + expectedChunks: expectedJavaChunks) + + try assertOutput( + input: text, + mode, .swift, + expectedChunks: expectedSwiftChunks) + } +} diff --git a/Tests/JExtractSwiftTests/FoundationImportTests.swift b/Tests/JExtractSwiftTests/FoundationImportTests.swift new file mode 100644 index 000000000..f961882f6 --- /dev/null +++ b/Tests/JExtractSwiftTests/FoundationImportTests.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing +import SwiftJavaConfigurationShared + +struct FoundationImportTests { + @Test("Import Foundation", arguments: [JExtractGenerationMode.jni, JExtractGenerationMode.ffm]) + func import_foundation(mode: JExtractGenerationMode) throws { + + try assertOutput( + input: "import Foundation", mode, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + "import Foundation" + ] + ) + } + + @Test("Import FoundationEssentials", arguments: [JExtractGenerationMode.jni, JExtractGenerationMode.ffm]) + func import_foundationEssentials(mode: JExtractGenerationMode) throws { + + try assertOutput( + input: "import FoundationEssentials", mode, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + "import FoundationEssentials" + ] + ) + } + + @Test("Import conditional foundation", arguments: [JExtractGenerationMode.jni, JExtractGenerationMode.ffm]) + func import_conditionalFoundation(mode: JExtractGenerationMode) throws { + let ifConfigImport = + """ + #if canImport(FoundationEssentials) + import FoundationEssentials + #else + import Foundation + #endif + """ + + try assertOutput( + input: ifConfigImport, mode, .swift, + detectChunkByInitialLines: 1, + expectedChunks: [ + ifConfigImport + ] + ) + } +} From 1e00493dab565bef837223e65264e8c2539e3942 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 27 Dec 2025 09:51:32 +0100 Subject: [PATCH 2/7] supported features --- .../Documentation.docc/SupportedFeatures.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 370f33925..e469e7c70 100644 --- a/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md +++ b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md @@ -69,6 +69,7 @@ SwiftJava's `swift-java jextract` tool automates generating Java bindings from S | Existential parameters `f(x: any (A & B)) ` | ❌ | ✅ | | Existential return types `f() -> any Collection ` | ❌ | ❌ | | Foundation Data and DataProtocol: `f(x: any DataProtocol) -> Data` | ✅ | ❌ | +| Foundation Date: `f(date: Date) -> Date` | ❌ | ✅ | | Opaque parameters: `func take(worker: some Builder) -> some Builder` | ❌ | ✅ | | Opaque return types: `func get() -> some Builder` | ❌ | ❌ | | Optional parameters: `func f(i: Int?, class: MyClass?)` | ✅ | ✅ | From 9201e63982a24c4fa76700b21cf2397810ba710a Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 1 Feb 2026 17:14:10 +0100 Subject: [PATCH 3/7] go away from "Instant" strategy --- .../test/java/com/example/swift/DateTest.java | 30 +++++--- ...ISwift2JavaGenerator+JavaTranslation.swift | 35 ++------- ...wift2JavaGenerator+NativeTranslation.swift | 21 ++---- .../JavaTypes/JavaType+JDK.swift | 4 -- .../Swift2JavaTranslator.swift | 13 +++- .../SwiftTypes/SwiftKnownModules.swift | 8 ++- .../SwiftTypes/SwiftKnownTypeDecls.swift | 5 +- Tests/JExtractSwiftTests/DateTests.swift | 72 ++++++++++++++----- 8 files changed, 107 insertions(+), 81 deletions(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java index 5a844046e..5dbff53c3 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java @@ -15,6 +15,7 @@ package com.example.swift; import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftArena; import java.time.Instant; @@ -22,15 +23,24 @@ public class DateTest { @Test - void date() { - assertEquals(Instant.ofEpochSecond(1000), MySwiftLibrary.dateFromSeconds(1000.0)); - assertEquals(Instant.ofEpochSecond(1000, 500_000_000), MySwiftLibrary.dateFromSeconds(1000.50)); - assertTrue(MySwiftLibrary.compareDates(Instant.ofEpochSecond(5000), Instant.ofEpochSecond(5000))); - assertFalse(MySwiftLibrary.compareDates(Instant.ofEpochSecond(4999, 500_000_000), Instant.ofEpochSecond(5000))); - assertTrue(MySwiftLibrary.compareDates(MySwiftLibrary.dateFromSeconds(1000.5), Instant.ofEpochSecond(1000, 500_000_000))); - - var date = MySwiftLibrary.dateFromSeconds(50000.5); - assertEquals(50_000, date.getEpochSecond()); - assertEquals(500_000_000, date.getNano()); + void date_functions() { + try (var arena = SwiftArena.ofConfined()) { + var date = MySwiftLibrary.dateFromSeconds(1000.50, arena); + assertEquals(1000.5, date.getTimeIntervalSince1970()); + + var date2 = Date.init(1000.5, arena); + assertTrue(MySwiftLibrary.compareDates(date, date2)); + + var date3 = Date.init(1000.49, arena); + assertFalse(MySwiftLibrary.compareDates(date, date3)); + } + } + + @Test + void date_timeIntervalSince1970() { + try (var arena = SwiftArena.ofConfined()) { + var date = Date.init(1000, arena); + assertEquals(1000, date.getTimeIntervalSince1970()); + } } } \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 6fa54a242..60cbb25a1 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -390,10 +390,8 @@ extension JNISwift2JavaGenerator { ) case .foundationDate, .essentialsDate: - return TranslatedParameter( - parameter: JavaParameter(name: parameterName, type: .javaTimeInstant, annotations: parameterAnnotations), - conversion: .instantToDouble - ) + // Handled as wrapped struct + break default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { @@ -678,12 +676,8 @@ extension JNISwift2JavaGenerator { ) case .foundationDate, .essentialsDate: - return TranslatedResult( - javaType: .javaTimeInstant, - annotations: resultAnnotations, - outParameters: [], - conversion: .doubleToInstant - ) + // Handled as wrapped struct + break default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { @@ -1123,12 +1117,6 @@ extension JNISwift2JavaGenerator { .method(.constant("Arrays"), function: "stream", arguments: [argument]) } - /// Convert a `java.time.Instant` to a seconds double - case instantToDouble - - /// Convert a double to `java.time.Instant` - case doubleToInstant - /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. @@ -1262,26 +1250,13 @@ extension JNISwift2JavaGenerator { case .requireNonNull(let inner, let message): let inner = inner.render(&printer, placeholder) return #"Objects.requireNonNull(\#(inner), "\#(message)")"# - - case .instantToDouble: - return "(\(placeholder).getEpochSecond() + (\(placeholder).getNano() / 1_000_000_000.0))" - - case .doubleToInstant: - printer.print( - """ - double $instant = \(placeholder); - long $seconds = (long) $instant; - long $nanos = (long) (($instant - $seconds) * 1_000_000_000); - """ - ) - return "java.time.Instant.ofEpochSecond($seconds, $nanos)" } } /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .constant, .isOptionalPresent, .combinedName, .instantToDouble, .doubleToInstant: + case .placeholder, .constant, .isOptionalPresent, .combinedName: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 48d00bddb..4c6fb3c9e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -99,18 +99,8 @@ extension JNISwift2JavaGenerator { return try translateArrayParameter(elementType: elementType, parameterName: parameterName) case .foundationDate, .essentialsDate: - return NativeParameter( - parameters: [ - JavaParameter(name: parameterName, type: .double) - ], - conversion: .method( - .constant("Date"), - function: "init", - arguments: [ - ("timeIntervalSince1970", .initFromJNI(.placeholder, swiftType: self.knownTypes.double)) - ] - ) - ) + // Handled as wrapped struct + break default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), @@ -499,11 +489,8 @@ extension JNISwift2JavaGenerator { return try translateArrayResult(elementType: elementType, resultName: resultName) case .foundationDate, .essentialsDate: - return NativeResult( - javaType: .double, - conversion: .getJNIValue(.member(.placeholder, member: "timeIntervalSince1970")), - outParameters: [] - ) + // Handled as wrapped struct + break default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift index 84657b6b5..e1aabd7fd 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift @@ -55,8 +55,4 @@ extension JavaType { static func future(_ T: JavaType) -> JavaType { .class(package: "java.util.concurrent", name: "Future", typeParameters: [T.boxedType]) } - - static var javaTimeInstant: JavaType { - .class(package: "java.time", name: "Instant") - } } diff --git a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift index 212a8e46c..53853b4a0 100644 --- a/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwiftLib/Swift2JavaTranslator.swift @@ -100,7 +100,11 @@ extension Swift2JavaTranslator { visitor.visit(inputFile: input) } - // If any API uses 'Foundation.Data' or 'FoundationEssentials.Data', + self.visitFoundationDeclsIfNeeded(with: visitor) + } + + private func visitFoundationDeclsIfNeeded(with visitor: Swift2JavaVisitor) { + // If any API uses 'Foundation.Data' or 'FoundationEssentials.Data', // import 'Data' as if it's declared in this module. if let dataDecl = self.symbolTable[.foundationData] ?? self.symbolTable[.essentialsData] { let dataProtocolDecl = (self.symbolTable[.foundationDataProtocol] ?? self.symbolTable[.essentialsDataProtocol])! @@ -108,6 +112,13 @@ extension Swift2JavaTranslator { visitor.visit(nominalDecl: dataDecl.syntax!.asNominal!, in: nil, sourceFilePath: "Foundation/FAKE_FOUNDATION_DATA.swift") } } + + // Foundation.Date + if let dateDecl = self.symbolTable[.foundationDate] ?? self.symbolTable[.essentialsDate] { + if self.isUsing(where: { $0 == dateDecl }) { + visitor.visit(nominalDecl: dateDecl.syntax!.asNominal!, in: nil, sourceFilePath: "Foundation/FAKE_FOUNDATION_DATE.swift") + } + } } package func prepareForTranslation() { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift index 9a111993f..c3bd51e88 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -114,7 +114,13 @@ private let foundationEssentialsSourceFile: SourceFileSyntax = """ public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) } - public struct Date {} + public struct Date { + /// The interval between the date object and 00:00:00 UTC on 1 January 1970. + public var timeIntervalSince1970: Double { get } + + /// Returns a `Date` initialized relative to 00:00:00 UTC on 1 January 1970 by a given number of seconds. + public init(timeIntervalSince1970: Double) + } """ private var foundationSourceFile: SourceFileSyntax { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 16240449a..6d061a5f1 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -14,7 +14,7 @@ import SwiftSyntax -enum SwiftKnownTypeDeclKind: String, Hashable { +enum SwiftKnownTypeDeclKind: String, Hashable, CaseIterable { // Swift case bool = "Swift.Bool" case int = "Swift.Int" @@ -75,7 +75,8 @@ enum SwiftKnownTypeDeclKind: String, Hashable { /// this type between jextract and wrap-java var isDirectlyTranslatedToWrapJava: Bool { switch self { - case .bool, .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string, .void: + case .bool, .int, .uint, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64, .float, .double, .string, + .void: return true default: return false diff --git a/Tests/JExtractSwiftTests/DateTests.swift b/Tests/JExtractSwiftTests/DateTests.swift index 2e88a6b3b..02b820af4 100644 --- a/Tests/JExtractSwiftTests/DateTests.swift +++ b/Tests/JExtractSwiftTests/DateTests.swift @@ -25,18 +25,16 @@ struct DateTests { /* expected Java chunks */ [ """ - public static void acceptDate(java.time.Instant date) { - SwiftModule.$acceptDate((date.getEpochSecond() + (date.getNano() / 1_000_000_000.0))); + public static void acceptDate(Date date) { + SwiftModule.$acceptDate(date.$memoryAddress()); } - """, + """ ], /* expected Swift chunks */ [ """ - @_cdecl("Java_com_example_swift_SwiftModule__00024acceptDate__D") - func Java_com_example_swift_SwiftModule__00024acceptDate__D(environment: UnsafeMutablePointer!, thisClass: jclass, date: jdouble) { - SwiftModule.acceptDate(date: Date.init(timeIntervalSince1970: Double(fromJNI: date, in: environment))) - } + @_cdecl("Java_com_example_swift_SwiftModule__00024acceptDate__J") + func Java_com_example_swift_SwiftModule__00024acceptDate__J(environment: UnsafeMutablePointer!, thisClass: jclass, date: jlong) { """ ], ) @@ -71,21 +69,14 @@ struct DateTests { /* expected Java chunks */ [ """ - public static java.time.Instant returnDate() { - double $instant = SwiftModule.$returnDate(); - long $seconds = (long) $instant; - long $nanos = (long) (($instant - $seconds) * 1_000_000_000); - return java.time.Instant.ofEpochSecond($seconds, $nanos); - } + public static Date returnDate(SwiftArena swiftArena$) { """ ], /* expected Swift chunks */ [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024returnDate__") - func Java_com_example_swift_SwiftModule__00024returnDate__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jdouble { - return SwiftModule.returnDate().timeIntervalSince1970.getJNIValue(in: environment) - } + func Java_com_example_swift_SwiftModule__00024returnDate__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { """ ] ) @@ -108,4 +99,53 @@ struct DateTests { mode, .swift, expectedChunks: expectedSwiftChunks) } + + @Test( + "Import: Date type", + arguments: [ + ( + JExtractGenerationMode.jni, + /* expected Java chunks */ + [ + """ + public final class Date implements JNISwiftInstance { + """, + """ + public static Date init(double timeIntervalSince1970, SwiftArena swiftArena$) { + """, + """ + public double getTimeIntervalSince1970() { + """, + ], + /* expected Swift chunks */ + [ + """ + @_cdecl("Java_com_example_swift_Date__00024init__D") + func Java_com_example_swift_Date__00024init__D(environment: UnsafeMutablePointer!, thisClass: jclass, timeIntervalSince1970: jdouble) -> jlong { + """, + """ + @_cdecl("Java_com_example_swift_Date__00024getTimeIntervalSince1970__J") + func Java_com_example_swift_Date__00024getTimeIntervalSince1970__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jdouble { + """ + ] + ) + ] + ) + func date_class(mode: JExtractGenerationMode, expectedJavaChunks: [String], expectedSwiftChunks: [String]) throws { + let text = + """ + import Foundation + public func f() -> Date + """ + + try assertOutput( + input: text, + mode, .java, + expectedChunks: expectedJavaChunks) + + try assertOutput( + input: text, + mode, .swift, + expectedChunks: expectedSwiftChunks) + } } From 683b75516dc0bd50fc35062f3b79fa1c10c34524 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 1 Feb 2026 17:15:40 +0100 Subject: [PATCH 4/7] cleanup --- Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 6d061a5f1..7f5b22c94 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -14,7 +14,7 @@ import SwiftSyntax -enum SwiftKnownTypeDeclKind: String, Hashable, CaseIterable { +enum SwiftKnownTypeDeclKind: String, Hashable { // Swift case bool = "Swift.Bool" case int = "Swift.Int" From 8c8be81b22f2ed258aceea3f8200ef27aa6fb4eb Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 1 Feb 2026 19:00:19 +0100 Subject: [PATCH 5/7] add helper methods --- .../test/java/com/example/swift/DateTest.java | 16 +++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 67 +++++++++++++++++++ Tests/JExtractSwiftTests/DateTests.swift | 6 ++ 3 files changed, 89 insertions(+) diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java index 5dbff53c3..9b13f37ec 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java @@ -18,6 +18,7 @@ import org.swift.swiftkit.core.SwiftArena; import java.time.Instant; +import java.time.temporal.ChronoUnit; import static org.junit.jupiter.api.Assertions.*; @@ -36,6 +37,21 @@ void date_functions() { } } + @Test + void date_helpers() { + try (var arena = SwiftArena.ofConfined()) { + var nowInstant = Instant.now(); + var date = Date.fromInstant(nowInstant, arena); + var converted = date.toInstant(); + + long diffNanos = Math.abs(ChronoUnit.NANOS.between(nowInstant, converted)); + System.out.println(diffNanos); + assertTrue(diffNanos < 1000, + "Precision loss should be contained to sub-microseconds. Actual drift: " + diffNanos + "ns"); + assertEquals(nowInstant.getEpochSecond(), converted.getEpochSecond()); + } + } + @Test void date_timeIntervalSince1970() { try (var arena = SwiftArena.ofConfined()) { diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index 28269ecd8..2e425dd0e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -265,12 +265,26 @@ extension JNISwift2JavaGenerator { printToStringMethods(&printer, decl) printer.println() + printSpecificTypeHelpers(&printer, decl) + printTypeMetadataAddressFunction(&printer, decl) printer.println() printDestroyFunction(&printer, decl) } } + /// Prints helpers for specific types like `Foundation.Date` + private func printSpecificTypeHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + guard let knownType = decl.swiftNominal.knownTypeKind else { return } + + switch knownType { + case .foundationDate, .essentialsDate: + printFoundationDateHelpers(&printer, decl) + + default: + break + } + } private func printToStringMethods(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printer.printBraceBlock("public String toString()") { printer in @@ -694,4 +708,57 @@ extension JNISwift2JavaGenerator { ) } } + + private func printFoundationDateHelpers(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printer.print( + """ + /** + * Converts this wrapped date to a Java {@link java.time.Instant}. + *

+ * This method constructs the {@code Instant} using the underlying {@code double} value + * representing seconds since the Unix Epoch (January 1, 1970). + *

+ * + * @return A {@code java.time.Instant} derived from the floating-point timestamp. + */ + public java.time.Instant toInstant() { + long seconds = (long) this.getTimeIntervalSince1970(); + long nanos = Math.round((this.getTimeIntervalSince1970() - seconds) * 1_000_000_000); + return java.time.Instant.ofEpochSecond(seconds, nanos); + } + """ + ) + printer.println() + printer.print( + """ + /** + * Initializes a date from a Java {@link java.time.Instant}. + * + *

Warning: Precision Loss

+ *

+ * The input precision will be degraded. + *

+ *

+ * Java's {@code Instant} stores time with nanosecond precision (9 decimal places). + * However, this class stores time as a 64-bit floating-point value. + *

+ *

+ * This leaves enough capacity for microsecond precision (approx. 6 decimal places). + *

+ *

+ * Consequently, the last ~3 digits of the {@code Instant}'s nanosecond field will be + * truncated or subjected to rounding errors during conversion. + *

+ * + * @param instant The source timestamp to convert. + * @return A date derived from the input instant with microsecond precision. + */ + public static Date fromInstant(java.time.Instant instant, SwiftArena swiftArena$) { + Objects.requireNonNull(instant, "Instant cannot be null"); + double timeIntervalSince1970 = instant.getEpochSecond() + (instant.getNano() / 1_000_000_000.0); + return Date.init(timeIntervalSince1970, swiftArena$); + } + """ + ) + } } diff --git a/Tests/JExtractSwiftTests/DateTests.swift b/Tests/JExtractSwiftTests/DateTests.swift index 02b820af4..f1423861c 100644 --- a/Tests/JExtractSwiftTests/DateTests.swift +++ b/Tests/JExtractSwiftTests/DateTests.swift @@ -116,6 +116,12 @@ struct DateTests { """ public double getTimeIntervalSince1970() { """, + """ + public static Date fromInstant(java.time.Instant instant, SwiftArena swiftArena$) { + """, + """ + public java.time.Instant toInstant() { + """ ], /* expected Swift chunks */ [ From 2f21ec97173065d8e48b5eb5dd94bc00ed962348 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 1 Feb 2026 19:11:52 +0100 Subject: [PATCH 6/7] amend after merging main --- Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift | 4 ++-- Tests/JExtractSwiftTests/DateTests.swift | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index a00386da3..a3d26a117 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -62,7 +62,7 @@ enum JNIJavaTypeTranslator { .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, - .array: + .array, .foundationDate, .essentialsDate: nil } } @@ -79,7 +79,7 @@ enum JNIJavaTypeTranslator { .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol, - .array: + .array, .foundationDate, .essentialsDate: nil } } diff --git a/Tests/JExtractSwiftTests/DateTests.swift b/Tests/JExtractSwiftTests/DateTests.swift index f1423861c..564c1b218 100644 --- a/Tests/JExtractSwiftTests/DateTests.swift +++ b/Tests/JExtractSwiftTests/DateTests.swift @@ -34,7 +34,7 @@ struct DateTests { [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024acceptDate__J") - func Java_com_example_swift_SwiftModule__00024acceptDate__J(environment: UnsafeMutablePointer!, thisClass: jclass, date: jlong) { + public func Java_com_example_swift_SwiftModule__00024acceptDate__J(environment: UnsafeMutablePointer!, thisClass: jclass, date: jlong) { """ ], ) @@ -76,7 +76,7 @@ struct DateTests { [ """ @_cdecl("Java_com_example_swift_SwiftModule__00024returnDate__") - func Java_com_example_swift_SwiftModule__00024returnDate__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + public func Java_com_example_swift_SwiftModule__00024returnDate__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { """ ] ) @@ -127,11 +127,11 @@ struct DateTests { [ """ @_cdecl("Java_com_example_swift_Date__00024init__D") - func Java_com_example_swift_Date__00024init__D(environment: UnsafeMutablePointer!, thisClass: jclass, timeIntervalSince1970: jdouble) -> jlong { + public func Java_com_example_swift_Date__00024init__D(environment: UnsafeMutablePointer!, thisClass: jclass, timeIntervalSince1970: jdouble) -> jlong { """, """ @_cdecl("Java_com_example_swift_Date__00024getTimeIntervalSince1970__J") - func Java_com_example_swift_Date__00024getTimeIntervalSince1970__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jdouble { + public func Java_com_example_swift_Date__00024getTimeIntervalSince1970__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jdouble { """ ] ) From 693eb91672d0820debd0ce98629214ae209dd01c Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sun, 1 Feb 2026 20:00:31 +0100 Subject: [PATCH 7/7] Update Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift --- .../JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index cc4e7db17..14b15217e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift @@ -727,7 +727,7 @@ extension JNISwift2JavaGenerator { printer.print( """ /** - * Initializes a date from a Java {@link java.time.Instant}. + * Initializes a Swift {@code Foundation.Date} from a Java {@link java.time.Instant}. * *

Warning: Precision Loss

*