JDK-8072426 : Can't compare Java enums to strings
  • Type: Bug
  • Component: core-libs
  • Sub-Component: jdk.nashorn
  • Affected Version: 9
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2015-02-03
  • Updated: 2015-09-29
  • Resolved: 2015-02-20
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u60Fixed 9 b53Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
Example: 

    java.math.RoundingMode.UP == "UP"

evaluates to true in Rhino and to false in Nashorn. Reported at: 
http://mail.openjdk.java.net/pipermail/nashorn-dev/2015-February/004142.html
Comments
I experimentally added the logic for evaluating [[DefaultValue]] on JSObject as per spec (valueOf/toString). An interesting side effect of this is that this snippet from test/script/basic/JDK-8024847.js no longer works: var obj = new JSObject() { toNumber: function() { return 42; } }; print(32 + obj); The reason this worked previously is that ScriptRuntime.ADD(Object, Object) will invoke toPrimitive on the JSObject, which in the old world would (violating the specification) just return the object itself, which'll then be subjected to toNumber conversion. In my (fixed) world though, toPrimitive will try to find valueOf and toString methods on the JSObject by invoking JSObject.getMember(), which this JSObject doesn't implement :-(
17-02-2015

BTW, the above can also be fixed by adding an explicit "isDate()" method to ScriptObjectMirror.
17-02-2015

There is one more case that needs to be considered: ToPrimitive of a JSObject. JSObject interface currently doesn't expose the functionality of [[DefaultValue]]. We can simulate it by implementing the "standard" [[DefaultValue]] as per http://es5.github.io/#x8.12.8: doing a getMember for "valueOf" and "toString" in requisite order and invoking them if they're callables, just as Global.getDefaultValue does. There's a corner case, though: a JSObject that is a ScriptObjectMirror wrapping a NativeDate would supposedly need to behave differently when no hint is specified, as 8.12.8 says: "When the [[DefaultValue]] internal method of O is called with no hint, then it behaves as if the hint were Number, unless O is a Date object (see 15.9.6), in which case it behaves as if the hint were String." This is presumably done because Date objects have a valueOf function (http://es5.github.io/#x15.9.5.8) We can either a) lose the expected [[DefaultValue]] behavior for mirrored native Date objects, or b) add a getDefaultValue() method to the JSObject interface.
17-02-2015

All options considered I am for choosing the first option, always invoking toString() for POJOs in [[DefaultValue]]. My thinking is that automatic conversion in string concatenation a feature that is used quite ubiquitously. On the other hand, it is the responsibility of the programmer to use type-converting equality operators cautiously. Usage of "==" and friends is generally discouraged in JS exactly because of this reason. In any case, the behaviour of POJOs would be the same as that of a native JavaScript object with a toString method. And of course it would help in the case that triggered this bug report (Java enums) where comparing an object to a string is the most convenient solution.
17-02-2015

If we decide to provide a reasonable [[DefaultValue]] implementation for POJOs, we can still decide to do it two ways: 1. always invoke Object.toString() 2. completely follow [[DefaultValue]] specification for POJOs in the same vein as for internal objects (see http://es5.github.io/#x8.12.8): if the hint is Number, try to invoke a method named "valueOf" on the POJO before invoking toString. We can use InvokeByName for this purpose. (NOTE: if the hint is String, invoking Obect.toString() first will always produce a primitive JS value, so with String hint there will never be a fallback to valueOf.)
13-02-2015

Further investigation of the matter shows that if we threw TypeError for [[DefaultValue]] on POJOs, that'd have ripple effects to lots of other places. Most notably, string concatenation would not be allowed to implicitly invoke toString, so ("" + java.math.RoundingMode.UP) will also throw a TypeError, as will {}[java.math.RoundingMode.UP], as well as myriad other situations.
13-02-2015

Technically, in order to fix this we need to do two things: - allow ScriptRuntime.EQ (our implementation of the Abstract Equality Comparison Algorithm) to operate on non-ScriptObject objects. Right now, it'll return false for comparing a POJO to anything. This is wrong, it should attempt ToPrimitive() on them. - we should implement ToPrimitive and ToNumber for POJOs. The proposal is to have them throw a TypeError though, preferably with a message to the effect of "Can not implicitly convert a Java object to a string/number. You need to explicitly convert a Java object to a string/number". Alternatively, we could implement ToPrimitive by invoking toString() on a POJO. We're still discussing which direction to take.
03-02-2015

My initial analysis from the e-mail thread: ------ This comparison falls under the case 9 (Object compared to String) of the "The Abstract Equality Comparison Algorithm" <http://es5.github.io/#x11.9.3>. It requires that we invoke ToPrimitive(x), which in turn requires evaluation of [[DefaultValue]] internal property on the enum object. The behavior of [[DefaultValue]] on ordinary JS objects is described in <http://es5.github.io/#x8.12.8> and we could choose to follow it: if the object has a "toString" callable property, invoke it. So by that token, we could indeed invoke toString() on a POJO here. However, a runtime is at liberty to redefine [[DefaultValue]] for host objects too, with the restriction that "If a host object implements its own [[DefaultValue]] internal method, it must ensure that its [[DefaultValue]] internal method can return only primitive values." (still section 8.12.8). In ES5, any object not being a native JS object is a "host object" - all POJOs are considered host objects in Nashorn. Going to section 8.6.2, "Object Internal Properties and Methods" <http://es5.github.io/#x8.6.2> it says that every object (even host objects) must have all properties in Table 9 ��� [[DefaultValue]] included, but also says: "However, the [[DefaultValue]] internal method may, for some objects, simply throw a TypeError exception." So with all that in mind, we have two choices for specification-compliant implementation of this comparison: 1. invoke toString() on the POJO when compared to a String (and everywhere else in the runtime where specification prescribes ToPrimitive() conversion ��� this can have some ripple effects���), or 2. throw TypeError when toPrimitive(x) is invoked on a POJO. From this PoV, returning false from that comparison indeed seems incorrect; we should either return true, or throw a TypeError. Problem is, we have hard time deciding which way to go. JS is a very permissive language and it really allows you to convert almost anything to almost everything. While we're in the pure JS-land, we can't have a choice but to follow it. However, when it comes to the boundary of JS and Java, we are at a discretion to enforce slightly more explicit typing, should we choose to do so. If we choose option 1 above, then your example will return true, but there will potentially be a lot of implicit toString() invocations on POJOs in code people write. We're somewhat wary of all those implicit toString()s. If we choose option 2 above, then your example will throw TypeError and you will have to add an explicit .toString() invocation if you want to make it work. While 1 seems convenient, note also that in other comparison cases JS would prescribe ToPrimitive() with a Number hint, and we couldn't really provide that either as POJOs are not readily convertible to a number, so we'd have a coercion that works for string comparison, but not for number comparison, which'd feel inconsistent. Consistently throwing TypeError would at the very least force the developer to acknowledge that you'll be doing a conversion here, and accept its costs as some toString()s are costly. Or even use a method other than toString() (which is really more meant to be a debugging method on Object anyway); also if you'd compare against a number, it'd make you decide what (if anything) is the number value of your object.
03-02-2015