As promised, changes reflected on the wiki:
http://wiki.ecmascript.org/doku.php?id=harmony:proto_climbing_refactoring

Let me know if you spot any remaining errors.

Cheers,
Tom

2012/4/27 Tom Van Cutsem <[email protected]>

> Jeff,
>
> I implemented your proposed algorithm in Javascript and ran it against my
> earlier test framework. It passed all cases. I had to add explicit checks
> for non-extensibility, otherwise Object.defineProperty would throw. See:
>
> http://code.google.com/p/es-lab/source/browse/trunk/src/es5adapt/setProperty.js?spec=svn678&r=678#114
>
> If no one raises any further objections, I'll add the updated algorithm to
> the wiki page.
>
> Cheers,
> Tom
>
> 2012/4/25 Tom Van Cutsem <[email protected]>
>
>> 2012/4/24 Jeff Walden <[email protected]>
>>
>>> In the [[SetP]] implementation on this page:
>>>
>>> http://wiki.ecmascript.org/doku.php?id=harmony:proto_climbing_refactoring
>>>
>>> In step 2, the property lookup should stop when a data descriptor of any
>>> sort, writable or non-writable is uncovered.  A property closer to the
>>> start of a lookup shadows one further along the prototype chain, and these
>>> semantics don't preserve that.
>>>
>>
>> Right.
>>
>>
>>> Step 5c should return true after calling the setter.
>>>
>>
>> Right again.
>>
>>
>>> Step 5d(i) to recheck for extensibility is redundant with
>>> [[DefineOwnProperty]]'s check of the same.
>>>
>>
>> This is true, except the difference is observable if Receiver is a proxy:
>> with the current algorithm, that proxy's defineProperty trap will not be
>> called. With the extensibility check removed, it will.
>>
>> The step 5d(i) check was inspired by the corresponding step 4 check in
>> ES5 8.12.4 [[CanPut]] (the spec for [[SetP]] was based on the structure of
>> [[CanPut]]). In other words: in ES5, technically, the extensibility check
>> is also performed twice: once in [[CanPut]] and once when
>> [[DefineOwnProperty]] is called in [[Put]].
>>
>> I would argue to keep the redundant check in there to avoid spurious
>> proxy trap invocations, but I don't feel strongly about this (the invariant
>> enforcement mechanism will force a non-extensible proxy's defineProperty
>> trap to return a consistent return value anyway).
>>
>>
>>> Technically, only step 2 needs to be changed in order to actually make
>>> the logic sane on the first point.  And the second point could be fixed
>>> with a one-line addition, and the third with a one-line removal.  But the
>>> algorithm's unwieldy enough with just adding more steps (particularly to
>>> step 2), I think you want a somewhat broader refactoring.  I make this
>>> proposal:
>>>
>>> [[SetP]](Receiver, P, V)
>>> When the [[SetP]] internal method of O is called with initial receiver
>>> Receiver, property name P, and value V, the following steps are taken:
>>>
>>> 1. Let ownDesc be the result of calling the [[GetOwnProperty]] internal
>>> method of O with argument P.
>>> 2. If ownDesc is not undefined, then
>>>   a. If IsAccessorDescriptor(ownDesc) is true, then
>>>      i.   Let setter be ownDesc.[[Set]].
>>>      ii.  If setter is undefined, return false.
>>>      iii. Call the [[Call]] internal method of setter providing Receiver
>>> as the this value and providing V as the sole argument.
>>>      iv.  Return true.
>>>   b. Otherwise IsDataDescriptor(ownDesc) must be true.
>>>      i.   If ownDesc.[[Writable]] is false, return false.
>>>      ii.  If Receiver === O, then
>>>           1. Let updateDesc be the Property Descriptor { [[Value]]: V }.
>>>           2. Return the result of calling the [[DefineOwnProperty]]
>>> internal method of Receiver passing P, updateDesc, and false as arguments.
>>>      iii. Else
>>>           1. Let newDesc be the Property Descriptor {[[Value]]: V,
>>> [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}.
>>>           2. Return the result of calling the [[DefineOwnProperty]]
>>> internal method of Receiver passing P, newDesc, and false as arguments.
>>> 3. Let proto be the value of the [[Prototype]] internal property of O.
>>> 4. If proto is null, then define the property on Receiver:
>>>   a. Let newDesc be the Property Descriptor {[[Value]]: V, [[Writable]]:
>>> true, [[Enumerable]]: true, [[Configurable]]: true}.
>>>   b. Return the result of calling the [[DefineOwnProperty]] internal
>>> method of Receiver passing P, newDesc, and false as arguments.
>>> 5. Return the result of calling the [[SetP]] internal method of proto
>>> with arguments Receiver, P, and V.
>>>
>>> Aside from fixing the noted bugs, this makes one further notable change.
>>>  When the property lookup to determine whether there's a setting conflict
>>> bottoms out at the end of the prototype chain, without finding the
>>> property, this algorithm simple defines the property on the receiver as a
>>> fully mutable property.  It doesn't reget the property on the receiver to
>>> determine if anything's "changed", to set the property consistent with its
>>> attributes at that instant.  First, this seems more efficient.  Under the
>>> current algorithm any property miss must make an effort to reget the
>>> original property, even "just in case".  Second, I have difficulty
>>> imagining how changes would legitimately happen, in a way that we might
>>> consider good coding style.  But perhaps I'm missing some reason why this
>>> reget is a design requirement; please let me know if I've missed it.
>>>
>>
>> Your alternative looks good to me. As noted above, I based myself on ES5
>> [[Put]] which, because of the call to [[CanPut]], also did the lookup twice.
>>
>> The only observable case I can come up with is something along the lines
>> of:
>>
>> var parent = Proxy({}, {
>>   set: function(target, name, value, receiver) {
>>     Object.defineProperty(receiver, name,
>>       { value: 0,
>>         writable: true,
>>         enumerable: true,
>>         configurable: false });
>>     return Reflect.set(target, name, value, receiver);
>>   }
>> });
>> var child = Object.create(parent);
>> child.x = 1;
>>
>> The last line would trigger parent's "set" trap, which then defines a
>> non-configurable 'x' on child, and continues the lookup by calling
>> Reflect.set explicitly. This Reflect.set call reaches the root of the
>> protochain and then tries to add 'x' again to 'child'. With an explicit
>> re-check, this will update x's value from 0 to 1 (leaving other attributes
>> unchanged). Without the re-check, adding 'x' as a configurable property
>> fails since it's non-configurable.
>>
>> As you note, this is not a very motivating example for burdening all
>> property assignments with redundant checks. In fact, I think the above
>> behavior without the re-check is equally fine.
>>
>>
>>> Anyway, comments welcome on this -- I'm working on implementing it now,
>>> so feedback is particularly timely for me, and I'll be able to provide
>>> implementation feedback quickly.
>>>
>>
>> I previously wrote a little test page that compared the output of ES5's
>> [[Put]] algorithm against the refactored version:
>> http://es-lab.googlecode.com/svn/trunk/src/es5adapt/testSetProperty.html
>> (source is here:
>> http://code.google.com/p/es-lab/source/browse/#svn%2Ftrunk%2Fsrc%2Fes5adapt
>>  )
>>
>> When I find the time I'll test your proposal to see if it passes.
>>
>> That said, any simplification we can make to this algorithm is welcome,
>> so I like your changes. I would perhaps add a check for [[Extensible]] in
>> step 4.b. to avoid trapping defineProperty on non-extensible proxy
>> Receivers)
>>
>> Cheers,
>> Tom
>>
>>
>>>
>>> Jeff
>>> _______________________________________________
>>> es-discuss mailing list
>>> [email protected]
>>> https://mail.mozilla.org/listinfo/es-discuss
>>>
>>
>>
>
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to