ES5 left-to-right evaluation order issues

# Allen Wirfs-Brock (16 years ago)

We generally claim that ECMAScript uses a left-to-right evaluation order and in ES5 we even fixed what was consider a bug in the ES3 spec (second item ES5 Annex D) regarding evaluation order of relational operators. Consider:

var a={valueOf: function () {alert(" a valueOf");return 1}}; var b={valueOf: function () {alert(" b valueOf");return 2}}; a<=b;

A strict implementation of the ES3 spec. would alert: "b valueOf" followed by "a valueOf" indicating what appears to be a right-to-left evaluation order. Chrome actually does this while at least IE and FF alert "a valueOf" followed by "b valueOf". The ES5 changes say that "a valueOf" should occur first as this is consistent with the general left-to-right rule.

However, when accessor properties were added to the specification we did not consider the observable ordering interaction between accessor get functions with side-effects and ToPrimitive operations (ie valueOf) with side-effects. A typical binary operator algorithm from section 11 looks like:

  1.        Let left be the result of evaluating MultiplicativeExpression.
    
  2.        Let leftValue be GetValue(left).
    
  3.        Let right be the result of evaluating UnaryExpression.
    
  4.        Let rightValue be GetValue(right).
    
  5.        Let leftNum be ToNumber(leftValue).
    
  6.        Let rightNum be ToNumber(rightValue).
    
  7.        Return the result of applying the specified operation (*, /, or %) to leftNum and rightNum. See the Notes below 11.5.1, 11.5.2, 11.5.3.
    

Note that the two calls to GetValue precede the two calls to ToNumber. Generally in ES3 GetValue doesn't have side-effects so left-to-right order is maintained (however the ordering is observable if the right argument throws a ReferenceError). The interweaved evaluation order is much easier to observe if accessor properties are implemented. Consider executing the following in the context of the declarations of a and b above:

var o={get a() {alert("get a");return a}, get b() {alert ("get b");return b}}; o.a*o.b;

GetValue calls the accessor get functions, so based upon the above algorithm, you would expect to see the alert sequence: "get a", "get b", "a valueOf", "b valueOf". This is exactly what happens for existing getter/setter implementations in at least FF and Chrome.

So, the ES5 specification of operators is consistent with existing implementations of accessor properties. However, this is not a strict left-to-right order of evaluation order. We don't have many options at this point. Here are the alternatives I see:

  1. Abandon the idea that ECMAScript does strict left-to-right evaluation (it never really did, anyway) and be content with the story that it generally evaluates left to right with assorted special case exceptions to this rule.
  2. Consider all existing violation of strict left-to-right evaluation as specification bugs and make a big note of that in the errata. Maybe they can be fixed in the ISO version of the ES5 spec.

It would take substantial work to fix this throughout the specification and actual implementation of the strict left-to-right rule would introduce numerous potentially observable changes to existing implementations. Based upon this, I suspect that we have to choose alternative #1 although I would personally be open to considering alternative #2.

Thoughts?

# Allen Wirfs-Brock (16 years ago)

Since I talked about valueOf in my original message I'm going to interpret Oliver's comment as saying that even in ES3, without getter/setter extensions, you can still observed the separation of "evaluation" from "coercion". That's correct. In general, the way the ES3 (and 5) spec is written for binary operators of the form expr1 op expr2 any coercion of the value of expr1 types place after the evaluation of expr2. Since expr2 can potentially be an arbitrary expression that includes function calls, side-effects can be generated whi8le evaluating expr2 that are observed to occur before any side-effects from coercion (side-effects of valueOf, etc.) of expr1.

"Fixing" this would require substantial changes to the specification and to existing implementations that I doubt anyone wants to make.

So, the main point is that a belief that ECMAScript has or is supposed to have a strict left-to-right evaluation order (as the concept is likely to be understood by typical users of the language) is wrong. For consistency, Dave-Sarah's observation that we first "evaluate" and then "coerce" the operands is probably the guideline we should continue to follow if we ever define any additional operators where the distinction is relevant.

# David-Sarah Hopwood (16 years ago)

Allen Wirfs-Brock wrote:

This is good...Perhaps we should have a "design rules" section of the Wiki

to capture stuff like this?

I don't have edit rights to the wiki (the former ES4 wiki, that is, not the Trac wiki), and AFAIK neither does anyone else who is not a member of TC39.