From discussion on IRC, and Bug 3812 on the legacy Bugzilla:
The current definition of Object properties is unintuitive and even confusing to some. Per 6.1.7 The Object Type, "An Object is logically a collection of properties." Yet, these properties are not intended to be accessed directly, only through their internal methods. This contributes to seemingly circular definitions of internal methods such as OrdinaryGetOwnProperty:
- If O does not have an own property with key P, return undefined.
while the unfortunately named HasOwnProperty abstract operation in turn calls OrdinaryGetOwnProperty by way of it being the [[GetOwnProperty]] internal method of many objects.
The status quo also mandates the seemingly unnecessary separation of Property Attributes (used only when accessing these properties internally) and Property Descriptors.
Define Objects instead as "a collection of internal methods and internal slots." As with the status quo, all Objects must have the essential internal methods, and also possibly [[Call]] and [[Construct]]. Different from the status quo, each Object also has a [[Properties]] (or [[IntrinsicProperties]]; name up for bikeshedding) internal slot, that is a "collection of properties" (a key-to-Property Descriptor mapping) this Object contains – to wit, separating out the current definition of an Object to an internal slot of one.
(A List is not used for the internal slot to preserve the legacy for-in iteration order behavior.)
With this definition, OrdinaryGetOwnProperty can instead be specified as:
- Assert: IsPropertyKey(_P_) is true.
- If _O_.[[Properties]] does not have a property with key _P_, return undefined.
- Return _O_.[[Properties]]'s property with key _P_.
ValidateAndApplyPropertyDescriptor:
...
If IsGenericDescriptor(_Desc_) is true or IsDataDescriptor(_Desc_) is true, then
- If _O_ is not undefined, then create an property named _P_ on _O_.[[Properties]] with value _Desc_. If any of [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] fields of _Desc_ are absent, the field is set to its default value at the property's creation.
- Else _Desc_ must be an accessor Property Descriptor,
- If _O_ is not undefined, then create an property named _P_ on _O_.[[Properties]] with value _Desc_. If any of [[Get]], [[Set]], [[Enumerable]] and [[Configurable]] fields of _Desc_ are absent, the field is set to its default value at the property's creation.
...
- For each field of _Desc_ that is present, set the corresponding field of the property named _P_ of object _O_.[[Properties]] to the value of the field.
(The language is a bit rough at the moment, but the idea should be clear.)
This is only an editorial change to the spec in the sense that it does not add or remove normative requirements.
CC @jmdyck @ljharb @domenic
If this happens, we'll need to update "if value has any internal slot other than [[Prototype]] or [[Extensible]]" in the HTML Standard's StructuredSerializeInternal algorithm to also include [[Properties]] (or whatever its name becomes).
I'm trying to understand the source of your confusion. I think it may be that you are conflating the abstract concept of the ECMAScript Object data type, as defined in clause 6.1.7 and "ordinary object" which is one of several specific concrete realization of that data type. The concepts of ordinary and exotic objects are introduced in the last paragraph of 6.1.7 and ordinary object is fully defined in 9.1.
The ECMAScript data types (including Object) are defined in 6.1 from the perspective of what distinguishes them for an ECMAScript programmer. An ES programmer should not be concerned about how the semantics of these data types are specified or implemented. From an ES programmers perspective the thing that distinguishes values of the Object type from ECMAScript values of other types is the concept that an object is fundamentally a collection of properties that are created and accessed using very specific language features. The actual semantics of those language features are specified in terms of their use of the _fundamental internal methods_. An indirection through the internal methods is used, rather than directly associating the object semantics with the language features, to support the variations of property collection semantics that exist among the various kinds of exotic objects. But that mechanism of variation (internal methods) is just a specification device and is not relevant to the typical ES programmer (unless they are defining Proxy objects).
Note in particular, that we chose to define the semantics that are applicable to all objects strictly behaviorally via the abstract definitions and invariants of the essential internal methods. We explicitly choose to avoid defining any internal slots (ie, state) that would be required for all kinds of objects to maintain. The reason is that the existence of an internal slot implies to many readers that an explicit piece of runtime state must exist and the possibility that the value of the state holder might be mutable. This may or may not be true depending upon the kind of exotic object involved. (For example, some kinds of objects might have a fixed value that is returned by [[GetPrototypeOf]]. That is preferably specified by that exotic object's behavior definition of [[GetPrototypeOf]] rather then by using a generic definition of [[GetPrototypeOf]] that access a [[Prototype]] slot.
The purpose of OrdinaryGetOwnProperty is not to define that aspect of property management for all objects. It only defines it for _ordinary objects_ (or exotic objects that are expliciltly specified as a delta on the ordinary object specification). The language of step 2 of OrdinaryGetOwnProperty should not be constructed as being circular with the abstract definition of object given in 6.1.7. Instead, it is a more concrete, specification level realization for ordinary objects of the abstract definition.
I think a possible source of confusion, is that 9.1 never explicitly says that "each ordinary object maintain a set (in the mathematical sense) of its currently accessible own properties" (or perhaps Property Descriptors). The existence of such a set is implicit in the definition of the ordinary object internal methods (for example by step 2 of OrdinaryObjectGetOwnProperty) but it has to be inferred by readers of the specification. It would probably be clearer if such a set was mentioned in the introductory paragraphs of 9.1.
I actually considered specify that set explicitly for ordinary object by using internal an slot whose value was an ordered List of Property Descriptors. This would be similar to how the semantics of Map and Set are specified. I didn't do it for three reasons:
1) There was a history of people interpreting thing like that as required implementation techniques rather than as specification devices for describing semantics. I didn't want anybody to be misled into thinking such a list had to explicitly exist in an implementation.
2) Some things are actually harder to specify with an explicit List. For example, I think steps 2-4 of OrdinaryGetOwnPropertyKeys provide a nice clear description of own property enumeration order that would be much more complicated if it had to be specified in terms of multiple passes over a single [[Properties]] list.
3) I ran out of time, and it wasn't essential to complete ES6.
So in summary:
The ECMAScript data types (including Object) are defined in 6.1 from the perspective of what distinguishes them for an ECMAScript programmer.
...
I think a possible source of confusion, is that 9.1 never explicitly says that "each ordinary object maintain a set (in the mathematical sense) of its currently accessible own properties" (or perhaps Property Descriptors).
This is indeed the crux of the problem. The only reason I went back to 6.1 was because I was looking for what "own properties" meant in this context and couldn't find any.
Your comment generally makes sense to me. I'm going to revise my original proposal from adding such an internal slot to all objects, to adding it only to ordinary objects and those exotic objects defined by the spec that currently use Ordinary* abstract operations in their internal methods (all of them except for Proxy objects).
Some of your questions/concerns:
- There was a history of people interpreting thing like that as required implementation techniques rather than as specification devices for describing semantics. I didn't want anybody to be misled into thinking such a list had to explicitly exist in an implementation.
That's really the reason why I explicitly said I wanted to be unopinionated in the underlying data structure for the [[Properties]] internal slot I proposed, and only use prose when operating on it. I don't want to prohibit any optimization that may be possible by using a more efficient data structure, or making the places where nondeterminism are currently allowed in the spec (e.g. for-in) fully deterministic.
- Some things are actually harder to specify with an explicit List. For example, I think steps 2-4 of OrdinaryGetOwnPropertyKeys provide a nice clear description of own property enumeration order that would be much more complicated if it had to be specified in terms of multiple passes over a single [[Properties]] list.
I agree that a List doesn't really simplify this. But following the example of the OrdinaryGetOwnProperty abstract operation I wrote about in the OP, OrdinaryOwnPropertyKeys would be specified as (diff from current shown):
- Let keys be a new empty List.
- For each own property key P of O.[[Properties]] that is an integer index, in ascending numeric index order, do
- Add P as the last element of keys.
- For each own property key P of O.[[Properties]] that is a String but is not an integer index, in ascending chronological order of property creation, do
- Add P as the last element of keys.
- For each own property key P of O.[[Properties]] that is a Symbol, in ascending chronological order of property creation, do
- Add P as the last element of keys.
- Return keys.
I think this is a strict improvement in clarity from the current
- I ran out of time, and it wasn't essential to complete ES6.
Well, I have all the time in the world :)
What do you think?
sounds like a good plan.
to adding it only to ordinary objects and those exotic objects defined by the spec that currently use Ordinary* abstract operations in their internal methods (all of them except for Proxy objects).
In theory, you should not have to add anything to the definitions of the exotic objects in the ES spec. because they all are supposed to say something like: "String exotic objects have the same internal slots as ordinary objects. They also have a [[StringData]] internal slot.". But this seems to be missing from Array exotic objects and Module Namespace exotic objects. It would be better to fix those omissions than to explicitly add [[Properties]] to every exotic object specification.
But this seems to be missing from Array exotic objects and Module Namespace exotic objects. It would be better to fix those omissions than to explicitly add [[Properties]] to every exotic object specification.
Ah, that's what I meant.
Proposed spec text:
(add) The default values for each field of a Property Descriptor value are defined in Table 4:
https://tc39.github.io/ecma262/#table-4
The Property Map type is used to describe own properties of ordinary objects (see 9.1) and built-in exotic objects (see 9.4). Each value of the Property Map type consists of zero or more associations between a String- or Symbol-typed key and a fully populated Property Descriptor value. Each one of these associations is called a property. Each key in a Property Map value must be unique, in that the same key can map to at most one Property Descriptor in a given Property Map value. A data property associates a key with a data Property Descriptor, while an accessor property associates a key with an accessor Property Descriptor.
While this specification does not prescribe the underlying data structure used to implement Property Map, the specification may carry out the following operations with or on Property Map values:
(existing) Every ordinary object has a Boolean-valued [[Extensible]] internal slot ...
(add) Every ordinary object also has an internal slot named [[OwnProperties]] that is a value of the Property Map type. The [[OwnProperties]] internal slot contains all own properties this ordinary object possesses, but does not contain information about inherited properties the ordinary object may have.
(existing) In the following algorithm descriptions, ...
See original post: https://github.com/tc39/ecma262/issues/1067#issue-288306198
See also: https://github.com/tc39/ecma262/issues/1067#issuecomment-358171696
(existing with amendments) This specification defines several kinds of built-in exotic objects. These objects generally behave similar to ordinary objects except for a few specific situations. The following exotic objects use the ordinary object internal methods except where it is explicitly specified otherwise below:
Example:
(existing with amendments) Bound function objects do not have the internal slots of ECMAScript function objects defined in Table 27. Instead they have the internal slots defined in Table 28. They do however have the same internal slots as ordinary objects (see 9.1).
(existing with amendments) Array exotic objects provide an alternative definition for the [[DefineOwnProperty]] internal method. Except for that internal method, Array exotic objects provide all of the other essential internal methods as specified in 9.1. Array exotic objects also have the internal slots ordinary objects do (see 9.1).
(existing with amendments) ... This object is called the proxy's target object. Proxy exotic objects do not have any other internal slots, including the internal slots that all ordinary objects have (see 9.1).
I would suggest using a List, and then in for-in, explicitly allowing the list to be shuffled into any order before enumerating it. Introducing a new Property Map specification type seems wasteful.
When you say that the 6 Property Descriptor fields have default values, do you mean that every PD record has all 6 fields (where the not-explicitly-specified ones have their default values)? If so, then algorithm conditions that test for the presence/absence of a field in a PD will always be true/false by definition. Which is at least an opportunity for code clean-up, but possibly a normative change.
Please no, this is much too big of a change and screws up the layering of the abstractions. I thought we were in agreement on a much smaller set of clarifications.
6.7.1 is an important part of the definition of ECMAScript language type Object. Nothing in it needs to be changed. It is all independent of defining ordinary or specific kinds of exotic objects. Just leave it alone.
6.2.4 works just fine as it is in connection with 6.7.1. Note that default values applies to properties actually defined within an object. Property are used to convey properties and property state changes separate from objects. Property descriptors do not need to fully populated with attributes, and a missing attribute in a descriptor does not mean use the default. It means don't muck with the current value.
Adding a hole new specification type is completely unnecessary and an overkill for what you are trying to accomplish. Specifications types are generally defined for abstractions that need to be used throughout the specification. What we are talking about here should logically be an encapsulated part of the ordinary object specification. At most all you need to say is that a [[OwnProperties]] is an initially empty list of property definitions and where each property definition consists of a property key and a fully populated property descriptor. If you think it would simplify things for you, you could at that point define a "property definition" as a Records with two fields, [[key]] and [[descriptor]]. But the existing language used in 9.1 which talks about properties in terms of property keys and property attribute value would still be fine without explicitly defining a Record to represent it.
I do see why you need to change the introductory paragraphs of 9.4.
Bound functions:
...Instead they have the same internal slots as ordinary objects plus the internal slots defined in Table 28.
Array exotic objects:
Move the sentence (slight modified) "Array exotic objects have the same internal slots as ordinary objects (see 9.1)." to the beginning of the paragraph before talking about internal methods. Try to always use exactly the same wording when in all the exotic object descriptions when talking about ordinary object properties.
Overall, the goal here should be a very small set of editorial clarifications. You aren't actually changing anything.
BTW, you should probably make sure that the current editor is in the loop for this sort of change.
(Legacy bug 3812 referenced in the original post can be found here.)