Inconsistency in unchecked cast warning

#1

In some code I’m developing, I need to dynamically check interface conformance of Enum classes, given their Java Class values.

Here is what I have: an interface (that I define), several enum classes (defined by consumers of my API, hopefully implementing my interface plus further requirements), and their Java Class references:

interface MyEnumInterface {
    val foo: String
}

enum class SomeEnum(
    override val foo: String
): MyEnumInterface {
    ITEM("bar")
}

val someEnumClass = SomeEnum::class.java

First of all I check that the class implements my interface:

require(someEnumClass.kotlin.isSubclassOf(MyEnumInterface::class))

That being checked, I need to assert some further requirements on all items of the enum. Here I came up with two ways of writing the code:

require((someEnumClass.enumConstants as Array<MyEnumInterface>).all { item ->
    // warning: unchecked cast:      ^^^^^^^^^^^^^^^^^^^^^^^^^
    item.foo == "bar"
})

require(someEnumClass.enumConstants.all {
    val item = it as MyEnumInterface
    // no warning!?
    item.foo == "bar"
})

The interesting part is that the first style gives a compiler warning, while the second one does not. Is the second style safer, for any reason? (I can’t see how.) Should I prefer it, just because it does not result in a warning? Should I report this missing warning to be fixed in the compiler?

Finally, is there a better way to write any part of these checks?

#2

The second version is safer, at least in most cases.
As you might know there are no type information about generics at runtime, it’s a limitation of the JVM. Therefor your cast in the first version doesn’t do anything at runtime. You just tell the compiler: treat this array like it is an array of type MyEnumInterface. There is no way for the compiler to check this and it will not generate any runtime check as well, it will just believe you.
Since you use the values in the array directly it’s no real problem, since it will just fail with an exception when you try to access foo. This might be problematic though if you only save a reference to the cast array to use it later, since it might be lead to exceptions somewhere else in your program.

someGlobalThatIsUsedOnlyRarely = someEnumClass.enumConstants as Array<MyEnumInterface>

This for example would be kinda bad, since this will not fail here, but when you use a value from the array.
In the second version it will actually create a runtime check. item = it as MyEnumInterface is the same as

if(it !is MyEnumInterface) throw Exception()
else {
    val item = (MyEnumInterface)it
    ...
}

So if you want to be sure that the elements in your array are of the right type you have to use the second version. But both of your versions work for your current problem, since you don’t keep a reference to the cast array.

1 Like