1
0
Fork 0

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
Mike Gerwitz 2014-03-06 01:51:49 -05:00
parent dd7b062474
commit 3005cda543
4 changed files with 114 additions and 19 deletions

View File

@ -226,7 +226,7 @@ function aoverride( name )
{
return function()
{
return this.___$$pmo$$.___$$parent$$[ name ]
return this.___$$super$$.prototype[ name ]
.apply( this.___$$pmo$$, arguments );
};
}

View File

@ -119,9 +119,9 @@ Trait.extend = function( dfn )
}
// 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
@ -178,9 +178,13 @@ function createConcrete( acls )
// protected member object (we define this as protected so that the
// parent ACLS has access to it (!), which is not prohibited since
// 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,
__construct: function( pmo )
'protected ___$$super$$': null,
__construct: function( base, pmo )
{
this.___$$super$$ = base;
this.___$$pmo$$ = pmo;
},
@ -289,16 +293,17 @@ function createVirtProxy( acls, dfn )
* @param {Trait} trait trait to mix in
* @param {Object} dfn definition object to merge into
* @param {Array} tc trait class context
* @param {Class} base target supertyep
*
* @return {Object} dfn
*/
function mixin( trait, dfn, tc )
function mixin( trait, dfn, tc, base )
{
// the abstract class hidden within the trait
var acls = trait.__acls;
// 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
// 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 {Object} dfn definition object of class being mixed into
* @param {Array} tc trait class object
* @param {Class} base target supertyep
*
* @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
// and the trait to instantiate
@ -486,7 +497,7 @@ function addTraitInst( T, dfn, tc )
// definition (this prevents warnings if there is not a supertype
// that defines the trait ctor)
dfn[ 'weak virtual ___$$tctor$$' ] = function() {};
dfn[ 'virtual override ___$$tctor$$' ] = createTctor( tc );
dfn[ 'virtual override ___$$tctor$$' ] = createTctor( tc, base );
}
return iname;
@ -504,9 +515,12 @@ function addTraitInst( T, dfn, tc )
* This will lazily create the concrete trait class if it does not already
* exist, which saves work if the trait is never used.
*
* @param {Object} tc trait class list
* @param {Class} base target supertype
*
* @return {undefined}
*/
function tctor( tc )
function tctor( tc, base )
{
// instantiate all traits and assign the object to their
// respective fields
@ -521,7 +535,7 @@ function tctor( tc )
// (but not private); in return, we will use its own protected
// visibility object to gain access to its protected members...quite
// 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
@ -536,14 +550,15 @@ function tctor( tc )
* trait class list.
*
* @param {Object} tc trait class list
* @param {Class} base target supertype
*
* @return {function()} trait constructor
*/
function createTctor( tc )
function createTctor( tc, base )
{
return function()
{
return tctor.call( this, tc );
return tctor.call( this, tc, base );
};
}

View File

@ -429,7 +429,7 @@ function createMixedClass( base, traits )
// "mix" each trait into the class definition object
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

View File

@ -119,5 +119,85 @@ require( 'common' ).testCase(
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 );
},
} );