[[Invoke]] and implicit method calls

Allen Wirfs-Brock allen at wirfs-brock.com
Mon Sep 23 09:36:37 PDT 2013


On Sep 23, 2013, at 1:29 AM, Tom Van Cutsem wrote:

> 2013/9/21 Allen Wirfs-Brock <allen at wirfs-brock.com>
> 
> ...
> 
> More concretely, existing code expects that 
>     obj.valueOf()
> is equivalent to
>     obj.valueOf.call()
> 
> but if obj is a transparent forwarding proxy on a map instance (or most other built-ins) the first form will work as expected and the second form will throw unless something like [[InvokeFunction]] is used within F.p.call.
> 
> Or if obj is a transparent forwarding proxy done right, i.e. whose "get" trap returns a wrapper function that re-binds |this| and then forwards (a membrane does this automatically).

I think it is unreasonable to expect anybody to  get this right. An arbitrary data property might be either a method (and hence should be wrapped) or just a data store containing a function value that should not be wrapped. You might use a white-list within the "get" trap to identify the actual methods that need wrappers.  But what about properties that are dynamically added to the object. Should they be treated as data or methods?  What about inherited properties?  You also need to include them in the white-list, but what happenswhen the proto links are mutated?

>  
> ...
> Using this specific example, you are saying the programmer expects the above to throw if unknownObject is a transparently forwarding proxy over an Array instance.  I doubt if that is the actual expectation.  I think the expectation is that the orginal_push will be invoked as if was the function retrieved via unknownObject.push.   In other words, it is not trying to change the normal this binding semantics of obj.push(), it is only trying to force a local bind of obj.push to a specific value (ie, circumvent obj.[[Get]]("push")). 
> 
> Given that Array.isArray(proxyForArray) is specced to return false, I don't see why the above code should work transparently on proxies-for-arrays, while a simple Array.isArray test would fail.
> 

The problem with Array.isArray is that it doesn't do a polymorphic dispatch on the value it is test. It's current definition was adequate for its original purpose when we didn't have proxies or formalized subclassing.  It's problematic now. 

The way to fix Array.isArray is to add a @@isArray own property to all exotic array objects when they are created and for Array.isArray to be redefined as:
      Array.isArray = (array) => array[@@isArray] === true;

(Array.isArray and @@create for exotic array objects are both built-ins so we don't need to directly expose @@isArray to ES code.  Hence no forgery issues.

>  
> I'm skeptical that there are many (any?) existing situations where F.o.call/apply is used with the failure expectation WRT transparent proxies that is implicit in your example. Do you know of any?
> 
> For situations that really want to do a naked [[Call]] without any proxy-based intercession the way to do it would be Reflect.call(original_push,unknownObject,...args) rather than
> original_push.call.(unknownObject,...args).
> 
> Indeed, except that I expressed concerns about the expectations of existing ES5 code, for which it is too late to opt-in to the new Reflect.apply primitive.

Yes, but I'm arguing that it there is little such code.  Can you identify any?   What I'm trying to do is make both existing and new code would as expected in the presence of transparent forwarding proxies (particularly those that target built-ins)

> 
>> 
>> JS fundamentally decouples property lookup from method call and thus has the ability to express non-polymorphic function calls. We shouldn't virtualize [[Call]].
> 
> Are you suggesting that we should not have a Proxy "apply" trap?
> 
> No, not at all. For proxies that represent/wrap functions, it's necessary to virtualize [[Call]].
> 
> My argument is that we should leave the [[Call]] behavior of *ordinary functions* alone.

Yes, and my proposal does that.  It is proposing a change to F.p.call/apply not to [[Call]] on ordinary function objects.  [[Invoke]] introduces a (sometimes) observable difference between obj.m() and obj.call("m").  That's the problem we need to fix.

> Re-specifying Function.prototype.{apply,call} in terms of a new [[InvokeFunction]] MOP would mean that proxies can intercept the call behavior of ordinary functions.

Only when the this value explicitly passed to apply/call is a proxy. This is exactly the  behavior we need to preserved consistency between obj.m() and obj.call("m").  

> ...
> 
> 
> Absolutely. Proxies enable many different possible wrappers. There is no "one true way" of building wrappers, so obviously the default behavior will be wrong on some occasions.
> 
> I'm willing to consider changing the behavior of ForwardingHandler.get to auto-wrap functions and re-bind their this-value. I think you may be right that for users subclassing ForwardingHandler, they probably want the this-rebinding to happen for them by default.

I think you should try to define it since you are arguing that such warppering is the solution.  However, as I described above, I don't think you can define a sufficiently generic wrappering semantics.

Allen







-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130923/360af679/attachment-0001.html>


More information about the es-discuss mailing list