Added support for abstract overrides
parent
14bd552361
commit
8480d8f92c
|
@ -146,7 +146,7 @@ exports.buildMethod = function(
|
|||
}
|
||||
else if ( prev )
|
||||
{
|
||||
if ( keywords.weak )
|
||||
if ( keywords.weak && !( prev_keywords[ 'abstract' ] ) )
|
||||
{
|
||||
// another member of the same name has been found; discard the
|
||||
// weak declaration
|
||||
|
@ -154,9 +154,15 @@ exports.buildMethod = function(
|
|||
}
|
||||
else if ( keywords[ 'override' ] || prev_keywords[ 'abstract' ] )
|
||||
{
|
||||
// if we have the `abstract' keyword at this point, then we are
|
||||
// an abstract override
|
||||
var override = ( keywords[ 'abstract' ] )
|
||||
? aoverride( name )
|
||||
: prev;
|
||||
|
||||
// override the method
|
||||
dest[ name ] = this._overrideMethod(
|
||||
prev, value, instCallback, cid
|
||||
override, value, instCallback, cid
|
||||
);
|
||||
}
|
||||
else
|
||||
|
@ -185,6 +191,47 @@ exports.buildMethod = function(
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates an abstract override super method proxy to NAME
|
||||
*
|
||||
* This is a fairly abstract concept that is disastrously confusing without
|
||||
* having been put into the proper context: This function is intended to be
|
||||
* used as a super method for a method override in the case of abstract
|
||||
* overrides. It only makes sense to be used, at least at this time, with
|
||||
* mixins.
|
||||
*
|
||||
* When called, the bound context (`this') will be the private member object
|
||||
* of the caller, which should contain a reference to the protected member
|
||||
* object of the supertype to proxy to. It is further assumed that the
|
||||
* protected member object (pmo) defines NAME such that it proxies to a
|
||||
* mixin; this means that invoking it could result in an infinite loop. We
|
||||
* therefore skip directly to the super-super method, which will be the
|
||||
* method we are interested in proxying to.
|
||||
*
|
||||
* There is one additional consideration: If this super method is proxying
|
||||
* from a mixin instance into a class, then it is important that we bind the
|
||||
* calling context to the pmo instaed of our own context; otherwise, we'll
|
||||
* be executing within the context of the trait, without access to the
|
||||
* members of the supertype that we are proxying to! The pmo will be used by
|
||||
* the ease.js method wrapper to look up the proper private member object,
|
||||
* so it is not a problem that the pmo is being passed in.
|
||||
*
|
||||
* That's a lot of text for such a small amount of code.
|
||||
*
|
||||
* @param {string} name name of method to proxy to
|
||||
*
|
||||
* @return {Function} abstract override super method proxy
|
||||
*/
|
||||
function aoverride( name )
|
||||
{
|
||||
return function()
|
||||
{
|
||||
return this.___$$pmo$$.___$$parent$$[ name ]
|
||||
.apply( this.___$$pmo$$, arguments );
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies a property to the appropriate member prototype, depending on
|
||||
* visibility, and assigns necessary metadata from keywords
|
||||
|
|
|
@ -212,12 +212,25 @@ exports.prototype.validateMethod = function(
|
|||
|
||||
// disallow overriding non-virtual methods
|
||||
if ( keywords[ 'override' ] && !( prev_keywords[ 'virtual' ] ) )
|
||||
{
|
||||
if ( !( keywords[ 'abstract' ] ) )
|
||||
{
|
||||
throw TypeError(
|
||||
"Cannot override non-virtual method '" + name + "'"
|
||||
);
|
||||
}
|
||||
|
||||
// at this point, we have `abstract override'
|
||||
if ( !( prev_keywords[ 'abstract' ] ) )
|
||||
{
|
||||
// TODO: test me
|
||||
throw TypeError(
|
||||
"Cannot perform abstract override on non-abstract " +
|
||||
"method '" + name + "'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// do not allow overriding concrete methods with abstract unless the
|
||||
// abstract method is weak
|
||||
if ( keywords[ 'abstract' ]
|
||||
|
|
12
lib/Trait.js
12
lib/Trait.js
|
@ -175,8 +175,10 @@ function createConcrete( acls )
|
|||
var dfn = {
|
||||
'protected ___$$trait$$': function() {},
|
||||
|
||||
// protected member object
|
||||
'private ___$$pmo$$': null,
|
||||
// protected member object (we define this as protected so that the
|
||||
// parent ACLS has access to it (!), which is not prohibited since
|
||||
// JS does not provide a strict typing mechanism...this is a kluge)
|
||||
'protected ___$$pmo$$': null,
|
||||
__construct: function( pmo )
|
||||
{
|
||||
this.___$$pmo$$ = pmo;
|
||||
|
@ -393,7 +395,7 @@ function mixMethods( src, dest, vis, iname )
|
|||
|
||||
// if abstract, then we are expected to provide the implementation;
|
||||
// otherwise, we proxy to the trait's implementation
|
||||
if ( keywords['abstract'] )
|
||||
if ( keywords[ 'abstract' ] && !( keywords[ 'override' ] ) )
|
||||
{
|
||||
// copy the abstract definition (N.B. this does not copy the
|
||||
// param names, since that is not [yet] important); the
|
||||
|
@ -405,7 +407,8 @@ function mixMethods( src, dest, vis, iname )
|
|||
{
|
||||
var vk = keywords['virtual'],
|
||||
virt = vk ? 'weak virtual ' : '',
|
||||
pname = ( vk ? '' : 'proxy ' ) + virt + vis + ' ' + f;
|
||||
ovr = ( keywords['override'] ) ? 'override ' : '',
|
||||
pname = ( vk ? '' : 'proxy ' ) + virt + ovr + vis + ' ' + f;
|
||||
|
||||
// if we have already set up a proxy for a field of this name,
|
||||
// then multiple traits have defined the same concrete member
|
||||
|
@ -546,3 +549,4 @@ function createTctor( tc )
|
|||
|
||||
|
||||
module.exports = Trait;
|
||||
|
||||
|
|
|
@ -305,7 +305,10 @@ exports.propParse = function( data, options )
|
|||
name = parse_data.name || prop;
|
||||
keywords = parse_data.keywords || {};
|
||||
|
||||
if ( options.assumeAbstract || keywords[ 'abstract' ] )
|
||||
// note the exception for abstract overrides
|
||||
if ( options.assumeAbstract
|
||||
|| ( keywords[ 'abstract' ] && !( keywords[ 'override' ] ) )
|
||||
)
|
||||
{
|
||||
// may not be set if assumeAbstract is given
|
||||
keywords[ 'abstract' ] = true;
|
||||
|
|
|
@ -177,8 +177,39 @@ require( 'common' ).testCase(
|
|||
* otherwise, override does not make sense, because I.M is clearly
|
||||
* abstract and there is nothing to override.
|
||||
*/
|
||||
'Trait can override virtual concrete interface methods at mixin':
|
||||
'Mixin can override virtual concrete method defined by interface':
|
||||
function()
|
||||
{
|
||||
var called = false,
|
||||
I = this.Interface( { foo: [] } );
|
||||
|
||||
var T = this.Sut.implement( I ).extend(
|
||||
{
|
||||
// the keyword combination `abstract override' indicates that we
|
||||
// should override whatever concrete implementation was defined
|
||||
// before our having been mixed in
|
||||
'abstract override foo': function()
|
||||
{
|
||||
called = true;
|
||||
},
|
||||
} );
|
||||
|
||||
var _self = this;
|
||||
var C = this.Class.implement( I ).extend(
|
||||
{
|
||||
// this should be overridden by the mixin and should therefore
|
||||
// never be called (for __super tests, see LinearizationTest)
|
||||
'virtual foo': function()
|
||||
{
|
||||
_self.fail( false, true,
|
||||
"Concrete class method was not overridden by mixin"
|
||||
);
|
||||
},
|
||||
} );
|
||||
|
||||
// mixing in a trait atop of C should yield the results described
|
||||
// above due to the `abstract override' keyword combination
|
||||
C.use( T )().foo();
|
||||
this.assertOk( called );
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -30,6 +30,7 @@ require( 'common' ).testCase(
|
|||
{
|
||||
this.Sut = this.require( 'Trait' );
|
||||
this.Class = this.require( 'class' );
|
||||
this.Interface = this.require( 'interface' );
|
||||
},
|
||||
|
||||
|
||||
|
@ -76,5 +77,47 @@ require( 'common' ).testCase(
|
|||
|
||||
this.assertOk( scalled );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* If a trait overrides a method of a class that it is mixed into, then
|
||||
* super calls within the trait method should resolve to the class
|
||||
* method.
|
||||
*/
|
||||
'Mixin overriding class method has class method as super method':
|
||||
function()
|
||||
{
|
||||
var _self = this;
|
||||
|
||||
var expected = {},
|
||||
I = this.Interface( { foo: [] } );
|
||||
|
||||
var T = this.Sut.implement( I ).extend(
|
||||
{
|
||||
// see ClassVirtualTest case for details on this
|
||||
'abstract override foo': function()
|
||||
{
|
||||
// should reference C.foo
|
||||
return this.__super( expected );
|
||||
},
|
||||
} );
|
||||
|
||||
var priv_expected = Math.random();
|
||||
|
||||
var C = this.Class.implement( I ).extend(
|
||||
{
|
||||
// asserting on this value will ensure that the below method is
|
||||
// invoked in the proper context
|
||||
'private _priv': priv_expected,
|
||||
|
||||
'virtual foo': function( given )
|
||||
{
|
||||
_self.assertEqual( priv_expected, this._priv );
|
||||
return given;
|
||||
},
|
||||
} );
|
||||
|
||||
this.assertStrictEqual( C.use( T )().foo(), expected );
|
||||
},
|
||||
} );
|
||||
|
||||
|
|
|
@ -55,6 +55,33 @@ require( 'common' ).testCase(
|
|||
},
|
||||
|
||||
|
||||
/**
|
||||
* As an exception to the above rule, a method shall not considered to be
|
||||
* abstract if the `override' keyword is too provided (an abstract
|
||||
* override---see the trait tests for more information).
|
||||
*/
|
||||
'Not considered abstract when `override\' also provided': function()
|
||||
{
|
||||
var _self = this;
|
||||
|
||||
var data = { 'abstract override foo': function() {} },
|
||||
found = null;
|
||||
|
||||
this.Sut.propParse( data, {
|
||||
method: function ( name, func, is_abstract )
|
||||
{
|
||||
_self.assertOk( is_abstract === false );
|
||||
_self.assertEqual( typeof func, 'function' );
|
||||
_self.assertOk( _self.Sut.isAbstractMethod( func ) === false );
|
||||
|
||||
found = name;
|
||||
},
|
||||
} );
|
||||
|
||||
this.assertEqual( found, 'foo' );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* The idea behind supporting this functionality---which is unsued at
|
||||
* the time of writing this test---is to allow eventual customization of
|
||||
|
|
Loading…
Reference in New Issue