Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin Char compareTo fails

Tags:

kotlin

I have a following code snippet in Kotlin:

val pair: Pair<Char,Char> = 'z' to 'z'
val comparison = pair.first.compareTo(pair.second)
println(comparison)

It fails at the second line with the following exception when I try to run it:

java.lang.ClassCastException: java.lang.Character cannot be cast to java.lang.Number

IDE (IntelliJ) doesn't complain about any wrong type. The issue is somehow related to the fact that Chars are coming from Pair<Char, Char> because 'z'.compareTo('z') works fine. Do you know how Kotlin resolves the following call?

I'm using Kotlin 1.0.4

like image 330
chalimartines Avatar asked Dec 04 '16 19:12

chalimartines


1 Answers

TL;DR Apparently, this is a compiler bug.

The reason for this behavior lies in the bytecode that the Kotlin compiler generates for these two calls. (If you use IntelliJ IDEA, you can inspect the bytecode using the bytecode viewing tool).

First, the bytecode generated for the 'z'.compareTo('z') call is:

LINENUMBER 10 L3
BIPUSH 122
BIPUSH 122
INVOKESTATIC kotlin/jvm/internal/Intrinsics.compare (II)I

It calls kotlin.jvm.internal.Intrisics.compare() that compares two Ints, and the Chars are pushed to the stack directly as Ints (BIPUSH means push byte as integer).

But if you look at the bytecode for pair.first.compareTo(pair.second), you will find something like this:

ALOAD 1
INVOKEVIRTUAL kotlin/Pair.getFirst ()Ljava/lang/Object;
CHECKCAST java/lang/Number
INVOKEVIRTUAL java/lang/Number.intValue ()I

ALOAD 1
INVOKEVIRTUAL kotlin/Pair.getSecond ()Ljava/lang/Object;
CHECKCAST java/lang/Number
INVOKEVIRTUAL java/lang/Number.intValue ()I

INVOKESTATIC kotlin/jvm/internal/Intrinsics.compare (II)I

It calls kotlin.jvm.internal.Intrisics.compare, too, but here's what it tries to do before:

  • Get the component from the pair (the ALOAD 1 and INVOKEVIRTUAL ... lines)
  • Check that the object can be cast to Number (CHECKCAST ...)
  • Take java.lang.Number.intValue() (INVOKEVIRTUAL ...)

The second and the third lines are the culprit, Char is not a Number. It simply looks like the compiler generated incorrect bytecode for this comparison (it would be correct for the Number types, seems like Char is just not handled separately).

There is an issue about this in the Kotlin issue tracker, it will likely be fixed in future releases.


To fix the call in your code for now, you can convert the Chars manually before the call:

pair.first.toInt().compareTo(pair.second.toInt())
like image 86
hotkey Avatar answered Oct 26 '22 07:10

hotkey