Skip to content

JNI: qualify enum payload types shadowed by Case record names - #5

Open
piercifani wants to merge 2 commits into
jni/03-skipped-declarationsfrom
jni/04-enum-case-name-collision
Open

piercifani wants to merge 2 commits into
jni/03-skipped-declarationsfrom
jni/04-enum-case-name-collision

Conversation

@piercifani

@piercifani piercifani commented Nov 3, 2026 •

Copy link
Copy Markdown

Stack 4/21 · base: jni/03-skipped-declarations · previous: #3 · next: #6

Problem

For an enum with associated values, JNI mode generates a sealed interface Case with one record per case. Inside that interface, each record's name shadows any type with the same simple name, so a case named like its payload type produces Java that doesn't compile:

public enum Destination {
  case home
  case market(Market)
}
public sealed interface Case {
  record Home() implements Case {}
  record Market(Market arg0) implements Case {}   // `Market` here is Case.Market itself
}
// error: incompatible types: Market cannot be converted to Destination.Case.Market

The same happens when one case's payload is named like another case's record (case market plus case featured(Market)), and for arrays of that type.

Changes

When printing the Case records, any package-less component type whose leading name matches one of the records is fully qualified with the module's Java package, recursively through arrays and type arguments:

record Market(com.example.swift.Market arg0) implements Case {}
record List(com.example.swift.Market[] arg0) implements Case {}

Package-less types are the ones jextract generates into the module's package: jextracted types plus Date, Data and URL. Generic parameters of the enum are never qualified. Payloads with no collision are printed as before.

Why Skip needs it

Naming a case after its payload type is a common Swift pattern. A production Skip Fuse app (16 Skip modules) has 33 such cases in its bridged enums.

Kotlin in this PR

EnumCaseNamesTest.kt with EnumCaseNames.swift: getCase() on case market(Market), and on an enum where another case's record shadows the payload type, including an array of it.

Testing

  • Kotlin sample: ./gradlew :Samples:SwiftJavaExtractJNIKotlinSampleApp:test passes at this point in the stack.
  • New JNIEnumCaseNameShadowingTests: a case named like its payload, another case's record shadowing a payload (including an array), and an unshadowed control. Both collision tests fail on the base branch; the control passes on both.
  • JNI sample: a StructHolder enum with case mySwiftStruct(MySwiftStruct) and a Java test reading the payload through getCase.
  • A standalone Swift 6 package passes Kotlin tests on the JVM for both shapes.
  • Full swift test --disable-experimental-prebuilts --disable-sandbox and ./gradlew :Samples:SwiftJavaExtractJNISampleApp:test pass.
  • swift format lint --strict on changed files; git diff --check.

Not in this PR

A payload type named Case or Discriminator would collide with the generated member types of the enum class itself; that isn't handled.

🤖 Generated with Claude Code

piercifani and others added 2 commits November 3, 2026 17:35
Inside the generated `sealed interface Case`, each record's name shadows a type
with the same simple name. For `case market(Market)` the record became
`record Market(Market arg0)`, whose component referred to the record itself,
and the generated Java didn't compile. The same happened when another case's
record had the payload's name, including for arrays.

Fully qualify package-less component types (types generated into this module's
Java package) whose leading name matches a record, recursively through arrays
and type arguments. Generic parameters and unshadowed payloads are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@piercifani
piercifani force-pushed the jni/04-enum-case-name-collision branch from 888b7d5 to a0aa245 Compare November 3, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant