Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concrete definitions of Object properties #1067

Open
TimothyGu opened this issue Jan 13, 2018 · 9 comments
Open

Concrete definitions of Object properties #1067

TimothyGu opened this issue Jan 13, 2018 · 9 comments

Comments

@TimothyGu
Copy link
Member

The Issue

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:

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

The Proposal

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:

  1. Assert: IsPropertyKey(P) is true.
  2. If O.[[Properties]] does not have a property with key P, return undefined.
  3. Return O.[[Properties]]'s property with key P.

ValidateAndApplyPropertyDescriptor:

  1. ...

    1. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then

      1. 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.
    2. Else Desc must be an accessor Property Descriptor,

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

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

@annevk
Copy link
Member

annevk commented Jan 13, 2018

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

@allenwb
Copy link
Member

allenwb commented Jan 17, 2018

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:

  • Please don't add any required internal slots that must be present for all objects. That would be contrary to be the overall design for specify object behavioral variations within the specification.
  • It would probably be a good idea to say something in 9.1 about ordinary objects maintaining a set of accessible own properties. Keep it as abstract as possible.
  • It would be ok to carefully re-write all of 9.1 to use an explicit [[Properties]] List that is carefully manipulated to maintain the enumeration order as properties are added and deleted. But, I'm not convinced that it would actually be an improvement. Somethings are more easily and clearly specified using prose rather than algorithms.

@TimothyGu
Copy link
Member Author

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:

  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.

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.

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

  1. Let keys be a new empty List.
  2. For each own property key P of O.[[Properties]] that is an integer index, in ascending numeric index order, do
    1. Add P as the last element of keys.
  3. 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
    1. Add P as the last element of keys.
  4. For each own property key P of O.[[Properties]] that is a Symbol, in ascending chronological order of property creation, do
    1. Add P as the last element of keys.
  5. Return keys.

I think this is a strict improvement in clarity from the current

  1. 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?

@allenwb
Copy link
Member

allenwb commented Jan 17, 2018

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.

@TimothyGu
Copy link
Member Author

TimothyGu commented Jan 17, 2018

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:

Remove clause 6.1.7.1 Property Attributes

Amend clause 6.2.5 The Property Descriptor Specification Type

(add) The default values for each field of a Property Descriptor value are defined in Table 4:

https://tc39.github.io/ecma262/#table-4

Add clause 6.2.6 The Property Map Specification Type

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:

  • Creating a new empty Property Map (a Property Map with no properties)
  • Creating a new property given a key that does not already exist the Property Map value and a Property Descriptor value
  • Checking if a Property Map value contains a property with a given key
  • Enumerating the keys within a Property Map value
    • in ascending chronological order of property creation
    • in an implementation-defined order (see 13.7.5.15 [note: EnumerateObjectProperties])
  • Obtaining the Property Descriptor corresponding to a given key in a Property Map value
  • Modifying the fields of the Property Descriptor value corresponding to a given key
  • Replacing the Property Descriptor value corresponding to a given key
  • Removing a property with a given key

Amend 9.1 Ordinary Object Internal Methods and Internal Slots

(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, ...

Amend 9.1.x.x Ordinary*

See original post: #1067 (comment)

See also: #1067 (comment)

Amend 9.1.12 ObjectCreate ( proto [ , internalSlotsList ] )

  1. Set obj.[[Extensible]] to true.
  2. Set obj.[[OwnProperties]] to a newly created empty Property Map.
  3. Return obj.

Amend 9.4 Built-in Exotic Object Internal Methods and Slots

(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:

9.4.1 Bound Function Exotic Objects

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

9.4.2 Array Exotic Objects

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

9.4.x Etc.

Amend 9.5 Proxy Object Internal Methods and Internal Slots

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

@domenic
Copy link
Member

domenic commented Jan 17, 2018

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.

@jmdyck
Copy link
Collaborator

jmdyck commented Jan 17, 2018

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.

@allenwb
Copy link
Member

allenwb commented Jan 17, 2018

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.

@jmdyck
Copy link
Collaborator

jmdyck commented Feb 4, 2019

(Legacy bug 3812 referenced in the original post can be found here.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants