Skip to content

JNI: Foundation values as standard Java types - #27

Open
piercifani wants to merge 2 commits into
jni/22-export-controlfrom
jni/23-foundation-java-types
Open

piercifani wants to merge 2 commits into
jni/22-export-controlfrom
jni/23-foundation-java-types

Conversation

@piercifani

Copy link
Copy Markdown

Stack 23/23 · base: jni/22-export-control · previous: #26

Problem

Data, Date and URL reach Java as Swift-backed wrapper classes generated into every module's Java package. In a multi-module app, a URL returned by one module's API is a different Java type from the URL another module's API accepts, so the value can't be passed along. Optional URL? parameters weren't supported either. Skip bridges these as java.net.URI, java.util.Date and ByteArray.

Changes

This ports upstream swiftlang#952. With "foundationTypesAsJavaTypes": true:

Swift Java / Kotlin
URL java.net.URI
Date java.util.Date
Data byte[] / ByteArray
Locale java.util.Locale
TimeZone java.util.TimeZone

CGFloat is a double either way. The values conform to JavaValue in SwiftJavaRuntimeSupport, and no wrapper classes are generated.

On top of swiftlang#952, for features that only exist in this stack:

Kotlin in this PR

FoundationValuesTest.kt with Bookmarks.swift:

val bookmark = Bookmark.init(URI.create("https://swift.org/docs"), null, Date(1_700_000_000_123),
    byteArrayOf(1, 2, -1), Locale.forLanguageTag("pt-BR"), TimeZone.getTimeZone("Europe/Rome"), 1.5)
bookmark.icon = URI.create("https://swift.org/favicon.ico")      // var icon: URL?
bookmark.onOpen = { url: URI -> opened += url }                   // var onOpen: ((URL) -> Void)?
bookmark.observeVisits { url, date -> visit = url to date }       // @escaping (URL, Date) -> Void
bookmark.refreshed().get().time                                   // async -> Date

Why Skip needs it

Skip bridges these types by value, to the same Java types. On a production Skip Fuse app (9 Skip modules) run through this stack in annotated mode, with the app's bridging annotations as @JavaExport markers:

Testing

🤖 Generated with Claude Code

piercifani and others added 2 commits November 4, 2026 14:34
Ports swiftlang#952: with `"foundationTypesAsJavaTypes": true`,
`URL`, `Date`, `Data`, `Locale` and `TimeZone` cross as `java.net.URI`,
`java.util.Date`, `byte[]`, `java.util.Locale` and `java.util.TimeZone`
instead of wrapper classes generated into every module, through `JavaValue`
conformances in SwiftJavaRuntimeSupport. `CGFloat` is a Java `double`.

On top of swiftlang#952, for this stack:

- Escaping closures pass these values to Java as they are, since their
  `@JavaInterface` is printed by jextract and the values are `JavaValue`s,
  and closures returned to Java box them with a new `JavaObjectValueBridge`.
  Protocols implemented in Java still skip them: wrap-java maps the Java
  types on its own terms.
- With `optionalsAsNullable`, an optional parameter whose Java type has a
  package, such as `Optional<java.net.URI>`, now becomes nullable too: its
  dots made the one-name spelling parse as a package and a class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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