mardi 26 janvier 2021

Kotlin smart cast to non-nullable type and ::class syntax not working together as expected

I'm trying to understand what's going on with the following code snippet (extracted from a larger project), in which I manipulate a value returned from a Java superclass. The value has type T, unbounded in Java, so it is mapped (I guess) to a platform type T! which I am treating as if it where a T?.

when (val value = node.getConcentration(molecule)) { // it's a Java method returning an unbounded generic T
    is Number -> value.toDouble()
    is String -> value.toDouble()
    is Time -> value.toDouble()
    null -> 0.0 // null has been ruled out, now Kotlin should smart-cast
    else -> throw IllegalStateException(
        "Expected a numeric value in $molecule at node ${node.id}, " +
            "but $value of type ${value::class.simpleName} was found"  // THIS IS THE RELEVANT LINE
    )
}

I would expect this to work, but I get an error on the type lookup: Expression in a class literal has a nullable type 'T', use !! to make the type non-nullable. It looks to me like Kotlin does not understand that since the null case has ben ruled out, the runtime type must be a subclass of Any.

However, it gets stranger:

when (val value = node.getConcentration(molecule)) {
    is Number -> value.toDouble()
    is String -> value.toDouble()
    is Time -> value.toDouble()
    null -> 0.0 // null has been ruled out, now Kotlin should smart-cast
    else -> throw IllegalStateException(
        "Expected a numeric value in $molecule at node ${node.id}, " +
            "but $value of type ${value!!::class.simpleName} was found" // THIS IS THE RELEVANT LINE
    )
}

This compiles, but besides being ugly, this one raises (correctly, IMHO) a warning: Unnecessary non-null assertion (!!) on a non-null receiver of type T -- but this means that the smart casting is work as expected!

I am currently solving as follows:

when (val value = node.getConcentration(molecule)) { 
    is Number -> value.toDouble()
    is String -> value.toDouble()
    is Time -> value.toDouble()
    null -> 0.0
    else -> throw IllegalStateException(
        "Expected a numeric value in $molecule at node ${node.id}, " +
            "but $value of type ${value.let { it::class.simpleName }} was found"
    )
}

This one compiles with no warning, and it is all in all a tolerable amount of boilerplate, but I can't find any reason for value::class.simpleName for raising errors, especially given that if I enforce non-nullability I get an expected warning.

Does anyone understand what's going on here? Is this a bug in Kotlin? I could not find any specific reference to this issue (by the way I'm on 1.4.21).





Aucun commentaire:

Enregistrer un commentaire