Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Samples/SwiftJavaExtractJNISampleApp/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<type(of: value)>.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$<T>(_ value: T) -> (Int64, Int64) {
let pointer = UnsafeMutablePointer<T>.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
"""
Expand Down
30 changes: 30 additions & 0 deletions Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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$<T>(_ value: T) -> (Int64, Int64) {
let pointer = UnsafeMutablePointer<T>.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<type(of: value)>"
]
)
}

@Test
func existentialBoxPropertyAccessors_swift() throws {
try assertOutput(
Expand Down