1
0
Fork 0

Proxy methods may now override abstract methods

The method for doing so is a kluge; see the test case for more info.
perfodd
Mike Gerwitz 2014-01-26 03:26:15 -05:00
parent 548c38503f
commit c10fe5e248
5 changed files with 185 additions and 15 deletions

View File

@ -89,7 +89,7 @@ exports.standard = {
// not to keep an unnecessary reference to the keywords object // not to keep an unnecessary reference to the keywords object
var is_static = keywords && keywords[ 'static' ]; var is_static = keywords && keywords[ 'static' ];
return function() var ret = function()
{ {
var context = getInst( this, cid ) || this, var context = getInst( this, cid ) || this,
retval = undefined, retval = undefined,
@ -122,6 +122,13 @@ exports.standard = {
? this ? this
: retval; : retval;
}; };
// 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
// just power through it; see test case for more info)
ret.__length = Infinity;
return ret;
}, },
}; };

View File

@ -19,7 +19,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
var AbstractClass = require( __dirname + '/class_abstract' ); var AbstractClass = require( __dirname + '/class_abstract' ),
ClassBuilder = require( __dirname + '/ClassBuilder' );
function Trait() function Trait()
@ -86,12 +87,39 @@ Trait.isTrait = function( trait )
*/ */
function createConcrete( acls ) function createConcrete( acls )
{ {
// start by providing a concrete implementation for our dummy method // start by providing a concrete implementation for our dummy method and
// a constructor that accepts the protected member object of the
// containing class
var dfn = { var dfn = {
'protected ___$$trait$$': function() {}, 'protected ___$$trait$$': function() {},
// protected member object
'private ___$$pmo$$': null,
__construct: function( pmo )
{
this.___$$pmo$$ = pmo;
},
// mainly for debugging; should really never see this.
__name: '#ConcreteTrait#',
}; };
// TODO: everything else // every abstract method should be overridden with a proxy to the
// protected member object that will be passed in via the ctor
var amethods = ClassBuilder.getMeta( acls ).abstractMethods;
for ( var f in amethods )
{
// TODO: would be nice if this check could be for '___'; need to
// replace amethods.__length with something else, then
if ( !( Object.hasOwnProperty.call( amethods, f ) )
|| ( f.substr( 0, 2 ) === '__' )
)
{
continue;
}
dfn[ 'public proxy ' + f ] = '___$$pmo$$';
}
return acls.extend( dfn ); return acls.extend( dfn );
} }
@ -135,7 +163,6 @@ function mixin( trait, dfn )
*/ */
function mixMethods( src, dest, vis, iname ) function mixMethods( src, dest, vis, iname )
{ {
// TODO: ignore abstract
for ( var f in src ) for ( var f in src )
{ {
if ( !( Object.hasOwnProperty.call( src, f ) ) ) if ( !( Object.hasOwnProperty.call( src, f ) ) )
@ -151,19 +178,31 @@ function mixMethods( src, dest, vis, iname )
continue; continue;
} }
// if abstract, then we are expected to provide the implementation;
// otherwise, we proxy to the trait's implementation
if ( src[ f ].___$$keywords$$['abstract'] )
{
// copy the abstract definition (N.B. this does not copy the
// param names, since that is not [yet] important)
dest[ 'weak abstract ' + f ] = src[ f ].definition;
}
else
{
var pname = vis + ' proxy ' + f; var pname = vis + ' proxy ' + f;
// if we have already set up a proxy for a field of this name, then // if we have already set up a proxy for a field of this name,
// multiple traits have defined the same concrete member // then multiple traits have defined the same concrete member
if ( dest[ pname ] !== undefined ) if ( dest[ pname ] !== undefined )
{ {
// TODO: between what traits? // TODO: between what traits?
throw Error( "Trait member conflict: `" + f + "'" ); throw Error( "Trait member conflict: `" + f + "'" );
} }
// proxy this method to what will be the encapsulated trait object // proxy this method to what will be the encapsulated trait
// object
dest[ pname ] = iname; dest[ pname ] = iname;
} }
}
} }

View File

@ -296,7 +296,6 @@ require( 'common' ).testCase(
name = 'foo', name = 'foo',
amethod = _self.util.createAbstractMethod( [ 'one' ] ); amethod = _self.util.createAbstractMethod( [ 'one' ] );
// abstract appears before // abstract appears before
this.quickFailureTest( name, 'compatible', function() this.quickFailureTest( name, 'compatible', function()
{ {

View File

@ -389,5 +389,27 @@ require( 'common' ).testCase(
"Should properly proxy to static membesr via static accessor method" "Should properly proxy to static membesr via static accessor method"
); );
}, },
/**
* A proxy method should be able to be used as a concrete implementation
* for an abstract method; this means that it must properly expose the
* number of arguments of the method that it is proxying to. The problem
* is---it can't, because we do not have a type system and so we cannot
* know what we will be proxying to at runtime!
*
* As such, we have no choice (since validations are not at proxy time)
* but to set the length to something ridiculous so that it will never
* fail.
*/
'Proxy methods are able to satisfy abstract method param requirements':
function()
{
var f = this._sut.standard.wrapProxy(
{}, null, 0, function() {}, '', {}
);
this.assertEqual( f.__length, Infinity );
},
} ); } );

View File

@ -0,0 +1,103 @@
/**
* Tests basic trait definition
*
* 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/>.
*/
require( 'common' ).testCase(
{
caseSetUp: function()
{
this.Sut = this.require( 'Trait' );
this.Class = this.require( 'class' );
this.AbstractClass = this.require( 'class_abstract' );
},
/**
* If a trait contains an abstract member, then any class that uses it
* should too be considered abstract if no concrete implementation is
* provided.
*/
'Abstract traits create abstract classes when used': function()
{
var T = this.Sut( { 'abstract foo': [] } );
var _self = this;
this.assertDoesNotThrow( function()
{
// no concrete `foo; should be abstract (this test is sufficient
// because AbstractClass will throw an error if there are no
// abstract members)
_self.AbstractClass.use( T ).extend( {} );
}, Error );
},
/**
* A class may still be concrete even if it uses abstract traits so long
* as it provides concrete implementations for each of the trait's
* abstract members.
*/
'Concrete classes may use abstract traits by definining members':
function()
{
var T = this.Sut( { 'abstract traitfoo': [ 'foo' ] } ),
C = null,
called = false;
var _self = this;
this.assertDoesNotThrow( function()
{
C = _self.Class.use( T ).extend(
{
traitfoo: function( foo ) { called = true; },
} );
} );
// sanity check
C().traitfoo();
this.assertOk( called );
},
/**
* The concrete methods provided by a class must be compatible with the
* abstract definitions of any used traits. This test ensures not only
* that the check is being performed, but that the abstract declaration
* is properly inherited from the trait.
*
* TODO: The error mentions "supertype" compatibility, which (although
* true) may be confusing; perhaps reference the trait that declared the
* method as abstract.
*/
'Concrete classes must be compatible with abstract traits': function()
{
var T = this.Sut( { 'abstract traitfoo': [ 'foo' ] } );
var _self = this;
this.assertThrows( function()
{
C = _self.Class.use( T ).extend(
{
// missing param in definition
traitfoo: function() { called = true; },
} );
} );
},
} );