Property Descriptors as script compatible representation (was: Property descriptors as ES6 Maps)

Allen Wirfs-Brock allen at
Thu Nov 1 09:10:37 PDT 2012

On Nov 1, 2012, at 6:03 AM, David Bruant wrote:

> Le 31/10/2012 19:03, Allen Wirfs-Brock a écrit :
>> Let me summarize what I think is your concern.
>> In ES5, property descriptor records are a specification device that is used to transport information about object properties between factored components of the ES specification. The same information can be expressed as an ES object that is produced/consumed by Object.getOwnPropertyDescriptor and Object.defineProperty.  These are raising/lowering operations that move information from the specification/implication level to the reflective ES language level.  One advantage of this layering is that the conversion to/from an actual object takes place once, at a well defined point in the execution sequence and any side-effects of object access occur only at that point.  Once the information is represented as an internal "record" we know that all internal consistency preconditions are satisfied and that no side-effects can be associated with access to such records.
> The part about precondition is particularly interesting and I agree it
> should be kept. However, it's possible to define ECMAScript contructs
> that enforce such pre-conditions. My point is that even for the spec,
> it's possible to define PropertyDescriptors by using ECMAScript
> constructs, without necessarily creating a new spec-only type.

It's possible, but not necessarily wise.  We current have a fairly complete stratification of the implementation/core semantics layer of ES from the reflection layer.  This is a good thing, and something that we should be trying to preserve as we introduce proxies.  Layer crossings should be minimized.  Using higher level level entities to represent lower layer abstractions would be going in the opposite direction. 
>> You concern seems to be that a proxy traps that deal with descriptor objects  have no such guarantees and in particular strange things might happen if a descriptor object is itself a proxy.
> My original concern was that the defineProperty and
> getOwnPropertyDescriptor traps I/O were interacting directly with
> PropertyDescriptor algorithms, but after re-reading the proxy spec page
> more carefully, I realized it is not the case. However, not being the
> case has a cost:
> The proxies_spec page [1] redefines some ES5 15.2.3.* built-ins.
> Core of the current Object.getOwnPropertyDescriptor is :
> 3) Let desc be the result of calling the [[GetOwnProperty]] internal
> method of O with argument name.
> 4) Return the result of calling FromPropertyDescriptor(desc) (8.10.4).
> Core of the proposed Object.getOwnPropertyDescriptor is :
> 3) If O is a proxy
>    Return the result of calling TrapGetOwnProperty(O, name)
> 4) Let desc be the result of calling the [[GetOwnProperty]] internal
> method of O with argument name.
> 5) Return the result of calling FromPropertyDescriptor(desc) (8.10.4).

The above isn't how this will be expressed in the final spec.  Instead we will have 

3) Let desc be the result of calling the [[GetOwnProperty]] internal
method of O with argument name.
4) Return the result of calling FromPropertyDescriptor(desc) (8.10.4).

And all Proxy objects will have a [[GetOwnProperty]] internal methods that looks something like:

1) Let O be the object upon which this internal method was invoked.
2) Let desc be the result of calling TrapGetOwnProperty(O,name)
3) Return ToPropertyDescriptor(desc)

(I left out details of exception handling) 

> If [[GetOwnProperty]] returned the same type of thing than
> TrapGetOwnProperty (which returns an object), the spec
> Object.getOwnPropertyDescriptor could remain the same.

But introduces a mutable object (regular a regular object or map doesn't matter) into the lower layer.

> Also, I have the
> feeling that there is duplicated logic between TrapGetOwnProperty and
> FromPropertyDescriptor or unnecessary back and forth conversions between
> internal PropertyDescriptor and objects.

Unnecessary duplication will be minimized in the final spec.  As will unnecessary conversions.  However some of these conversions are necessary to stratify the reflection layer.  The are also necessary to prevent property descriptor objects from being unintentionally shared or used as a transport mechanism through the implementation layer.  

> Currently, TrapGetOwnProperty returns an object, but when there is an
> actual trap, it goes through:
> * NormalizeAndCompletePropertyDescriptor(Object) -> Object (step 6)
> * ToCompletePropertyDescriptor(Object) -> PropertyDescriptor (step 3 of
> NormalizeAndCompletePropertyDescriptor)
> * FromPropertyDescriptor(PropertyDescriptor) -> Object (step 4 of
> NormalizeAndCompletePropertyDescriptor)

Possibly details to cleanup, I need to look deeper.  The bottom line, all specification algorithms such as Object.getOwnPropertyDescriptor must be expressed in terms of essential, trapable internal methods and should not be directly aware of Proxies. All such internal methods are polymorphic across ordinary objects, proxy objects, and possibly other exotic objects. 

> We might as well get rid of the spec-only PropertyDescriptor, define an
> equivalently pre-condition/invariant enforcing ES5 construct and
> manipulate that both internally and in trap boundaries.

No we want stratification and and uniform polymorphic interface to the essential internal methods.

>> The getOwnPropertyDescriptor trap is a lowering operation. (...)
>> The defineProperty trap is a raising operation. (...)
>> The other possible concern would be that a trap might directly use Object.getOwnPropertyDescriptor on a proxy object and that this might provide a bogus descriptor.  But it can't.  (...)
> I agree with what you wrote here. Basically, the thing that prevent
> potentially harmful descriptors-as-proxies to being harmful is that from
> getOwnPropertyDescriptor and to defineProperty traps, the "essence" of
> the descriptor is copied to a fresh object created internally.
>> so, I just don't see any basis for your concern.  Perhaps, you could elaborate on the nature of the problem as you perceive it.
> Let's try to ASCII-art it. Here are the paths that property descriptors
> take. Arrows represent a internal type conversion and/or descriptor
> completion/normalization
> # Object.defineProperty on regular object:
> ES Object (in user script) --> PropertyDescriptor (and stored as such)
> # Object.defineProperty on proxies (as I understand its current
> specification):
> ES Object --> PropertyDescriptor --> ES Object (for trap argument) -->
> PropertyDescriptor
> The last arrow is not compulsory, but we can decently assume that for
> most cases, the defineProperty trap will forward the operation to the target
> # What I'm suggesting is to do the following in both cases:
> ES Object --> ESUsablePropDesc (used for proxy trap arguments)

If the above conversions is actually creating a new ES object (and it must to prevent the target from modifying the original descriptor object), it amounts to the same thing.

Note that an implementation of Object.defineProperty may recognize proxies and take such short cuts as long as it preserves the observable semantics.  But, at the specification level we are trying to be explicit about those required semantics.  Optimization is the job for implementors.

> I think I was wrong in suggesting using raw ES6 Maps (and I think I
> understand that's what Andreas meant regarding heterogeneous types). The
> idea of ESUsablePropDesc is that it preserves pre-conditions and
> invariants of property descriptors very much like the internal type
> currently does. It can be used both internally in the spec as well as in
> proxy traps.
> When forwarding the ESUsablePropDesc object to the target, there is no
> need for conversion once again. The engine generated it and
> enforced/normalized anything it needed already.
> Potentially, when using ESUsablePropDesc descriptors directly (which
> could become an option), the engine may not need to convert them,
> because it already has enforced invariants/normalization, prevented
> inconsistent states, etc.
> The way I see it now, ESUsablePropDesc would be a regular object with a
> bunch of getter/setters to enforce property descriptor invariants.
> Everything would remain compatible (unless people really cared that ES5
> descriptors have data properties). The proxy side of things would be
> more efficient, the door would be open to make non-proxy use of
> Object.defineProperty/getOwnPropertyDescriptor more efficient.
> David
> [1]

More information about the es-discuss mailing list