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

Normative: Remove [[Enumerate]] and associated reflective capabilities #367

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 39 additions & 109 deletions spec.html
Original file line number Diff line number Diff line change
Expand Up @@ -1293,17 +1293,6 @@ <h1>Object Internal Methods and Internal Slots</h1>
Create or alter the own property, whose key is _propertyKey_, to have the state described by _PropertyDescriptor_. Return *true* if that property was successfully created/updated or *false* if the property could not be created or updated.
</td>
</tr>
<tr>
<td>
[[Enumerate]]
</td>
<td>
()<b>&rarr;</b>Object
</td>
<td>
Return an iterator object that produces the keys of the string-keyed enumerable properties of the object.
</td>
</tr>
<tr>
<td>
[[OwnPropertyKeys]]
Expand Down Expand Up @@ -1504,12 +1493,6 @@ <h2>[[Delete]] ( _P_ )</h2>
If P was previously observed to be a non-configurable own data or accessor property of the target, [[Delete]] must return false.
</li>
</ul>
<h2>[[Enumerate]] ( )</h2>
<ul>
<li>
The Type of the return value must be Object.
</li>
</ul>
<h2>[[OwnPropertyKeys]] ( )</h2>
<ul>
<li>
Expand Down Expand Up @@ -4415,8 +4398,7 @@ <h1>EnumerableOwnNames (_O_)</h1>
1. Let _desc_ be ? _O_.[[GetOwnProperty]](_key_).
1. If _desc_ is not *undefined*, then
1. If _desc_.[[Enumerable]] is *true*, append _key_ to _names_.
1. If _O_.[[Enumerate]] is the ordinary object [[Enumerate]] internal method (<emu-xref href="#sec-ordinary-object-internal-methods-and-internal-slots-enumerate"></emu-xref>), then
1. Order the elements of _names_ so they are in the same relative order as would be produced by the Iterator that would be returned if the [[Enumerate]] internal method was invoked on _O_.
1. Order the elements of _names_ so they are in the same relative order as would be produced by the Iterator that would be returned if the EnumerateObjectProperties internal method was invoked with _O_.
1. Return _names_.
</emu-alg>
<emu-note>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the note on the next line ("<p>The order of elements in the returned list is the same as the enumeration order that is used by a for-in statement.</p>" but github has trouble showing it and won't let me comment on it) still necessary? It seems implied by EnumerateObjectProperties.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, removed the note.

Expand Down Expand Up @@ -6517,39 +6499,6 @@ <h1>[[Delete]] (_P_)</h1>
</emu-alg>
</emu-clause>

<!-- es6num="9.1.11" -->
<emu-clause id="sec-ordinary-object-internal-methods-and-internal-slots-enumerate">
<h1>[[Enumerate]] ()</h1>
<p>When the [[Enumerate]] internal method of _O_ is called the following steps are taken:</p>
<emu-alg>
1. Return an Iterator object (<emu-xref href="#sec-iterator-interface"></emu-xref>) whose `next` method iterates over all the String-valued keys of enumerable properties of _O_. The Iterator object must inherit from %IteratorPrototype% (<emu-xref href="#sec-%iteratorprototype%-object"></emu-xref>). The mechanics and order of enumerating the properties is not specified but must conform to the rules specified below.
</emu-alg>
<p>The iterator's `next` method processes object properties to determine whether the property key should be returned as an iterator value. Returned property keys do not include keys that are Symbols. Properties of the target object may be deleted during enumeration. A property that is deleted before it is processed by the iterator's `next` method is ignored. If new properties are added to the target object during enumeration, the newly added properties are not guaranteed to be processed in the active enumeration. A property name will be returned by the iterator's `next` method at most once in any enumeration.</p>
<p>Enumerating the properties of the target object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively; but a property of a prototype is not processed if it has the same name as a property that has already been processed by the iterator's `next` method. The values of [[Enumerable]] attributes are not considered when determining if a property of a prototype object has already been processed. The enumerable property names of prototype objects must be obtained as if by invoking the prototype object's [[Enumerate]] internal method. [[Enumerate]] must obtain the own property keys of the target object as if by calling its [[OwnPropertyKeys]] internal method. Property attributes of the target object must be obtained as if by calling its [[GetOwnProperty]] internal method.</p>
<emu-note>
<p>The following is an informative definition of an ECMAScript generator function that conforms to these rules:</p>
<pre><code class="javascript">
function* enumerate(obj) {
let visited = new Set;
for (let key of Reflect.ownKeys(obj)) {
if (typeof key === "string") {
let desc = Reflect.getOwnPropertyDescriptor(obj, key);
if (desc) {
visited.add(key);
if (desc.enumerable) yield key;
}
}
}
let proto = Reflect.getPrototypeOf(obj)
if (proto === null) return;
for (let protoName of Reflect.enumerate(proto)) {
if (!visited.has(protoName)) yield protoName;
}
}
</code></pre>
</emu-note>
</emu-clause>

<!-- es6num="9.1.12" -->
<emu-clause id="sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys">
<h1>[[OwnPropertyKeys]] ( )</h1>
Expand Down Expand Up @@ -8078,16 +8027,6 @@ <h1>[[Delete]] (_P_)</h1>
</emu-alg>
</emu-clause>

<!-- es6num="9.4.6.11" -->
<emu-clause id="sec-module-namespace-exotic-objects-enumerate">
<h1>[[Enumerate]] ()</h1>
<p>When the [[Enumerate]] internal method of a module namespace exotic object _O_ is called the following steps are taken:</p>
<emu-alg>
1. Let _exports_ be the value of _O_'s [[Exports]] internal slot.
1. Return CreateListIterator(_exports_).
</emu-alg>
</emu-clause>

<!-- es6num="9.4.6.12" -->
<emu-clause id="sec-module-namespace-exotic-objects-ownpropertykeys">
<h1>[[OwnPropertyKeys]] ( )</h1>
Expand Down Expand Up @@ -8231,14 +8170,6 @@ <h1>Proxy Object Internal Methods and Internal Slots</h1>
`defineProperty`
</td>
</tr>
<tr>
<td>
[[Enumerate]]
</td>
<td>
`enumerate`
</td>
</tr>
<tr>
<td>
[[OwnPropertyKeys]]
Expand Down Expand Up @@ -8646,32 +8577,6 @@ <h1>[[Delete]] (_P_)</h1>
</emu-note>
</emu-clause>

<!-- es6num="9.5.11" -->
<emu-clause id="sec-proxy-object-internal-methods-and-internal-slots-enumerate">
<h1>[[Enumerate]] ()</h1>
<p>When the [[Enumerate]] internal method of a Proxy exotic object _O_ is called the following steps are taken:</p>
<emu-alg>
1. Let _handler_ be the value of the [[ProxyHandler]] internal slot of _O_.
1. If _handler_ is *null*, throw a *TypeError* exception.
1. Assert: Type(_handler_) is Object.
1. Let _target_ be the value of the [[ProxyTarget]] internal slot of _O_.
1. Let _trap_ be ? GetMethod(_handler_, `"enumerate"`).
1. If _trap_ is *undefined*, then
1. Return ? _target_.[[Enumerate]]().
1. Let _trapResult_ be ? Call(_trap_, _handler_, &laquo; _target_ &raquo;).
1. If Type(_trapResult_) is not Object, throw a *TypeError* exception.
1. Return _trapResult_.
</emu-alg>
<emu-note>
<p>[[Enumerate]] for proxy objects enforces the following invariants:</p>
<ul>
<li>
The result of [[Enumerate]] must be an Object.
</li>
</ul>
</emu-note>
</emu-clause>

<!-- es6num="9.5.12" -->
<emu-clause id="sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys">
<h1>[[OwnPropertyKeys]] ( )</h1>
Expand Down Expand Up @@ -16008,7 +15913,7 @@ <h1>Runtime Semantics: ForIn/OfHeadEvaluation ( _TDZnames_, _expr_, _iterationKi
1. If _exprValue_.[[value]] is *null* or *undefined*, then
1. Return Completion{[[type]]: ~break~, [[value]]: ~empty~, [[target]]: ~empty~}.
1. Let _obj_ be ToObject(_exprValue_).
1. Return ? _obj_.[[Enumerate]]().
1. Return ? EnumerateObjectProperties(_obj_).
1. Else,
1. Assert: _iterationKind_ is ~iterate~.
1. Return ? GetIterator(_exprValue_).
Expand Down Expand Up @@ -16079,6 +15984,40 @@ <h1>Runtime Semantics: Evaluation</h1>
1. Return ? ResolveBinding(_bindingId_).
</emu-alg>
</emu-clause>

<!-- es6num="9.1.11" -->
<emu-clause id="sec-enumerate-object-properties" aoid="EnumerateObjectProperties">
<h1>EnumerateObjectProperties (_O_)</h1>
<p>When the abstract operation EnumerateObjectProperties is called with argument _O_, the following steps are taken:</p>
<emu-alg>
1. Assert: Type(_O_) is Object.
1. Return an Iterator object (<emu-xref href="#sec-iterator-interface"></emu-xref>) whose `next` method iterates over all the String-valued keys of enumerable properties of _O_. The Iterator object must inherit from %IteratorPrototype% (<emu-xref href="#sec-%iteratorprototype%-object"></emu-xref>). The mechanics and order of enumerating the properties is not specified but must conform to the rules specified below.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it makes sense to define that the return method of the iterator object is null? This should ensure that the iterator object cannot escape to user code when a break or return statement is nested in the for-in loop.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I guess both throw and return should be null.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And actually the requirement that it MUST inherit from %IteratorPrototype% is kind of bogus since the intention is this isn't observable. Can I simply remove that line?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can remove that line. I'd probably also add the note "The Iterator object is never directly accessible to ECMAScript code." to underline that implementations don't actually need to create a JavaScript object.

</emu-alg>
<p>The iterator's `next` method processes object properties to determine whether the property key should be returned as an iterator value. Returned property keys do not include keys that are Symbols. Properties of the target object may be deleted during enumeration. A property that is deleted before it is processed by the iterator's `next` method is ignored. If new properties are added to the target object during enumeration, the newly added properties are not guaranteed to be processed in the active enumeration. A property name will be returned by the iterator's `next` method at most once in any enumeration.</p>
<p>Enumerating the properties of the target object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively; but a property of a prototype is not processed if it has the same name as a property that has already been processed by the iterator's `next` method. The values of [[Enumerable]] attributes are not considered when determining if a property of a prototype object has already been processed. The enumerable property names of prototype objects must be obtained as if by invoking EnumerateObjectProperties passing the prototype object as the argument. EnumerateObjectProperties must obtain the own property keys of the target object as if by calling its [[OwnPropertyKeys]] internal method. Property attributes of the target object must be obtained as if by calling its [[GetOwnProperty]] internal method.</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to change "as if by calling" to "by calling" to properly define the behaviour when enumerating the properties of a proxied object?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is necessary, can you expand? The difference between the two seems slight, with the former signaling that the implementation need not actually be a recursive proto-walk, but the result must look like it was a recursive proto-walk. Seems like allowing this for proxies isn't bad as long as the difference isn't observable. Probably missing something.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"as if by calling" was added in https://bugs.ecmascript.org/show_bug.cgi?id=4107, because we need the same set of property names, but we don't want to require to call [[OwnPropertyKeys]] (or [[GetOwnProperty]]). From getify/You-Dont-Know-JS#423 (comment):

"as if" has a specific meaning in standards and is fine to use in normative text. "as if x" means: the observable results will be the same as you would get if you did x but it is ok to get those results some other way.

The last part ("get those results some other way") is clearly not applicable for proxy objects (and we don't want to mislead implementers to do something other than calling [[OwnPropertyKeys]] on proxies), so it seems wrong to me to leave it in.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I agree, will remove all as-ifs. Implementations must invoke [[OwnPropertyKeys]] once per object in the prototype chain and [[GetOwnProperty]] once for each property, in some order.

<emu-note>
<p>The following is an informative definition of an ECMAScript generator function that conforms to these rules:</p>
<pre><code class="javascript">
function* EnumerateObjectProperties(obj) {
let visited = new Set;
for (let key of Reflect.ownKeys(obj)) {
if (typeof key === "string") {
let desc = Reflect.getOwnPropertyDescriptor(obj, key);
if (desc) {
visited.add(key);
if (desc.enumerable) yield key;
}
}
}
let proto = Reflect.getPrototypeOf(obj)
if (proto === null) return;
for (let protoName of EnumerateObjectProperties(proto)) {
if (!visited.has(protoName)) yield protoName;
}
}
</code></pre>
</emu-note>
</emu-clause>
</emu-clause>
</emu-clause>

Expand Down Expand Up @@ -35082,16 +35021,6 @@ <h1>Reflect.deleteProperty ( _target_, _propertyKey_ )</h1>
</emu-alg>
</emu-clause>

<!-- es6num="26.1.5" -->
<emu-clause id="sec-reflect.enumerate">
<h1>Reflect.enumerate ( _target_ )</h1>
<p>When the `enumerate` function is called with argument _target_ the following steps are taken:</p>
<emu-alg>
1. If Type(_target_) is not Object, throw a *TypeError* exception.
1. Return ? _target_.[[Enumerate]]().
</emu-alg>
</emu-clause>

<!-- es6num="26.1.6" -->
<emu-clause id="sec-reflect.get">
<h1>Reflect.get ( _target_, _propertyKey_ [ , _receiver_ ])</h1>
Expand Down Expand Up @@ -35275,8 +35204,9 @@ <h1>[ @@iterator ] ( )</h1>
<p>When the @@iterator method is called with no arguments, the following steps are taken:</p>
<emu-alg>
1. Let _N_ be the *this* value.
1. If Type(_N_) is not Object, throw a *TypeError* exception.
1. Return ? _N_.[[Enumerate]]().
1. If _N_ is not a module namespace exotic object, throw a *TypeError* exception.
1. Let _exports_ be the value of _N_'s [[Exports]] internal slot.
1. Return ! CreateListIterator(_N_).
</emu-alg>
<p>The value of the `name` property of this function is `"[Symbol.iterator]"`.</p>
</emu-clause>
Expand Down