6

Given this code:

val value = "something"

println(value.toUpperCase().toLowerCase() == value)   // prints true
println(value.toUpperCase().toLowerCase() === value)  // prints false

On Kotlin/JVM 1.3.40, I get:

true
false

On Kotlin/JS 1.3.40, I get:

true
true

I would expect the same results on both, and I would expect the Kotlin/JVM results overall (as I should have different String objects).

Why am I getting different results based on runtime environment?

CommonsWare
  • 910,778
  • 176
  • 2,215
  • 2,253

2 Answers2

4

This is because of how the runtime handles it.

On the JVM, == maps to equals, and === maps to == (identity checking), as outlined here. Meanwhile, JavaScript's equals operators are weirder. If you decompile your code, you get this with JS:

kotlin.kotlin.io.output.flush();
if (typeof kotlin === 'undefined') { 
    throw new Error("Error loading module 'moduleId'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'moduleId'."); 
}
var moduleId = function (_, Kotlin) { 
    'use strict'; 
    var equals = Kotlin.equals; 
    var println = Kotlin.kotlin.io.println_s8jyv4$; 
    function main(args) { 
        var value = 'something';
        println(equals(value.toUpperCase().toLowerCase(), value)); // NOTE: equals
        println(value.toUpperCase().toLowerCase() === value);      // NOTE: ===
    } 
    _.main_kand9s$ = main; 
    main([]); 
    Kotlin.defineModule('moduleId', _); 
    return _; 
}(typeof moduleId === 'undefined' ? {} : moduleId, kotlin); 
kotlin.kotlin.io.output.buffer;

Now, if you consider the equivalent Java code (slightly shortened and without Kotlin):

public static void main(String[] args){
    String value = "something";

    System.out.println(value.toUpperCase().toLowerCase().equals(value));
    System.out.println(value.toUpperCase().toLowerCase() == value);
}

toUpperCase().toLowerCase() creates a new object, which breaks the == comparison, which is identity checking.

While === is outlined as identity checking as well, a === b is true if a and b are strings that contain the same characters. As you can tell from the decompiled Kotlin code, Kotlin.JS compiles to primitive Strings, not String objects. Because of that, the === in JS will return true when you're dealing with primitive strings.

Zoe
  • 23,712
  • 16
  • 99
  • 132
  • Does this constitute a bug in the Kotlin/JS implementation? According to the Kotlin lang ref, "Referential equality is checked by the === operation (and its negated counterpart !==). **a === b evaluates to true if and only if a and b point to the same object.** For values which are represented as primitive types at runtime (for example, Int), the === equality check is equivalent to the == check." (https://kotlinlang.org/docs/reference/equality.html#referential-equality) AFAICT, strings are not represented by primitive types (https://kotlinlang.org/docs/reference/basic-types.html). – LarsH Aug 22 '19 at 17:08
  • @LarsH maybe. I'm not entirely sure tbh, because I'm not sure if it's intended to support differences in the underlaying native differences to some of the operators. I suggest you open an issue on the [Kotlin issue tracker](https://youtrack.jetbrains.com/oauth?state=%2Fissues%2FKT). If it isn't a bug, we'll at least get clarity as to whether it's by design or not. – Zoe Aug 22 '19 at 17:25
  • Maybe @OP should open a bug report. :-) – LarsH Aug 22 '19 at 19:31
  • @LarsH but in Kotlin/JS they are. – Alexey Romanov Aug 23 '19 at 21:40
  • @Alexey Do you mean, in Kotlin/JS (1.3.40) strings are represented by primitive types? Yes I think we're agreed on that. – LarsH Aug 23 '19 at 23:25
1

In JavaScript there are both primitive strings and string objects (see e.g. "Distinction between string primitives and String objects" in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String).

value.toUpperCase().toLowerCase() === value in Kotlin/JS compiles to value.toUpperCase().toLowerCase() === value in JavaScript (as you can verify by looking at the "Generated JavaScript code" tab at https://try.kotlinlang.org/). value.toUpperCase().toLowerCase() returns a primitive string. === on primitive strings is normal equality.

Alexey Romanov
  • 154,018
  • 31
  • 276
  • 433
  • 1
    "it does in pure JavaScript, I expect Kotlin/JS to compile to it" -- but shouldn't Kotlin's transpiler generate code that gives the same results for pure-Kotlin operations that Kotlin/JVM does? How can we write Kotlin/Multiplatform projects if things like object equality differ by platform? – CommonsWare Jul 01 '19 at 12:14
  • 1
    For me, object equality is exactly the kind of thing you should _expect_ to differ by platform. – Alexey Romanov Jul 01 '19 at 12:25
  • @Alexey If the Kotlin language definition specifies certain behavior irrespective of platform, we should not expect the specified behavior to differ by platform. – LarsH Aug 22 '19 at 16:43