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;
|
: 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
|
||||||
|
|
66
lib/Trait.js
66
lib/Trait.js
|
@ -213,23 +213,31 @@ function createVirtProxy( acls, dfn )
|
||||||
? 'public'
|
? 'public'
|
||||||
: 'protected';
|
: 'protected';
|
||||||
|
|
||||||
dfn[ vis + ' virtual override ' + f ] = ( function()
|
|
||||||
{
|
|
||||||
// this is the aforementioned proxy method; see the docblock for
|
// this is the aforementioned proxy method; see the docblock for
|
||||||
// more information
|
// more information
|
||||||
|
dfn[ vis + ' virtual override ' + f ] = ( function()
|
||||||
|
{
|
||||||
return function()
|
return function()
|
||||||
{
|
{
|
||||||
var pmo = this.___$$pmo$$,
|
var pmo = this.___$$pmo$$,
|
||||||
o = pmo[ f ],
|
o = pmo[ f ];
|
||||||
op = o.___$$proxy_to$$;
|
|
||||||
|
|
||||||
// XXX: a better way to do this would be nice, since this
|
// proxy to virtual override from the class we are mixed
|
||||||
// does a substring check on every call; avoids infinite
|
// into, if found; otherwise, proxy to our supertype
|
||||||
// recursion from proxying to self
|
return ( o )
|
||||||
return ( o && !( op && op.substr( 0, 7 ) === '___$to$' ) )
|
? o.apply( pmo, arguments )
|
||||||
? pmo[ f ].apply( pmo, arguments )
|
|
||||||
: this.__super.apply( this, 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 );
|
} )( f );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,7 +279,7 @@ function mixin( trait, dfn, tc )
|
||||||
* @param {Object} src visibility object to scavenge from
|
* @param {Object} src visibility object to scavenge from
|
||||||
* @param {Object} dest destination definition object
|
* @param {Object} dest destination definition object
|
||||||
* @param {string} vis visibility modifier
|
* @param {string} vis visibility modifier
|
||||||
* @param {string} ianem proxy destination (trait instance)
|
* @param {string} iname proxy destination (trait instance)
|
||||||
*
|
*
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
|
@ -307,8 +315,9 @@ function mixMethods( src, dest, vis, iname )
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var virt = keywords['virtual'] ? 'weak virtual ' : '',
|
var vk = keywords['virtual'],
|
||||||
pname = virt + vis + ' proxy ' + f;
|
virt = vk ? 'weak virtual ' : '',
|
||||||
|
pname = ( vk ? '' : 'proxy ' ) + virt + vis + ' ' + f;
|
||||||
|
|
||||||
// if we have already set up a proxy for a field of this name,
|
// if we have already set up a proxy for a field of this name,
|
||||||
// then multiple traits have defined the same concrete member
|
// 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 + "'" );
|
throw Error( "Trait member conflict: `" + f + "'" );
|
||||||
}
|
}
|
||||||
|
|
||||||
// proxy this method to what will be the encapsulated trait
|
// if non-virtual, a normal proxy should do
|
||||||
// object
|
// TODO: test return value; see below
|
||||||
|
if ( !( keywords['virtual'] ) )
|
||||||
|
{
|
||||||
dest[ pname ] = iname;
|
dest[ pname ] = iname;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxy this method to what will be the encapsulated trait
|
||||||
|
// 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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(
|
require( 'common' ).testCase(
|
||||||
|
@ -34,23 +37,24 @@ require( 'common' ).testCase(
|
||||||
*/
|
*/
|
||||||
'Class inherits virtual trait method': function()
|
'Class inherits virtual trait method': function()
|
||||||
{
|
{
|
||||||
var expected = 'foobar';
|
var called = false;
|
||||||
|
|
||||||
var T = this.Sut(
|
var T = this.Sut(
|
||||||
{
|
{
|
||||||
'virtual foo': function()
|
'virtual foo': function()
|
||||||
{
|
{
|
||||||
return expected;
|
called = true;
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
var C = this.Class.use( T ).extend( {} );
|
var C = this.Class.use( T ).extend( {} );
|
||||||
|
|
||||||
// ensure that we are actually using the method
|
// 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
|
// if virtual, we should be able to override it
|
||||||
var expected2 = 'foobaz',
|
var called2 = false,
|
||||||
C2;
|
C2;
|
||||||
|
|
||||||
this.assertDoesNotThrow( function()
|
this.assertDoesNotThrow( function()
|
||||||
|
@ -59,12 +63,13 @@ require( 'common' ).testCase(
|
||||||
{
|
{
|
||||||
'override foo': function()
|
'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