I stumbled upon a bug in my code that I thought would be noticed & prevented by the compilation:
val ints: List<Int> = listOf(1, 2, 3)
val uuids: MutableList<UUID> = mutableListOf(UUID.randomUUID())
// No compilation error here:
val result = uuids.intersect(ints)
// Compilation error here, as expected
uuids.add(1)
IJ mentions that result
is of type Set<{Comparable<*> & Serializable}>
And intersect signature seems correct:
public infix fun <T> Iterable<T>.intersect(other: Iterable<T>): Set<T> {
My bug was that I just checked wether the intersection was empty, thus not seeing that by comparing incompatible objects, that would always be true.
So do I miss something obvious? Thanks
I agree that it's a bit surprising that it is valid to call intersect
with List
objects of two different types. However, this is an inevitable consequence of Kotlin's type inference system and the type projection on List
objects.
No type is specified in your call uuids.intersect(ints)
so Kotlin checks to see if a type exists that makes the call valid, and for the most specific type that the output can be. The answer to that there is such a type so the call is valid; and that type is a Set
of Comparable<*> & Serializable
objects. Thus Kotlin makes that type the generic type T
for the call.
This is the case because both List<UUID>
and List<Int>
are both subtypes of List<Comparable<*> & Serializable>
(not that you can write that intersection type in Kotlin1). In turn, this is true because both UUID
and Int
are subtypes of both Comparable<*>
and Serializable
, and List
s are covariant2.
I don't see any way the signature of the function intersect
could be written to avoid this problem, barring some special Kotlin annotation to change the usual type inference algorithm.
Instead, if you have a function of the form:
fun <T> GenericType<T>.doSomething(target: GenericType<T>)
and GenericType
was invariant (ie no type projection) then you will get an error if you try to call it with an argument GenericType<S>
(where S
is not the same as T
). I suspect this case you would more often come across, and this would not give surprising results.
If you specify the type for your call, either as the type of result
or the generic type of the call as UUID
(the type you were surely expecting), then you do get an error:
val result: Set<UUID> = uuids.intersect(ints) // type mismatch error
val result = uuids.intersect<UUID>(ints) // type mismatch error
In such cases, there is no type inference to do and the compiler can immediately see there is a mismatch.
1 See the YouTrack issue for the feature request
2 ie out
projection types, meaning a List<A>
is a subtype of List<B>
if A
is a subtype of B