1
0
Fork 0

Implemented abstract traits

This is just an initial implementation, so there may be some quirks; there
are more tests to come.
perfodd
Mike Gerwitz 2014-01-26 03:30:52 -05:00
parent 987a2b88ec
commit b04a8473b8
2 changed files with 109 additions and 7 deletions

View File

@ -41,6 +41,9 @@ Trait.extend = function( dfn )
// just in case DFN does not contain any abstract members itself
dfn[ 'abstract protected ___$$trait$$' ] = [];
// give the abstract trait class a distinctive name for debugging
dfn.__name = '#AbstractTrait#';
function TraitType()
{
throw Error( "Cannot instantiate trait" );
@ -118,7 +121,15 @@ function createConcrete( acls )
continue;
}
dfn[ 'public proxy ' + f ] = '___$$pmo$$';
// we know that if it's not public, then it must be protected
var vis = ( acls.___$$methods$$['public'][ f ] !== undefined )
? 'public'
: 'protected';
// setting the correct visibility modified is important to prevent
// visibility de-escalation errors if a protected concrete method is
// provided
dfn[ vis + ' proxy ' + f ] = '___$$pmo$$';
}
return acls.extend( dfn );
@ -178,13 +189,18 @@ function mixMethods( src, dest, vis, iname )
continue;
}
var keywords = src[ f ].___$$keywords$$;
// if abstract, then we are expected to provide the implementation;
// otherwise, we proxy to the trait's implementation
if ( src[ f ].___$$keywords$$['abstract'] )
if ( keywords['abstract'] )
{
// copy the abstract definition (N.B. this does not copy the
// param names, since that is not [yet] important)
dest[ 'weak abstract ' + f ] = src[ f ].definition;
// param names, since that is not [yet] important); the
// visibility modified is important to prevent de-escalation
// errors on override
var vis = keywords['protected'] ? 'protected' : 'public';
dest[ vis + ' weak abstract ' + f ] = src[ f ].definition;
}
else
{

View File

@ -1,5 +1,5 @@
/**
* Tests basic trait definition
* Tests abstract trait definition and use
*
* Copyright (C) 2014 Mike Gerwitz
*
@ -96,8 +96,94 @@ require( 'common' ).testCase(
C = _self.Class.use( T ).extend(
{
// missing param in definition
traitfoo: function() { called = true; },
traitfoo: function() {},
} );
} );
},
/**
* If a trait defines an abstract method, then it should be able to
* invoke a concrete method of the same name defined by a class.
*/
'Traits can invoke concrete class implementation of abstract method':
function()
{
var expected = 'foobar';
var T = this.Sut(
{
'public getFoo': function()
{
return this.echo( expected );
},
'abstract protected echo': [ 'value' ],
} );
var result = this.Class.use( T ).extend(
{
// concrete implementation of abstract trait method
'protected echo': function( value )
{
return value;
},
} )().getFoo();
this.assertEqual( result, expected );
},
/**
* Even more kinky is when a trait provides a concrete implementation
* for an abstract method that is defined in another trait that is mixed
* into the same class. This makes sense, because that class acts as
* though the trait's abstract method is its own. This allows for
* message passing between two traits with the class as the mediator.
*
* This is otherwise pretty much the same as the above test. Note that
* we use a public `echo' method; this is to ensure that we do not break
* in the event that protected trait members break (that is: are not
* exposed to the class).
*/
'Traits can invoke concrete trait implementation of abstract method':
function()
{
var expected = 'traitbar';
// same as the previous test
var Ta = this.Sut(
{
'public getFoo': function()
{
return this.echo( expected );
},
'abstract public echo': [ 'value' ],
} );
// but this is new
var Tc = this.Sut(
{
// concrete implementation of abstract trait method
'public echo': function( value )
{
return value;
},
} );
this.assertEqual(
this.Class.use( Ta, Tc ).extend( {} )().getFoo(),
expected
);
// order shouldn't matter (because that'd be confusing and
// frustrating to users, depending on how the traits are named), so
// let's do this again in reverse order
this.assertEqual(
this.Class.use( Tc, Ta ).extend( {} )().getFoo(),
expected,
"Crap; order matters?!"
);
},
} );