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
var is_static = keywords && keywords[ 'static' ];
return function()
var ret = function()
{
var context = getInst( this, cid ) || this,
retval = undefined,
@ -122,6 +122,13 @@ exports.standard = {
? this
: 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/>.
*/
var AbstractClass = require( __dirname + '/class_abstract' );
var AbstractClass = require( __dirname + '/class_abstract' ),
ClassBuilder = require( __dirname + '/ClassBuilder' );
function Trait()
@ -86,12 +87,39 @@ Trait.isTrait = function( trait )
*/
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 = {
'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 );
}
@ -135,7 +163,6 @@ function mixin( trait, dfn )
*/
function mixMethods( src, dest, vis, iname )
{
// TODO: ignore abstract
for ( var f in src )
{
if ( !( Object.hasOwnProperty.call( src, f ) ) )
@ -151,19 +178,31 @@ function mixMethods( src, dest, vis, iname )
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;
// if we have already set up a proxy for a field of this name, then
// multiple traits have defined the same concrete member
// if we have already set up a proxy for a field of this name,
// then multiple traits have defined the same concrete member
if ( dest[ pname ] !== undefined )
{
// TODO: between what traits?
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;
}
}
}

View File

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

View File

@ -389,5 +389,27 @@ require( 'common' ).testCase(
"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; },
} );
} );
},
} );