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 a3f510d94..1a412094d 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 isPointerIdentityEqual = lhsTypePointer == rhsTypePointer && lhsPointer == rhsPointer + 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 isPointerIdentityEqual } 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 isPointerIdentityEqual } func perform(lhsType: L.Type, rhsType: R.Type) -> Bool {