Repository navigation
JNI: wrappers of the same Swift object are equal; arena-free getCase() - #13
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/09-object-identity
branch
from
November 3, 2026 15:56
0b58963 to
5706c92
Compare
Each returned class instance gets its own pointer box, and for classes that aren't Equatable `SwiftObjects.equals` compared the boxes, so two wrappers of one object were never equal and hashed differently. - `equals`: when both sides are class instances, compare the objects with `===` first (wrappers of one instance are equal whatever their static type); other instances of an Equatable class still use `==`. - `hashCode`: non-Hashable classes hash by `ObjectIdentifier`, or by type when they are Equatable, so equal objects always hash the same. - With `allowGlobalAutomatic`, enums whose cases need an arena also get an arena-free `getCase()`. 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/09-object-identity
branch
from
November 3, 2026 16:46
5706c92 to
22e6da6
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 9/21 · base:
jni/08-async-cancellation· previous: #10 · next: #14Problem
Two Java wrappers of the same Swift object weren't equal. Each returned class instance gets its own pointer box. For a class that isn't
Equatable,SwiftObjects.equalscompared the box addresses, soSession.shared()called twice gave two unequal objects with different hash codes. Kotlin==,HashSet/HashMap, and Compose's change detection (which skips recomposition for equal values) all depend on this.getCase()on an enum with payloads always needed an arena, even inallowGlobalAutomaticmode, where every other member also gets an arena-free overload.Changes
SwiftObjects.equals: when both sides are class instances, load the references and compare them with===first. Wrappers of the same instance are equal whatever their static type, for example aDogwrapper and anAnimalwrapper of the same object. Different instances of anEquatableclass still compare with==; other classes compare by identity. Value types are unchanged.SwiftObjects.hashCode: classes that aren'tHashablehash withObjectIdentifierof the instance. A class that'sEquatablebut notHashablehashes by its type instead, so objects that compare equal always hash the same.getCase(): withallowGlobalAutomatic, enums whose cases need an arena also getgetCase(), which uses the default automatic arena. In Kotlin that reads as the propertyroute.case.Kotlin in this PR
IdentityTest.ktwithIdentity.swift:Session.getShared()twice gives equal objects, and asetOfof them has one element.Dogpassed through a function typedAnimalcomes back equal, with the same hash code.route.caseworks without an arena.Why Skip needs it
Skip's bridge compares Kotlin peers by the Swift object they hold, and Compose state relies on that equality. A production Skip Fuse app (16 Skip modules) bridges about 1,200 classes this way.
Testing
./gradlew :Samples:SwiftJavaExtractJNIKotlinSampleApp:testpasses at this point in the stack.JNIEnumGetCaseArenaTests: the overload exists inallowGlobalAutomaticmode and not inexplicitmode.Squareround-tripped through aShapeparameter equals the original and has the same hash code, and a differentSquaredoesn't equal it.swift test --disable-experimental-prebuilts --disable-sandboxand./gradlew :Samples:SwiftJavaExtractJNISampleApp:testpass.swift format lint --stricton changed files;git diff --check.Not in this PR
===on wrappers is still false.🤖 Generated with Claude Code