ToPropertyDescriptor and [[DefineOwnProperty]], regarding Object.defineProperty

# Jeff Walden (16 years ago)

I'm working on implementing Object.defineProperty now, so I'm spending some quality time with ToPropertyDescriptor and [[DefineOwnProperty]].

I find the arcane structure of property descriptors[0], which have a fixed set of named fields but which might end up omitting any of those fields, very complicated. Recall that the properties of a descriptor are [[Enumerable]], [[Configurable]], [[Writable]], [[Value]], [[Get]], and [[Set]]. That's 26 possible ways that fields may or may not be present. Looking simply at ToPropertyDescriptor, we can eliminate some combinations of fields as possible through the second-to-last step, (9 * 22) of them at a quick glance (math may be wrong). That still leaves 28 different possible combinations of fields that may be present in a valid descriptor. Testing that many different combinations isn't quite simple -- and we're only in ToPropertyDescriptor yet.

Now consider the complexity multiplier of [[DefineOwnProperty]], specified on the numbered page 39 (PDF page 53) of the candidate spec. Take a look at that method, and try to formulate an accurate estimate of the number of different ways this method splits based on which fields are or are not present in the input property descriptor.

...

Could you guesstimate such a number? I'm not sure I could without dropping a lot of time to do so. Oh, and let's not even forget that that's only for non-array objects -- they have their own [[DefineOwnProperty]] that further increases complexity. (Of course, even beyond the complexity omittable fields introduces, let's not forget complexity due to the field values!)

I don't see the point in making fields of property descriptors omittable. We can't avoid complexity based on the field values themselves, but having fields be omittable seems like mostly needless complexity to me. Specifying default values for property descriptors which might otherwise have omitted fields (in ToPropertyDescriptor and [[GetOwnProperty]]) appears not to be especially complicated. A property descriptor, then, would be treated as an accessor descriptor if [[Get]] or [[Set]] were not undefined, and otherwise it would be treated as a data descriptor. This might, in rare circumstances, require the user to specify more values in the object to be converted to a property descriptor as provided to Object.definePropert(y|ies), but I don't see that as a particular problem, and I expect it will be extremely rare for a property to be defined more than once with these methods.

Making property descriptors have a fixed number of fields eliminates a fair amount of complexity at not much expense, and I don't see a reason why the spec shouldn't say this.

Jeff

  1. Throughout this email any reference to a "property descriptor" refers to the specification type, not to the reification of one as exposed by Object.getOwnPropertyDescriptor or to the object taken as an argument by ToPropertyDescriptor.
# Mike Shaver (16 years ago)

On Mon, Jun 1, 2009 at 3:40 PM, Jeff Walden <jwalden+es at mit.edu> wrote:

I don't see the point in making fields of property descriptors omittable.

At some point we'll need to deal with that, if future standards or non-standard extensions from implementors add new fields and we wish for existing scripts to continue to work.

That said, I think it will be extremely confusing to developers if these ostensibly-boolean fields are really tri-state (true, false, unspecified), so it seems like they should default to one of the true or false behaviours?

Mike

# Jeff Walden (16 years ago)

On 1.6.09 13:24, Mike Shaver wrote:

On Mon, Jun 1, 2009 at 3:40 PM, Jeff Walden<jwalden+es at mit.edu> wrote:

I don't see the point in making fields of property descriptors omittable.

At some point we'll need to deal with that, if future standards or non-standard extensions from implementors add new fields and we wish for existing scripts to continue to work.

That said, I think it will be extremely confusing to developers if these ostensibly-boolean fields are really tri-state (true, false, unspecified), so it seems like they should default to one of the true or false behaviours?

Note the footnote: I was referring to the spec type, not to an object describing a property descriptor provided by JS code. That omitted properties on such objects (even extension properties) be treated as if they were set to their default values was precisely what I was requesting.

I was also asked in IRC discussion how an omitted property is different from having a certain value, and the most obvious place where that matters is 8.12.10 step 5, for the case Object.defineProperty({}, "foo", {}) -- a case whose utility seems extremely suspect.

# Jim Blandy (16 years ago)

Object.defineProperty lets one change the Writeable attribute with:

Object.defineProperty(obj, prop, ({writeable:state}))

