__construct and __mixin ordering guarantees
See test cases for rationale. I'm not satisfied with this implementation, but the current state of ease.js makes this kind of thing awkward. Will revisit at some point.textend
parent
f3cb815baa
commit
90a32a104f
|
@ -880,12 +880,11 @@ exports.prototype.createConcreteCtor = function( cname, members )
|
||||||
// generate and store unique instance id
|
// generate and store unique instance id
|
||||||
attachInstanceId( this, ++_self._instanceId );
|
attachInstanceId( this, ++_self._instanceId );
|
||||||
|
|
||||||
// handle internal trait initialization logic, if provided
|
if ( typeof this.___$$ctor$pre$$ === 'function' )
|
||||||
if ( typeof this.___$$tctor$$ === 'function' )
|
|
||||||
{
|
{
|
||||||
// FIXME: we're exposing _priv to something that can be
|
// FIXME: we're exposing _priv to something that can be
|
||||||
// malicously set by the user; encapsulate tctor
|
// malicously set by the user
|
||||||
this.___$$tctor$$.call( this, _priv );
|
this.___$$ctor$pre$$( _priv );
|
||||||
}
|
}
|
||||||
|
|
||||||
// call the constructor, if one was provided
|
// call the constructor, if one was provided
|
||||||
|
@ -897,6 +896,11 @@ exports.prototype.createConcreteCtor = function( cname, members )
|
||||||
this.__construct.apply( this, ( args || arguments ) );
|
this.__construct.apply( this, ( args || arguments ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( typeof this.___$$ctor$post$$ === 'function' )
|
||||||
|
{
|
||||||
|
this.___$$ctor$post$$( _priv );
|
||||||
|
}
|
||||||
|
|
||||||
args = null;
|
args = null;
|
||||||
|
|
||||||
// attach any instance properties/methods (done after
|
// attach any instance properties/methods (done after
|
||||||
|
|
28
lib/Trait.js
28
lib/Trait.js
|
@ -23,6 +23,10 @@ var AbstractClass = require( './class_abstract' ),
|
||||||
ClassBuilder = require( './ClassBuilder' ),
|
ClassBuilder = require( './ClassBuilder' ),
|
||||||
Interface = require( './interface' );
|
Interface = require( './interface' );
|
||||||
|
|
||||||
|
|
||||||
|
function _fvoid() {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trait constructor / base object
|
* 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
|
// retrieve the private member name that will contain this trait object
|
||||||
var iname = addTraitInst( trait, dfn, tc, base );
|
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
|
// 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)
|
||||||
mixinCls( acls, dfn, iname );
|
mixinCls( acls, dfn, iname );
|
||||||
|
@ -819,5 +841,11 @@ function createTctor( tc, base )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function _tctorApply()
|
||||||
|
{
|
||||||
|
this.___$$tctor$$.apply( this, arguments );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = Trait;
|
module.exports = Trait;
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*** XXX __construct or __mixin first? __mixin with no parameters should
|
||||||
|
* permit standard trait with initialization procedure ***/
|
||||||
|
|
||||||
require( 'common' ).testCase(
|
require( 'common' ).testCase(
|
||||||
{
|
{
|
||||||
caseSetUp: function()
|
caseSetUp: function()
|
||||||
|
@ -284,6 +287,84 @@ require( 'common' ).testCase(
|
||||||
this.assertEqual( args.length, 2 );
|
this.assertEqual( args.length, 2 );
|
||||||
this.assertStrictEqual( args[0][0], vals[0] );
|
this.assertStrictEqual( args[0][0], vals[0] );
|
||||||
this.assertStrictEqual( args[1][0], vals[1] );
|
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();
|
||||||
|
} );
|
||||||
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue