1
0
Fork 0

__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
Mike Gerwitz 2014-06-05 23:35:03 -04:00
parent f3cb815baa
commit 90a32a104f
3 changed files with 118 additions and 5 deletions

View File

@ -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

View File

@ -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;

View File

@ -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();
} );
},
} );