y
10/05/2023, 7:24 AMsealed class Foo : SomeInterface1, SomeInterface2 {
@kotlinx.serialization.Serializable
object A : Foo() {
private fun readResolve(): Any = this
override fun hashCode() = /* ... */
}
@kotlinx.serialization.Serializable
object B : Foo() {
private fun readResolve(): Any = this
override fun hashCode() = /* ... */
}
@kotlinx.serialization.Serializable
object C : Foo() {
private fun readResolve(): Any = this
override fun hashCode() = /* ... */
}
fun doTheThing(baz: List<String>): String = when (this) {
A -> /* ... */
B -> /* ... */
C -> /* ... */
}
}
the bug report was a kotlin.NoWhenBranchMatchedException thrown from doTheThing(). how could this be? is this necessarily a kotlin bug?Sam
10/05/2023, 7:32 AMFoo, it could end up with an instance that isn't one of the required subclasses.Youssef Shoaib [MOD]
10/05/2023, 8:40 AMdata object instead of object. What's happening is that the object is being recreated I thinky
10/05/2023, 8:59 AMtoString(), equals() and hashCode(), right? so if if we already implement hashCode() (hopefully correctly) and even readResolve(), would there be any difference with using a data object?
@Sam thanks - no mocking involved afaikYoussef Shoaib [MOD]
10/05/2023, 9:00 AMequals is what matters here I believe. That's why the when is failingYoussef Shoaib [MOD]
10/05/2023, 9:01 AMA -> B-> etc with is A-> is B-> and that should also work, because a data object's equals simply does an is checky
10/05/2023, 9:08 AM1.9 (which we might not want to do - not my decision)? is it as simple as adding the following boilerplate?
override fun equals(other: Any?) = hashCode() == (other as? A)?.hashCode() (and same for B and C)y
10/05/2023, 9:12 AMoverride fun equals(other: Any?) = other is A (and same for B and C)y
10/05/2023, 9:12 AMis A -> solution would work without 1.9Youssef Shoaib [MOD]
10/05/2023, 9:13 AMy
10/05/2023, 9:14 AMis A -> branches in the when? no equals() override required?Youssef Shoaib [MOD]
10/05/2023, 9:15 AMis A ->y
10/05/2023, 9:16 AMKlitos Kyriacou
10/05/2023, 9:56 AMy
10/05/2023, 9:57 AMas? check would then return null in that case (which means, the actual equality here is on the type and the check with the hashcode is superfluous)Klitos Kyriacou
10/05/2023, 10:00 AMthis.hashCode() happens to return 0, it will equal null.hashCode() so will give an incorrect true result.y
10/05/2023, 10:00 AMKlitos Kyriacou
10/05/2023, 10:03 AM(other as? A)?.hashCode() would be null?.hashCode() so it will be comparing 0 with null, which is false so it would give the right answer anyway.ephemient
10/05/2023, 2:19 PMobject doesn't override equals, so it doesn't rely on `hashCode`; it's purely the default equals, which is instance-basedephemient
10/05/2023, 2:20 PMobject in pure Kotlin, but it can happen through Java serialization or reflectionephemient
10/05/2023, 2:20 PMmockk and other mocking libraries do under the covers)y
10/05/2023, 2:22 PMis be the preferred way of making sure all instances of the same `object`s test the same?ephemient
10/05/2023, 2:23 PMYoussef Shoaib [MOD]
10/05/2023, 2:24 PMdata object it handles all of that because it overrides equals and hashcodeephemient
10/05/2023, 2:24 PMreadResolve to ensure Java serialization doesn't return non-singleton instances, if you think that's what your users are running into, but there's no way to protect against reflectionephemient
10/05/2023, 2:27 PMreadResolveephemient
10/05/2023, 2:27 PMy
10/05/2023, 2:29 PMephemient
10/05/2023, 2:30 PMobject A {
private fun readResolve(): Any = A
}
if you want to ensure that only the singleton A.INSTANCE exists (after the serialization machinery)ephemient
10/05/2023, 2:32 PMenum class Foo : SomeInterface1, SomeInterface2 {
A,
B,
C,
}
then Java serialization will just worky
10/05/2023, 2:40 PMreadResolve() implementation is probably it! thanks for catching it