diff --git a/lib/Trait.js b/lib/Trait.js index 7237b2c..1179079 100644 --- a/lib/Trait.js +++ b/lib/Trait.js @@ -294,7 +294,7 @@ function mixMethods( src, dest, vis, iname ) function addTraitInst( T, dfn ) { var tc = ( dfn.___$$tc$$ = ( dfn.___$$tc$$ || [] ) ), - iname = '___$to$' + tc.length; + iname = '___$to$' + T.__acls.__cid; // the trait object array will contain two values: the destination field // and the trait to instantiate @@ -307,7 +307,11 @@ function addTraitInst( T, dfn ) // create internal trait ctor if not available if ( dfn.___$$tctor$$ === undefined ) { - dfn.___$$tctor$$ = tctor; + // TODO: let's check for inheritance or something to avoid this weak + // 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 ); } return iname; @@ -327,11 +331,10 @@ function addTraitInst( T, dfn ) * * @return {undefined} */ -function tctor() +function tctor( tc ) { // instantiate all traits and assign the object to their // respective fields - var tc = this.___$$tc$$; for ( var t in tc ) { var f = tc[ t ][ 0 ], @@ -345,7 +348,29 @@ function tctor() // the intimate relationship this[ f ] = C( this.___$$vis$$ ).___$$vis$$; } + + // if we are a subtype, be sure to initialize our parent's traits + this.__super && this.__super(); }; +/** + * Create trait constructor + * + * This binds the generic trait constructor to a reference to the provided + * trait class list. + * + * @param {Object} tc trait class list + * + * @return {function()} trait constructor + */ +function createTctor( tc ) +{ + return function() + { + return tctor.call( this, tc ); + }; +} + + module.exports = Trait; diff --git a/test/Trait/MixedExtendTest.js b/test/Trait/MixedExtendTest.js index 80e44f7..4a39646 100644 --- a/test/Trait/MixedExtendTest.js +++ b/test/Trait/MixedExtendTest.js @@ -71,4 +71,69 @@ require( 'common' ).testCase( // o's supertype mixes in T this.assertOk( this.Class.isA( T, o ) ); }, + + + /** + * Subtyping should impose no limits on mixins (except for the obvious + * API compatibility restrictions inherent in OOP). + */ + 'Subtype can mix in additional traits': function() + { + var a = false, + b = false; + + var Ta = this.Sut( + { + 'public ta': function() { a = true; }, + } ), + Tb = this.Sut( + { + 'public tb': function() { b = true; }, + } ), + C = null; + + var _self = this; + this.assertDoesNotThrow( function() + { + var sup = _self.Class.use( Ta ).extend( {} ); + + // mixes in Tb; supertype already mixed in Ta + C = _self.Class.use( Tb ).extend( sup, {} ); + } ); + + this.assertDoesNotThrow( function() + { + // ensures that instantiation does not throw an error and that + // the methods both exist + var o = C(); + o.ta(); + o.tb(); + } ); + + // ensure both were properly called + this.assertOk( a ); + this.assertOk( b ); + }, + + + /** + * As a sanity check, ensure that subtyping does not override parent + * type data with respect to traits. + * + * Note that this test makes the preceding test redundant, but the + * separation is useful for debugging any potential regressions. + */ + 'Subtype trait types do not overwrite supertype types': function() + { + var Ta = this.Sut( {} ), + Tb = this.Sut( {} ), + C = this.Class.use( Ta ).extend( {} ), + o = this.Class.use( Tb ).extend( C, {} )(); + + // o's supertype mixes in Ta + this.assertOk( this.Class.isA( Ta, o ) ); + + // o mixes in Tb + this.assertOk( this.Class.isA( Tb, o ) ); + }, } );