I like it! Cc'ing others who may have missed it. Boris is DOM guru you seek.

Does it address the bound function issue you cited in the previous thread? It appears not to, but I might be missing something (jetlag).

/be

Claude Pache wrote:
Please do the following substitutions in my message:

* "created-but-initialised" → "created-but-not-initialised"
* "an ordinary object" → "an object (i.e. a value of type Object) that is not a 
Non-Constructed Object" (2x)

—Claude

Le 27 juin 2014 à 15:56, Claude Pache<claude.pa...@gmail.com>  a écrit :

In the currently specced design of classes, the fact that the creation step and 
the initialisation step of built-in object may be separated by arbitrary user 
code is thought to be problematic.

Jason proposed a @@new behaviour in replacement of @@create that would avoid 
the issue [1]. Here is a counter-proposal (or an improved proposal, ad 
libidum), which is not tightly coupled to @@new. In fact, it is designed to 
(well, TBH, it happens to) just work in absence of @@create or @@new hook, 
although it is possible to introduce them.

The basic idea is the following: the creation process (the @@create step) is 
deferred as late as possible.
It will appear that, for built-in base classes like Array, creation occurs just 
before initialisation, making the created-but-initialised state of such objects 
unobservable, at least in absence of user-overridable @@create hook.

Non-Constructed Objects
-----------------------

Non-Constructed Objects are introduced in order to describe the state of 
not-yet-defined this-bindings.

Non-Constructed Objects is probably not be the greatest approach to spec the 
thing, especially when considering the awful Step 3 of InitializeThisBindings() 
algorithm below;
it is just a convenient hack that allows me to expose the idea with minimal 
change from the current specification draft.

A Non-Constructed Object is a special placeholder exotic object with the 
following internal slots:

* [[Constructor]], which holds a reference to the constructor;
* [[NonConstructed]], set to `true`.

Non-Constructed Objects may only appear as value of this-bindings inside 
functions
(or, using the spec language, as value of the `thisValue` component of a 
function environment record).
Any attempt to get an explicit reference to such an object in user code  will 
throw an error.
The only way to pass (implicitely) a reference to a Non-Constructed Object 
between different function environment records,
is through calls to `super`.

InitializeThisBindings(nonconstructedObj, obj) abstract operation
----------------------------------------------------------------------

This operation performs the actual initialisation of the this-bindings that 
were previously deferred:

   1. Assert `nonconstructedThisObj` is a Non-Constructed Object.
   2. Assert `obj` is an ordinary object.
   3. Replace all references to `nonconstructedThisObj` with references to 
`obj`.
     (In particular, this step will effectively initialise the this-binding of 
every function environment record that used to reference `nonconstructedObj`.)

Additional runtime semantics of the `this` keyword
--------------------------------------------------

Any attempt to get an explicit reference to the `thisValue` of a function 
environment record while it holds a Non-Constructed Object, shall throw an 
error.


new C(...args)
----------------

When a constructor `C` is called with arguments `args`, the following steps are 
taken.
In particular, the actual initialisation of the this-value is deferred.

1. Let `thisValue` be a new Non-Constructed Object, with its internal slot 
[[Constructor]] set to `C`.
2. Let `R` be the result of `C`.[[Call]](`thisValue`, `args`).
3. NOTE. The operation InitializeThisBinding() may have been called during the 
previous step, meaning that `thisValue` may now be a regular object.
4. ReturnIfAbrupt(`R`).
5. If Type(`R`) is Object, return `R`.
6. If `thisValue` is a Non-Constructed Object, throw an error.
7. Return `thisValue`.

No more [[Construct]]
----------------------

[[Construct]] internal method is gone. Actually it is conflated with the 
[[Call]] internal method, modified as below.
The key fact is that [[Call]] is able to see if it should have a 
[[Construct]]-like behaviour,
by examining whether its `thisArgument` is a Non-Constructed Object.


F.[[Call]] (thisArgument, argumentsList) for user-defined functions
-----------------------------------------------------------------------------------

User-defined functions has the currently specced [[Call]] behaviour [Section 
9.2.2],
with the following additional step inserted somewhere near the beginning of the 
algorithm, e.g., after step 1:

1bis. If the `thisArgument` is a Non-Constructed Object,
   a. If `F`’s [[NeedsSuper]] internal slot is set to false (IOW, if `F`’s code 
doesn’t contain `super`),
       i. Let `proto` be the result of 
