I was reading <this blog about Dart 3> and their r...
# language-evolution
k
I was reading this blog about Dart 3 and their roadmap to sound null safety and found something interesting mentioned about Kotlin:
Kotlin has several unsound exceptions, in part due to its goal to interoperate with Java. As you can see in this Kotlin example, generic types can trigger cases where null values can flow into a list declared as holding non-null elements.
I understand why Java libraries can sometimes be troublesome (
T!
), but I'm a bit confused about the generic types example they're giving, this is the offending code:
Copy code
private fun <E> List<E>.addAnything(element: E) {
    if (this is MutableList<E>) { this.add(element) }
}

fun main() {
    // A list of non-null Ints.
    var myList : List<Int> = arrayListOf(1, 2, 42)

    // Add a null.
    myList.addAnything(null)
    
    // Add a non-number.
    myList.addAnything("This is not even a number")

    // Print resulting list, not matching `List<Int>`.
    print(myList);
}
Which prints:
Copy code
[1, 2, 42, null, This is not even a number]
Why is this happening? My first guess is that the
if
in
addAnything
is true because of type-erasure, is this correct?
m
The
E
is completely ignored in the runtime type check due to type erasure. The other issue is that with Kotln.
List<Number>
is a subtype of
List<Number?>
, but
MutableList<Number>
is not a subtype of
MutableList<Number?>
. So the normal type checking would prevent this logic, but the type erased unsafe cast to
MutableList
breaks the compiler safety. The string works for the same reason.
List<Int>
is a subtype of
List<Any>
so the compiler calls the extension function assuming that you wanted the list to be of type
List<Any>
.
p
I guess that is what is called
unsafe casting
, you are aware it could be messy.
i
In this example, the unsoundness happens not due to the Java interoperability, but just due to a bug in the compiler: https://youtrack.jetbrains.com/issue/KT-7972/Type-safety-breach-when-is-check-ignores-variance-incompatibility Once this bug is fixed (though at this point, it would require some deprecation cycle), this example won't compile anymore.
p
I was thinking that the smart casting wasn't that smart, kinda broke PECS rule. A class that has T only as 'out' position should not be cast to a subclass that uses T as 'in' position. Great info thanks
u
In this example E can be nullable. You need to declare E:Any of you want to avoid null