The ES6 MOP (Was: New ES6 draft now available)

# Allen Wirfs-Brock (13 years ago)

(Changing Subject to better identify what is probably an important topic.)

On Nov 24, 2012, at 5:31 AM, David Bruant wrote:

Hi,

I haven't been following very closely some of the most recent discussions, so I appologize if my comments have been addressed already

(The "possible excessive proxy invariants..." thread esdiscuss/2012-November/026510 and a few others are quite relevant to some of the points you raise below)

Before I plunge in, I want to provide a little back ground for lurkers.

In the Rev 12 ES6 draft, section 8.1.6 and its subsections define what I consider to be the ES Meta-Object Protocol (MOP). I don't actually use that terminology in the specification, but it is a convenient and concise term for something important so I'm going to use it below and probably in other discussions.

The MOP generically defines the capabilities of all objects. The MOP is the abstraction layer that generic language features see and use interact with generic objects. If a (generic) capability is not exposed via the MOP is isn't available for use in specifying language features that apply to all kinds of objects. There may be many different forms of objects that provide alternative implementations of the MOP interface. These alternative forms include ordinary objects and various specified exotic objects including Proxy objects, Symbol objects, Array objects, etc. Other alternative forms includes what was formerly called "host objects" and encompasses the object forms specified by W3C's WebIDL. The MOP is the semantic extension point for plugging new forms of objects into ECMAScript.

There could be many alternative MOP designs, and the current design is highly derived form the legacy ES<=5.1 internal method model. There are various principles that might be applied to MOP design. Two principles I believe in are: 1) The MOP should be as narrow as possible (minimize number of MOP methods), and 2) Don't replicate common functionality across multiple MOP methods. The current ES6 draft MOP makes compromises on these principles. I think we can get closer it closer to them.

Le 23/11/2012 18:48, Allen Wirfs-Brock a écrit :

Changes include:

• Reorganized Chapter 8 into separate language type and specification type sub sections • Added Symbol property keys to the definition of the Object Type. terminology migration from talking about “property names” to “property keys”. I support this change. It's somewhat minor, but clearly indicates that a shift has occured.

