-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
how does for-in test for presence of a property in the enumerated object? #1281
Comments
I think there’s an open bug on v8 about this. |
Well we should probably figure out how we expect it to work before they change their implementation. Anyway, do you have a link? |
IIRC the ambiguity here is deliberate, to permit multiple strategies for executing for-of loops that different implementations take (sorry, not sure where to find a reference). Do you think we should nail down one interpretation or another? Any suggestion as to which one? |
Looks like the one I was thinking of is https://bugs.chromium.org/p/v8/issues/detail?id=705, and it's been fixed. In a non-Proxy situationI would expect wrt I'd also think it reasonable for engines to be allowed to treat with a Proxy as one of the objects in the chainI would expect that the Proxy would have its list of own keys retrieved once at the beginning of the loop, and that after that, I wrote this without reading the spec text first; how does my interpretation differ from yours, or from what specific engines do? If it doesn't differ, what specific ambiguities do you see? |
@littledan I believe this text was written in a time before proxies, where the difference between an |
@ljharb They're completely different (except XS matches SpiderMonkey in all cases 🤔) 1Chakra
JavaScriptCore
SpiderMonkey, XS
V8
2Chakra
JavaScriptCore
SpiderMonkey, XS
V8
3Chakra
JavaScriptCore
SpiderMonkey, XS
V8
edit: Interestingly, JSC and V8 agree except JSC never triggers the first |
Nope. Knew all about proxies when written.
Do any of the implementation violate the requirements given in prose section? Those are the only normative requirements.
The informative function is simply an example of a valid implementation, but not the required implementation.
Sent from BlueMail
…On Aug 6, 2018, 10:41 PM, at 10:41 PM, Michael Ficarra ***@***.***> wrote:
I believe this text was written in a time before proxies, where the
difference between an `in` test and `getOwnPropertyDescriptor` with
manual prototype traversal was less observable. Additionally, we may
want to specify the short-circuiting of the prototype traversal or the
interleaving of the tests for presence and the loop body evaluation.
--
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
#1281 (comment)
|
The reason I opened this issue is because I can't tell. I've posted the implementation results. You tell me if any violate the requirements. For example,
If a property is being enumerated because it was present on the prototype, but is deleted from the prototype and added to the target object (or elsewhere in the prototype chain), does that count as a deleted property? Because everybody but Chakra thinks it is not deleted. |
I think it would definitely be a shame to forbid JavaScriptCore's optimization here (i.e., you don't need to check if the very first property exists, if you're not worried about proxy traps, since there's no way for it to get deleted between the enumeration and the first pass of the loop). Though I guess it could just have a slow path for proxies. @allenwb, the problem is that the prose is not sufficiently specific to tell which implementations violate it. For example, it is not clear to me
|
All that is not forbidden is allowed. More tomorrow... |
Ok, first some background. For-in enumeration order has always been specified to be implementation dependent and there have also been desires to precisely specify it going back to the development days of ES3. In practice that has never been possible, both for pragmatic (implementations want freedom to optimize/aren't willing to deoptimize existing implementations) and technical reasons (the desire to interleave traversal of a complex meta-mutable (ie, includes exotic objects/proxies) data structure with user provided, potentially mutating code). Starting with ES3 it was concluded that the best that was possible was to impose some basic nominal constraints on the stream of keys produced by the enumeration. That, itself, proved to be challenging. 13.7.5.15 is the 4th generation attempt at defining those constraints. Every word of the ES6 equivalent of 13.7.5.15 was carefully chosen and scrutinized during the latter part of ES2015 development and it reflects the consensus of the committee at that time. The changes made subsequent to ES2015 (ES2016??) don't appear to reflect any significant changes of that consensus. Is it possible that the prose might be improved? Probably. Is it possible to get agreement on more constraints or a stronger ordering requirement? Maybe, but it will be hard. A starting point should probably be researching the ES6 era meeting notes and bugs.ecmascript.org. Also, note that the word "target" as used in the second prose paragraph is not related to Proxy target objects. The wording should be changed if it is being interpreted as implying some special processing requirements for Proxies. There is nothing special about Proxies for this operation. Proxies are just exotic objects and everything "bad" that a proxy might do could also be done by a host provided exotic object. The most important normative requirements are those stated by this prose:
The the importance proceeds in reverse order of the sentences:
These requirements are intend to be sufficient such that an implementation, when enumerating an ordinary object whose prototypes are also all ordinary objects, has the option of either precomputing a complete list of enumerate property keys (but still subject to existence filtering) before yielding any keys or may interleave the determination of the list of keys with yielding keys and client processing of keys. What does "adding" and "deleting" properties mean. For this purpose, we only need to define it for own properties because the second prose paragraph says that the same implementation defined enumeration algorithm must be used when climbing the prototype chain. An operation "adds" a own property to an object if the result of a call to to that object's [[OwnPropertyKeys]] immediately before the operation does not include the property's key and a call to that object's [[OwnPropertyKeys]] immediately after the operation does include the key. "delete" can be defined similarly, except that the ordering of "does not include" and "does include" are reversed. Note that the operations in question are not just [[DefineOwnProperty]] and [[Delete]]. Any operation that has this observable effect is an add or delete. It could even be an operation that does nothing if the object's [[OwnPropertyKeys]] returns random results on each call. |
@allenwb, thanks, that clarifies a lot and I think we should probably try to expand the normative prose to include some of that. Can you also say what "a key must provably exist as a own or inherited property of the object" means, in the context of proxies? In particular, does that imply a requirement to trigger any particular traps? Implementations seem to rely on This is particularly tricky because proxies are permitted to violate invariants which can otherwise be assumed; for example, if you define "an operation 'deletes' an own property to an object if the result of a call to to that object's [[OwnPropertyKeys]] immediately before the operation includes the property's key and a call to that object's [[OwnPropertyKeys]] immediately after the operation does not include the key", by a strict reading that implies that satisfying the "A property that is deleted before it is processed by the iterator's next method is ignored" constraint requires triggering the The basic problem I'm running into is that the prose is given in terms of "the properties of an object", and since proxies have at least three different traps ( I would be happier if we could nail this down more consistently in terms of the MOP operations, rather than relying on words like "delete" or "has" whose meaning, in a world with proxies, is imprecise. |
That's a delete and a add, so the delete and add rules apply independently.
Implementation dependent because the order to prototype traversal isn't specified. But use of the same EnumerateObjectProperties algorithm is required at each level and information from a (lower/higher) level are passed among those calls. But the intent is that starting EnumerateObjectProperties invocation make the ultimate determination of what it yields. II'm pretty sure that we assume recursion would proceed up the prototype chain.; Maybe that needs to be made explicit.
Additions up the chain would not show up in a well behaved implementation of [[OwnPropertyKeys]]. But an ill mannered [[OwnPropertyKeys]] or any other ill mannered behaviors could. In such cases, the basic constraints apply "any property addition after the start may be ignored by an implementation and never yield the same key more than once.
see #1281 (comment)
But in the presence of Proxy it is impossible to guarantee global consistency of an enumeration algorithm that encounters them. All you can do is make sure that your basic enumeration algorithm algorithm does not violate the yield no duplicates requirements and otherwise acts reasonably with ordinary object equivalent trap behavior. In general, all you can expect from a Proxy is enforcement of the essential invariants and stepwise consistency. If successive calls to a Proxy's traps yields crazy results you only need to maintain you own internal invariants as you progress through each call. You aren't required to produce sane results when dealing with a crazy proxy. You can decide to bail out and throw something or you can yield your own crazy results as long as you don't violate you own requirements such as the no duplicates rule. |
As an implementor, would you be ok with the spec. saying you are only allowed to use [[OwnPropertyKeys]], [[GetOwnProperty]], and [[GetPrototypeOf]] in your enumeration algorithm? Is that too restrictive? Of course, if you know you are dealing with ordinary objects you always get to use the "as if" exemption. |
@allenwb, I would personally be OK with that and consider it an improvement, certainly. AFAIK no major implementation deviates that. But I think it is worth nailing this down more precisely, at least for some cases. I've just come across four novel bugs in three engines relating to this part of the spec - in JavaScriptCore (x, x), SpiderMonkey (x), and XS (x). These are actual bugs according to the spec as it stands, not just cases where their behavior differs from other engines but is allowed by the spec, but I think this sort of bug is much more difficult to avoid when trying to conform to the sort of long-form prose in this section rather than the more usual algorithm steps. Edit: also an inconsistency in v8; I can't actually tell whether or not it's a bug. |
https://github.com/tc39/proposal-for-in-order improved this situation for many objects, but it is still unclear for some objects, especially proxies. We'll need to make a (normative) decision about what we do or do not want to allow for the remaining cases, and then write that down more clearly. |
For the record, I care quite a bit that the proxy cases are well defined. I want to be able to rewrite a for-in of a proxy to a combination of other JavaScript primitives in a spec-compliant way. |
Consider the program
What does this print?
What does this print?
What does this print?
The relevant spec text is in §13.7.5.15 and answers these questions ambiguously. Implementations also do not match the informative implementation given in the specification.
The text was updated successfully, but these errors were encountered: