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..9b13f37ec --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/DateTest.java @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// 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 org.swift.swiftkit.core.SwiftArena; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static org.junit.jupiter.api.Assertions.*; + +public class DateTest { + @Test + 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_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()) { + var date = Date.init(1000, arena); + assertEquals(1000, date.getTimeIntervalSince1970()); + } + } +} \ 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 18051cdb0..a3d26a117 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -45,7 +45,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 } } @@ -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/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift index bff76603f..14b15217e 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 @@ -689,4 +703,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 Swift {@code Foundation.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/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index 5b8ea99b0..756b09747 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -405,6 +405,10 @@ extension JNISwift2JavaGenerator { parameterName: parameterName ) + case .foundationDate, .essentialsDate: + // Handled as wrapped struct + break + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) @@ -687,6 +691,10 @@ extension JNISwift2JavaGenerator { elementType: elementType ) + case .foundationDate, .essentialsDate: + // Handled as wrapped struct + break + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index bb3a122ff..583666921 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -98,6 +98,10 @@ extension JNISwift2JavaGenerator { } return try translateArrayParameter(elementType: elementType, parameterName: parameterName) + case .foundationDate, .essentialsDate: + // Handled as wrapped struct + break + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { @@ -507,6 +511,10 @@ extension JNISwift2JavaGenerator { } return try translateArrayResult(elementType: elementType, resultName: resultName) + case .foundationDate, .essentialsDate: + // Handled as wrapped struct + break + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType, config: self.config), javaType.implementsJavaValue else { throw JavaTranslationError.unsupportedSwiftType(swiftResult.type) @@ -1175,4 +1183,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 0068ccd5f..dd82f82d9 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -745,6 +745,8 @@ extension JNISwift2JavaGenerator { """ ) + + self.lookupContext.symbolTable.printImportedModules(&printer) } private func printTypeMetadataAddressThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) { 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 1be8071c4..c3bd51e88 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownModules.swift @@ -113,6 +113,14 @@ private let foundationEssentialsSourceFile: SourceFileSyntax = """ public var count: Int { get } public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) -> Void) } + + 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 5401f0fab..7f5b22c94 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 @@ -73,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/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/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md b/Sources/SwiftJavaDocumentation/Documentation.docc/SupportedFeatures.md index 89c51b2f3..2f9b7fdad 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?)` | ✅ | ✅ | diff --git a/Tests/JExtractSwiftTests/DateTests.swift b/Tests/JExtractSwiftTests/DateTests.swift new file mode 100644 index 000000000..564c1b218 --- /dev/null +++ b/Tests/JExtractSwiftTests/DateTests.swift @@ -0,0 +1,157 @@ +//===----------------------------------------------------------------------===// +// +// 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(Date date) { + SwiftModule.$acceptDate(date.$memoryAddress()); + } + """ + ], + /* expected Swift chunks */ + [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024acceptDate__J") + public func Java_com_example_swift_SwiftModule__00024acceptDate__J(environment: UnsafeMutablePointer!, thisClass: jclass, date: jlong) { + """ + ], + ) + ] + ) + 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 Date returnDate(SwiftArena swiftArena$) { + """ + ], + /* expected Swift chunks */ + [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024returnDate__") + public func Java_com_example_swift_SwiftModule__00024returnDate__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + """ + ] + ) + ] + ) + 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) + } + + @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() { + """, + """ + public static Date fromInstant(java.time.Instant instant, SwiftArena swiftArena$) { + """, + """ + public java.time.Instant toInstant() { + """ + ], + /* expected Swift chunks */ + [ + """ + @_cdecl("Java_com_example_swift_Date__00024init__D") + public func Java_com_example_swift_Date__00024init__D(environment: UnsafeMutablePointer!, thisClass: jclass, timeIntervalSince1970: jdouble) -> jlong { + """, + """ + @_cdecl("Java_com_example_swift_Date__00024getTimeIntervalSince1970__J") + public 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) + } +} 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 + ] + ) + } +}