Began Scala-influenced linearization implementation
More information on this implementation and the rationale behind it will appear in the manual. See future commits. (Note the TODOs; return values aren't quite right here, but that will be handled in the next commit.)perfodd
parent
75e1470582
commit
6473cf35ae
|
@ -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
|
||||
|
|
66
lib/Trait.js
66
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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 );
|
||||
},
|
||||
} );
|
||||
|
|
@ -17,6 +17,9 @@
|
|||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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" );
|
||||
},
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue