=============================== Exposed Object.defineProperty() =============================== Original algorithm ================== The algorithm is specified in E5 Section 15.2.3.6: 1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception. 2. Let ``name`` be ``ToString(P)``. (Note: this may have side effects.) 3. Let ``desc`` be the result of calling ``ToPropertyDescriptor`` with ``Attributes`` as the argument. 4. Call the ``[[DefineOwnProperty]]`` internal method of ``O`` with arguments ``name``, ``desc``, and ``true``. (Note: the last argument, ``true``, is the ``Throw`` flag.) 5. Return ``O``. The algorithm returns the object, which allows chaining; for instance:: var o = {}; Object.defineProperty(o, 'foo', { value: 'bar' } ).seal(); ``ToPropertyDescriptor()`` is a helper called only from ``Object.defineProperty()`` and ``Object.defineProperties()``. It converts a property descriptor expressed as an ECMAScript object into a "specification descriptor", doing boolean coercions and cross checking the descriptor. For instance, ``ToPropertyDescriptor()`` will reject any property descriptor which contains fields indicating it is both a data property descriptor and an accessor property descriptor. Example from Node / V8:: > var o = {}; > Object.defineProperty(o, 'foo', ... { value: 'bar', set: function() {} }); TypeError: Invalid property. A property cannot both have accessors and be writable or have a value, # The result of the property descriptor conversion is an "internal descriptor". Note that unlike when dealing with existing object properties, this descriptor may not be fully populated, i.e. may be missing fields. From an implementation perspective this means that the descriptor needs to be represented differently. The current implementation doesn't have an explicit representation for the "internal descriptor" which exists for the duration of ``Object.defineProperty()``; the descriptor is represented by a bunch of local variables indicating the presence and coerced values of the descriptor fields (for instance: ``has_writable`` and ``is_writable`` are separate variables). The ``ToPropertyDescriptor()`` algorithm is reformulated in the restatements section. Other notes: * The key is coerced to a string leniently while the object is just checked and never coerced. * ``[[DefineOwnProperty]]`` is always called with ``Throw`` set to ``true``, so the implementation doesn't need to expose a "throw flag". First draft =========== Starting from the original algorithm and inlining both ``ToPropertyDescriptor()`` and the ``[[DefineOwnProperty]]`` algorithm with exotic behaviors, we get: 1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception. 2. Let ``name`` be ``ToString(P)``. (Note: this may have side effects.) 3. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception. 4. Let ``desc`` be a new, empty Property Descriptor. 5. If ``O.[[HasProperty]]("enumerable")`` === ``true``, then set ``desc.[[Enumerable]]`` to ``ToBoolean(O.[[Get]]("enumerable"))``. 6. If ``O.[[HasProperty]]("configurable")`` === ``true``, then set ``desc.[[Configurable]]`` to ``ToBoolean(O.[[Get]]("configurable"))``. 7. If ``O.[[HasProperty]]("value")`` === ``true``, then set ``desc.[[Value]]`` to ``O.[[Get]]("value")``. 8. If ``O.[[HasProperty]]("writable")`` === ``true``, then set ``desc.[[Writable]]`` to ``ToBoolean(O.[[Get]]("writable"))``. 9. If ``O.[[HasProperty]]("get")`` === ``true``, then: a. Set ``desc.[[Get]]`` to ``O.[[Get]]("get")``. b. If ``desc.[[Get]]`` !== ``undefined`` and ``IsCallable(desc.[[Get]])`` === ``false``, then throw a ``TypeError`` exception. 10. If ``O.[[HasProperty]]("set")`` === ``true``, then: a. Set ``desc.[[Set]]`` to ``O.[[Get]]("set")``. b. If ``desc.[[Set]]`` !== ``undefined`` and ``IsCallable(desc.[[Set]])`` === ``false``, then throw a ``TypeError`` exception. 11. If either ``desc.[[Get]]`` or ``desc.[[Set]]`` are present, then: a. If either ``desc.[[Value]]`` or ``desc.[[Writable]]`` are present, then throw a ``TypeError`` exception. 12. Let ``Throw`` be ``true``. 13. Set ``pendingWriteProtect`` to ``false``. 14. If ``O`` is not an ``Array`` object, goto SKIPARRAY. 15. Let ``oldLenDesc`` be the result of calling the ``[[GetOwnProperty]]`` internal method of ``O`` passing ``"length"`` as the argument. The result will never be ``undefined`` or an accessor descriptor because ``Array`` objects are created with a length data property that cannot be deleted or reconfigured. 16. Let ``oldLen`` be ``oldLenDesc.[[Value]]``. (Note that ``oldLen`` is guaranteed to be a unsigned 32-bit integer.) 17. If ``P`` is ``"length"``, then a. If the ``[[Value]]`` field of ``Desc`` is absent, then goto SKIPARRAY. b. Let ``newLen`` be ``ToUint32(Desc.[[Value]])``. c. If ``newLen`` is not equal to ``ToNumber(Desc.[[Value]])``, goto REJECTRANGE. d. Set ``Desc.[[Value]]`` to ``newLen``. e. If ``newLen`` >= ``oldLen``, then goto SKIPARRAY. f. Goto REJECT if ``oldLenDesc.[[Writable]]`` is ``false``. g. If ``Desc.[[Writable]]`` has the value ``false``: 1. Need to defer setting the ``[[Writable]]`` attribute to ``false`` in case any elements cannot be deleted. 2. Set ``pendingWriteProtect`` to ``true``. 3. Set ``Desc.[[Writable]]`` to ``true``. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) 18. Else if ``P`` is an array index (E5 Section 15.4), then: a. Let ``index`` be ``ToUint32(P)``. b. Goto REJECT if ``index`` >= ``oldLen`` and ``oldLenDesc.[[Writable]]`` is ``false``. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) 19. **SKIPARRAY**: Let ``current`` be the result of calling the ``[[GetOwnProperty]]`` internal method of ``O`` with property name ``P``. 20. Let ``extensible`` be the value of the ``[[Extensible]]`` internal property of ``O``. 21. If ``current`` is ``undefined``: a. If ``extensible`` is ``false``, then goto REJECT. b. If ``IsGenericDescriptor(Desc)`` or ``IsDataDescriptor(Desc)`` is ``true``, then 1. Create an own data property named ``P`` of object ``O`` whose ``[[Value]]``, ``[[Writable]]``, ``[[Enumerable]]`` and ``[[Configurable]]`` attribute values are described by ``Desc``. If the value of an attribute field of ``Desc`` is absent, the attribute of the newly created property is set to its default value. c. Else, ``Desc`` must be an accessor Property Descriptor so, 1. Create an own accessor property named ``P`` of object ``O`` whose ``[[Get]]``, ``[[Set]]``, ``[[Enumerable]]`` and ``[[Configurable]]`` attribute values are described by ``Desc``. If the value of an attribute field of ``Desc`` is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. 22. Goto SUCCESS, if every field in ``Desc`` also occurs in ``current`` and the value of every field in ``Desc`` is the same value as the corresponding field in ``current`` when compared using the ``SameValue`` algorithm (E5 Section 9.12). (This also covers the case where every field in ``Desc`` is absent.) 23. If the ``[[Configurable]]`` field of ``current`` is ``false`` then a. Goto REJECT, if the ``[[Configurable]]`` field of ``Desc`` is true. b. Goto REJECT, if the ``[[Enumerable]]`` field of ``Desc`` is present and the ``[[Enumerable]]`` fields of ``current`` and ``Desc`` are the Boolean negation of each other. 24. If ``IsGenericDescriptor(Desc)`` is ``true``, then goto VALIDATED. 25. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)`` have different results, then a. Goto REJECT, if the ``[[Configurable]]`` field of ``current`` is ``false``. b. If ``IsDataDescriptor(current)`` is true, then 1. Convert the property named ``P`` of object ``O`` from a data property to an accessor property. Preserve the existing values of the converted property's ``[[Configurable]]`` and ``[[Enumerable]]`` attributes and set the rest of the property's attributes to their default values. c. Else, 1. Convert the property named ``P`` of object ``O`` from an accessor property to a data property. Preserve the existing values of the converted property's ``[[Configurable]]`` and ``[[Enumerable]]`` attributes and set the rest of the property's attributes to their default values. d. Goto VALIDATED. 26. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)`` are both true, then a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then 1. Goto REJECT, if the ``[[Writable]]`` field of ``current`` is ``false`` and the ``[[Writable]]`` field of ``Desc`` is ``true``. 2. Goto REJECT, If the ``[[Writable]]`` field of ``current`` is ``false``, and the ``[[Value]]`` field of ``Desc`` is present, and ``SameValue(Desc.[[Value]], current.[[Value]])`` is ``false``. b. Goto VALIDATED. 27. Else, ``IsAccessorDescriptor(current)`` and ``IsAccessorDescriptor(Desc)`` are both ``true`` so, a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then 1. Goto REJECT, if the ``[[Set]]`` field of ``Desc`` is present and ``SameValue(Desc.[[Set]], current.[[Set]])`` is ``false``. 2. Goto REJECT, if the ``[[Get]]`` field of ``Desc`` is present and ``SameValue(Desc.[[Get]], current.[[Get]])`` is ``false``. b. Goto VALIDATED. 28. **VALIDATED:** For each attribute field of ``Desc`` that is present, set the correspondingly named attribute of the property named ``P`` of object ``O`` to the value of the field. 29. **SUCCESS:** If ``O`` is an ``Array`` object: a. If ``P`` is ``"length"``, and ``newLen`` < ``oldLen``, then: 1. Let ``shortenSucceeded``, ``finalLen`` be the result of calling the internal helper ``ShortenArray()`` with ``oldLen`` and ``newLen``. 2. Update the property (``"length"``) value to ``finalLen``. 3. If ``pendingWriteProtect`` is ``true``, update the property (``"length"``) to have ``[[Writable]] = false``. 4. Goto REJECT, if ``shortenSucceeded`` is ``false``. b. If ``P`` is an array index and ``index`` >= ``oldLen``: 1. Update the ``"length"`` property of ``O`` to the value ``index + 1``. This always succeeds, because we've checked in the pre-step that the ``"length"`` is writable, and since ``P`` is an array index property, the length must still be writable here. 30. If ``O`` is an arguments object which has a ``[[ParameterMap]]`` internal property: a. Let ``map`` be the value of the ``[[ParameterMap]]`` internal property of the arguments object. b. If the result of calling the ``[[GetOwnProperty]]`` internal method of ``map`` passing ``P`` as the argument is not ``undefined``, then: 1. If ``IsAccessorDescriptor(Desc)`` is ``true``, then: a. Call the ``[[Delete]]`` internal method of ``map`` passing ``P``, and ``false`` as the arguments. (This removes the magic binding for ``P``.) 2. Else (``Desc`` may be generic or data descriptor): a. If ``Desc.[[Value]]`` is present, then: 1. Call the ``[[Put]]`` internal method of ``map`` passing ``P``, ``Desc.[[Value]]``, and ``Throw`` as the arguments. (This updates the bound variable value.) b. If ``Desc.[[Writable]]`` is present and its value is ``false``, then: 1. Call the ``[[Delete]]`` internal method of ``map`` passing ``P`` and ``false`` as arguments. (This removes the magic binding for ``P``, and must happen after a possible update of the variable value.) 31. Return ``O``. 32. **REJECT**: If ``Throw`` is ``true``, then throw a ``TypeError`` exception, otherwise return ``false``. 33. **REJECTRANGE**: Throw a ``RangeError`` exception. Note that this is unconditional (thrown even if ``Throw`` is ``false``). Notes: * Step 3 is redundant (it comes from ``ToPropertyDescriptor()`` because of step 1. * Since ``Throw`` is always ``true``, step 12 can be removed and step 32 changed to throw ``TypeError`` unconditionally. Note that ``Throw`` is also given as a parameter in step 30.b.2.1 as an argument for an internal ``[[Put]]`` to the parameter map. This actually has no effect on behavior (the internal setter will be called, and the ``Throw`` flag is not visible to the setter). Some cleanup ============ 1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception. 2. Let ``name`` be ``ToString(P)``. (Note: this may have side effects.) 3. Let ``desc`` be a new, empty Property Descriptor. 4. If ``O.[[HasProperty]]("enumerable")`` === ``true``, then set ``desc.[[Enumerable]]`` to ``ToBoolean(O.[[Get]]("enumerable"))``. 5. If ``O.[[HasProperty]]("configurable")`` === ``true``, then set ``desc.[[Configurable]]`` to ``ToBoolean(O.[[Get]]("configurable"))``. 6. If ``O.[[HasProperty]]("value")`` === ``true``, then set ``desc.[[Value]]`` to ``O.[[Get]]("value")``. 7. If ``O.[[HasProperty]]("writable")`` === ``true``, then set ``desc.[[Writable]]`` to ``ToBoolean(O.[[Get]]("writable"))``. 8. If ``O.[[HasProperty]]("get")`` === ``true``, then: a. Set ``desc.[[Get]]`` to ``O.[[Get]]("get")``. b. If ``desc.[[Get]]`` !== ``undefined`` and ``IsCallable(desc.[[Get]])`` === ``false``, then throw a ``TypeError`` exception. 9. If ``O.[[HasProperty]]("set")`` === ``true``, then: a. Set ``desc.[[Set]]`` to ``O.[[Get]]("set")``. b. If ``desc.[[Set]]`` !== ``undefined`` and ``IsCallable(desc.[[Set]])`` === ``false``, then throw a ``TypeError`` exception. 10. If either ``desc.[[Get]]`` or ``desc.[[Set]]`` are present, then: a. If either ``desc.[[Value]]`` or ``desc.[[Writable]]`` are present, then throw a ``TypeError`` exception. 11. Set ``pendingWriteProtect`` to ``false``. 12. If ``O`` is not an ``Array`` object, goto SKIPARRAY. 13. Let ``oldLenDesc`` be the result of calling the ``[[GetOwnProperty]]`` internal method of ``O`` passing ``"length"`` as the argument. The result will never be ``undefined`` or an accessor descriptor because ``Array`` objects are created with a length data property that cannot be deleted or reconfigured. 14. Let ``oldLen`` be ``oldLenDesc.[[Value]]``. (Note that ``oldLen`` is guaranteed to be a unsigned 32-bit integer.) 15. If ``P`` is ``"length"``, then a. If the ``[[Value]]`` field of ``Desc`` is absent, then goto SKIPARRAY. b. Let ``newLen`` be ``ToUint32(Desc.[[Value]])``. c. If ``newLen`` is not equal to ``ToNumber(Desc.[[Value]])``, goto REJECTRANGE. d. Set ``Desc.[[Value]]`` to ``newLen``. e. If ``newLen`` >= ``oldLen``, then goto SKIPARRAY. f. Goto REJECT if ``oldLenDesc.[[Writable]]`` is ``false``. g. If ``Desc.[[Writable]]`` has the value ``false``: 1. Need to defer setting the ``[[Writable]]`` attribute to ``false`` in case any elements cannot be deleted. 2. Set ``pendingWriteProtect`` to ``true``. 3. Set ``Desc.[[Writable]]`` to ``true``. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) 16. Else if ``P`` is an array index (E5 Section 15.4), then: a. Let ``index`` be ``ToUint32(P)``. b. Goto REJECT if ``index`` >= ``oldLen`` and ``oldLenDesc.[[Writable]]`` is ``false``. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) 17. **SKIPARRAY**: Let ``current`` be the result of calling the ``[[GetOwnProperty]]`` internal method of ``O`` with property name ``P``. 18. Let ``extensible`` be the value of the ``[[Extensible]]`` internal property of ``O``. 19. If ``current`` is ``undefined``: a. If ``extensible`` is ``false``, then goto REJECT. b. If ``IsGenericDescriptor(Desc)`` or ``IsDataDescriptor(Desc)`` is ``true``, then 1. Create an own data property named ``P`` of object ``O`` whose ``[[Value]]``, ``[[Writable]]``, ``[[Enumerable]]`` and ``[[Configurable]]`` attribute values are described by ``Desc``. If the value of an attribute field of ``Desc`` is absent, the attribute of the newly created property is set to its default value. c. Else, ``Desc`` must be an accessor Property Descriptor so, 1. Create an own accessor property named ``P`` of object ``O`` whose ``[[Get]]``, ``[[Set]]``, ``[[Enumerable]]`` and ``[[Configurable]]`` attribute values are described by ``Desc``. If the value of an attribute field of ``Desc`` is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. 20. Goto SUCCESS, if every field in ``Desc`` also occurs in ``current`` and the value of every field in ``Desc`` is the same value as the corresponding field in ``current`` when compared using the ``SameValue`` algorithm (E5 Section 9.12). (This also covers the case where every field in ``Desc`` is absent.) 21. If the ``[[Configurable]]`` field of ``current`` is ``false`` then a. Goto REJECT, if the ``[[Configurable]]`` field of ``Desc`` is true. b. Goto REJECT, if the ``[[Enumerable]]`` field of ``Desc`` is present and the ``[[Enumerable]]`` fields of ``current`` and ``Desc`` are the Boolean negation of each other. 22. If ``IsGenericDescriptor(Desc)`` is ``true``, then goto VALIDATED. 23. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)`` have different results, then a. Goto REJECT, if the ``[[Configurable]]`` field of ``current`` is ``false``. b. If ``IsDataDescriptor(current)`` is true, then 1. Convert the property named ``P`` of object ``O`` from a data property to an accessor property. Preserve the existing values of the converted property's ``[[Configurable]]`` and ``[[Enumerable]]`` attributes and set the rest of the property's attributes to their default values. c. Else, 1. Convert the property named ``P`` of object ``O`` from an accessor property to a data property. Preserve the existing values of the converted property's ``[[Configurable]]`` and ``[[Enumerable]]`` attributes and set the rest of the property's attributes to their default values. d. Goto VALIDATED. 24. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)`` are both true, then a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then 1. Goto REJECT, if the ``[[Writable]]`` field of ``current`` is ``false`` and the ``[[Writable]]`` field of ``Desc`` is ``true``. 2. Goto REJECT, If the ``[[Writable]]`` field of ``current`` is ``false``, and the ``[[Value]]`` field of ``Desc`` is present, and ``SameValue(Desc.[[Value]], current.[[Value]])`` is ``false``. b. Goto VALIDATED. 25. Else, ``IsAccessorDescriptor(current)`` and ``IsAccessorDescriptor(Desc)`` are both ``true`` so, a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then 1. Goto REJECT, if the ``[[Set]]`` field of ``Desc`` is present and ``SameValue(Desc.[[Set]], current.[[Set]])`` is ``false``. 2. Goto REJECT, if the ``[[Get]]`` field of ``Desc`` is present and ``SameValue(Desc.[[Get]], current.[[Get]])`` is ``false``. b. Goto VALIDATED. 26. **VALIDATED:** For each attribute field of ``Desc`` that is present, set the correspondingly named attribute of the property named ``P`` of object ``O`` to the value of the field. 27. **SUCCESS:** If ``O`` is an ``Array`` object: a. If ``P`` is ``"length"``, and ``newLen`` < ``oldLen``, then: 1. Let ``shortenSucceeded``, ``finalLen`` be the result of calling the internal helper ``ShortenArray()`` with ``oldLen`` and ``newLen``. 2. Update the property (``"length"``) value to ``finalLen``. 3. If ``pendingWriteProtect`` is ``true``, update the property (``"length"``) to have ``[[Writable]] = false``. 4. Goto REJECT, if ``shortenSucceeded`` is ``false``. b. If ``P`` is an array index and ``index`` >= ``oldLen``: 1. Update the ``"length"`` property of ``O`` to the value ``index + 1``. This always succeeds, because we've checked in the pre-step that the ``"length"`` is writable, and since ``P`` is an array index property, the length must still be writable here. 28. If ``O`` is an arguments object which has a ``[[ParameterMap]]`` internal property: a. Let ``map`` be the value of the ``[[ParameterMap]]`` internal property of the arguments object. b. If the result of calling the ``[[GetOwnProperty]]`` internal method of ``map`` passing ``P`` as the argument is not ``undefined``, then: 1. If ``IsAccessorDescriptor(Desc)`` is ``true``, then: a. Call the ``[[Delete]]`` internal method of ``map`` passing ``P``, and ``false`` as the arguments. (This removes the magic binding for ``P``.) 2. Else (``Desc`` may be generic or data descriptor): a. If ``Desc.[[Value]]`` is present, then: 1. Call the ``[[Put]]`` internal method of ``map`` passing ``P``, ``Desc.[[Value]]``, and ``true`` as the arguments. (This updates the bound variable value. Note that the ``Throw`` flag is irrelevant, ``true`` used now.) b. If ``Desc.[[Writable]]`` is present and its value is ``false``, then: 1. Call the ``[[Delete]]`` internal method of ``map`` passing ``P`` and ``false`` as arguments. (This removes the magic binding for ``P``, and must happen after a possible update of the variable value.) 29. Return ``O``. 30. **REJECT**: Throw a ``TypeError`` exception. 31. **REJECTRANGE**: Throw a ``RangeError`` exception.