I am compiling a Kotlin multiplatform (1.6.20) project with kotlin js target (among others).
While developing a multiplatform API, I have stumbled across a problematic fact. Type safety concerning numbers seems unreliable when targeting the browser. It seems like all number types (except for Long) are interchangeable regarding type identification with the βisβ operator.
In a UnitTest targeting both headless Chrome and headless Firefox, I wrote the following to isolate the problem:
val short = 7.toShort()
val byte = 7.toByte()
val int = 7
val long = 7L
val float = 7f
val double = 7.0
val decimalFloat = 7.5f
val decimalDouble = 7.5
val list = listOf<Pair<String, Number>>(
"short" to short,
"byte" to byte,
"int" to int,
"long" to long,
"float" to float,
"double" to double,
"decimalFloat" to decimalFloat,
"decimalDouble" to decimalDouble
)
for (pair in list) {
val typeName = pair.first
val num = pair.second
val realTypeName = num::class.simpleName
println("$typeName: num = $num ($realTypeName)")
println(" is Short: ${num is Short}")
println(" is Byte: ${num is Byte}")
println(" is Int: ${num is Int}")
println(" is Long: ${num is Long}")
println(" is Float: ${num is Float}")
println(" is Double: ${num is Double}")
}
This results in the following output (takeout):
...
[log] int: num = 7 (Int)
[log] is Short: true
[log] is Byte: true
[log] is Int: true
[log] is Long: false
[log] is Float: true
[log] is Double: true
[log] long: num = 7 (Long)
[log] is Short: false
[log] is Byte: false
[log] is Int: false
[log] is Long: true
[log] is Float: false
[log] is Double: false
...
The point is, that all except Long seem to be identified falsely. Any number that is not a long is any numeric Kotlin type at the same time, while Long is (as the exception to the rule) identified correctly.
My assumption is, that this has to do with the native numeric types in JS that are not typed as strongly as they are in Java. My question is, but what I am most curious about is the fact, that even numbers outside the range of Byte are identified as Byte, which leads to the fact that a number a can be identified as Byte but at the same time be greater than Byte.MAX_VALUE or smaller than Byte.MIN_VALUE. Also decimal numbers are identified as integer numbers as well.
At the same time, all non-decimal values identify either as Int or Long, when printing the class name, and as Double, if they do have decimals, regardless of defined type. The reduced output looks like this:
[log] short: num = 7 (Int)
[log] byte: num = 7 (Int)
[log] int: num = 7 (Int)
[log] long: num = 7 (Long)
[log] float: num = 7 (Int)
[log] double: num = 7 (Int)
[log] decimalFloat: num = 7.5 (Double)
[log] decimalDouble: num = 7.5 (Double)
Is there a way to enable strict Kotlin number typing (accepting performance losses) when working within pure Kotlin code?