diff --git a/lib/ClassBuilder.js b/lib/ClassBuilder.js index 18795ec..dedf9bb 100644 --- a/lib/ClassBuilder.js +++ b/lib/ClassBuilder.js @@ -880,12 +880,11 @@ exports.prototype.createConcreteCtor = function( cname, members ) // generate and store unique instance id attachInstanceId( this, ++_self._instanceId ); - // handle internal trait initialization logic, if provided - if ( typeof this.___$$tctor$$ === 'function' ) + if ( typeof this.___$$ctor$pre$$ === 'function' ) { // FIXME: we're exposing _priv to something that can be - // malicously set by the user; encapsulate tctor - this.___$$tctor$$.call( this, _priv ); + // malicously set by the user + this.___$$ctor$pre$$( _priv ); } // call the constructor, if one was provided @@ -897,6 +896,11 @@ exports.prototype.createConcreteCtor = function( cname, members ) this.__construct.apply( this, ( args || arguments ) ); } + if ( typeof this.___$$ctor$post$$ === 'function' ) + { + this.___$$ctor$post$$( _priv ); + } + args = null; // attach any instance properties/methods (done after diff --git a/lib/Trait.js b/lib/Trait.js index 233feb8..0cca02f 100644 --- a/lib/Trait.js +++ b/lib/Trait.js @@ -23,6 +23,10 @@ var AbstractClass = require( './class_abstract' ), ClassBuilder = require( './ClassBuilder' ), Interface = require( './interface' ); + +function _fvoid() {}; + + /** * Trait constructor / base object * @@ -535,6 +539,24 @@ function mixin( trait, dfn, tc, base ) // retrieve the private member name that will contain this trait object var iname = addTraitInst( trait, dfn, tc, base ); + // TODO: this should not be necessary for dfn metadata + dfn[ 'weak virtual ___$$ctor$pre$$' ] = _fvoid; + dfn[ 'weak virtual ___$$ctor$post$$' ] = _fvoid; + + // TODO: this is a kluge; generalize and move + // this ensures __construct is called before __mixin when mixing into + // the base class + if ( base === ClassBuilder.ClassBase ) + { + dfn[ 'virtual override ___$$ctor$post$$' ] = _tctorApply; + dfn[ 'virtual override ___$$ctor$pre$$' ] = _fvoid; + } + else + { + dfn[ 'virtual override ___$$ctor$post$$' ] = _fvoid; + dfn[ 'virtual override ___$$ctor$pre$$' ] = _tctorApply; + } + // recursively mix in trait's underlying abstract class (ensuring that // anything that the trait inherits from is also properly mixed in) mixinCls( acls, dfn, iname ); @@ -819,5 +841,11 @@ function createTctor( tc, base ) } +function _tctorApply() +{ + this.___$$tctor$$.apply( this, arguments ); +} + + module.exports = Trait; diff --git a/test/Trait/ParameterTest.js b/test/Trait/ParameterTest.js index 31730f5..3fb7e62 100644 --- a/test/Trait/ParameterTest.js +++ b/test/Trait/ParameterTest.js @@ -19,6 +19,9 @@ * along with this program. If not, see . */ +/*** XXX __construct or __mixin first? __mixin with no parameters should + * permit standard trait with initialization procedure ***/ + require( 'common' ).testCase( { caseSetUp: function() @@ -284,6 +287,84 @@ require( 'common' ).testCase( this.assertEqual( args.length, 2 ); this.assertStrictEqual( args[0][0], vals[0] ); this.assertStrictEqual( args[1][0], vals[1] ); - } + }, + + + /** + * This decision is not arbitrary. + * + * We shall consider two different scenarios: first, the case of mixing + * in some trait T atop of some class C. Assume that C defines a + * __construct method; it does not know whether or not a trait will be + * mixed in, nor should it care---it should proceed initializing its + * state as normal. However, what if a trait were to be mixed in, + * overriding certain behaviors? It is then imperative that T be + * initialized prior to any calls by C#__construct. It is not important + * that C be initialized prior to T#__mixin, because T can know that it + * should not invoke any methods that will fail---it should be used only + * to initialize state. (In the future, ease.js may enforce this + * restriction.) + * + * The second scenario is described in the test that follows. + */ + 'Invokes __mixin before __construct when C.use(T)': function() + { + var mixok = false; + + var T = this.createParamTrait( function() { mixok = true } ), + C = this.Class( + { + __construct: function() + { + if ( !mixok ) throw Error( + "__construct called before __mixin" + ); + } + } ); + + this.assertDoesNotThrow( function() + { + C.use( T )(); + } ); + }, + + + /** + * (Continued from above test.) + * + * In the reverse situation---whereby C effectively extends T---we want + * __construct to instead be called *after* __mixin of T (and any other + * traits in the set). This is because __construct may wish to invoke + * methods of T, but what would cause problems if T were not + * initialized. Further, T would not have knowledge of C and, if it + * expected a concrete implementation to be called from T#__mixin, then + * T would have already been initialized, or C's concrete implementation + * would know what not to do (in the case of a partial initialization). + * + * This is also more intuitive---we are invoking initialize methods as + * if they were part of a stack. + */ + 'Invokes __construct before __mixin when Class.use(T).extend()': + function() + { + var cok = false; + + var T = this.createParamTrait( function() + { + if ( !cok ) throw Error( + "__mixin called before __construct" + ); + } ); + + var C = this.Class.use( T ).extend( + { + __construct: function() { cok = true } + } ); + + this.assertDoesNotThrow( function() + { + C(); + } ); + }, } );