The way that's currently defined, we just convert that one-property object to a property descriptor and pass it to [[DefineOwnProperty]].
It's a nice way to change a single attribute; the rationale paper[0] talks about this. If property descriptors couldn't omit the other fields, how would this work?

That is, of course it can be made to work; one does a getOwnPropertyDescriptor, modifies the field, and then writes it back.
But what I wrote above is pretty sweet.

[0] Proposed ECMAScript 3.1 Static Object Functions: Use Cases and Rationale, linked to from es3.1:es3.1_proposal_working_draft

# Mark S. Miller (16 years ago)

On Mon, Jun 1, 2009 at 12:40 PM, Jeff Walden <jwalden+es at mit.edu<jwalden%2Bes at mit.edu>

wrote:

I don't see the point in making fields of property descriptors omittable.

The point is exactly so that absent descriptor fields can default to their existing values when re-defining an already existing property, but default to canonical values (false or undefined) when defining a new property.

A different factoring that I've toyed with would split [[DefineOwnProperty]] into two distinct phases: 1) use existing property settings to flesh out partial property descriptor arguments into "fully populated" property descriptors. 2) Do all the acceptance testing and mutation using only these fully populated descriptor arguments.

In the end, I refrained from suggesting it because I concluded it did not result in any net simplification of the overall spec.

# Allen Wirfs-Brock (16 years ago)

Jeff Walden wrote:

  1. Throughout this email any reference to a "property descriptor" refers to the specification type, not to the reification of one as exposed by Object.getOwnPropertyDescriptor or to the object taken as an argument by ToPropertyDescriptor.

The main reason we needed to deal with such partial property descriptors is to enable the use case of Object.defineProperty where someone wants to set the state of some specific attribute of an already existing property. We wanted to enable a programmer to, for example, write: Object.defineProperty(myObj,"myProp", {configurable:false}); Rather than: var desc= Object.getOwnPropertyDescriptor(myObj,"myProp"); desc.configurable=false; Object.defineProperty(myObj,"myProp", desc);

If we are going to allow such modification of individual attributes (or subsets of attributes) then we need to specify the acceptable attribute transformations for an existing property. That is specified by the algorithm steps of [[DefineOwnProperty]]. The specific encoding use within internal property descriptors to represent the intention to change or to leave unmodified a particular attribute doesn't significantly change the complexity of [[DefineOwnProperty]]. In earlier drafts we actually experimented with using fully populated property descriptors and attribute fields with three possible values logic (true, false, unspecified). This actually added some complexity to the specification (for example, we couldn't use the internal Boolean type to define the expected field values) so we decided to use the absence of attribute field to indicate that its value is not being explicitly specified.

Note the such partial descriptors primarily come from Object.defineProperty via ToPropertyDescriptor. Within the actual specification we have generally tried to use write populated property descriptors in situations where we need to explicitly specify the attributes of a property. ToPropertyDescriptor adds another layer of complexity that logically is really part of Object.defineProperty/defineProperties and is necessary to ensure that the user hasn't specified bogus values for the various properties.

I agree that the rules of [[DefineOwnProperty]] are fairly complex. I originally used a decision table to work out what they needed to be. I don't see any good way to reduce this complexity without eliminating support for the motivating use case for partial descriptors in Object.defineProperty.

# Jim Blandy (16 years ago)

On 06/01/2009 01:31 PM, Jeff Walden wrote:

Note the footnote: I was referring to the spec type, not to an object describing a property descriptor provided by JS code. That omitted properties on such objects (even extension properties) be treated as if they were set to their default values was precisely what I was requesting.

Wouldn't this lose the ability to change individual attributes with a single call?

# Brendan Eich (16 years ago)

On Jun 1, 2009, at 1:47 PM, Allen Wirfs-Brock wrote:

Jeff Walden wrote:

  1. Throughout this email any reference to a "property descriptor"
    refers to the specification type, not to the reification of one as exposed
    by Object.getOwnPropertyDescriptor or to the object taken as an
    argument by ToPropertyDescriptor.

The main reason we needed to deal with such partial property
descriptors is to enable the use case of Object.defineProperty where
someone wants to set the state of some specific attribute of an
already existing property.

Hi Allen -- please note regarding this mail that I'm not proposing any
change to draft ES5. Possibly Jeff's mail could lead to informative
material rationalizing the particular API design decision noted below,
so that others won't run into the same issue. It does seem that the
ability to "define" an existing property in order to change its
attributes is the source of this issue (apologies if I missed it in
your rationale doc).

We wanted to enable a programmer to, for example, write: Object.defineProperty(myObj,"myProp", {configurable:false}); Rather than: var desc= Object.getOwnPropertyDescriptor(myObj,"myProp"); desc.configurable=false; Object.defineProperty(myObj,"myProp", desc);

You could imagine an alternative where Object.changeProperty were
specified, and it insisted on a name parameter identifying an existing
property, and more intuitively tolerated a partial property
descriptor. The "defineProperty" name suggests an all-or-nothing act
of creation (replacing an previous property, perhaps). This is how
"define" is generally used in ES1-3 and other specs and languages.

With getters and setters, some ambiguity crept into certain contexts,
e.g. defineGetter and defineSetter may both be called with the
same |this| object and name parameter, to define the two "halves" of
an accessor property. This looks like the precedent for
Object.defineProperty both "creating" and "accumulating" or "changing"
attributes.

If we are going to allow such modification of individual attributes
(or subsets of attributes) then we need to specify the acceptable
attribute transformations for an existing property. That is
specified by the algorithm steps of [[DefineOwnProperty]].

It's true that a "funnel" into which all control flows, which leads to
the decision logic to update or create a property, exists in any
alternative scheme. The only issue here, it seems, is API veneer,
specifically the number of Object methods and the connotation of verbs
in API names.

Note the such partial descriptors primarily come from
Object.defineProperty via ToPropertyDescriptor. Within the actual
specification we have generally tried to use write populated
property descriptors in situations where we need to explicitly
specify the attributes of a property. ToPropertyDescriptor adds
another layer of complexity that logically is really part of
Object.defineProperty/defineProperties and is necessary to ensure
that the user hasn't specified bogus values for the various
properties.

This too can't be avoided with Object.changeProperty, unless that
method were to split into a painfully large set of methods for
changing each possible attribute or internal property -- and then we'd
lose atomicity. These issues, IIRC, are in the rationale doc.

I agree that the rules of [[DefineOwnProperty]] are fairly complex.
I originally used a decision table to work out what they needed to
be. I don't see any good way to reduce this complexity without
eliminating support for the motivating use case for partial
descriptors in Object.defineProperty.

One plausible change would be to make Object.defineProperty always
create a new property (throwing if one already exists), and add
Object.changeProperty which would require an existing property.

But this seems more complex due to the two APIs and error conditions.
So again I'm content with the current design, but interested in what
kind of informative prose we could add to the spec (not too much) to
clarify this design decision.

# Jeff Walden (16 years ago)

On 1.6.09 13:47, Allen Wirfs-Brock wrote:

The main reason we needed to deal with such partial property descriptors is to enable the use case of Object.defineProperty where someone wants to set the state of some specific attribute of an already existing property. We wanted to enable a programmer to, for example, write: Object.defineProperty(myObj,"myProp", {configurable:false}); Rather than: var desc= Object.getOwnPropertyDescriptor(myObj,"myProp"); desc.configurable=false; Object.defineProperty(myObj,"myProp", desc);

If we are going to allow such modification of individual attributes (or subsets of attributes) then we need to specify the acceptable attribute transformations for an existing property. That is specified by the algorithm steps of [[DefineOwnProperty]]. The specific encoding use within internal property descriptors to represent the intention to change or to leave unmodified a particular attribute doesn't significantly change the complexity of [[DefineOwnProperty]]. In earlier drafts we actually experimented with using fully populated property descriptors and attribute fields with three possible values logic (true, false, unspecified). This actually added some complexity to the specification (for example, we couldn't use the internal Boolean type to define the expected field values) so we decided to use the absence of attribute field to indicate that its value is not being explicitly specified.

It would be simpler to implement, less complicated to understand, and more naturally named if this only-twiddle-attributes functionality were its own method -- for example, Object.alterProperty or Object.modifyProperty or Brendan's Object.changeProperty. For a rough specification, simply start with a property descriptor initialized from [[GetOwnProperty]], crib the steps of the current ToPropertyDescriptor that deal with the boolean fields of property descriptors to change attributes, then call [[DefineOwnProperty]] with the desired property and potentially-modified descriptor. This would allow twiddling of individual bits, but it wouldn't add another possibility for the value of every field in a property descriptor.