diff --git a/lib/MethodWrappers.js b/lib/MethodWrappers.js
index c2479b9..086d2ad 100644
--- a/lib/MethodWrappers.js
+++ b/lib/MethodWrappers.js
@@ -123,9 +123,6 @@ 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 a83f12f..7d1ed7e 100644
--- a/lib/Trait.js
+++ b/lib/Trait.js
@@ -213,23 +213,31 @@ function createVirtProxy( acls, dfn )
? 'public'
: 'protected';
+ // this is the aforementioned proxy method; see the docblock for
+ // more information
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$$;
+ o = pmo[ f ];
- // 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 )
+ // proxy to virtual override from the class we are mixed
+ // into, if found; otherwise, proxy to our supertype
+ return ( o )
+ ? o.apply( pmo, arguments )
: this.__super.apply( this, arguments );
- }
+ };
+ } )( f );
+
+ // this guy bypasses the above virtual override check, which is
+ // necessary in certain cases to prevent infinte recursion
+ dfn[ vis + ' virtual __$$' + f ] = ( function( f )
+ {
+ return function()
+ {
+ this.___$$parent$$[ f ].apply( this, arguments );
+ };
} )( f );
}
}
@@ -271,7 +279,7 @@ function mixin( trait, dfn, tc )
* @param {Object} src visibility object to scavenge from
* @param {Object} dest destination definition object
* @param {string} vis visibility modifier
- * @param {string} ianem proxy destination (trait instance)
+ * @param {string} iname proxy destination (trait instance)
*
* @return {undefined}
*/
@@ -307,8 +315,9 @@ function mixMethods( src, dest, vis, iname )
}
else
{
- var virt = keywords['virtual'] ? 'weak virtual ' : '',
- pname = virt + vis + ' proxy ' + f;
+ var vk = keywords['virtual'],
+ virt = vk ? 'weak virtual ' : '',
+ pname = ( vk ? '' : 'proxy ' ) + virt + vis + ' ' + f;
// if we have already set up a proxy for a field of this name,
// then multiple traits have defined the same concrete member
@@ -318,9 +327,36 @@ function mixMethods( src, dest, vis, iname )
throw Error( "Trait member conflict: `" + f + "'" );
}
+ // if non-virtual, a normal proxy should do
+ // TODO: test return value; see below
+ if ( !( keywords['virtual'] ) )
+ {
+ dest[ pname ] = iname;
+ continue;
+ }
+
// proxy this method to what will be the encapsulated trait
- // object
- dest[ pname ] = iname;
+ // object (note that we do not use the proxy keyword here
+ // beacuse we are not proxying to a method of the same name)
+ dest[ pname ] = ( function( f )
+ {
+ return function()
+ {
+ var pdest = this[ iname ];
+
+ // invoke the direct method on the trait instance; this
+ // bypasses the virtual override check on the trait
+ // method to ensure that it is invoked without
+ // additional overhead or confusion
+ var ret = pdest[ '__$$' + f ].apply( pdest, arguments );
+
+ // TODO: test return value
+ // if the trait returns itself, return us instead
+ return ( ret === iname )
+ ? this
+ : ret;
+ };
+ } )( f );
}
}
}
diff --git a/test/Trait/LinearizationTest.js b/test/Trait/LinearizationTest.js
new file mode 100644
index 0000000..88d0728
--- /dev/null
+++ b/test/Trait/LinearizationTest.js
@@ -0,0 +1,80 @@
+/**
+ * Tests trait/class linearization
+ *
+ * Copyright (C) 2014 Mike Gerwitz
+ *
+ * This file is part of GNU ease.js.
+ *
+ * ease.js is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * GNU ease.js adopts Scala's concept of `linearization' with respect to
+ * resolving calls to supertypes; the tests that follow provide a detailed
+ * description of the concept, but readers may find it helpful to read
+ * through the ease.js manual or Scala documentation.
+ */
+
+require( 'common' ).testCase(
+{
+ caseSetUp: function()
+ {
+ this.Sut = this.require( 'Trait' );
+ this.Class = this.require( 'class' );
+ },
+
+
+ /**
+ * When a class mixes in a trait that defines some method M, and then
+ * overrides it as M', then this.__super within M' should refer to M.
+ * Note that this does not cause any conflicts with any class supertypes
+ * that may define a method of the same name as M, because M must have
+ * been an override, otherwise an error would have occurred.
+ */
+ 'Class super call refers to mixin that is part of a class definition':
+ function()
+ {
+ var _self = this,
+ scalled = false;
+
+ var T = this.Sut(
+ {
+ // after mixin, this should be the super method
+ 'virtual public foo': function()
+ {
+ scalled = true;
+ },
+ } );
+
+ this.Class.use( T ).extend(
+ {
+ // overrides mixed-in foo
+ 'override public foo': function()
+ {
+ // should invoke T.foo
+ try
+ {
+ this.__super();
+ }
+ catch ( e )
+ {
+ _self.fail( false, true,
+ "Super invocation failure: " + e.message
+ );
+ }
+ },
+ } )().foo();
+
+ this.assertOk( scalled );
+ },
+} );
+
diff --git a/test/Trait/VirtualTest.js b/test/Trait/VirtualTest.js
index df02742..82ac37f 100644
--- a/test/Trait/VirtualTest.js
+++ b/test/Trait/VirtualTest.js
@@ -17,6 +17,9 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
+ *
+ * Note that tests for super calls are contained within LinearizationTest;
+ * these test cases simply ensure that overrides are actually taking place.
*/
require( 'common' ).testCase(
@@ -34,23 +37,24 @@ require( 'common' ).testCase(
*/
'Class inherits virtual trait method': function()
{
- var expected = 'foobar';
+ var called = false;
var T = this.Sut(
{
'virtual foo': function()
{
- return expected;
+ called = true;
}
} );
var C = this.Class.use( T ).extend( {} );
// ensure that we are actually using the method
- this.assertEqual( C().foo(), expected );
+ C().foo();
+ this.assertOk( called, "Virtual method not called" );
// if virtual, we should be able to override it
- var expected2 = 'foobaz',
+ var called2 = false,
C2;
this.assertDoesNotThrow( function()
@@ -59,12 +63,13 @@ require( 'common' ).testCase(
{
'override foo': function()
{
- return expected2;
+ called2 = true;
}
} );
} );
- this.assertEqual( C2().foo(), expected2 );
+ C2().foo();
+ this.assertOk( called2, "Method not overridden" );
},