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 // just in case DFN does not contain any abstract members itself
dfn[ 'abstract protected ___$$trait$$' ] = []; dfn[ 'abstract protected ___$$trait$$' ] = [];
// give the abstract trait class a distinctive name for debugging
dfn.__name = '#AbstractTrait#';
function TraitType() function TraitType()
{ {
throw Error( "Cannot instantiate trait" ); throw Error( "Cannot instantiate trait" );
@ -118,7 +121,15 @@ function createConcrete( acls )
continue; 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 ); return acls.extend( dfn );
@ -178,13 +189,18 @@ function mixMethods( src, dest, vis, iname )
continue; continue;
} }
var keywords = src[ f ].___$$keywords$$;
// if abstract, then we are expected to provide the implementation; // if abstract, then we are expected to provide the implementation;
// otherwise, we proxy to the trait's 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 // copy the abstract definition (N.B. this does not copy the
// param names, since that is not [yet] important) // param names, since that is not [yet] important); the
dest[ 'weak abstract ' + f ] = src[ f ].definition; // 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 else
{ {

View File

@ -1,5 +1,5 @@
/** /**
* Tests basic trait definition * Tests abstract trait definition and use
* *
* Copyright (C) 2014 Mike Gerwitz * Copyright (C) 2014 Mike Gerwitz
* *
@ -88,7 +88,7 @@ require( 'common' ).testCase(
*/ */
'Concrete classes must be compatible with abstract traits': function() 'Concrete classes must be compatible with abstract traits': function()
{ {
var T = this.Sut( { 'abstract traitfoo': [ 'foo' ] } ); var T = this.Sut( { 'abstract traitfoo': [ 'foo' ] } );
var _self = this; var _self = this;
this.assertThrows( function() this.assertThrows( function()
@ -96,8 +96,94 @@ require( 'common' ).testCase(
C = _self.Class.use( T ).extend( C = _self.Class.use( T ).extend(
{ {
// missing param in definition // 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?!"
);
},
} ); } );