Skip to content

JNI: fix existential result boxing in Swift 6 language mode - #930

Open
Om-singhaI wants to merge 2 commits into
swiftlang:mainfrom
Om-singhaI:jni-existential-result-swift6
Open

Om-singhaI wants to merge 2 commits into
swiftlang:mainfrom
Om-singhaI:jni-existential-result-swift6

Conversation

@Om-singhaI

Copy link
Copy Markdown
Contributor

When a JNI function returns any P or some P, the thunk boxes the result with this:

#if hasFeature(ImplicitOpenExistentials)
let resultBoxed$: (Int64, Int64) = {
  let value = resultExistential$
  let pointer = UnsafeMutablePointer<type(of: value)>.allocate(capacity: 1)
  ...

type(of: value) is a value, not a type, so it can't be a generic argument. Swift 5 mode never takes that branch, and the JNI sample app is the only package in CI that extracts any P or some P returns (ReturnProtocol.swift and others). It builds with .swiftLanguageMode(.v5), so CI doesn't compile it. In Swift 6 mode, or with the ImplicitOpenExistentials upcoming feature on, the generated file fails to build:

error: '>' is not a postfix unary operator
error: generic parameter 'Pointee' could not be inferred

This is the Swift 6 failure I mentioned at the end of #925, and one of the cases behind #472.

The resultBox$<T> helper now sits above the #if, and the feature branch calls it directly. With implicit opening that call opens the existential, so T is the concrete type, the same as _openExistential gives in Swift 5. The existential property setter already does it this way. Swift 5 mode compiles the same code as before.

I added returnsExistentialProtocol_boxing_swift to JNIProtocolTests. It fails without the source change, and JNIProtocolTests and JNIAsyncTests pass with it.

I also typechecked the generated thunks with swiftc (Swift 6.4) for the JNI sample's ReturnProtocol.swift, plus a computed property and a class method returning any P. On main they fail with the errors above in Swift 6 mode and with -enable-upcoming-feature ImplicitOpenExistentials. With this change they are clean in both of those and in Swift 5 mode. A small runtime check confirmed that in Swift 6 the helper gets the concrete type, not any P.

Results of type `any P` or `some P` are boxed in a branch guarded by
`#if hasFeature(ImplicitOpenExistentials)`. That branch used
`UnsafeMutablePointer<type(of: value)>`, which is not valid Swift, so
the generated thunks failed to compile in Swift 6 language mode or with
the upcoming feature enabled.

Define the generic box helper above the `#if` and call it directly when
the feature is on, since the call then opens the existential. Swift 5
mode still uses `_openExistential` and compiles the same code as before.
@Om-singhaI
Om-singhaI requested a review from ktoso as a code owner October 6, 2026 00:13

@ktoso ktoso left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a runtime test exercising this path so we're confident it functions
Runtime tests are located inside the samples basically writing the s swift and Java sides as if you were to use this normally

The JNI sample builds MySwiftLibrary in Swift 5 mode, so its runtime
tests only went through the `_openExistential` branch of the generated
thunks, and the branch taken in Swift 6 mode was never compiled in CI.

Enable the ImplicitOpenExistentials upcoming feature for that target so
the protocol tests run through that branch, and add GreeterFactory to
cover a method and a computed property returning `any Greeter`, and a
method returning `some Greeter`. The new tests also check that the
returned value carries the concrete type's metadata.
@Om-singhaI

Copy link
Copy Markdown
Contributor Author

I turned on the ImplicitOpenExistentials upcoming feature for MySwiftLibrary in the JNI sample, so ReturnProtocolTest and the other protocol tests now run the hasFeature(ImplicitOpenExistentials) code at runtime. I also added a GreeterFactory class with tests for a method and a computed property returning any Greeter and a method returning some Greeter, and they check that the returned value keeps its concrete Swift type. With the generator change reverted, the sample fails to build with the two errors from the description. One tradeoff is that the sample no longer runs the _openExistential path, so if you would rather keep both covered I can move the new code into a separate target.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants