1
0
Fork 0

Initial trait virtual member proxy implementation

This has some flaws that should be addressed, but those will be detailed in
later commits; this works for now.
perfodd
Mike Gerwitz 2014-02-03 23:54:21 -05:00
parent 3c7cd0e57a
commit 66cab74cc1
3 changed files with 93 additions and 0 deletions

View File

@ -123,6 +123,9 @@ exports.standard = {
: retval; : retval;
}; };
// TODO: need a test for this; yes, we want to store a reference
ret.___$$proxy_to$$ = proxy_to;
// ensures that proxies can be used to provide concrete // ensures that proxies can be used to provide concrete
// implementations of abstract methods with param requirements (we // implementations of abstract methods with param requirements (we
// have no idea what we'll be proxying to at runtime, so we need to // have no idea what we'll be proxying to at runtime, so we need to

View File

@ -132,10 +132,63 @@ function createConcrete( acls )
dfn[ vis + ' proxy ' + f ] = '___$$pmo$$'; dfn[ vis + ' proxy ' + f ] = '___$$pmo$$';
} }
// virtual methods need to be handled with care to ensure that we invoke
// any overrides
createVirtProxy( acls, dfn );
return acls.extend( dfn ); return acls.extend( dfn );
} }
/**
* Create virtual method proxies for all virtual members
*
* Virtual methods are a bit of hassle with traits: we are in a situation
* where we do not know at the time that the trait is created whether or not
* the virtual method has been overridden, since the class that the trait is
* mixed into may do the overriding. Therefore, we must check if an override
* has occured *when the method is invoked*; there is room for optimization
* there (by making such a determination at the time of mixin), but we'll
* leave that for later.
*
* @param {AbstractClass} acls abstract trait class
* @param {Object} dfn destination definition object
*
* @return {undefined}
*/
function createVirtProxy( acls, dfn )
{
var vmembers = ClassBuilder.getMeta( acls ).virtualMembers;
// f = `field'
for ( var f in vmembers )
{
var vis = ( acls.___$$methods$$['public'][ f ] !== undefined )
? 'public'
: 'protected';
dfn[ vis + ' virtual override ' + f ] = ( function()
{
// this is the aforementioned proxy method; see the docblock for
// more information
return function()
{
var pmo = this.___$$pmo$$,
o = pmo[ f ],
op = o.___$$proxy_to$$;
// XXX: a better way to do this would be nice, since this
// does a substring check on every call; avoids infinite
// recursion from proxying to self
return ( o && !( op && op.substr( 0, 7 ) === '___$to$' ) )
? pmo[ f ].apply( pmo, arguments )
: this.__super.apply( this, arguments );
}
} )( f );
}
}
/** /**
* Mix trait into the given definition * Mix trait into the given definition
* *

View File

@ -97,4 +97,41 @@ require( 'common' ).testCase(
this.assertEqual( C().foo(), expected ); this.assertEqual( C().foo(), expected );
}, },
/**
* If C uses T and overrides T.Ma, and there is some method T.Mb that
* invokes T.Ma, then T.Mb should instead invoke C.Ma.
*/
'Class-overridden virtual trait method is accessible by trait':
function()
{
var _self = this;
var T = this.Sut(
{
'public doFoo': function()
{
// should call overridden, not the one below
this.foo();
},
// to be overridden
'virtual protected foo': function()
{
_self.fail( true, false, "Method not overridden." );
},
} );
var called = false;
var C = this.Class.use( T ).extend(
{
// should be called by T.doFoo
'override protected foo': function() { called = true },
} );
C().doFoo();
this.assertOk( called );
},
} ); } );