Proxies: wrong "receiver" used in default "set" trap

Allen Wirfs-Brock allen at wirfs-brock.com
Thu Dec 20 11:23:28 PST 2012


On Dec 20, 2012, at 2:21 AM, Tom Van Cutsem wrote:

> 2012/12/19 Allen Wirfs-Brock <allen at wirfs-brock.com>
> If we make the Option A change that seems right for  [[GetP]]/[[SetP]] then we will have an inconsistency between the this value used for a method invoked as proxy.foo() and a accessor invoked as proxy.bar
> 
> To clarify, if we apply the fix to [[SetP]] described in my previous message, the following remains the default forwarding behavior of proxies:
> 
> If target.foo is a method:
> proxy.foo() will call target.foo() with |this| bound to the proxy (i.e. Lieberman-style delegation)
> 
> If target.bar is an accessor:
> proxy.foo will call the target.foo getter with |this| bound to the proxy (delegation)
> proxy.foo = 42 will call the target.foo setter with |this| bound to the proxy (delegation)
> 
> If target.baz is a writable data property
> proxy.baz = 42 will eventually call Object.defineProperty(proxy, 'baz', {value:42})
> (and *not* Object.defineProperty(proxy, 'baz', {value:42,enumerable:true,writable:true,configurable:true}) as it did previously)
> This behavior is consistent with the method and accessor case: in all cases, the target delegates back to the proxy.

This is actually essential to maintaining ES5 default semantics because {value:42} and {value: 42, enumerable:true,writable:true,configurable:true} have quite different meanings to the ordinary [[DefineOwnProperty]].

It is equally important that if target.baz does not exist that [[DefineOwnProperty]] is called on proxy with with the full  {value: 42, enumerable:true,writable:true,configurable:true} descriptor to ensure that the new property gets created using the "crated by assignment" attributes rather than the [[DefineOwnProperty]] defaults.

> 
> This bears repeating: the above are only defaults, and proxy authors are free to change the policy by actually implementing traps and taking control.
> 
> One thing that I learned from all this is that it's simpler to think of the proxy as *delegating* (as opposed to forwarding) to its target by default. And under the semantics of invoke = get + apply, that is actually the simplest option.

Yes, this is the best way I have found to think about them and I've been patiently waiting for you to see the light :-)   However, a corollary is that most  internal method calls within derived internal methods/traps need to delegate back to the original "receiver".  In some cases, we don't provide the original receiver as an extra argument, so it isn't available to do this.

A quick scan of the ordinary internal method suggests that we may have this problem for [[Delete]], [[Enumerate]], [[Keys]], and [[GetOwnProertyKeys]].

More generally, I would argue that all Proxy traps (and the corresponding Reflect functions) potentially need access to the original "receiver".  We don't know what somebody is going to do in such traps and they well need to call back to the original receiver for the same sort of consistency issues we have encountered with  [[SetP]].  this is particularly apparent if you think multiple levels of proxies chained through their target slots. 

This is subtle and maybe we can slip by without addressing it.  But a consistent application of delegation semantics would seem to require it.

Delete 






> 
> Cheers,
> Tom

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


More information about the es-discuss mailing list