Proxy methods may now override abstract methods
The method for doing so is a kluge; see the test case for more info.perfodd
parent
548c38503f
commit
c10fe5e248
|
@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
|
|
65
lib/Trait.js
65
lib/Trait.js
|
@ -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,18 +178,30 @@ function mixMethods( src, dest, vis, iname )
|
|||
continue;
|
||||
}
|
||||
|
||||
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 ( dest[ pname ] !== undefined )
|
||||
// if abstract, then we are expected to provide the implementation;
|
||||
// otherwise, we proxy to the trait's implementation
|
||||
if ( src[ f ].___$$keywords$$['abstract'] )
|
||||
{
|
||||
// TODO: between what traits?
|
||||
throw Error( "Trait member conflict: `" + f + "'" );
|
||||
// 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;
|
||||
|
||||
// proxy this method to what will be the encapsulated trait object
|
||||
dest[ pname ] = iname;
|
||||
// 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
|
||||
dest[ pname ] = iname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -296,7 +296,6 @@ require( 'common' ).testCase(
|
|||
name = 'foo',
|
||||
amethod = _self.util.createAbstractMethod( [ 'one' ] );
|
||||
|
||||
|
||||
// abstract appears before
|
||||
this.quickFailureTest( name, 'compatible', function()
|
||||
{
|
||||
|
|
|
@ -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 );
|
||||
},
|
||||
} );
|
||||
|
||||
|
|
|
@ -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; },
|
||||
} );
|
||||
} );
|
||||
},
|
||||
} );
|
Loading…
Reference in New Issue