'Strange value comparison issue in Kotlin, "===" returns true but "==" returns false

I have encountered a very strange value comparison issue in Kotlin that I cannot explain, the following code prints false

data class Foo (
    val a: Byte
)
fun main() {
    val NUM: Byte = 1
    var m: Foo? = Foo(NUM)
    println(m?.a == NUM)
}

But if I change the last line to

println(m?.a === NUM)

or

println(m!!.a == NUM)

it prints true, I'm so confused, anyone could help to explain? Thanks.



Solution 1:[1]

The issue only appears in version 1.5.20, while 1.5.10 is not affected.

This seems to be an issue in the newer kotlin compiler version.

With some bytecode, we can explain the problem (data class was called Blah, func was called blah).

This is the bytecode, compiled with 1.5.10, that returns True for println(m?.a == NUM) - everything seems to be fine. We're doing a primitive not equals of the two numbers, which returns False (correct since 1 != 1 is False).

Compiled from "WtfTest.kt"
public final class de.sfxr.WtfTest {
  public de.sfxr.WtfTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public final void blah();
    Code:
       0: iconst_1
       1: istore_1
       2: new           #14                 // class de/sfxr/Blah
       5: dup
       6: iload_1
       7: invokespecial #17                 // Method de/sfxr/Blah."<init>":(B)V
      10: astore_2
      11: aload_2
      12: astore_3
      13: aload_3
      14: invokevirtual #21                 // Method de/sfxr/Blah.getA:()B
      17: iload_1
      18: istore_3
      19: iload_3
      // PRIMITIVE NOT EQUALS => False
      20: if_icmpne     27                  
      23: iconst_1
      24: goto          28
      27: iconst_0
      28: istore_3
      29: iconst_0
      30: istore        4
      32: getstatic     #27                 // Field java/lang/System.out:Ljava/io/PrintStream;
      35: iload_3
      36: invokevirtual #33                 // Method java/io/PrintStream.println:(Z)V
      39: return
}

However in Version 1.5.20, the bytecode instructs for an object comparison using the JVMs Intrinsics.areEqual on a boxed Integer with content 1 and a boxed Byte with content 1, which will return False, since it uses equals on Byte. This is the cause of this issue. The compiler devs surely wanted a True at this point.

But why does this evaluate to false? Here's a snippet of the Byte.equals's description "The result is true if and only if the argument is not null and is a Byte object that contains the same byte value as this object."

...and the bytecode for explaination:

Compiled from "WtfTest.kt"
public final class de.sfxr.WtfTest {
  public de.sfxr.WtfTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public final void blah();
    Code:
       0: iconst_1
       1: istore_1
       2: new           #14                 // class de/sfxr/Blah
       5: dup
       6: iload_1
       7: invokespecial #17                 // Method de/sfxr/Blah."<init>":(B)V
      10: astore_2
      11: aload_2
      12: astore_3
      13: aload_3
      14: invokevirtual #21                 // Method de/sfxr/Blah.getA:()B
      17: invokestatic  #27                 // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte;
      20: iload_1
      21: invokestatic  #32                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      // OBJECT COMPARISON VIA JVM ON BOXED BYTE(1) AND BOXED INT(1) => False
      24: invokestatic  #38                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z    
      27: istore_3
      28: iconst_0
      29: istore        4
      31: getstatic     #44                 // Field java/lang/System.out:Ljava/io/PrintStream;
      34: iload_3
      35: invokevirtual #50                 // Method java/io/PrintStream.println:(Z)V
      38: return
}

UPDATE

The guys from jetbrains commented the issued ticket https://youtrack.jetbrains.com/issue/KT-47717 with "That's definitely a bug." and priority major.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1