Property Descriptors as script compatible representation

# David Bruant (13 years ago)

Le 02/11/2012 09:33, Tom Van Cutsem a écrit :

2012/11/1 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>

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)

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.

I think the deal-breaker, as Allen pointed out, is that what you call an ESUsablePropDesc would still need to be mutable (if it wants to mimic current property-descriptors-as-objects-with-some-invariants).

Since the mutability would be limited by invariant enforcement, I don't think it would be a problem. Maybe this problem is bigger than I see it?

Plus, it doesn't get rid of the implicit conversions at proxy boundaries. In the case of defineProperty you'd still have to convert between a plain object passed in as 3rd arg, and an ESUsablePropDesc. In the case of getOwnPropertyDescriptor, to preserve current semantics, you must still copy the (mutable) ESUsablePropDesc to ensure that all calls to getOwnPropertyDescriptor return fresh, independent objects.

Arguably, if there is a construct that represent property descriptors, it may no longer be necessary to return a fresh object each time.

Thanks for drawing my attention to the auxiliary functions: it made me realize that Aux.3 NormalizePropertyDescriptor and Aux 4. NormalizeAndCompletePropertyDescriptor are only called in one place. I think that by in-lining their definitions we might get rid of some redundant conversions.

That said, the "redundancy" you point out, i.e. the conversion from Object -> PropDesc immediately followed by PropDesc -> Object is not really redundant: it's needed to "normalize" the descriptor.

No. What's needed is an algorithm to normalize the descriptor. The conversion is just a convenience to reuse the spec algorithm. The other alternative would be to duplicate the logic to make it applicable to ECMAScript objects. I'm not satisfied with any of these choices (but apparently I'm the only one)

Implementations can probably optimize so that they don't actually have to allocate an intermediate internal property descriptor, but immediately create an Object copy.

Tell me if I'm wrong, but if implementations can get rid of the extra allocation, so could the spec, no? (that's what Allen suggests in his answer).

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).

Unfortunately it's not a question of caring: currently, an ES5 meta-program can make the valid assumption that all "standard" attributes of a property descriptor object are data properties. Changing this assumption is not backwards-compatible. ... but it may be a corner-case that can be changed if not depended upon by content on the web. It has happen many times already. Whether it can be done in this particular case would require research.

# David Bruant (13 years ago)

Le 02/11/2012 12:20, Tom Van Cutsem a écrit :

Hi Allen,

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

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)

Actually, while reviewing the spec, I realized that there is a good reason why the spec isn't currently specified this way (i.e. why the Proxy spec redefines Object.defineProperty and Object.getOwnPropertyDescriptor and does not just override [[DefineOwnProperty]] and [[GetOwnProperty]])

It is to avoid the lossy conversion that otherwise occurs between the return value of [[GetOwnProperty]] and Object.getOwnPropertyDescriptor.

Just to be sure I fully understand: what is the loss exactly? I see that custom property descriptor attributes would be stripped out (because FromPropertyDescriptor outputs an object with 4 props mapping 1-to-1 to an accessor or data property descriptor). Is there another loss?

If that's the only thing, redefining the property descriptor type to accept any field would prevent that loss. Such a change would be more aligned with the intention in the proxy spec of letting internal operations manipulate custom attributes passed to Object.defineProperty.

I'm arguing in favor of switching to a script-usable type, but the change I mention can occur in the spec-only type.

# Tom Van Cutsem (13 years ago)

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

Le 02/11/2012 12:20, Tom Van Cutsem a écrit :

It is to avoid the lossy conversion that otherwise occurs between the return value of [[GetOwnProperty]] and Object.getOwnPropertyDescriptor.

Just to be sure I fully understand: what is the loss exactly? I see that custom property descriptor attributes would be stripped out (because FromPropertyDescriptor outputs an object with 4 props mapping 1-to-1 to an accessor or data property descriptor). Is there another loss?

No, just the loss of custom attributes.

If that's the only thing, redefining the property descriptor type to accept any field would prevent that loss. Such a change would be more aligned with the intention in the proxy spec of letting internal operations manipulate custom attributes passed to Object.defineProperty.

I'm arguing in favor of switching to a script-usable type, but the change I mention can occur in the spec-only type.

I went ahead and refactored the Proxy spec to inline Aux.3 NormalizePropertyDescriptor and Aux.4 NormalizeAndCompletePropertyDescriptor [1]. The new behavior is identical except that:

  • both TrapGetOwnProperty and TrapDefineOwnProperty do one less needless conversion from Object to PropDesc
  • both TrapGetOwnProperty and TrapDefineOwnProperty can now do all invariant checks against an internal PropDesc instead of against a normalized property descriptor Object
  • in TrapGetOwnProperty, we can defer conversion from PropDesc to Object, and copying of custom attributes, to the last possible moment, only after all the invariant checks succeed.

To my mind, this refactoring further reduces the need for some new script-usable property descriptor type.

Cheers, Tom

[1] doku.php?id=harmony:proxies_spec&rev=1349982638&do=diff

# David Bruant (13 years ago)

Le 02/11/2012 16:39, Allen Wirfs-Brock a écrit :

So, yes, in that case Object.getOwnPropertyDescriptor and Object.defineProperty need to pass through object level descriptors from/to the corresponding proxy traps which means they can't normalize via a property descriptor record. So, it is perfectly appropriate for them to explicitly test if O is a proxy and act accordingly. I think, this should be the only place where an explicit Proxy test is required outside of the actual proxy object specification algorithms.

For some time after this sentence I wondered what justified Object.getOwnPropertyDescriptor and Object.defineProperty to have special-casing for proxies. The reason is that proxies have an additional ability that regular objects don't: accepting custom attributes. More specifically, proxies are expected to be able to regurgitate custom attributes on Object.getOwnPropertyDescriptor after they've been set with Object.defineProperty.

Then, I wondered why it matters that only proxies can do it. The reason is future-proofing. If a property descriptor record accepts any key and value besides the standards ones, then it may become impossible to extend property descriptor records in the future by assigning semantics to currently-unused field names.

Keeping property descriptor records as tightly defined is for the best for the future.