Repository navigation
JNI: fix Int closures and inout thunks; infer unannotated variable types - #6
Open
piercifani wants to merge 2 commits into
Open
piercifani wants to merge 2 commits into
piercifani wants to merge 2 commits into
Conversation
piercifani
force-pushed
the
jni/05-codegen-fixes
branch
from
November 3, 2026 14:41
d43cc45 to
f937361
Compare
piercifani
force-pushed
the
jni/05-codegen-fixes
branch
from
November 3, 2026 15:19
f937361 to
0e88ab3
Compare
piercifani
force-pushed
the
jni/05-codegen-fixes
branch
from
November 3, 2026 15:56
0e88ab3 to
37460af
Compare
- `(Int) -> Int` closures: the parameter translation chose the closure's own interface (by Swift type), but the helper printer matched `LongUnaryOperator` by Java type and skipped printing it, so the Java didn't compile; the upcall likewise looked up `applyAsLong` by JNI signature on an object that only has `apply`. Both now use the Swift-type lookup, and the method name travels with the `closureLowering` conversion. - `inout` parameters: the thunk passed a converted temporary, which doesn't compile. They have no Java mapping, so such functions are skipped with `inoutParametersNotSupported` (and reported). - Unannotated variables: infer the type from literals (`Int`, `Double`, `String`, `Bool`) and from `T(...)` / `A.B(...)` initializer calls whose callee resolves to a nominal type, instead of dropping them with `missingTypeAnnotation`. Other initializers still need an annotation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
piercifani
force-pushed
the
jni/05-codegen-fixes
branch
from
November 3, 2026 16:46
37460af to
1810a9f
Compare
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 5/21 · base:
jni/04-enum-case-name-collision· previous: #5 · next: #7Three small JNI code-generation fixes and one analysis improvement, each found by building small modules shaped like Skip Fuse code in Swift 6 mode.
1.
(Int) -> Intclosures emit Java that doesn't compileFor a non-escaping closure, the parameter translation picks the Java interface with
KnownJavaFunctionalInterface.find(SwiftFunctionType). That lookup only knowsInt32/Int64, so(Int) -> Intgets its own interface (SwiftModule.apply.f). The helper-class printer instead used the lookup by Java type, whereIntislong, matchedLongUnaryOperator, and skipped printing the interface. The method then referenced an interface that didn't exist. The Swift upcall had the same mismatch: it looked the method up by JNI signature ((J)J→applyAsLong) on an object that only hasapply, which would crash inGetMethodID(...)!.Fix: the helper printer and the upcall both use the Swift-type lookup. The method name to call travels with the
closureLoweringconversion from where the interface is chosen. The Java API forIntclosures is unchanged (its own interface);Int64closures still useLongUnaryOperator.Once these closures compiled, 32-bit Android (armv7) exposed one more bug: closure lowering converted
IntwithInt(fromJNI:)andgetJValue, which assumeInt.JNITypeisjlong; on 32-bit it'sjint.Int/UIntclosure arguments and results now go throughInt64/UInt64, matching the Javalong. A result that doesn't fit in 32 bits traps, as converting the integer in Swift would.2.
inoutparameters produce Swift that doesn't compileThe thunk passed a converted temporary to an
inoutparameter (cannot pass immutable value as inout argument). Java has noinout, so functions withinoutparameters are now skipped withinoutParametersNotSupported. With #3 they're reported as a warning, or fail the build withfailOnSkippedDeclarations.3. Unannotated variables are inferred when unambiguous
public static let answer = 42was dropped withmissingTypeAnnotation. Analysis now infers the type when the initializer settles it without type checking:Int, float →Double, string →String,true/false→BoolT(...)/A.B(...)whose callee resolves to a nominal type (not an unresolved placeholder)Everything else still needs an annotation, including
T.member,T.make()andmakeValue(), because the type can't be known without the type checker.Why Skip needs it
In a production Skip Fuse app (16 Skip modules), the bridged modules have 594 public stored properties without a type annotation. Most are literals or
T()initializers, such aspublic var isLoading = false.Kotlin in this PR
InferredTypesTest.ktwithInferredTypes.swift: Kotlin lambdas as(Int) -> Intand(Int, Int) -> Intclosures, and unannotated properties (= 42,= false,= "Untitled",= 0.5,= Item(...)) read and written from Kotlin. With strict mode (#3) on, the sample's build would fail if any were still dropped.Testing
./gradlew :Samples:SwiftJavaExtractJNIKotlinSampleApp:testpasses at this point in the stack.JNICodegenFixesTests, 8 tests, one of which checks the 64-bit widening ofIntclosure arguments and results. The tests for the fixes fail on the base branch; the 2 controls pass on both (anInt64closure still usesLongUnaryOperator, andT.make(),makeValue()andT.memberare still skipped).applyIntClosureandClosureConstants.answerwith Java tests.(Int) -> Int,(Int, Int) -> Int, a throwing closure and an untypedstatic let. A module with aninoutfunction builds and reports the skip.swift test --disable-experimental-prebuilts --disable-sandboxand./gradlew :Samples:SwiftJavaExtractJNISampleApp:testpass.swift format lint --stricton changed files;git diff --check.Not in this PR
() throws -> Tclosures are called as non-throwing.inoutremains unsupported (a mapping such as a one-element array is a design question).🤖 Generated with Claude Code