Repository navigation
JNI: payloadlessEnumsAsJavaEnums generates Java enums - #17
Open
piercifani wants to merge 2 commits into
Open
piercifani wants to merge 2 commits into
piercifani wants to merge 2 commits into
Conversation
With the new `payloadlessEnumsAsJavaEnums` option, a Swift enum whose cases have no associated values is generated as a Java `enum` with one constant per case, so Kotlin gets exhaustive `when`, identity and `entries`. The enum keeps the two entry points every conversion uses: `$memoryAddress()` returns a Swift value each constant creates once with its case native, and `wrapMemoryAddressUnsafe` maps a received Swift value to its case's constant and destroys it. Java enums don't override the final `equals`/`hashCode`. Generic, `Error` and caseless enums stay classes. Off by default: it changes the Java API of these enums. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack 13/21 · base:
jni/12-optional-nested-arrays· previous: #16 · next: #18 (jni/14-default-arguments)Problem
A Swift enum whose cases have no associated values becomes a Java class that wraps a Swift value. Kotlin code can't use it the way it uses an enum:
when:when (status) { … }can't be exhaustive. Callers must go throughgetDiscriminator()orgetCase().OrderStatus.FILLED, only factories such asOrderStatus.filled()that allocate a new Swift value on each call.values()orentriesto stand in forCaseIterable.Changes
A new configuration option,
payloadlessEnumsAsJavaEnums(defaultfalse), generates these enums as Javaenums with one constant per case (IDLE, LOADING, DONE, uppercased like the existingDiscriminator). It's opt-in because it changes the Java API of every such enum.The rest of the generator doesn't change, because the enum keeps the two entry points every conversion goes through:
$memoryAddress()returns the Swift value that belongs to the constant. Each constant creates its value once, on first use, with the existing case native ($idle()), and keeps it for the life of the process. So parameters, arrays, optionals, struct properties and bridges pass it as before, without allocating.wrapMemoryAddressUnsafe(selfPointer[, arena])reads the case with the existingSwiftObjects.getRawDiscriminator, destroys the Swift value it received (the caller transferred ownership), and returns the constant. So results, array elements, enum payloads, closure arguments and async results all produce the constant.equals/hashCode, which are final on Java enums. One constant per case already gives Swift's equality.$cleanup()returns a cleanup that never runs.Everything else the class had is unchanged: methods, properties,
rawValue,init(rawValue:),getDiscriminator(),getCase(), the case factories andtoString().Excluded, so they stay classes: generic enums,
Errorenums (which extendSwiftError), enums without cases (namespaces), and enums with any associated value.Most of the diff in
JavaBindingsPrintingis existing code re-indented under the newif; GitHub's "Hide whitespace" shows the real change.Kotlin in this PR
JavaEnumsTest.ktwithOrderStatus.swift; the library module turns the option on:The test also gets the constant back from every place a value crosses: optional result, dictionary value, struct property, enum case payload,
@Sendableclosure argument,asyncresult, plus 10,000 round trips.Why Skip needs it
Skip turns these enums into Kotlin
enum classes. A production Skip Fuse app (16 Skip modules) bridges 524 of them, and its Kotlin code relies on exhaustivewhenover them about 730 times and onallCases56 times (entrieshere).One difference remains: Skip keeps Swift's case names (
OrderStatus.filled), while this follows jextract's existing uppercaseDiscriminatornames (OrderStatus.FILLED).Testing
./gradlew :Samples:SwiftJavaExtractJNIKotlinSampleApp:testpasses (37 tests).JNIJavaEnumTests(3 tests, 5 cases): the Java enum with its constants, Swift value table,wrapMemoryAddressUnsafeand$memoryAddress, and noequals/hashCode/constructor; a class without the option; payload,Errorand empty enums stay classes with it.swift test --disable-experimental-prebuilts --disable-sandbox(661 tests) and./gradlew :Samples:SwiftJavaExtractJNISampleApp:test(292 tests) pass. The JNI sample doesn't turn the option on, so its existing enum tests cover the default.swift run generate-config-docs --check(the new option is documented inSwiftJavaConfigFile.md);swift format lint --stricton changed files;git diff --check.Not in this PR
🤖 Generated with Claude Code