evaluation order and the [[Invoke]] MOP operation

Allen Wirfs-Brock allen at wirfs-brock.com
Mon Aug 19 09:33:07 PDT 2013


See Bug 1593

In ES5, an expression such as:
      obj.m(arg)
is visibly evaluated  in this order:
1.     thisValue <- evaluate("obj")
2.    f <- thisValue.[[Get]]("m");
2.a        possible visible side-effets of getting property "m"
3.    arg1 <- evaluate("arg")
3.a         possible visible side-effects of evaluating "arg"
4.    f.[[Call]] (thisValue, ArgList(arg1))

In the current ES6 draft, using the [[Invoke]] operation the evaluation order is
1.     thisValue <- evaluate("obj")
2.    arg1 <- evaluate("arg")
2.a         possible visible side-effects of evaluating "arg".
3.    thisValue.[[Invoke]] ("m", ArgList(arg1))
4            f <- thisValue.[[Get]]("m");
4.a                possible visible side-effets of getting property "m"
5            f.[[Call]](thisValue, ArgList(arg1))

or, reducing it to just the observable side-effects( ignoring Proxies)

es5:
2.a        possible visible side-effets of accessing property "m"
3.a         possible visible side-effects of evaluating "arg"

es6
2.a         possible visible side-effects of evaluating "arg".
4.a                possible visible side-effets of getting property "m"

This is a possibly breaking change.

This ordering difference is inherent in the current design of the [[Invoke]] operation because it requires as arguments an evaluated argument list and an unevaluated method access.

Note that this ordering actually changed in ES5.  ES<=3 specified the same observable order as the current ES6 draft.  The ES5 spec. says in Annex D: "Edition 5 reverses the order of steps 2 and 3 of the algorithm.  The original order as specified in Editions 1 through 3 was incorrectly specified such that side-effects of evaluating Arguments could affect the result of evaluating MemberExpression."

Other than backing out the addition of [[Invoke]] I see to alternative paths forward.

1:  Accept the breaking change.

The breaking change in ES5 doesn't seem to have raised significant real word compatibility issues. So, reversing that change is unlikely to cause such issues.

2:  Redesign [[Invoke]]

We could redesign [[Invoke]] to permit pre-evaluation of the method.  It's new signature would be:
     thisObj.[[Invoke]](propertyKey, function, argumentList)
and obj.m(arg) would evaluation as:

1.     thisValue <- evaluate("obj")
2.     f <- thisValue.[[Get]]("m");
2.a        possible visible side-effets of getting property "m",  may evaluate to undefined
3.    arg1 <- evaluate("arg")
3.a         possible visible side-effects of evaluating "arg"
4.    thisValue.[[Invoke]] ("m", f,  ArgList(arg1))

When  [[Invoke]] is called, f might be undefined or it might be the value of obj.m.  It is up to the [[Invoke]] handler to decide whether to use the value of f that was passed to it or to interpret "m" in some other manner.  In the normal case, the evaluation order would be exactly as currently specified by ES5.  In some weird Proxy cases there would be an potentially observable get access to obj.m that would not occur in using the current ES6 specification of [[Invoke]].

The simplest thing to do is just to leave things as currently specified and take the breaking change.  However, alternative 2 seems valid and a bit intriguing.

Thoughts?

Allen 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130819/b7f1bc9f/attachment.html>


More information about the es-discuss mailing list