On Nov 12, 2012, at 1:52 AM, Tom Van Cutsem wrote:

> 2012/11/12 Allen Wirfs-Brock <[email protected]>
> It isn't clear to me why the [[HasOwnProperty]] internal method and the 
> hasOwn Proxy trap need to exist as object behavior extension points.
> 
> [...]
> let p = new Proxy({}, {
>    hasOwn(target,key) {return key === 'foo' ? false: 
> Reflect.hasOwn(target,key)}
>    has(target,key) {return key === 'foo' ? true: Reflect.has(target,key)}
> });
> 
> console.log(Reflect.hasOwn(p,"foo"));  //will display: false
> console.log(Reflect.has(p,"foo"));          //will display: true
> 
> I think you intended to swap the outcomes: the above is perfectly consistent 
> with a normal object that inherits a "foo" property. The more awkward 
> behavior would be an object that says it does have a "foo" own property, but 
> no "foo" own or inherited property.

right, I flipped the example... if hasOwn(k) is true, then has(k) should also 
be true.


>  
> Given,that [[HasOwnProperty]] is not really essential, has good alternatives, 
>  and exposes the potential for such inconsistency I think we should just 
> eliminate it.  Does anyone want to argue that we need to keep it?
> 
> I would argue to keep it, on the general principle that all "derived" traps 
> (like "hasOwn") enable a more "efficient" virtualization (concretely: they 
> avoid unnecessary object allocations). In this particular case, the 
> alternative solution would allocate a property descriptor only to test 
> whether it's not undefined.

Yes, but we are defining the MOP that is necessary to support the ES language, 
it doesn't have to do much else.  A this point in time, there are no core 
features of the language that makes independent use of  
[[HasOwnProperty]]/hasOwn.  The only situation currently using 
[[HasOwnProperty]] where replacing it with [[GetOwnProperty]] would result in 
allocation of a property descriptor is in the implementation of 
O.p.hasOwnProperty.  A engine's implementation of that built-in could easily 
short-circut that allocation for  ordinary objects.

Another approach would be to change the shape of the API and provide a single 
trap that is parameterized to determine whether the query was restricted to own 
properties:

[[HasProperty]](P,  onlyOwn)
has:    function(target, name, onlyOwn) -> boolean

If onlyOwn is true, return true if P/name is the key of an existing own 
property.
If onlyOwn is false, return true if P/name is the key of an existing own or 
inherited property

This sort of API may seem less elegant, but it eliminates the footgun of 
forgetting to handle has when a proxy has over-ridden hasOwn (or visa versa).

In particular, I'm thinking about this in the context of the Proxy 
implementation which automatically delegates to the target object if the 
handler does not  have method for a corresponding trap.   Someone might 
reasonably expect that handling hasOwn is sufficient because they assume that 
the default implementation of has will call back to the hasOwn they provide.   
However, the target's [[HasProperty]] (assume for this discussion that the 
target is an ordinary object) will call the target's [[HasOwnProperty]] not the 
original proxy object's handed hasOwn handler method.  

> We could of course revisit this principle, but that would argue in favor of 
> abandoning all the derived traps, as they can be dealt with similarly (see 
> <http://wiki.ecmascript.org/doku.php?id=harmony:virtual_object_api>).

Yes, I just started this discussion with [[HasProperty]]/[[HasOwnProperty]] 
because it is the simplest case.  There are actually much worse possible 
inconsistencies (for example, [[GetOwnProperty]] and [[GetP]] exposing 
different values for the same own property).

I think the root issue relates to polymorphic dispatch of the internal methods 
and the transparent forwarding of unhanded traps to the Proxy target object.

Every-time the spec. calls an internal method it is implicitly doing a 
polymorphic dispatch using that object's "handler"  as the "type". (we can 
think of ordinary objects as having a intrinsic handler that dispatches to the 
ordinary implementation of the internal methods).  The specification's 
implementation of the ordinary internal methods generally assumes that the 
"this object" that the internal method is operating upon is the object that did 
the original polymorphic dispatch (remember, no inheritance of internal 
methods).  This means that within those internal method algorithms it can be 
assume that a self-calll to an internal method will dispatch to the same 
ordinary handler.  [[HasProperty]] can self-call [[HasOwnProperty]] safely 
assuming that it will be using the same two coordinated implementation. 

However, if the ordinary [[HasProperty]] is being evaluated on a Proxy target 
because it was forwarded by the Proxy [[HasProperty]], its "this object" is the 
target, not the proxy and so its call to [[HasOwnProperty]] dispatches via the 
target and not the proxy.  We get an answer that is consistent, relative to the 
target object but not consistent relative to the proxy-based object that 
[[HasProperty]] was originally invoked upon. What we have is a situation that 
is very similar to that which requires the inclusion of a receiver argument in 
[[GetP]]/[[SetP]] calls, but along a different dimension.  

> 
> Or we could make ad hoc exceptions on the general principle. For instance you 
> could argue that "hasOwn" is a much less common operation than "has" (i.e. 
> the in-operator), such that we don't need a "hasOwn" trap but we do still 
> need a "has" trap. I'm not a fan of this option though.

or, as described above, combine them into a single trap.  It would solve this 
case but necessarily others.

> 
> (of course, this isn't the only situation where a proxy can be defined that 
> exhibits inconsistent responses for the internal methods.  I'm thinking about 
> some others too, but this one seems fairly straightforward to eliminate).
> 
> This is a general issue with all derived traps. Derived traps avoid 
> allocations, but always open the way to inconsistencies with the fundamental 
> traps from which they are normally derived.
> 
> Note that the "invariant enforcement" technique still doesn't allow a proxy 
> with non-config/non-extensibility invariants to lie about its own properties. 
> There are post-condition assertions on both "getOwnPropertyDescriptor" and 
> "hasOwn" that ensure this. More generally: as long as a proxy p doesn't 
> answer "true" to Object.isFrozen(p), its behavior can be arbitrary. Only when 
> some sensible invariants are established on the proxy's target is the proxy's 
> behavior restrained.

Yes, and the concern is that most Proxy uses will not be in the context of  
frozen object so in most cases none of the "invariant  enforcement" will not 
help with this problem.

I've never been a big fan of dynamic invariant enforcement for proxies and I'm 
still not.  I am concerned that the current interfaces and factoring of 
derived/fundamental along with the target forwarding semantics is a footgun 
that is going to led to difficult to identify bugs. For now, I can spec. the 
current factoring I think we should continue to look at it from this 
perspective and see if we can minimize the size of the footgun.  

Allen



_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to