• MOP changes: Added [[GetInheritance]]/[[SetInheritance]] as internal methods for accessing [[Prototype]] internal prototype chain. Why not [[GetPrototype]] and [[SetPrototype]]? We have a absurd number of excellent resources (including but not limited to Dmitry Soshnikov and Axel Rauschmayer's websites/blogs) which use extensively the ES5 internal [[Prototype]]. It's also been very hard to get people to understand the difference between .prototype and [[Prototype]]. I'm afraid a new change would create a lot of confusion.

[[Prototype]] is now specified as internal state of ordinary objects and only some kinds of exotic objects. (For example, symbols and Proxy objects don't have that internal state). At the MOP level [[GetP]], [[SetP]] and [[Enumerate]] are the only methods whose ordinary implementations cares about "inheritance". There really isn't anything in the MOP that mandates prototypal inheritance or even single inheritance. The MOP is currently rich enough that it is even possible to define object forms that support various styles of multiple inheritance.

However, we do have legacy language and library operations for setting (proto) and retrieving [[Prototype]]. To define those language features they need to show up generically in the MOP. That's what [[GetInhertiance]]/[[SetInheritance]] provides. I choose those names because I wanted to place some distance between the generic MOP level operation and the specific [[Prototype]] "internal data property" that is present in ordinary objects. BTW, "internal data property" is a formally defined term that means private per object state. Such state is not part of the MOP.

I think in the long run, the new names should reduce confusion, but I could be wrong. Regardless, I figured that there would be some controversy about these new names and they could easily be [[GetPrototype]]/[[SetPrototype]] but I wanted to run it up the flag pole and get reactions.

Added [[[IsExtensible]]/[[PreventExtensions]]. ES5 had a note mentioning that there was no standard mechanism to set [[Extensible]] from false to true; this new way makes the note unnecessary by design of having internal methods instead of an internal property. OCaps FTW I guess :-)

The MOP only works in terms of methods. All internal state is defined as parts of specific kinds of objects that implement the MOP methods.

Replaced [[Get]]/[[Put]] with [[GetP]]/[[SetP]]. At some point we may be able to rename these as [[Get]]/[[Set]]. +1 for [[Get]]/[[Set]]. That's the accessor terminology, that's what JS devs are used to. It makes the spec easier to read for them.

yes, I hope to do this but I didn't want to change too much from the current Proxy spec. in one step.

Eliminated [[GetProperty]], [[CanPut]], [[HasProperty]], [[DefaultValue]]. So good to see this cleanup :-)

Added Keys, [[OwnPropertyKeys]], [[Freeze]], [[Seal]], [[IsFrozen]], [[IsSealed]]. For both the enumerations and the layer on top of [[PreventExtension]], I'm uneasy.

So am I and this has been one of the topics in recent public and private threads.

  • [[Enumerate]], [[Keys]] and [[OwnPropertyKeys]] are very close operations
  • So are [[PreventExtensions]]/[[Freeze]]/[[Seal]] on one side and [[IsExtensible]]/[[IsFrozen]]/[[IsSealed]]

I'm afraid that making them distinct operations increases footgun-ness. [[HasProperty]] has been removed in favored of [[HasOwnProperty]] (which might be removed in favor of only keeping [[GetOwnProperty]], though the conclusion of the discussion was the keep both IIRC) because the former could be "robustly" composed between

Yes, I agree. In particular I think [[Enumerate]], [[Keys]], and [[OwnPropertyKeys]] should be reduced to a single parameterized trap. Multiple traps make it harder to create an internally consistent MOP provider (eg, Proxy handler).

For proxies, [[Extensible]]ity-related operations have invariants so inconsistencies between [[PreventExtensions]], [[Freeze]], [[Seal]], [[IsExtensible]], [[IsFrozen]] and [[IsSealed]] are impossible. However, one can legitimately wonder what will happen if a new built-in operation using [[PreventExtensions]] is introduced in a later version of ECMAScript. Let's say experience with unique/private symbols reveals that the current Object.freeze behavior (whatever it is in regard to symbols) is inappropriate and that adding a new Object.freezeWithDifferentSymbolHandling is introduced. The current pattern suggests that adding new [[FreezeWithDifferentSymbolHandling]]/[[IsFrozenWithDifferentSymbolHandling]] internals will be the way to go. I'm not sure it's a good pattern.

I think, TomV and I have tentative agreement that we could eliminate [[Freeze]]/[[Seal]]/[[IsFrozen]]//IsSealed]] from the MOP and place equivalent abstract operations into 9.3 (generic operations upon objects). However, subsequently I've come around to what I think is better approach. See esdiscuss/2012-November/026558 which I quote:

Also replace [[PreventExtensions]]/[[IsExtensible]] with [SetIntegrity]//[[GetIntegrity]] where state is one of "non-extensible", "sealed", "frozen" and only lower to higher transitions are allowed. Also, for proxies this would generate a "notify only" trap. The Object.sealed/freeze/isSealed/isFrozen/preventExtrensions/isExtensible functions could then all be implemented in terms of those two traps.

Note that this would all future addition of additional integrity states. Other reasons are discussed in that thread.

The issue seems worse in footgunness scale for enumeration operations because different operations could yield completely unrelated results. And once again, if it is wanted to add a new enumeration operation on syntax surface, it seems that a new internal operation will be necessary.

Yes I totally agree.

Here is an idea to uniformize the enumeration story while removing enumeration inconsistency footgun. I'll describe it in proxy trap terms. A unique trap (or internal operation) keyEnumerate: () -> iterator for {propertyKey, enumerable} There is this unique operation which returns an iterator not for property keys, but for an object containing a property key and a boolean signifying the enumerable boolean. Using this, each userland operation would use this iterator, drain it out (unless the iteration is being aborted like by throwing in a for-in loop) and filter out based on the exact userland operation so that:

  • All would filter out if propertyKey is ever a private symbol
  • Object.getOwnPropertyNames would keep all other property keys regardless of the enumerable value
  • Object.keys would filter out properties which are described as non-enumerable
  • for-in would filter out non-enumerable and retrieve the different keyEnumerate from the prototype chain objects. With this unique internal operation, an object is able to communicate its intent regarding what's enumerated and each enumeration userland operation only keeps what it's interested in.

Yes, something like this. My inclination would be to add a hint parameter indicating one of the currently known variations. I think it is justified as enabling the "handler" to optimize its internal work of collecting the set of keys.

• instanceof operator no longer uses an internal method. Instead, extensible via a @@hasInstance symbol-keyed regular method. I don't remember a discussion on this topic, but I think it's an interesting change. Is it for browser APIs? There might be an associated security worry here if any object can have its @@hasInstance changed, but one good thing is that the debate of trapping instanceof ends if it's a symbol-keyed method.

I found wiki material stating that this approach should be taken instead for further formalizing the [[HasInstance]] as part of the function part of the MOP. You can make @@hasInstance non-configurable, in situations where you care. Just like you would need to do with the prototype property. Note that (some/?) legacy DOM implementations apparently use a non-standard semantics of instanceof, so it seems like a necessary MOP extension point.

• Defined all of the functions in the @Reflect module corresponding to MOP internal operations. IIRC proto has been agreed to be an data property and there was no Reflect.setPrototypeOf operation in the harmony Reflect module proposal on purpose so that 'delete Object.prototype.proto' at any point would be enough to be sure that all [[Prototype]]-setting capabilities are removed. So I think the Reflect.setPrototypeOf should be removed.

Do you want to be able to set proto on DOM objects and other exotic objects? If so, it needs to be part of the MOP. "In for a penny, in for a pound". If we are going to make proto part of the language than we have to accommodate just like any other feature. If you want to be able to disable it make deletion of Reflect.setPrototypeOf be the switch. Or define a new function for doing it. But I don't think we should be bending the MOP to accommodate idioms like deleting proto.. Remember, the first point above. [[SetInheritance]] is a generic operation, only some objects implement via a [[Prototype]] internal data property.

# David Bruant (13 years ago)

Le 24/11/2012 18:10, Allen Wirfs-Brock a écrit :

  • [[Enumerate]], [[Keys]] and [[OwnPropertyKeys]] are very close operations
  • So are [[PreventExtensions]]/[[Freeze]]/[[Seal]] on one side and [[IsExtensible]]/[[IsFrozen]]/[[IsSealed]]

I'm afraid that making them distinct operations increases footgun-ness. [[HasProperty]] has been removed in favored of [[HasOwnProperty]] (which might be removed in favor of only keeping [[GetOwnProperty]], though the conclusion of the discussion was the keep both IIRC) because the former could be "robustly" composed between

Yes, I agree. In particular I think [[Enumerate]], [[Keys]], and [[OwnPropertyKeys]] should be reduced to a single parameterized trap. Multiple traps make it harder to create an internally consistent MOP provider (eg, Proxy handler).

(...)

Here is an idea to uniformize the enumeration story while removing enumeration inconsistency footgun. I'll describe it in proxy trap terms. A unique trap (or internal operation) keyEnumerate: () -> iterator for {propertyKey, enumerable} There is this unique operation which returns an iterator not for property keys, but for an object containing a property key and a boolean signifying the enumerable boolean. Using this, each userland operation would use this iterator, drain it out (unless the iteration is being aborted like by throwing in a for-in loop) and filter out based on the exact userland operation so that:

  • All would filter out if propertyKey is ever a private symbol
  • Object.getOwnPropertyNames would keep all other property keys regardless of the enumerable value
  • Object.keys would filter out properties which are described as non-enumerable
  • for-in would filter out non-enumerable and retrieve the different keyEnumerate from the prototype chain objects. With this unique internal operation, an object is able to communicate its intent regarding what's enumerated and each enumeration userland operation only keeps what it's interested in.

Yes, something like this. My inclination would be to add a hint parameter indicating one of the currently known variations. I think it is justified as enabling the "handler" to optimize its internal work of collecting the set of keys.

I'm afraid an hint parameter has the same downside than having several traps: offering the possibility to make different code paths for different userland enumeration operations. This very possibility is the footgun in my opinion. I felt satisfied with the [[keyEnumerate]] internal operation, because as a trap, people will have to return an iterator that iterates over everything and the engine (not the trap) makes the call of what is to be actually enumerated by the different variations. Not letting the trap know how the result will be used is the best way to prevent people from writing erroneous traps in my opinion.

• Defined all of the functions in the @Reflect module corresponding to MOP internal operations. IIRC proto has been agreed to be an data property and there was no Reflect.setPrototypeOf operation in the harmony Reflect module proposal on purpose so that 'delete Object.prototype.proto' at any point would be enough to be sure that all [[Prototype]]-setting capabilities are removed. So I think the Reflect.setPrototypeOf should be removed.

Do you want to be able to set proto on DOM objects and other exotic objects?

I personally don't, but for sure others do. I'm happy with the static inheritance mechanism we will soon have and cover the vast majority of current proto use cases. Adding Reflect.setPrototypeOf has exactly the same issues than an extractable proto setter. See May meeting notes [1]: "DH: I can predict the security bugs: the implementor just thinks about the normal case, but the attacker takes the accessor out, installs it on an object that inherits from a proxy to an object from another global etc. etc. and something internal breaks MM: that's the most compelling argument I've heard. the additional testing surface area is much bigger"

proto really is in ES6 as a de facto standard. I'm not sure it is a good enough reason to make setting the [[Prototype]] a first citizen of the language.

If so, it needs to be part of the MOP. "In for a penny, in for a pound". If we are going to make proto part of the language than we have to accommodate just like any other feature. If you want to be able to disable it make deletion of Reflect.setPrototypeOf be the switch. Or define a new function for doing it. But I don't think we should be bending the MOP to accommodate idioms like deleting proto.. Remember, the first point above. [[SetInheritance]] is a generic operation, only some objects implement via a [[Prototype]] internal data property.

In practice, it's all of them except in platforms with no proto, no? Even proxies have no way to override [[SetInheritance]]. Actually, if Reflect.setPrototypeOf is added, a setPrototypeOf handler trap needs to be added as well. Some previous discussions about proto and proxies occurred at the July meeting [2].

David

[1] esdiscuss/2012-May/022834 [2] harmony:direct_proxies#discussed_during_tc39_july_2012_meeting_microsoft_redmond

# Allen Wirfs-Brock (13 years ago)

On Nov 24, 2012, at 9:54 AM, David Bruant wrote:

Le 24/11/2012 18:10, Allen Wirfs-Brock a écrit :

  • [[Enumerate]], [[Keys]] and [[OwnPropertyKeys]] are very close operations
  • So are [[PreventExtensions]]/[[Freeze]]/[[Seal]] on one side and [[IsExtensible]]/[[IsFrozen]]/[[IsSealed]]

I'm afraid that making them distinct operations increases footgun-ness. [[HasProperty]] has been removed in favored of [[HasOwnProperty]] (which might be removed in favor of only keeping [[GetOwnProperty]], though the conclusion of the discussion was the keep both IIRC) because the former could be "robustly" composed between

Yes, I agree. In particular I think [[Enumerate]], [[Keys]], and [[OwnPropertyKeys]] should be reduced to a single parameterized trap. Multiple traps make it harder to create an internally consistent MOP provider (eg, Proxy handler).

(...)

Here is an idea to uniformize the enumeration story while removing enumeration inconsistency footgun. I'll describe it in proxy trap terms. A unique trap (or internal operation) keyEnumerate: () -> iterator for {propertyKey, enumerable} There is this unique operation which returns an iterator not for property keys, but for an object containing a property key and a boolean signifying the enumerable boolean. Using this, each userland operation would use this iterator, drain it out (unless the iteration is being aborted like by throwing in a for-in loop) and filter out based on the exact userland operation so that:

  • All would filter out if propertyKey is ever a private symbol
  • Object.getOwnPropertyNames would keep all other property keys regardless of the enumerable value
  • Object.keys would filter out properties which are described as non-enumerable
  • for-in would filter out non-enumerable and retrieve the different keyEnumerate from the prototype chain objects. With this unique internal operation, an object is able to communicate its intent regarding what's enumerated and each enumeration userland operation only keeps what it's interested in.

Yes, something like this. My inclination would be to add a hint parameter indicating one of the currently known variations. I think it is justified as enabling the "handler" to optimize its internal work of collecting the set of keys. I'm afraid an hint parameter has the same downside than having several traps: offering the possibility to make different code paths for different userland enumeration operations. This very possibility is the footgun in my opinion. I felt satisfied with the [[keyEnumerate]] internal operation, because as a trap, people will have to return an iterator that iterates over everything and the engine (not the trap) makes the call of what is to be actually enumerated by the different variations. Not letting the trap know how the result will be used is the best way to prevent people from writing erroneous traps in my opinion.

I think it is a much smaller footgun than having 3 independent traps that don't have to be provided as a unit. If you have a single well specified trap, I expect an trap implementor to know or read the trap specification and see that there is a parameter that must be dealt with in a valid implementation. In practice, they are likely to handle all the variations in a single loop rather than having three separate loops and possibly deviant loop implementations in three separate traps.

Also, implementations are going to want to optimize and always produce the complete inherited set is going to be observable (via proxy traps as the proto chain is climbed). Also note that the behavior of these traps for ordinary objects will be fully defined so that means that just getting "own property names" necessarily means that all proto and probably inherited properties must be observably visited and this even applies to code doing Proxy invariant checking that might need to access the "own property names" list.

I think a single operation with a filter "hint" is a good trap off between trying to minimize footgunness and allowing implementations (including via Proxy traps) to apply reasonable optimizations.

• Defined all of the functions in the @Reflect module corresponding to MOP internal operations. IIRC proto has been agreed to be an data property and there was no Reflect.setPrototypeOf operation in the harmony Reflect module proposal on purpose so that 'delete Object.prototype.proto' at any point would be enough to be sure that all [[Prototype]]-setting capabilities are removed. So I think the Reflect.setPrototypeOf should be removed.

Do you want to be able to set proto on DOM objects and other exotic objects? I personally don't, but for sure others do.

and it current happens....

I'm happy with the static inheritance mechanism we will soon have and cover the vast majority of current proto use cases. Adding Reflect.setPrototypeOf has exactly the same issues than an extractable proto setter. See May meeting notes [1]: "DH: I can predict the security bugs: the implementor just thinks about the normal case, but the attacker takes the accessor out, installs it on an object that inherits from a proxy to an object from another global etc. etc. and something internal breaks MM: that's the most compelling argument I've heard. the additional testing surface area is much bigger"

proto really is in ES6 as a de facto standard. I'm not sure it is a good enough reason to make setting the [[Prototype]] a first citizen of the language.

"in for a penny..." If it isn't part of the MOP then implementors will just by-pass the the MOP for situations like the DOM and do it anyway. In that case, who knows if they will respect these conventions. If we are standardizing the feature we might as well understand it. If we want to make it a disable-able feature (per realm?? per something else??) we can probably do that. But just leaving it as an idiom of manipulating Object.prototype (what if O.p is frozen before you the a chance to delete it?) seems like we are still trying to pretend that dynamic prototype mutation isn't a real feature of the language.

If so, it needs to be part of the MOP. "In for a penny, in for a pound". If we are going to make proto part of the language than we have to accommodate just like any other feature. If you want to be able to disable it make deletion of Reflect.setPrototypeOf be the switch. Or define a new function for doing it. But I don't think we should be bending the MOP to accommodate idioms like deleting proto.. Remember, the first point above. [[SetInheritance]] is a generic operation, only some objects implement via a [[Prototype]] internal data property. In practice, it's all of them except in platforms with no proto, no?

The specifications I wrote for Symbol and Proxy objects doen't have any [[Prototype]] state. Some MOP handler providers (not the current Proxy handlers) might have other kinds of inheritance state. Also, note that even with the current Proxy semantics, [[GetP]]/[[SetP]] are required to use the target's [[Prototype]] (if it has one ;-) for inheritance lookup. such proxies can have completely different internal state that they use for their inheritance model.

Even proxies have no way to override [[SetInheritance]]. Actually, if Reflect.setPrototypeOf is added, a setPrototypeOf handler trap needs to be added as well. Some previous discussions about proto and proxies occurred at the July meeting [2].

Absolutely, If you need to trap on GetPrototypeOf (even just for notification) you will also need one for SetPrototypeOf (eg, assigning to proto). Same logic applies to both. Proxy's may impose restrictions on both of them but you still will want the notification. Non-proxy implementations of the MOP.

I see that [2] call for filtering proto access in Get/Put handlers. I think that dealing with it in a Get/SetInheritance handler would be a much more efficient way to hand this extremely rare operation. Rather than filtering for it on every property access, an handler that cares only needs to worry about the actual Get/SetInhertiance.

# Tom Van Cutsem (13 years ago)

2012/11/24 Allen Wirfs-Brock <allen at wirfs-brock.com>

I see that [2] call for filtering proto access in Get/Put handlers. I think that dealing with it in a Get/SetInheritance handler would be a much more efficient way to hand this extremely rare operation. Rather than filtering for it on every property access, an handler that cares only needs to worry about the actual Get/SetInhertiance.

Hmm, so you're arguing that proxy.proto would be equivalent to Object.getPrototypeOf(proxy), and trigger the "getPrototypeOf" trap, rather than triggering the "get" trap?

I guess that more accurately reflects the magical behavior of proto.

Paradoxically, this will move the filter check into the proxy implementation itself, to properly intercept proxy["proto"]. On first sight, this seems to introduce more overhead.

# David Bruant (13 years ago)

Le 26/11/2012 19:00, Tom Van Cutsem a écrit :

2012/11/24 Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>>

I see that [2] call for filtering __proto__ access in Get/Put
handlers.   I think that dealing with it in a Get/SetInheritance
handler would be a much more efficient way to hand this extremely
rare operation.  Rather than filtering for it on every property
access, an handler that cares only needs to worry about the actual
Get/SetInhertiance.

Hmm, so you're arguing that proxy.proto would be equivalent to Object.getPrototypeOf(proxy), and trigger the "getPrototypeOf" trap, rather than triggering the "get" trap?

I guess that more accurately reflects the magical behavior of proto.

The magical behavior as it currently is in some engines, but not how it's described in the current strawman:

 var o = Object.create(null);
 var a  = o.__proto__;

a is undefined according to the current strawman. In this case, the "get" trap is what should be called if o was a proxy. With proto spec'ed as in the strawman (own Object.prototype property), "get" really is the accurate trap.

# Allen Wirfs-Brock (13 years ago)

On Nov 26, 2012, at 10:00 AM, Tom Van Cutsem wrote:

2012/11/24 Allen Wirfs-Brock <allen at wirfs-brock.com>

I see that [2] call for filtering proto access in Get/Put handlers. I think that dealing with it in a Get/SetInheritance handler would be a much more efficient way to hand this extremely rare operation. Rather than filtering for it on every property access, an handler that cares only needs to worry about the actual Get/SetInhertiance.

Hmm, so you're arguing that proxy.proto would be equivalent to Object.getPrototypeOf(proxy), and trigger the "getPrototypeOf" trap, rather than triggering the "get" trap?

I guess that more accurately reflects the magical behavior of proto.

Paradoxically, this will move the filter check into the proxy implementation itself, to properly intercept proxy["proto"]. On first sight, this seems to introduce more overhead.

We need to pin down exactly what backwards compatible semantics of "proto" we need to provide.

My preference would be to recognize expr.proto as a syntactic form with its own runtime semantics. That would exclude things like expr["proto"] and I'd be quite happy with that if it does have any real world compat issue.

# Tom Van Cutsem (13 years ago)

2012/11/26 David Bruant <bruant.d at gmail.com>

Le 26/11/2012 19:00, Tom Van Cutsem a écrit :

2012/11/24 Allen Wirfs-Brock <allen at wirfs-brock.com>

I see that [2] call for filtering proto access in Get/Put handlers. I think that dealing with it in a Get/SetInheritance handler would be a much more efficient way to hand this extremely rare operation. Rather than filtering for it on every property access, an handler that cares only needs to worry about the actual Get/SetInhertiance.

Hmm, so you're arguing that proxy.proto would be equivalent to Object.getPrototypeOf(proxy), and trigger the "getPrototypeOf" trap, rather than triggering the "get" trap?

I guess that more accurately reflects the magical behavior of proto.

The magical behavior as it currently is in some engines, but not how it's described in the current strawman:

var o = Object.create(null);
var a  = o.__proto__;

a is undefined according to the current strawman. In this case, the "get" trap is what should be called if o was a proxy. With proto spec'ed as in the strawman (own Object.prototype property), "get" really is the accurate trap.

Yes, and that was my original intent.

Re. my earlier message about extra overhead: I guess the overhead of special-casing proto on property access is already present in engines that support proto, so for those it would be a sunk cost.

# Allen Wirfs-Brock (13 years ago)

On Nov 26, 2012, at 10:13 AM, David Bruant wrote:

Le 26/11/2012 19:00, Tom Van Cutsem a écrit :

2012/11/24 Allen Wirfs-Brock <allen at wirfs-brock.com>

I see that [2] call for filtering proto access in Get/Put handlers. I think that dealing with it in a Get/SetInheritance handler would be a much more efficient way to hand this extremely rare operation. Rather than filtering for it on every property access, an handler that cares only needs to worry about the actual Get/SetInhertiance.

Hmm, so you're arguing that proxy.proto would be equivalent to Object.getPrototypeOf(proxy), and trigger the "getPrototypeOf" trap, rather than triggering the "get" trap?

I guess that more accurately reflects the magical behavior of proto. The magical behavior as it currently is in some engines, but not how it's described in the current strawman:

var o = Object.create(null);
var a  = o.__proto__; 

a is undefined according to the current strawman. In this case, the "get" trap is what should be called if o was a proxy. With proto spec'ed as in the strawman (own Object.prototype property), "get" really is the accurate trap.

With syntactic detection of o.proto the semantics would be something like:

  1. If o and object with dunder proto enabled, then return [[GetInheritance]] of O (using current draft internal method names)
  2. else return [[GetP]] of o with "proto")
