diff --git a/Samples/SwiftJavaExtractJNISampleApp/Package.swift b/Samples/SwiftJavaExtractJNISampleApp/Package.swift index 4f86b1a9e..ba0313608 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Package.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Package.swift @@ -51,7 +51,10 @@ let package = Package( "swift-java.config" ], swiftSettings: [ - .swiftLanguageMode(.v5) + .swiftLanguageMode(.v5), + // Generated thunks use a different code path when existentials are opened + // implicitly (the Swift 6 default), so enable it to build and test that path. + .enableUpcomingFeature("ImplicitOpenExistentials"), ], plugins: [ .plugin(name: "JExtractSwiftPlugin", package: "swift-java") diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ReturnProtocol.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ReturnProtocol.swift index d82574ed6..0111894a9 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ReturnProtocol.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ReturnProtocol.swift @@ -68,3 +68,23 @@ public func makeOpaqueGreeter(name: String) -> some Greeter { public func describeGreeter(_ greeter: any Greeter) -> String { greeter.greeting() } + +public final class GreeterFactory { + public let name: String + + public init(name: String) { + self.name = name + } + + public var englishGreeter: any Greeter { + EnglishGreeter(name: name) + } + + public func makeDanishGreeter() -> any Greeter { + DanishGreeter(name: name) + } + + public func makeOpaqueGreeter() -> some Greeter { + DanishGreeter(name: name) + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ReturnProtocolTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ReturnProtocolTest.java index 2095e7c08..0e713490b 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ReturnProtocolTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ReturnProtocolTest.java @@ -72,4 +72,40 @@ void returnedProtocolRoundTripsIntoParameter() { assertEquals("Hej, Verden!", MySwiftLibrary.describeGreeter(greeter)); } } + + @Test + void returnExistentialFromMethod() { + try (var arena = SwiftArena.ofConfined()) { + GreeterFactory factory = GreeterFactory.init("Verden", arena); + Greeter greeter = factory.makeDanishGreeter(arena); + long danishType = DanishGreeter.init("Verden", arena).$typeMetadataAddress(); + assertEquals(danishType, ((JNISwiftInstance) greeter).$typeMetadataAddress()); + assertEquals("Hej, Verden!", greeter.greeting()); + assertEquals("Hej, Verden! Hej, Verden!", greeter.repeated(2)); + } + } + + @Test + void returnOpaqueFromMethod() { + try (var arena = SwiftArena.ofConfined()) { + GreeterFactory factory = GreeterFactory.init("Verden", arena); + Greeter greeter = factory.makeOpaqueGreeter(arena); + long danishType = DanishGreeter.init("Verden", arena).$typeMetadataAddress(); + assertEquals(danishType, ((JNISwiftInstance) greeter).$typeMetadataAddress()); + assertEquals("Hej, Verden!", greeter.greeting()); + assertEquals("Hej, Verden!", MySwiftLibrary.describeGreeter(greeter)); + } + } + + @Test + void returnExistentialFromComputedProperty() { + try (var arena = SwiftArena.ofConfined()) { + GreeterFactory factory = GreeterFactory.init("World", arena); + Greeter greeter = factory.getEnglishGreeter(arena); + long englishType = EnglishGreeter.init("World", arena).$typeMetadataAddress(); + assertEquals(englishType, ((JNISwiftInstance) greeter).$typeMetadataAddress()); + assertEquals("Hello, World!", greeter.greeting()); + assertEquals("Hello, World! Hello, World!", greeter.repeated(2)); + } + } } diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 229e48f55..764edaa6f 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -1640,23 +1640,20 @@ extension JNISwift2JavaGenerator { // existential so the boxing helper below can open it uniformly. printer.print("let \(existentialName): \(existentialType) = \(inner)") + // With ImplicitOpenExistentials, passing the existential to the generic + // helper opens it, so T is the dynamic type. Without the feature, T would + // be the existential type itself, so it has to be opened explicitly. printer.print( """ - #if hasFeature(ImplicitOpenExistentials) - let \(boxedName): (Int64, Int64) = { - let value = \(existentialName) - let pointer = UnsafeMutablePointer.allocate(capacity: 1) - pointer.initialize(to: value) - let metadataPointer = unsafeBitCast(type(of: value), to: UnsafeRawPointer.self) - return (Int64(Int(bitPattern: pointer)), Int64(Int(bitPattern: metadataPointer))) - }() - #else func \(name)Box$(_ value: T) -> (Int64, Int64) { let pointer = UnsafeMutablePointer.allocate(capacity: 1) pointer.initialize(to: value) let metadataPointer = unsafeBitCast(T.self, to: UnsafeRawPointer.self) return (Int64(Int(bitPattern: pointer)), Int64(Int(bitPattern: metadataPointer))) } + #if hasFeature(ImplicitOpenExistentials) + let \(boxedName) = \(name)Box$(\(existentialName)) + #else let \(boxedName) = _openExistential(\(existentialName), do: \(name)Box$) #endif """ diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index fc1e971bf..eef6119d9 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -542,6 +542,36 @@ struct JNIProtocolTests { ) } + @Test + func returnsExistentialProtocol_boxing_swift() throws { + try assertOutput( + input: returnSource, + config: config, + .jni, + .swift, + detectChunkByInitialLines: 2, + expectedChunks: [ + """ + let resultExistential$: (any Greeter) = SwiftModule.makeGreeter() + func resultBox$(_ value: T) -> (Int64, Int64) { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.initialize(to: value) + let metadataPointer = unsafeBitCast(T.self, to: UnsafeRawPointer.self) + return (Int64(Int(bitPattern: pointer)), Int64(Int(bitPattern: metadataPointer))) + } + #if hasFeature(ImplicitOpenExistentials) + let resultBoxed$ = resultBox$(resultExistential$) + #else + let resultBoxed$ = _openExistential(resultExistential$, do: resultBox$) + #endif + """ + ], + notExpectedChunks: [ + "UnsafeMutablePointer" + ] + ) + } + @Test func existentialBoxPropertyAccessors_swift() throws { try assertOutput(