Please explain this behavior in Kotlin:
data class A<T>(val p: T)
class B
fun f1(): A<Number> = A(B()) as A<Number>
fun f2(): A<Unit> = A(B()) as A<Unit>
fun main() {
println("f1: ${f1().p::class.simpleName}") // print "f1: B"
println("f2: ${f2().p::class.simpleName}") // print "f2: Unit"
}
I would expect the output of "B" in both the first and second cases.
Also, interestingly, its work correctly for data class A<T>(val p: T?)
.
Is it some kind of compiler optimization?
When return type is declared as Unit
, Kotlin applies specific behavior, it coerces the result to Unit
ignoring the actual return value.
It is stated in Kotlin specs: Coercion to kotlin.Unit.
In this line fun f2(): A<Unit> = A(B()) as A<Unit>
, it is declared that the type of p
has to be Unit
. So Kotlin discards the actual return value of f2().p
and coerces return value to Unit
.
The same behavior applies to functions with an explicitly declared return type of Unit
// compiled and returns Unit
fun compiledFun(): Unit = runBlocking {
"String"
}
// compilation exception: Argument type mismatch: actual type is 'kotlin.String', but 'kotlin.Int' was expected.
fun notCompiledFun(): Int = runBlocking {
"String"
}
This behavior is often used when writing JUnit tests, because JUnit does not run test methods unless their return type is Unit
. Similar to Java, where JUnit does not run test methods unless their return is void
.
@Test
fun `JUnit runs me`(): Unit = runBlocking {
assertEquals(4, 2 + 2)
"Done"
}
@Test
fun `JUnit does NOT run me`() = runBlocking {
assertEquals(4, 2 + 2)
"Done"
}
Regarding case data class A<T>(val p: T?)
:
The type of p
is Unit?
in case of A(B()) as A<Unit>
, and I guess Kotlin distinguishes between Unit
and Unit?
and therefore does not apply the coercion logic.