I found something in Kotlin that had always been intuitive to me, but I recently realised I can't explain the low level details of this.
Consider what the return type of x.returnT
, and the parameter types of consumeT
and aMoreComplicatedCase
would be in the below code:
val x: Foo<*> = TODO()
class Foo<T> {
fun returnT(): T = TODO()
fun consumeT(t: T) {}
fun aMoreComplicatedCase(func: (T) -> T) {}
}
IntelliJ tells me that x.returnT
returns Any?
, consumeT
takes Nothing
, and aMoreComplicatedCase
takes (Any?) -> Nothing
.
Using the spec, we know that *
roughly means out Any?
and in Nothing
at the same time, and applying PECS (Or maybe this should be called POCI in Kotlin since producer out
consumer in
), we know x
is a producer of Any?
and a consumer of nothing.
An even more intuitive explanation is that *
means "we know nothing about what goes in the <>
of Foo
at all", and because of that, of course returnT
can return anything, even nullable things. Similarly, of course consumeT
can't take anything - it takes T
, the exact thing we don't know.
Similar arguments can be applied to cases where it is out SomeType
or in SomeType
.
Where I am stuck
Having been programming in Java for a long time, I did not expect this result at all. I expected something similar to what IntelliJ would say for the similar Java code:
public class Main {
public static void main(String[] args) {
Foo<?> foo = new Foo<>();
}
}
class Foo<T> {
public T returnT() { return null; }
public void consumeT(T t) { }
public void aMoreComplicatedCase(Function<T, T> func) { }
}
IntelliJ says that x.returnT
returns a capture of ?
, consumeT
takes a capture of ?
, and aMoreComplicatedCase
takes Function<capture of ?, capture of ?>
:
I looked around the Kotlin spec and found that Kotlin also have captured types, which are fresh type variables with certain bounds. So where does Nothing
and Any?
come from? Or in the case of using in SomeType
as the projection, consumeT
will take a SomeType
instead. Why is it SomeType
, but not a capture type with SomeType
as its subtype? After all, the spec says in the type capturing section:
- For a contravariant type argument Ai
in Ai
, if Fi is a covariant type parameter, Ki is an ill-formed type. Otherwise, Ki :> Ai.- For a bivariant type argument
⋆
,kotlin.Nothing
<: Ki <:kotlin.Any?
I'm guessing something else happens after type capturing? What is that process called? Or is there a totally different process that is not type capturing going on here?
(I guess there is also the possibility that this is just a matter of "what string representation IntelliJ/kotlinc uses for display, when the type is a captured type" and has nothing to do with the type system itself...)