traits are now impossible in ES6 until ES7 since rev32?

Luke Scott luke at cywh.com
Fri Feb 6 11:38:06 PST 2015


> On Feb 6, 2015, at 10:35 AM, Claude Pache <claude.pache at gmail.com> wrote:
> 
>> 
>> Le 6 févr. 2015 à 18:04, Ben Newman <benjamin at cs.stanford.edu <mailto:benjamin at cs.stanford.edu>> a écrit :
>> 
>> The specific line in rev32 of the spec that prevents [[Call]]ing "classConstructor" functions is 9.2.2.2 <https://people.mozilla.org/~jorendorff/es6-draft.html#sec-ecmascript-function-objects-call-thisargument-argumentslist>:
>> 
>> 2. If F’s [[FunctionKind]] internal slot <https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object-internal-methods-and-internal-slots> is "classConstructor", throw a TypeError exception.
>> 
>> From my reading of the spec, I think the idiomatic Foo.call(this) pattern that Luke Scott described would work if we simply changed that line to something slightly weaker:
>> 
>> 2. If F’s [[FunctionKind]] internal slot <https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object-internal-methods-and-internal-slots> is "classConstructor" and InstanceofOperator <https://people.mozilla.org/~jorendorff/es6-draft.html#sec-instanceofoperator>(thisArgument, F) is false, throw a TypeError exception.
>> 
>> This mirrors an assertion discipline that has saved me from many bugs due to forgetting the new operator:
>> 
>> function Base() {
>>   assert.ok(this instanceof Base);
>>   ...
>> }
>> 
>> function Derived() {
>>   assert.ok(this instanceof Derived);
>>   Base.call(this);
>>   ...
>> }
>> 
>> Derived.prototype = Object.create(Base.prototype, {
>>   constructor: { value: Derived, ... }
>> });
>> 
>> Is the addition of the instanceof check naive? Would it invalidate any of the assumptions involved in the invocation of F?
>> 
>> I'm happy to file a bug if this change merits further consideration.
>> 
>> It may be worth noting that only constructors created by class syntax will have their [[FunctionKind]] internal slot set to "classConstructor", so (even with the current spec) you can still invoke ordinary constructor functions using [[Call]]. However, it seems regrettable that you have to know whether a constructor was created by class syntax in order to know whether the Foo.call(this) pattern is safe.
> 
> The issue is deeper. In short, you cannot feed an ES6-class constructor with an already allocated object, whatever that object is.
> 
> With a user-defined good ol' pre-ES6 constructor:
> 
>     foo = new Foo // allocate a Foo object and initialise it
>     
> is usually equivalent to:
> 
>     foo = Object.create(Foo.prototype) // allocate a Foo object ...
>     Foo.call(foo) // ... and initilise it
> 
> With the new ES6-class semantics, for the sake of subclassability of builtins, it is, on purpose, not possible to separate allocation from initialisation that way. Even before ES6, builtin classes, did not support such a pattern, e.g.,
> 
>     arr = new Array(2, 3) // allocate a new array and initialise it
> 
> is in no way equivalent to:
> 
>     arr = Object.create(Array.prototype) // allocate an new object, but it won't be an array...
>     Array.call(arr, 2, 3) // ... and don't initialise it, but create uselessly a new Array
>     
> And it appeared that introducing the possibility for builtins to have separate allocation and initialisation phases was problematic.
> 
> —Claude

The following should be sufficient with the current ES6, spec, right? It currently works in 6to5.

function mixin(classObject, …traits) {
    var newClassObject = class extends classObject{}
    // … fill in prototype here
}

Below is a possible workaround for pre-ES6 code (without being transpired), provided that an Object.isClass is added (checks for internal classConstructor flag), which would always return false in pollyfills.

function mixin(classObject, traits) {
    var newClassObject;
    if (Object.isClass(classObject)) {
        newClassObject = eval("class extends classObject{}”);
    } else {
        newClassObject = function() {classObject.apply(this, arguments);};
        newClassObject.prototype = Object.create(classObject.prototype);
        newClassObject.prototype.constructor = newClassObject;
    }
    // … fill in prototype here
}

It’s an ugly hack. Unfortunately the eval is necessary because Chrome throws “Unexpected reserved word” for the class keyword.

--
Luke

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20150206/6bfebcdf/attachment-0001.html>


More information about the es-discuss mailing list