Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until now, mixins could not be stacked without some ugly errors. This also allows mixins to be stacked atop of themselves, duplicating their effect. This would naturally have limited use, but it's there. This differs slightly from Scala. For example, consider this ease.js mixin: C.use( T ).use( T )() This is perfectly valid---it has the effect of stacking T twice. In reality, ease.js is doing this: - C' = C.use( T ); - new C'.use( T ); That is, it each call to `use' creates another class with T mixed in. Scala, on the other hand, complains in this situation: new C with T with T will produce an error stating that "trait T is inherited twice". You can work around this, however, by doing this: class Ca extends T new Ca with T In fact, this is precisely what ease.js is doing, as mentioned above; the "use.use" syntax is merely shorthand for this: new C.use( T ).extend( {} ).use( T ) Just keep that in mind.perfodd
parent
dd7b062474
commit
3005cda543
|
@ -226,7 +226,7 @@ function aoverride( name )
|
||||||
{
|
{
|
||||||
return function()
|
return function()
|
||||||
{
|
{
|
||||||
return this.___$$pmo$$.___$$parent$$[ name ]
|
return this.___$$super$$.prototype[ name ]
|
||||||
.apply( this.___$$pmo$$, arguments );
|
.apply( this.___$$pmo$$, arguments );
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
39
lib/Trait.js
39
lib/Trait.js
|
@ -119,9 +119,9 @@ Trait.extend = function( dfn )
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoked to trigger mixin
|
// invoked to trigger mixin
|
||||||
TraitType.__mixin = function( dfn, tc )
|
TraitType.__mixin = function( dfn, tc, base )
|
||||||
{
|
{
|
||||||
mixin( TraitType, dfn, tc );
|
mixin( TraitType, dfn, tc, base );
|
||||||
};
|
};
|
||||||
|
|
||||||
// mixes in implemented types
|
// mixes in implemented types
|
||||||
|
@ -178,9 +178,13 @@ function createConcrete( acls )
|
||||||
// protected member object (we define this as protected so that the
|
// protected member object (we define this as protected so that the
|
||||||
// parent ACLS has access to it (!), which is not prohibited since
|
// parent ACLS has access to it (!), which is not prohibited since
|
||||||
// JS does not provide a strict typing mechanism...this is a kluge)
|
// JS does not provide a strict typing mechanism...this is a kluge)
|
||||||
|
// and target supertype---that is, what __super calls should
|
||||||
|
// referene
|
||||||
'protected ___$$pmo$$': null,
|
'protected ___$$pmo$$': null,
|
||||||
__construct: function( pmo )
|
'protected ___$$super$$': null,
|
||||||
|
__construct: function( base, pmo )
|
||||||
{
|
{
|
||||||
|
this.___$$super$$ = base;
|
||||||
this.___$$pmo$$ = pmo;
|
this.___$$pmo$$ = pmo;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -289,16 +293,17 @@ function createVirtProxy( acls, dfn )
|
||||||
* @param {Trait} trait trait to mix in
|
* @param {Trait} trait trait to mix in
|
||||||
* @param {Object} dfn definition object to merge into
|
* @param {Object} dfn definition object to merge into
|
||||||
* @param {Array} tc trait class context
|
* @param {Array} tc trait class context
|
||||||
|
* @param {Class} base target supertyep
|
||||||
*
|
*
|
||||||
* @return {Object} dfn
|
* @return {Object} dfn
|
||||||
*/
|
*/
|
||||||
function mixin( trait, dfn, tc )
|
function mixin( trait, dfn, tc, base )
|
||||||
{
|
{
|
||||||
// the abstract class hidden within the trait
|
// the abstract class hidden within the trait
|
||||||
var acls = trait.__acls;
|
var acls = trait.__acls;
|
||||||
|
|
||||||
// retrieve the private member name that will contain this trait object
|
// retrieve the private member name that will contain this trait object
|
||||||
var iname = addTraitInst( trait, dfn, tc );
|
var iname = addTraitInst( trait, dfn, tc, base );
|
||||||
|
|
||||||
// recursively mix in trait's underlying abstract class (ensuring that
|
// recursively mix in trait's underlying abstract class (ensuring that
|
||||||
// anything that the trait inherits from is also properly mixed in)
|
// anything that the trait inherits from is also properly mixed in)
|
||||||
|
@ -464,12 +469,18 @@ function mixMethods( src, dest, vis, iname )
|
||||||
* @param {Class} T trait
|
* @param {Class} T trait
|
||||||
* @param {Object} dfn definition object of class being mixed into
|
* @param {Object} dfn definition object of class being mixed into
|
||||||
* @param {Array} tc trait class object
|
* @param {Array} tc trait class object
|
||||||
|
* @param {Class} base target supertyep
|
||||||
*
|
*
|
||||||
* @return {string} private member into which C instance shall be stored
|
* @return {string} private member into which C instance shall be stored
|
||||||
*/
|
*/
|
||||||
function addTraitInst( T, dfn, tc )
|
function addTraitInst( T, dfn, tc, base )
|
||||||
{
|
{
|
||||||
var iname = '___$to$' + T.__acls.__cid;
|
var base_cid = base.__cid;
|
||||||
|
|
||||||
|
// creates a property of the form ___$to$N$M to hold the trait object
|
||||||
|
// reference; M is required because of the private member restrictions
|
||||||
|
// imposed to be consistent with pre-ES5 fallback
|
||||||
|
var iname = '___$to$' + T.__acls.__cid + '$' + base_cid;
|
||||||
|
|
||||||
// the trait object array will contain two values: the destination field
|
// the trait object array will contain two values: the destination field
|
||||||
// and the trait to instantiate
|
// and the trait to instantiate
|
||||||
|
@ -486,7 +497,7 @@ function addTraitInst( T, dfn, tc )
|
||||||
// definition (this prevents warnings if there is not a supertype
|
// definition (this prevents warnings if there is not a supertype
|
||||||
// that defines the trait ctor)
|
// that defines the trait ctor)
|
||||||
dfn[ 'weak virtual ___$$tctor$$' ] = function() {};
|
dfn[ 'weak virtual ___$$tctor$$' ] = function() {};
|
||||||
dfn[ 'virtual override ___$$tctor$$' ] = createTctor( tc );
|
dfn[ 'virtual override ___$$tctor$$' ] = createTctor( tc, base );
|
||||||
}
|
}
|
||||||
|
|
||||||
return iname;
|
return iname;
|
||||||
|
@ -504,9 +515,12 @@ function addTraitInst( T, dfn, tc )
|
||||||
* This will lazily create the concrete trait class if it does not already
|
* This will lazily create the concrete trait class if it does not already
|
||||||
* exist, which saves work if the trait is never used.
|
* exist, which saves work if the trait is never used.
|
||||||
*
|
*
|
||||||
|
* @param {Object} tc trait class list
|
||||||
|
* @param {Class} base target supertype
|
||||||
|
*
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
function tctor( tc )
|
function tctor( tc, base )
|
||||||
{
|
{
|
||||||
// instantiate all traits and assign the object to their
|
// instantiate all traits and assign the object to their
|
||||||
// respective fields
|
// respective fields
|
||||||
|
@ -521,7 +535,7 @@ function tctor( tc )
|
||||||
// (but not private); in return, we will use its own protected
|
// (but not private); in return, we will use its own protected
|
||||||
// visibility object to gain access to its protected members...quite
|
// visibility object to gain access to its protected members...quite
|
||||||
// the intimate relationship
|
// the intimate relationship
|
||||||
this[ f ] = C( this.___$$vis$$ ).___$$vis$$;
|
this[ f ] = C( base, this.___$$vis$$ ).___$$vis$$;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we are a subtype, be sure to initialize our parent's traits
|
// if we are a subtype, be sure to initialize our parent's traits
|
||||||
|
@ -536,14 +550,15 @@ function tctor( tc )
|
||||||
* trait class list.
|
* trait class list.
|
||||||
*
|
*
|
||||||
* @param {Object} tc trait class list
|
* @param {Object} tc trait class list
|
||||||
|
* @param {Class} base target supertype
|
||||||
*
|
*
|
||||||
* @return {function()} trait constructor
|
* @return {function()} trait constructor
|
||||||
*/
|
*/
|
||||||
function createTctor( tc )
|
function createTctor( tc, base )
|
||||||
{
|
{
|
||||||
return function()
|
return function()
|
||||||
{
|
{
|
||||||
return tctor.call( this, tc );
|
return tctor.call( this, tc, base );
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -429,7 +429,7 @@ function createMixedClass( base, traits )
|
||||||
// "mix" each trait into the class definition object
|
// "mix" each trait into the class definition object
|
||||||
for ( var i = 0, n = traits.length; i < n; i++ )
|
for ( var i = 0, n = traits.length; i < n; i++ )
|
||||||
{
|
{
|
||||||
traits[ i ].__mixin( dfn, tc );
|
traits[ i ].__mixin( dfn, tc, ( base || ClassBuilder.ClassBase ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the mixed class from the above generated definition
|
// create the mixed class from the above generated definition
|
||||||
|
|
|
@ -119,5 +119,85 @@ require( 'common' ).testCase(
|
||||||
|
|
||||||
this.assertStrictEqual( C.use( T )().foo(), expected );
|
this.assertStrictEqual( C.use( T )().foo(), expected );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar in spirit to the previous test: a supertype with a mixin
|
||||||
|
* should be treated just as any other class.
|
||||||
|
*
|
||||||
|
* Another way of phrasing this test is: "traits are stackable".
|
||||||
|
* Importantly, this also means that `virtual' must play nicely with
|
||||||
|
* `abstract override'.
|
||||||
|
*/
|
||||||
|
'Mixin overriding another mixin method M has super method M': function()
|
||||||
|
{
|
||||||
|
var called = {};
|
||||||
|
|
||||||
|
var I = this.Interface( { foo: [] } );
|
||||||
|
|
||||||
|
var Ta = this.Sut.implement( I ).extend(
|
||||||
|
{
|
||||||
|
'virtual abstract override foo': function()
|
||||||
|
{
|
||||||
|
called.a = true;
|
||||||
|
this.__super();
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
var Tb = this.Sut.implement( I ).extend(
|
||||||
|
{
|
||||||
|
'abstract override foo': function()
|
||||||
|
{
|
||||||
|
called.b = true;
|
||||||
|
this.__super();
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
this.Class.implement( I ).extend(
|
||||||
|
{
|
||||||
|
'virtual foo': function() { called.base = true; },
|
||||||
|
} ).use( Ta ).use( Tb )().foo();
|
||||||
|
|
||||||
|
this.assertOk( called.a );
|
||||||
|
this.assertOk( called.b );
|
||||||
|
this.assertOk( called.base );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Essentially the same as the above test, but ensures that a mixin can
|
||||||
|
* be stacked multiple times atop of itself with no ill effects. We
|
||||||
|
* assume that all else is working (per the previous test).
|
||||||
|
*
|
||||||
|
* The number of times we stack the mixin is not really relevant, so
|
||||||
|
* long as it is >= 2; we did 3 here just for the hell of it to
|
||||||
|
* demonstrate that there is ideally no limit.
|
||||||
|
*/
|
||||||
|
'Mixin can be mixed in atop of itself': function()
|
||||||
|
{
|
||||||
|
var called = 0,
|
||||||
|
calledbase = false;
|
||||||
|
|
||||||
|
var I = this.Interface( { foo: [] } );
|
||||||
|
|
||||||
|
var T = this.Sut.implement( I ).extend(
|
||||||
|
{
|
||||||
|
'virtual abstract override foo': function()
|
||||||
|
{
|
||||||
|
called++;
|
||||||
|
this.__super();
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
this.Class.implement( I ).extend(
|
||||||
|
{
|
||||||
|
'virtual foo': function() { calledbase = true; },
|
||||||
|
} ).use( T ).use( T ).use( T )().foo();
|
||||||
|
|
||||||
|
|
||||||
|
// mixed in thrice, so it should have stacked thrice
|
||||||
|
this.assertEqual( called, 3 );
|
||||||
|
this.assertOk( calledbase );
|
||||||
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue