1
0
Fork 0

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
Mike Gerwitz 2014-02-19 00:26:57 -05:00
parent 75e1470582
commit 6473cf35ae
4 changed files with 142 additions and 24 deletions

View File

@ -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

View File

@ -213,23 +213,31 @@ function createVirtProxy( acls, dfn )
? 'public' ? 'public'
: 'protected'; : 'protected';
// this is the aforementioned proxy method; see the docblock for
// more information
dfn[ vis + ' virtual override ' + f ] = ( function() dfn[ vis + ' virtual override ' + f ] = ( function()
{ {
// this is the aforementioned proxy method; see the docblock for
// more information
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 + "'" );
} }
// 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 // proxy this method to what will be the encapsulated trait
// object // object (note that we do not use the proxy keyword here
dest[ pname ] = iname; // 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 );
} }
} }
} }

View File

@ -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 );
},
} );

View File

@ -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" );
}, },