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
|
// 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;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
53
lib/Trait.js
53
lib/Trait.js
|
@ -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,20 +178,32 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 );
|
||||||
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
@ -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