Added support for abstract overrides
parent
14bd552361
commit
8480d8f92c
|
@ -146,7 +146,7 @@ exports.buildMethod = function(
|
||||||
}
|
}
|
||||||
else if ( prev )
|
else if ( prev )
|
||||||
{
|
{
|
||||||
if ( keywords.weak )
|
if ( keywords.weak && !( prev_keywords[ 'abstract' ] ) )
|
||||||
{
|
{
|
||||||
// another member of the same name has been found; discard the
|
// another member of the same name has been found; discard the
|
||||||
// weak declaration
|
// weak declaration
|
||||||
|
@ -154,9 +154,15 @@ exports.buildMethod = function(
|
||||||
}
|
}
|
||||||
else if ( keywords[ 'override' ] || prev_keywords[ 'abstract' ] )
|
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
|
// override the method
|
||||||
dest[ name ] = this._overrideMethod(
|
dest[ name ] = this._overrideMethod(
|
||||||
prev, value, instCallback, cid
|
override, value, instCallback, cid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
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
|
* Copies a property to the appropriate member prototype, depending on
|
||||||
* visibility, and assigns necessary metadata from keywords
|
* visibility, and assigns necessary metadata from keywords
|
||||||
|
|
|
@ -212,12 +212,25 @@ exports.prototype.validateMethod = function(
|
||||||
|
|
||||||
// disallow overriding non-virtual methods
|
// disallow overriding non-virtual methods
|
||||||
if ( keywords[ 'override' ] && !( prev_keywords[ 'virtual' ] ) )
|
if ( keywords[ 'override' ] && !( prev_keywords[ 'virtual' ] ) )
|
||||||
|
{
|
||||||
|
if ( !( keywords[ 'abstract' ] ) )
|
||||||
{
|
{
|
||||||
throw TypeError(
|
throw TypeError(
|
||||||
"Cannot override non-virtual method '" + name + "'"
|
"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
|
// do not allow overriding concrete methods with abstract unless the
|
||||||
// abstract method is weak
|
// abstract method is weak
|
||||||
if ( keywords[ 'abstract' ]
|
if ( keywords[ 'abstract' ]
|
||||||
|
|
12
lib/Trait.js
12
lib/Trait.js
|
@ -175,8 +175,10 @@ function createConcrete( acls )
|
||||||
var dfn = {
|
var dfn = {
|
||||||
'protected ___$$trait$$': function() {},
|
'protected ___$$trait$$': function() {},
|
||||||
|
|
||||||
// protected member object
|
// protected member object (we define this as protected so that the
|
||||||
'private ___$$pmo$$': null,
|
// 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 )
|
__construct: function( pmo )
|
||||||
{
|
{
|
||||||
this.___$$pmo$$ = pmo;
|
this.___$$pmo$$ = pmo;
|
||||||
|
@ -393,7 +395,7 @@ function mixMethods( src, dest, vis, iname )
|
||||||
|
|
||||||
// if abstract, then we are expected to provide the implementation;
|
// if abstract, then we are expected to provide the implementation;
|
||||||
// otherwise, we proxy to the trait's 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
|
// copy the abstract definition (N.B. this does not copy the
|
||||||
// param names, since that is not [yet] important); the
|
// param names, since that is not [yet] important); the
|
||||||
|
@ -405,7 +407,8 @@ function mixMethods( src, dest, vis, iname )
|
||||||
{
|
{
|
||||||
var vk = keywords['virtual'],
|
var vk = keywords['virtual'],
|
||||||
virt = vk ? 'weak 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,
|
// if we have already set up a proxy for a field of this name,
|
||||||
// then multiple traits have defined the same concrete member
|
// then multiple traits have defined the same concrete member
|
||||||
|
@ -546,3 +549,4 @@ function createTctor( tc )
|
||||||
|
|
||||||
|
|
||||||
module.exports = Trait;
|
module.exports = Trait;
|
||||||
|
|
||||||
|
|
|
@ -305,7 +305,10 @@ exports.propParse = function( data, options )
|
||||||
name = parse_data.name || prop;
|
name = parse_data.name || prop;
|
||||||
keywords = parse_data.keywords || {};
|
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
|
// may not be set if assumeAbstract is given
|
||||||
keywords[ 'abstract' ] = true;
|
keywords[ 'abstract' ] = true;
|
||||||
|
|
|
@ -177,8 +177,39 @@ require( 'common' ).testCase(
|
||||||
* otherwise, override does not make sense, because I.M is clearly
|
* otherwise, override does not make sense, because I.M is clearly
|
||||||
* abstract and there is nothing to override.
|
* 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()
|
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.Sut = this.require( 'Trait' );
|
||||||
this.Class = this.require( 'class' );
|
this.Class = this.require( 'class' );
|
||||||
|
this.Interface = this.require( 'interface' );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,5 +77,47 @@ require( 'common' ).testCase(
|
||||||
|
|
||||||
this.assertOk( scalled );
|
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 idea behind supporting this functionality---which is unsued at
|
||||||
* the time of writing this test---is to allow eventual customization of
|
* the time of writing this test---is to allow eventual customization of
|
||||||
|
|
Loading…
Reference in New Issue