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 failingA ->
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
)override fun equals(other: Any?) = other is A
(and same for B
and C
)is A ->
solution would work without 1.9
Youssef 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-basedobject
in pure Kotlin, but it can happen through Java serialization or reflectionmockk
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 reflectionreadResolve
y
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)enum 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