Incorrect Number Type Identification with 'is' Operator for JS Target

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?

1 Like