Repository navigation
JNI: escaping closures taking jextracted types; @Sendable/@MainActor closure types - #2
Open
piercifani wants to merge 2 commits into
Open
piercifani wants to merge 2 commits into
piercifani wants to merge 2 commits into
Conversation
This was referenced Nov 3, 2026
piercifani
force-pushed
the
jni/02-closures-jextracted-types
branch
from
November 3, 2026 14:41
9fbed84 to
e9e6fe2
Compare
piercifani
force-pushed
the
jni/02-closures-jextracted-types
branch
from
November 3, 2026 15:18
e9e6fe2 to
ae76f9d
Compare
…mainactor closure types Escaping closures whose parameters were jextracted types produced Swift that didn't compile: the @javainterface wrapper declared the Swift type (which isn't a JavaValue), and the statements wrapping the value in its Java class were rendered into a discarded printer, leaving `_0Class`/`_0Pointer` undefined. Closure types were also matched by substring, so only `@escaping` was understood and any other attribute without it made the type unsupported. - Parse function-type attributes individually: `@escaping`, `@Sendable`, `@MainActor`, `@convention(c|swift)`. - Declare jextracted parameters in the closure's @javainterface as their Java class (`JavaItem?`, `JavaOptional<JavaItem>?` for optionals) and emit the argument conversions inside the closure body. - Without Java callbacks (no wrap-java run), print a minimal @javaclass wrapper with `wrapMemoryAddressUnsafe` for each such type, once per module. - A @sendable closure can run on any thread, so re-wrap the Java object with the calling thread's JNIEnv on each call instead of the creating thread's. - Treat a closure property's setter `newValue` as escaping, since stored closures always are; it previously captured the call's JNIEnv and local ref. Adds generator tests (all fail without this change), JNI sample fixtures with Java tests, and documentation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closures with jextracted and optional parameters, a @sendable closure invoked on another thread, and a Callbacks struct of @mainactor @sendable closures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
piercifani
force-pushed
the
jni/02-closures-jextracted-types
branch
from
November 3, 2026 15:56
ae76f9d to
700089f
Compare
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 2/21 · base:
jni/01-main-actor-thunks· previous: #11 · next: #3Problem
In JNI mode:
@JavaInterfacewrapper declared the Swift type (not aJavaValue). The statements that wrap the value in its Java class were rendered into a printer that was then discarded, leaving_0Class/_0Pointerundefined.@escapingwas understood;@Sendableand@MainActorwere dropped, and without@escapingany attribute made the type unsupported. A stored property of type@MainActor @Sendable (Item) -> Voidwas skipped.JNIEnvof the thread that created it, so a@Sendableclosure called from another thread used the wrong environment.JNIEnvand a local reference that are invalid once the call returns.Changes
@escaping,@Sendable,@MainActor,@convention(c|swift). Other attributes are still tolerated next to@escaping, as before.@JavaInterface, declare jextracted parameters as their Java class (JavaItem?, andJavaOptional<JavaItem>?for optionals), and emit the argument conversions inside the closure body.@JavaClasswrapper withwrapMemoryAddressUnsafefor each such type, once per module. With callbacks on, wrap-java's classes are used.@Sendableclosures, re-wrap the Java object with the calling thread'sJNIEnv(JavaVirtualMachine.shared().environment()) on each call.newValueas escaping.@Sendable/@MainActorclosure types.Why Skip needs it
Skip Fuse view models take a
Callbacksstruct of@MainActor @Sendableclosures whose parameters are app structs. In a production Skip Fuse app (16 Skip modules), 849 of 1,351 bridged closures have non-primitive parameter or result types, and 419 are main-actor closures.Kotlin in this PR
ClosuresTest.ktwithClosures.swift: Kotlin lambdas passed as escaping closures taking a struct, as a closure with an optional struct parameter, and as a@Sendableclosure that Swift calls on another thread. The test also builds aCallbacksstruct of@MainActor @Sendableclosures (macOS only).Testing
./gradlew :Samples:SwiftJavaExtractJNIKotlinSampleApp:testpasses at this point in the stack.JNIEscapingClosureJExtractedTypesTests(7 tests); all 7 fail on the base branch and pass with this change.swift test --disable-experimental-prebuilts --disable-sandboxpasses.deliverStruct,deliverOptionalStruct) with Java tests;./gradlew :Samples:SwiftJavaExtractJNISampleApp:testpasses 275/275. The sample builds with Java callbacks enabled, so that mode is covered.@Sendableclosure invoked from a background thread, and aCallbacksstruct of@MainActor @Sendableclosures.swift format lint --stricton changed files;git diff --check.Not in this PR
initholder type for initializers that take closures (JavaFoo.init.onOpen), which doesn't compile. That's a separate wrap-java fix.🤖 Generated with Claude Code