On Jul 29, 2013, at 9:06 AM, Tom Van Cutsem wrote:

> Hi,
> 
> Originally, direct proxies were specified such that a direct proxy would 
> inherit the [[Class]] of the target object it wraps. The intent was for e.g. 
> proxies-for-arrays to be recognized properly as arrays.
> 
> In ES6 the [[Class]] property is gone and the current draft instead uses 
> wording such as "if O is an exotic Array object..."
> 
> My understanding is that a proxy for an array would not pass such a test, 
> unless we explicitly state otherwise.

yes that's correct

> 
> Allen, could you clarify your intent?

What you are running into is very closely related to vthe private state access 
issues that led us to define DelegatingHandler/ForwardHandler, etc.  The 
difference here is that you are concerned about external behavioral branding 
rather than internal implementation branding of the sort that occurs for 
private state access.

The legacy [[Class]] internal property conflated these two concepts.  Sometimes 
it was used for to ensure that a built-in method was operating upon an instance 
that actually had the internal state or conformed to other implementation level 
invariants needed by the method. Other times, [[Class]] was tested  for basic 
external behavioral classification purposes that don't really care at all about 
implementation level object invariants.

In most cases, the ES6 spec. language such as  "if O is an exotic X object" or 
"does X have a [[XXX]] internal data property" works fine as a direct 
replacement of an ES5  [[Class]] test because there are a one-to-one correspond 
between a ES6 built-in that is represented by specific kind of exotic object or 
that has a specific internal data property and with a ES5 built-in with the 
corresponding [[Class]] value. 

However, a proxy on such a built-in does not pass such a test.  A Proxy object 
whose target is an Array is an exotic proxy object, not an exotic array object. 
 It would be unsafe to allow such a proxy to be recognized an an exotic array 
regardless of whether it was done via the ES6 spec. language or via 
[[Class]]==Array  if we still had it.  A proxy for an array and an array is not 
likely to have the same implementation shape.  A method that tests for an 
exotic array object might should be able to assume, for example, that its 
implementation depended encoding of the "length" property is present.  Disaster 
would follow if the method tried to operate upon a proxy instance instead of an 
actual exotic array instance.

The general solution to this problem is to decouple the shape branding/testing 
from the behavior testing. This is already done a number of places in the ES6 
spec to support subclassing flexibility.

For example, a number of the ES<=5 String method such as "replace" have 
different behavior depending upon whether an argument is a RegExp or a string. 
In the ES6 spec. the [[Class]]==RegExp test is replaced by an HasProperty test 
for @@isRegExp and and a form of double dispatching.  This permits subclasses 
of RegExp (or even alternative regular expression engines that don't inherit 
from the built-in RegExp) to be used in that argument position.  It may also be 
transparent to proxying (depending upon the handler). 

In general, an case by case analysis needs to be done for each of the 
situations where [[Class]] is used in ES5 for behavioral checking to se if and 
how an alternative needs to be provided for ES6.

> 
> If proxies for arrays do not pass such tests, some built-ins behave in 
> unexpected ways. 

What's expected?  Just because a proxy has an exotic array object as its target 
doesn't mean that is functions as an exotic array.


> 
> Here's the list of relevant built-ins, based on searching the ES6 spec for 
> "is an exotic Array object":
> * Object.prototype.toString

As currently spec'ed. toString will invoke the HasProperty(@@toStringTag) of 
the proxy and if the result is true invoke the Get trap to retrieve the value 
used to construct the toString result. All of the new built-ins have 
@@toStringTag methods.  I considered adding them for the built--ins that carry 
over from ES5.  Then, if the @@toStringTag of Array.prototype is "Array" then 
Object.prototype.toString(new Proxy([ ], { })) should return "[Object Array]".  
However, this would be a breaking change for existing code explicitly wires 
their prototype chain to inherit from Array.prototype.  


> * Array.isArray

We've discussed the meaning of this in the past and have agreed that it should 
be considered a test for exotic array-ness and in particular the length 
invariant. A Proxy whose target is an exotic array may or may not qualify. We 
could add a @@isArray tag, but I'm not convinced that it is really needed.   

> * Array.prototype.concat

As currently spec'ed the [[Class]] test has been replaced with an exotic array 
test on the this value (supporting creating subclass instances) and a 
IsConcatSpreadable abstract operation (defined in terms of 
@@isConcatSpreadable) falling back to an exotic array test) used to decide 
whether to spread argument values.

To make concat spread a proxied array you would have to explicitly define the 
@@isConcatSpreadable property of the target object.  To maintain legacy 
computability with objects inheriting from Array.prototype we can't just have 
include @@isConcatSpreadable on Array.prototype.

There may be other teaks we could do.  But we talked about concat at last weeks 
meeting WRT Typed Arrays and concluded that concat is pretty bogus and it may 
be a turd that is not worth polishing.


> * JSON.stringify

There are two distinct use cases of [[Class]]==Array in the ES5 spec of this 
function.  Both are currently in the ES6 spec. as  exotic array tests.  The 
first use case is to see if the "replaceer" argument is an white-list array.  
This could be special cased via a @@isJSONArray that would work through a 
proxy, but  I dubious that the additional complexity is justified. 

The other use case is when deciding whether to use { } or [ ] notation to 
encode the object.  The exotic array test  is the right one for ES5 
compatibility.   The appropriate JSON encoding  of a Proxy that has an exotic 
array target is going to be application dependent.   JSON.parse already has an 
extensibility hook (toJSON) that presumably should be sufficient to deal with 
such objects. 

> * JSON.parse

I believe that this [[Class]]==Array (now is exotic array) test is only applied 
to objects created by evaluating the JSON text as if it was an object literal.  
Such objects will never be proxies.


> * ArrayBuffer.isView

yup.  The use cases for isView aren't all that clear to me.  It could be 
expressed a @@isView test if it has important use cases.


> 
> If we don't make proxies-for-arrays work transparently, we get results such 
> as:
> 
> var p = new Proxy( [1,2] , {} );

Yes, and the only reason Array.prototype methods will work is because they have 
all been carefully defined to be generic and not depend upon any internal array 
state.  The methods of  most other built-ins will fail if you create an object 
in this manner because it defaults to delegating rather than forwarding.

> 
> Object.prototype.toString.call(p) // "[object Object]", expected "[object 
> Array]"
> Array.isArray(p) // false, expected true
> [0].concat(p) // [0,p], expected [0,1,2]
> JSON.stringify(p) // "{\"0\":1,\"1\":2}", expected "[1,2]"
> 
> Note that none of these built-ins actually relies upon some structural array 
> invariant. Passing in a proxy that has properties such as "length", "0", etc. 
> works equally well.

see above...

> 
> 
> It's worth noting that I hit upon these issues because users of my 
> harmony-reflect shim, which are using direct proxies today in ES5, have 
> reported them (see [1],[2]). This adds some evidence that users expect the 
> above built-ins to behave transparently w.r.t. proxies for their use cases. 
> My library patches some of these built-ins to recognize my own emulated 
> proxies, but this is just an ad hoc solution. ES6 users will obviously not be 
> able to do this.

They may expect this, but I don't see what generalizations we can make.  
Whether a proxy over a built-in is behaviorally substitutable for the built-in 
completely dependent upon the the definition of the specific proxy.  I think 
this is just something that anybody who uses Proxy needs to be aware of.  

Allen
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to