diff --git a/lib/MemberBuilder.js b/lib/MemberBuilder.js index ab568e6..a184d64 100644 --- a/lib/MemberBuilder.js +++ b/lib/MemberBuilder.js @@ -226,7 +226,7 @@ function aoverride( name ) { return function() { - return this.___$$pmo$$.___$$parent$$[ name ] + return this.___$$super$$.prototype[ name ] .apply( this.___$$pmo$$, arguments ); }; } diff --git a/lib/Trait.js b/lib/Trait.js index ff335a7..279297e 100644 --- a/lib/Trait.js +++ b/lib/Trait.js @@ -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,10 +178,14 @@ 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.___$$pmo$$ = pmo; + this.___$$super$$ = base; + this.___$$pmo$$ = pmo; }, // mainly for debugging; should really never see this. @@ -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) @@ -461,15 +466,21 @@ function mixMethods( src, dest, vis, iname ) * Here, `tc' and `to' are understood to be, respectively, ``trait class'' * and ``trait object''. * - * @param {Class} T trait - * @param {Object} dfn definition object of class being mixed into - * @param {Array} tc trait class object + * @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 @@ -535,15 +549,16 @@ function tctor( tc ) * This binds the generic trait constructor to a reference to the provided * trait class list. * - * @param {Object} 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 ); }; } diff --git a/lib/class.js b/lib/class.js index 9bd0c92..6454a2c 100644 --- a/lib/class.js +++ b/lib/class.js @@ -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 diff --git a/test/Trait/LinearizationTest.js b/test/Trait/LinearizationTest.js index 3906bd1..579751b 100644 --- a/test/Trait/LinearizationTest.js +++ b/test/Trait/LinearizationTest.js @@ -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 ); + }, } );