From 82405f1fc79b488ad96137327c35da6844f55285 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sun, 14 Jun 2026 12:18:52 +0200 Subject: [PATCH 1/3] identity equality --- Sources/SwiftJava/SwiftObjects.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftJava/SwiftObjects.swift b/Sources/SwiftJava/SwiftObjects.swift index a3f510d94..39b958005 100644 --- a/Sources/SwiftJava/SwiftObjects.swift +++ b/Sources/SwiftJava/SwiftObjects.swift @@ -101,12 +101,15 @@ extension SwiftObjects { @JavaMethod public static func equals(environment: UnsafeMutablePointer!, lhsPointer: Int64, lhsTypePointer: Int64, rhsPointer: Int64, rhsTypePointer: Int64) -> Bool { + // Fallback for non-equatable types (such as classes) + let isEquatableByIdentity = lhsPointer == rhsPointer && lhsTypePointer == rhsTypePointer + guard let lhsType$ = UnsafeRawPointer(bitPattern: Int(lhsTypePointer)) else { fatalError("lhsType metadata address was null") } let lhsMetatype = unsafeBitCast(lhsType$, to: Any.Type.self) guard let lhsMetatype = lhsMetatype as? (any Equatable.Type) else { - return false + return isEquatableByIdentity } guard let rhsType$ = UnsafeRawPointer(bitPattern: Int(rhsTypePointer)) else { @@ -114,7 +117,7 @@ extension SwiftObjects { } let rhsMetatype = unsafeBitCast(rhsType$, to: Any.Type.self) guard let rhsMetatype = rhsMetatype as? (any Equatable.Type) else { - return false + return isEquatableByIdentity } func perform(lhsType: L.Type, rhsType: R.Type) -> Bool { From c31180df68884bc45182b7281fffc64cb3ee9020 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Mon, 15 Jun 2026 11:36:12 +0900 Subject: [PATCH 2/3] Add some tests, rename variable --- .../java/com/example/swift/HashableTest.java | 31 +++++++++++++++++++ Sources/SwiftJava/SwiftObjects.swift | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/HashableTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/HashableTest.java index 03bedd448..f05379264 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/HashableTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/HashableTest.java @@ -69,6 +69,37 @@ void hashSetValueType() { } } + @Test + void nonEquatableReferenceTypeFallsBackToIdentity() { + // MySwiftClass does not conform to Swift's `Equatable`, so the JNI + // bridge should fall back to identity equality. + try (var arena = SwiftArena.ofConfined()) { + var a = MySwiftClass.init(42, 1, arena); + var b = MySwiftClass.init(42, 1, arena); + assertEquals(a, a); + assertEquals(b, b); + // Different instances with equal field values must NOT be equal, + // since the type is not Equatable + assertNotEquals(a, b); + assertNotEquals(b, a); + assertNotEquals(a, "foo"); + } + } + + @Test + void nonEquatableReferenceTypeHashSetUsesIdentity() { + try (var arena = SwiftArena.ofConfined()) { + var a = MySwiftClass.init(42, 1, arena); + var b = MySwiftClass.init(42, 1, arena); + var set = new HashSet<>(List.of(a, b)); + assertTrue(set.contains(a)); + assertTrue(set.contains(b)); + // Two distinct instances of a non-Equatable class are distinct + // entries in the set + assertEquals(2, set.size()); + } + } + @Test void hashSetReferenceType() { try (var arena = SwiftArena.ofConfined()) { diff --git a/Sources/SwiftJava/SwiftObjects.swift b/Sources/SwiftJava/SwiftObjects.swift index 39b958005..0d57d4066 100644 --- a/Sources/SwiftJava/SwiftObjects.swift +++ b/Sources/SwiftJava/SwiftObjects.swift @@ -102,7 +102,7 @@ extension SwiftObjects { @JavaMethod public static func equals(environment: UnsafeMutablePointer!, lhsPointer: Int64, lhsTypePointer: Int64, rhsPointer: Int64, rhsTypePointer: Int64) -> Bool { // Fallback for non-equatable types (such as classes) - let isEquatableByIdentity = lhsPointer == rhsPointer && lhsTypePointer == rhsTypePointer + let isPointerIdentityEqual = lhsTypePointer == rhsTypePointer && lhsPointer == rhsPointer guard let lhsType$ = UnsafeRawPointer(bitPattern: Int(lhsTypePointer)) else { fatalError("lhsType metadata address was null") From 49148e5f6c64b063e6e1fe7c0b4a2356ac50cc2b Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Mon, 15 Jun 2026 11:52:22 +0900 Subject: [PATCH 3/3] Fix typo --- Sources/SwiftJava/SwiftObjects.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftJava/SwiftObjects.swift b/Sources/SwiftJava/SwiftObjects.swift index 0d57d4066..1a412094d 100644 --- a/Sources/SwiftJava/SwiftObjects.swift +++ b/Sources/SwiftJava/SwiftObjects.swift @@ -109,7 +109,7 @@ extension SwiftObjects { } let lhsMetatype = unsafeBitCast(lhsType$, to: Any.Type.self) guard let lhsMetatype = lhsMetatype as? (any Equatable.Type) else { - return isEquatableByIdentity + return isPointerIdentityEqual } guard let rhsType$ = UnsafeRawPointer(bitPattern: Int(rhsTypePointer)) else { @@ -117,7 +117,7 @@ extension SwiftObjects { } let rhsMetatype = unsafeBitCast(rhsType$, to: Any.Type.self) guard let rhsMetatype = rhsMetatype as? (any Equatable.Type) else { - return isEquatableByIdentity + return isPointerIdentityEqual } func perform(lhsType: L.Type, rhsType: R.Type) -> Bool {