GetPrototypeFromConstructor(`thisArgument`.[[Constructor]], 
"%ObjectPrototype%").
       ii. ReturnIfAbrupt(`proto`).
       iii. Let `obj` be ObjectCreate(`proto`).
       iv. Perform InitializeThisBindings(`thisArgument`, `obj`).
       v. Assert: Now, `thisArgument` is an ordinary object.
   b. Else, `thisArgument` is left untouched. // it is meant to be handled at 
the occasion of the enclosed `super` call.


F.[[Call]] (thisArgument, argumentsList) for bound functions
-------------------------------------------------------------

In the algorithm sepcced in [Section 9.4.1.1], step 2 is replaced with:

2. If the `thisArgument` is a Non-Constructed Object, let `boundThis` be 
`thisArgument`. // this is the current [[Construct]] behaviour
2bis. Else, let `boundThis` be the value of `F`’s [[BoundThis]] internal slot.  
// this is the current [[Call]] behaviour


F.[[Call]] (thisArgument, argumentsList) for the built-in Object constructor
-----------------------------------------------------------------------------

There is no change: `Object(...)` acts as a factory rather than as a 
constructor, as currently specified, and the `thisArgument` is ignored.
In particular trying to subclass `Object` will lead to unexpected results. Note 
however that `new Object` does still work.


F.[[Call]] (thisArgument, argumentsList) for the built-in Array contsructor
---------------------------------------------------------------------------

The [[ArrayInitializationState]] internal slot is gone, and Step 4 of the 
algorithms in [Sections 22.1.1.*] is replaced with (where `O` is the 
this-value):

4. If `O` is a Non-Constructed Object,
   a. Let `proto` be the result of GetPrototypeFromConstructor(`O`.[[Constructor]], 
"%ArrayPrototype%").
   b. ReturnIfAbrupt(`proto`)
   a. Let `array` be ArrayCreate(<<length>>, `proto`).
   b. Perform InitializeThisBindings(`thisArgument`, `array`)
5. Else, etc.

The [[Call]] behaviour of other built-in constructors is left as an exercise to 
the reader.

Comments
--------

There is a nice side-effect of the proposal: The new internal check intended to 
discriminate between call-as-function and call-as-constructor is easier and 
more robust. In particular,

* hacks such as [[ArrayInitializationState]] are no longer needed;
* bound functions are truly subclassable (see [2]).

However, it remains very hard for user-defined functions to distinguish 
correctly between constructor/initialisation-calls and method/function-calls, 
or to write code that works well in both cases. (At least the situation is not 
worse than in ES5-.)

Optional: the @@create hook
----------------------------

A @@create hook can easily be placed as follows:
In each [[Call]] internal methods defined above, a call to 
(`thisArgument`.[[Constructor]]).@@create
could replace the steps spanning from GetPrototypeFromConstructor(...) 
inclusive to InitializeThisBindings(...) exclusive.

Whether such a hook is compatible with, e.g., DOM constructors, is left to the 
appreciation of the competent people. At worst, a built-in constructor could 
cheat by defining its own [[Call]] internal method that would refuse to run the 
@@create hook.

Optional: the @@new hook
-------------------------

Alternatively, the following hook may be installed:
At the beginning of each call to F.[[Call]], the following steps are taken:

1. if `thisArgument` is a Non-Constructed Object,
   a. If `F` has an *own* property named @@new,
       i. Let `R` be the result of 
`F`[@@new].[[Call]](`thisArgument`.[[Constructor]], `argumentsList`).
       ii. ReturnIfAbrupt(`R`).
       iii. If Type(`R`) is Object,
           α. InitializeThisBindings(`thisArgument`, `R`).
       iv. Return `R`.
   b. etc.
2. etc.

Note that we don’t look for inherited @@new property, in order to preserve the 
initialise-at-latest-time behaviour.

—Claude

[Section 9.2.2]: 
http://people.mozilla.org/~jorendorff/es6-draft.html#sec-ecmascript-function-objects-call-thisargument-argumentslist
[Section 9.4.1.1]: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-call
[Sections 22.1.1.*]: 
http://people.mozilla.org/~jorendorff/es6-draft.html#sec-array-constructor
[1]: http://esdiscuss.org/topic/new
[2]: 
http://esdiscuss.org/topic/issue-when-subclassing-a-bound-function-used-as-constructor

_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to