From 66cab74cc154688794e0a9fd11389cb0160a81ea Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Mon, 3 Feb 2014 23:54:21 -0500 Subject: [PATCH] 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. --- lib/MethodWrappers.js | 3 +++ lib/Trait.js | 53 +++++++++++++++++++++++++++++++++++++++ test/Trait/VirtualTest.js | 37 +++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/lib/MethodWrappers.js b/lib/MethodWrappers.js index 086d2ad..c2479b9 100644 --- a/lib/MethodWrappers.js +++ b/lib/MethodWrappers.js @@ -123,6 +123,9 @@ exports.standard = { : 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 // implementations of abstract methods with param requirements (we // have no idea what we'll be proxying to at runtime, so we need to diff --git a/lib/Trait.js b/lib/Trait.js index 0ccf47c..7237b2c 100644 --- a/lib/Trait.js +++ b/lib/Trait.js @@ -132,10 +132,63 @@ function createConcrete( acls ) 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 ); } +/** + * 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 * diff --git a/test/Trait/VirtualTest.js b/test/Trait/VirtualTest.js index 6d61cd2..abcf44a 100644 --- a/test/Trait/VirtualTest.js +++ b/test/Trait/VirtualTest.js @@ -97,4 +97,41 @@ require( 'common' ).testCase( 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 ); + }, } );