Hi,

I think Sean is correct: under the ES5 semantics, the "receiver" argument to
"get" and "set" is not required, because "get" and "set" are not supposed to
be invoked on a proxy acting as a prototype. In both cases, property lookup
is indeed via [[GetProperty]]. That means "get" will only ever be invoked
with |receiver === proxy|.

Let me try to reconstruct how we ended up with the current API.

When Mark and I sketched out the initial API, we reasoned that the
[[GetProperty]] internal method for Objects would never be called on a
Proxy. All the algorithms where this method was invoked ([[Get]], [[Has]],
[[Put]]) would all be replaced by calling a trap instead. Also, this was
before Object.getPropertyDescriptor was proposed, and before missing traps
(such as 'has') would fall back on derived traps. I even think there was no
'getPropertyDescriptor' trap at that point in the API.

So, with [[GetProperty]] bypassed, the only sensible traps the engine could
invoke during property lookup when encountering a proxy in a prototype chain
were [[Has]] to query the proxy, and then [[Get]] or [[Set]] to perform the
access/assignment.

I just noticed, a remaining artifact of that design can still be seen in the
description of [[GetProperty]] for proxies: <
http://wiki.ecmascript.org/doku.php?id=harmony:proxies_semantics#detailed_semantics_of_behavior_for_object_and_function_proxies>.
I think [[GetProperty]] for a Proxy should now simply invoke the
"getPropertyDescriptor" trap.


That said, I also had a look at my test suite for proxies and studied the
behavior on the latest tracemonkey version.

First, there was already a test for delegated property access, but only for
function properties, not for accessor properties: <
http://hg.ecmascript.org/tests/harmony/file/b99e5976db56/proxies/TestCases/invokeDelegator.js
>

I just extended this test case to also check whether, in the function
returned by "get", |receiver === this|. This is indeed the case, so proxy
writers could use the nested |this| instead of |receiver| to access the
delegating object. So even without a "receiver" argument, proxies would be
able to transparently forward "method invocations" (function property
access) without affecting the |this|-binding.

Triggered by this discussion, I added a test for delegated accessor property
access: <
http://hg.ecmascript.org/tests/harmony/file/b99e5976db56/proxies/TestCases/delegatedAccessor.js
>

With the getPropertyDescriptor trap now in place, it turns out this trap is
indeed all one needs for correct transparent forwarding of access/assignment
to accessor properties.

This test also revealed an interesting asymmetry in the current tracemonkey
behavior:
|delegator.foo| will call the 'get' trap of the proxy if it's implemented.
This behavior does require 'get' to take the 'receiver' as argument,
otherwise it can't transparently forward the access. However, |delegator.foo
= 24| does not appear to call the 'set' trap, even when provided, but
instead always calls 'getPropertyDescriptor'.

Long story short:
- with the 'getPropertyDescriptor' trap in place, this seems to be the
preferred trap to be invoked on a proxy on the prototype chain during
property lookup/assignment (following ES5 semantics)
- Hence, 'get' and 'set' will never be called in a situation where |receiver
!== proxy|
- Hence, "receiver" is, strictly speaking, unnecessary.

Cheers,
Tom

2011/4/28 David Bruant <[email protected]>

> Le 28/04/2011 10:32, Andreas Gal a écrit :
>
>  On Apr 28, 2011, at 1:26 AM, David Bruant wrote:
>>
>>  Le 27/04/2011 23:09, Sean Eagan a écrit :
>>>
>>>> As explained before, the existing ES5 semantics would cause the proxy's
>>>> "getPropertyDescriptor" trap to be called thus obtaining any "getter"
>>>> / "setter" that the proxy wants.  The |this| binding of this "getter"
>>>> / "setter" will then be set to the "receiver" by ES5 8.12.3 step 13
>>>> for a "getter" or ES5 section 8.12.5 step 5.b for a "setter".  The
>>>> proxy's "get" / "set" trap would not get called, and thus would not
>>>> need the "receiver" arguments.
>>>>
>>> Interesting. What you're saying is that if a proxy in on the prototype
>>> chain, then its "set" and "get" traps are never called by a get or set on
>>> the base object.
>>>
>>> In any case, in any example we could write, the |this| binding is correct
>>> not because of the get/set trap on the prototype chain, but because of the
>>> |this| binding that is performed at the own layer level.
>>>
>>> I think you're right on removing the receiver argument.
>>> It should be noted that on that page [1] there is a consensus that a
>>> receiver argument should be added to all proto-climbing traps. I don't think
>>> we've had the explanation of this point yet. If I recall correctly, this
>>> necessity was raised by Andreas Gal (CC'ed). SpiderMonkey-related bug [2]
>>>
>> Actually we are still going back and forth on this. Proto-climbing might
>> be the wrong selector here. "Can call a getter or setter" seems to be the
>> better category, and that would be only get and set. get and set definitely
>> do need the receiver though. If you have a proxy on the proto chain and you
>> don't find what you are looking for in the direct object, the next step is
>> invoking get on the prototype (proxy), and you need the proper receiver if
>> you have to call a getter (the direct object, not the proxy).
>>
> Sean's point is that, as per ES5, in the case you are describing the
> prototype climbing doesn't occur with the get/set trap on the prototype
> object, but rather with getPropertyDescriptor (ES5 - 8.12.3 step 8 (which is
> the first step of the algorithm. Wrong numbering)). The |this| binding then
> occurs during the get/set call of the base object.
>
> According to ES5, the following example should throw an error:
> ---
> var handler = {
>    has: function (name) {
>        return name == 'foo';
>    },
>    get: function (rcvr, name) {
>        if (name != 'foo')
>            return undefined;
>        print(proxy !== rcvr);
>        return "bye";
>    },
> };
>
> var proxy = Proxy.create(handler);
> var c = Object.create(proxy);
> print(c.foo);
> ---
> As per ES5, "c.foo" should call c.[[Get]] (1) which should call
> c.[[GetProperty]] (2). This call should fail at finding "foo" at the own
> layer and should recursively call proxy.[[GetProperty]] (3) (not
> proxy.[[Get]] ! The example should throw an error, because handler doesn't
> have a getPropertyDescriptor trap to reify proxy.[[GetProperty]]). When the
> property descriptor is found and returned from (3), it is returned to (2)
> then to the c.[[Get]] call (1). If the property descriptor happened to be an
> accessor descriptor, then the |this| binding should be perfomed at the end
> of (1) where c.[[Get]] is called and where there is no need to leak the c
> object (misnumbered 11-13 steps of ES5 - 8.12.3).
>
> As a consequence of the call sequence I have described, Sean's point is
> that there is actually no need for a receiver argument even in get and set
> traps.
>
> Cheers,
> David
>
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to