[Harmony Proxies] get/set trap receiver argument unnecessary

Tom Van Cutsem tomvc.be at gmail.com
Wed May 4 00:10:18 PDT 2011


I wrote up a draft strawman for making this change: <
http://wiki.ecmascript.org/doku.php?id=strawman:proxy_drop_receiver>.

2011/4/28 Tom Van Cutsem <tomvc.be at gmail.com>

> 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 <david.bruant at labri.fr>
>
>> 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
>>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20110504/7012c9c1/attachment.html>


More information about the es-discuss mailing list