gwtgwt-jsinteropgwt-elemental

Bug? JsNumber toFixed returns different values in SuperDev and JS


I'm using GWT 2.8.2.

When I run the following code in SuperDev mode, it logs 123.456, which is what I expect.

double d = 123.456789;
JsNumber num = Js.cast(d);
console.log(num.toFixed(3));

When I compile to JavaScript and run, it logs 123 (i.e. it does not show the decimal places).

I have tried running the code on Android Chrome, Windows Chrome and Windows Firefox. They all exhibit the same behavior.

Any idea why there is a difference and is there anything I can do about it?


Update: After a bit more digging, I've found that it's to do with the coercion of the integer parameter.

console.log(num.toFixed(3));  // 123 (wrong)
console.log(num.toFixed(3d)); // 123.456 (correct)

It seems that the JsNumber class in Elemental2 has defined the signature as:

public native String toFixed(Object digits);

I think it should be:

public native String toFixed(int digits);

I'm still not sure why it works during SuperDev mode and not when compiled though.


Solution

  • Nice catch! This appears to be a bug in the jsinterop-generator configuration used when generating Elemental2's sources. Since JS doesn't have a way to say that a number is either an integer or a floating point value, the source material that jsinterop-generator works with can't accurately describe what that argument needs to be.

    Usually, the fix is to add this to the integer-entities.txt (https://github.com/google/elemental2/blob/master/java/elemental2/core/integer_entities.txt), so that the generator knows that this parameter can only be an integer. However, when I made this change, the generator didn't act on the new line, and logged this fact. It turns out that it only makes this change when the parameter is a number of some kind, which Object clearly isn't.

    The proper fix also then is probably to fix the externs that are used to describe what "JsNumber.toFixed" is supposed to take as an argument. The spec says that this can actually take some non-number value and after converting to a number, doesn't even need to be an integer (see https://www.ecma-international.org/ecma-262/5.1/#sec-15.7.4.5 and https://www.ecma-international.org/ecma-262/5.1/#sec-9.3).

    So, instead we need to be sure to pass whatever literal value that the Java developer provides to the function, so that it is parsed correctly within JS - this means that the argument needs to be annotated with @DoNotAutobox. Or, we could clarify this to say that it can either be Object or Number for the argument, and the toFixed(Object) will still be emitted, but now there will be a numeric version too.


    Alternatively, you can work around this as you have done, or by providing a string value of the number of digits you want:

    console.log(num.toFixed("3"));
    

    Filed as https://github.com/google/elemental2/issues/129