# David Bruant (13 years ago)

Le 26/11/2012 19:37, Allen Wirfs-Brock a écrit :

On Nov 26, 2012, at 10:00 AM, Tom Van Cutsem wrote:

2012/11/24 Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>>

I see that [2] call for filtering __proto__ access in Get/Put
handlers.   I think that dealing with it in a Get/SetInheritance
handler would be a much more efficient way to hand this extremely
rare operation.  Rather than filtering for it on every property
access, an handler that cares only needs to worry about the
actual Get/SetInhertiance.

Hmm, so you're arguing that proxy.proto would be equivalent to Object.getPrototypeOf(proxy), and trigger the "getPrototypeOf" trap, rather than triggering the "get" trap?

I guess that more accurately reflects the magical behavior of proto.

Paradoxically, this will move the filter check into the proxy implementation itself, to properly intercept proxy["proto"]. On first sight, this seems to introduce more overhead.

We need to pin down exactly what backwards compatible semantics of "proto" we need to provide.

My preference would be to recognize expr.proto as a syntactic form with its own runtime semantics. That would exclude things like expr["proto"] and I'd be quite happy with that if it does have any real world compat issue.

The decoupling of [] and property access [1] could take care of expr["proto"]. What I mean by that is that the spec for proto can be as it is now and could separate both forms. If [1] makes it to any version of ES6, it will be the way to explain the difference.

David

[1] strawman:object_model_reformation