Object.{getPropertyDescriptor,getPropertyNames}
Already in harmony: namespace on the wiki:
doku.php?id=harmony:extended_object_api&s=object+getpropertydescriptor
(note the &s=... query part shows I found this by searching -- worked!)
Duly chastised. :)
Le 03/09/2011 00:33, David Herman a écrit :
The proxies page refers to Object.getPropertyDescriptor and Object.getPropertyNames as the obvious operations that they ought to be, but also points out that they aren't in ES5. Is there a part of the wiki specs that explicitly state that they should be included in ES6?
If not, we all agree they should be, yes?
If i recall correctly, this proposal was on hold because of the fixed properties proposal (only getPropertyDescriptor I think). The idea was that if there is a getPropertyDescriptor trap, then it became impossible (or it would just require some proto-climbing with some performance issue?) to guarantee the invariants of non-configurable properties. But now that I think about it, i can't remember exactly what was the annoying use case.
Adding Tom in copy. Maybe he'll remember.
2011/9/3 David Bruant <david.bruant at labri.fr>
If i recall correctly, this proposal was on hold because of the fixed properties proposal (only getPropertyDescriptor I think). The idea was that if there is a getPropertyDescriptor trap, then it became impossible (or it would just require some proto-climbing with some performance issue?) to guarantee the invariants of non-configurable properties. But now that I think about it, i can't remember exactly what was the annoying use case.
Adding Tom in copy. Maybe he'll remember.
The issue was that with the earlier fixed properties proposal (the one that stopped trapping access to cached non-configurable properties), the proxy would also cache inherited non-configurable property descriptors, and in doing so the properties were converted into "own" properties of the proxy (Object.getOwnPropertyDescriptor would find them in the cache).
In the latest design of the fixed properties proposal, inherited non-configurable properties are simply no longer checked. So the short answer: the getPropertyDescriptor trap is no longer problematic, so neither should Object.getPropertyDescriptor.
If you want to understand why I think getPropertyDescriptor is no longer problematic in the latest design, read on (apologies for the lengthy explanation, this stuff is hard to formulate concisely!)
- When the getPropertyDescriptor trap returns a non-configurable property, in the absence of any other information, the caller cannot know whether that property is an own or an inherited property. Concretely:
var desc = Object.getPropertyDescriptor(obj, "foo"); // does "desc" describe an own or inherited "foo"? Can't know at this point.
- If the property is inherited, then even if it is non-configurable, it may still be shadowed later by a configurable property. Say our |obj| example object inherits from |proto1|, which inherits from |proto2|. Assume "foo" was defined as a non-configurable property on |proto2|. Then our |desc| object above denotes a non-configurable property. Now, assume "foo" is later added to |proto1| as a configurable property. Re-executing the call to getPropertyDescriptor will then result in "desc" denoting a configurable property. That is within the bounds of ES5.1 behavior.
If |obj| would be a proxy with a FixedHandler, the FixedHandler should not complain that "foo" was previously reported as non-configurable. That is why inherited descriptors returned by getPropertyDescriptor are not checked for invariants.
- Now assume a different scenario in which "foo" is an own, non-configurable property of |obj|. The only way for code to find out that "foo" is indeed an own, non-configurable property is to call Object.getOwnPropertyDescriptor. Doing so will cause the FixedHandler to mark "foo" as a fixed own property. Now, if code calls Object.getPropertyDescriptor(obj, "foo"), the getPropertyDescriptor trap will find "foo" marked as an own property, so it knows it can't be inherited and it must check the validity of the returned result. If getPropertyDescriptor were to return a configurable descriptor at this stage, the FixedHandler will throw.
Thus, the following interaction could occur if |obj| were a "faulty" proxy whose getPropertyDescriptor trap reports "foo" as a configurable property while at the same time its getOwnPropertyDescriptor trap reports it as a non-configurable property (which is inconsistent):
var desc1 = Object.getPropertyDescriptor(obj, "foo"); // desc1 = { ... , configurable: true } var desc2 = Object.getOwnPropertyDescriptor(obj, "foo"); // desc2 = { ..., configurable: false } // (now FixedHandler knows that "foo" was observed as an own non-configurable property) var desc3 = Object.getPropertyDescriptor(obj, "foo");
TypeError: can't report "foo" as configurable as it was previously exposed as a non-configurable own property
I hope this clarifies why I think getPropertyDescriptor is no longer problematic.
Le 06/09/2011 16:12, Tom Van Cutsem a écrit :
2011/9/3 David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>>
If i recall correctly, this proposal was on hold because of the fixed properties proposal (only getPropertyDescriptor I think). The idea was that if there is a getPropertyDescriptor trap, then it became impossible (or it would just require some proto-climbing with some performance issue?) to guarantee the invariants of non-configurable properties. But now that I think about it, i can't remember exactly what was the annoying use case. Adding Tom in copy. Maybe he'll remember.
The issue was that with the earlier fixed properties proposal (the one that stopped trapping access to cached non-configurable properties), the proxy would also cache inherited non-configurable property descriptors, and in doing so the properties were converted into "own" properties of the proxy (Object.getOwnPropertyDescriptor would find them in the cache).
In the latest design of the fixed properties proposal, inherited non-configurable properties are simply no longer checked. So the short answer: the getPropertyDescriptor trap is no longer problematic, so neither should Object.getPropertyDescriptor.
If you want to understand why I think getPropertyDescriptor is no longer problematic in the latest design, read on (apologies for the lengthy explanation, this stuff is hard to formulate concisely!)
- When the getPropertyDescriptor trap returns a non-configurable property, in the absence of any other information, the caller cannot know whether that property is an own or an inherited property. Concretely:
var desc = Object.getPropertyDescriptor(obj, "foo"); // does "desc" describe an own or inherited "foo"? Can't know at this point.
- If the property is inherited, then even if it is non-configurable, it may still be shadowed later by a configurable property. Say our |obj| example object inherits from |proto1|, which inherits from |proto2|. Assume "foo" was defined as a non-configurable property on |proto2|. Then our |desc| object above denotes a non-configurable property. Now, assume "foo" is later added to |proto1| as a configurable property. Re-executing the call to getPropertyDescriptor will then result in "desc" denoting a configurable property. That is within the bounds of ES5.1 behavior.
If |obj| would be a proxy with a FixedHandler, the FixedHandler should not complain that "foo" was previously reported as non-configurable. That is why inherited descriptors returned by getPropertyDescriptor are not checked for invariants.
- Now assume a different scenario in which "foo" is an own, non-configurable property of |obj|. The only way for code to find out that "foo" is indeed an own, non-configurable property is to call Object.getOwnPropertyDescriptor. Doing so will cause the FixedHandler to mark "foo" as a fixed own property. Now, if code calls Object.getPropertyDescriptor(obj, "foo"), the getPropertyDescriptor trap will find "foo" marked as an own property, so it knows it can't be inherited and it must check the validity of the returned result. If getPropertyDescriptor were to return a configurable descriptor at this stage, the FixedHandler will throw.
Thus, the following interaction could occur if |obj| were a "faulty" proxy whose getPropertyDescriptor trap reports "foo" as a configurable property while at the same time its getOwnPropertyDescriptor trap reports it as a non-configurable property (which is inconsistent):
var desc1 = Object.getPropertyDescriptor(obj, "foo"); // desc1 = { ... , configurable: true } var desc2 = Object.getOwnPropertyDescriptor(obj, "foo"); // desc2 = { ..., configurable: false } // (now FixedHandler knows that "foo" was observed as an own non-configurable property) var desc3 = Object.getPropertyDescriptor(obj, "foo"); TypeError: can't report "foo" as configurable as it was previously exposed as a non-configurable own property
I hope this clarifies why I think getPropertyDescriptor is no longer problematic.
Ok. Thank for these explanations. Leaving the liberty to getPropertyDescriptor until something is proven for own properties sounds like a good behavior.
For the sake of completeness, i'd like to say that some "composed invariants" will break with that model. For instance:
var o = Proxy.create({...}, null); var d = Object.getPropertyDescriptor(o, "foo"); // d.configurable === false var d2 = Object.getOwnPropertyDescriptor(o, "foo"); // d2.configurable === true
Based on your proposal, this scenario is possible while it shouldn't because o doesn't inherit from anything (o.[[Prototype]] === null). This could be fixed when the prototype is null, but this case is the tree that hides the forrest. The forrest being that the getPropertyDescriptor can return pretty much whatever it wants as long as the "own layer" of the proxy has nothing to do against it. This allows to lie about the prototype chain. For instance, if the prototype chain is composed of non-extensible objects which do not have a "foo" property, returning a descriptor with configurable = false is breaking invariants (or are they just expectations?) regarding property configurability and inheritance.
I guess i should ask the question more directly: Should (some) current inheritance-based expectations be turn into invariants? Are there good arguments to do so? Or are own layer invariants enough? In a way, having a non-trappable (and thus reliable) Object.getPrototypeOf and reliable non-configurable properties and non-extensibility invariants could be enough, but it's worth asking (adding Mark in cc).
[+stay]
On Tue, Sep 6, 2011 at 7:35 AM, David Bruant <david.bruant at labri.fr> wrote: [...]
I guess i should ask the question more directly: Should (some) current inheritance-based expectations be turn into invariants? Are there good arguments to do so? Or are own layer invariants enough? In a way, having a non-trappable (and thus reliable) Object.getPrototypeOf and reliable non-configurable properties and non-extensibility invariants could be enough, but it's worth asking (adding Mark in cc).
Hi David, thanks for your vigilance!
I think the following set are enough: a) reliable own invariants b) reliable and non-trapping Object.getPrototypeOf c) non-mutation of the prototype chain.
This still provides a simple and learnable model of what is and is not guaranteed to be reliable. To get reliable non-own information, ascend the prototype chain manually.
It is problematic that #c is currently only guaranteed for non-extensible objects. I would prefer prohibiting mutation of the prototype chain for all objects. Is there any way we could get agreement on that for ES6?
As a contrary example, Mike Stay (cc'ed) is checking emulated extensibility at < code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/es53.js#5067>
within his proxy emulation in ES5/3 (Caja's ES5 to ES3 translator and runtime) to avoid a problematic case, relying on the following invariant:
- If all the objects on the prototype chain above obj are non-extensible, and I enumerate all their properties now to mask them on obj undeletably, then afterwards a [[Get]] on obj will never obtain a value provided by obj's prototype chain.
Obviously, we will not try to run ES5/3 on an ES6 system, so it does not matter that actual proxies invalidate this particular bit of code. But it does illustrate how easy it is to bake into code assumptions of indirect invariants which are guaranteed in earlier versions of JS.
Nevertheless, having noted this contrary example for the record (and my possible future shame ;)), I still think the above set of three weaker invariants represents a good compromise.
The proxies page refers to Object.getPropertyDescriptor and Object.getPropertyNames as the obvious operations that they ought to be, but also points out that they aren't in ES5. Is there a part of the wiki specs that explicitly state that they should be included in ES6?
If not, we all agree they should be, yes?