Added support for weak abstract methods
This adds the `weak' keyword and permits abstract method definitions to appear in the same definition object as the concrete implementation. This should never be used with hand-written code---it is intended for code generators (e.g. traits) that do not know if a concrete implementation will be provided, and would waste cycles duplicating the property parsing that ease.js will already be doing. It also allows for more concise code generator code.perfodd
parent
18ac37c871
commit
548c38503f
|
@ -525,11 +525,21 @@ exports.prototype.buildMembers = function buildMembers(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_self._memberBuilder.buildMethod(
|
var used = _self._memberBuilder.buildMethod(
|
||||||
dest, null, name, func, keywords, instLookup,
|
dest, null, name, func, keywords, instLookup,
|
||||||
class_id, base
|
class_id, base
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// do nothing more if we didn't end up using this definition
|
||||||
|
// (this may be the case, for example, with weak members)
|
||||||
|
if ( !used )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// note the concrete method check; this ensures that weak
|
||||||
|
// abstract methods will not count if a concrete method of the
|
||||||
|
// smae name has already been seen
|
||||||
if ( is_abstract )
|
if ( is_abstract )
|
||||||
{
|
{
|
||||||
abstract_methods[ name ] = true;
|
abstract_methods[ name ] = true;
|
||||||
|
|
|
@ -140,7 +140,6 @@ exports.buildMethod = function(
|
||||||
}
|
}
|
||||||
else if ( prev )
|
else if ( prev )
|
||||||
{
|
{
|
||||||
|
|
||||||
if ( keywords[ 'override' ] || prev_keywords[ 'abstract' ] )
|
if ( keywords[ 'override' ] || prev_keywords[ 'abstract' ] )
|
||||||
{
|
{
|
||||||
// override the method
|
// override the method
|
||||||
|
@ -148,6 +147,12 @@ exports.buildMethod = function(
|
||||||
prev, value, instCallback, cid
|
prev, value, instCallback, cid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
else if ( keywords.weak && keywords[ 'abstract' ] )
|
||||||
|
{
|
||||||
|
// another member of the same name has been found; discard the
|
||||||
|
// weak declaration
|
||||||
|
return false;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// by default, perform method hiding, even if the keyword was not
|
// by default, perform method hiding, even if the keyword was not
|
||||||
|
@ -170,6 +175,7 @@ exports.buildMethod = function(
|
||||||
|
|
||||||
// store keywords for later reference (needed for pre-ES5 fallback)
|
// store keywords for later reference (needed for pre-ES5 fallback)
|
||||||
dest[ name ].___$$keywords$$ = keywords;
|
dest[ name ].___$$keywords$$ = keywords;
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -138,8 +138,12 @@ exports.prototype.validateMethod = function(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not allow overriding concrete methods with abstract
|
// do not allow overriding concrete methods with abstract unless the
|
||||||
if ( keywords[ 'abstract' ] && !( prev_keywords[ 'abstract' ] ) )
|
// abstract method is weak
|
||||||
|
if ( keywords[ 'abstract' ]
|
||||||
|
&& !( keywords.weak )
|
||||||
|
&& !( prev_keywords[ 'abstract' ] )
|
||||||
|
)
|
||||||
{
|
{
|
||||||
throw TypeError(
|
throw TypeError(
|
||||||
"Cannot override concrete method '" + name + "' with " +
|
"Cannot override concrete method '" + name + "' with " +
|
||||||
|
@ -147,9 +151,19 @@ exports.prototype.validateMethod = function(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lenprev = prev,
|
||||||
|
lennow = value;
|
||||||
|
if ( keywords.weak && !( prev_keywords[ 'abstract' ] ) )
|
||||||
|
{
|
||||||
|
// weak abstract declaration found after its concrete
|
||||||
|
// definition; check in reverse order
|
||||||
|
lenprev = value;
|
||||||
|
lennow = prev;
|
||||||
|
}
|
||||||
|
|
||||||
// ensure parameter list is at least the length of its supertype
|
// ensure parameter list is at least the length of its supertype
|
||||||
if ( ( value.__length || value.length )
|
if ( ( lennow.__length || lennow.length )
|
||||||
< ( prev.__length || prev.length )
|
< ( lenprev.__length || lenprev.length )
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
throw TypeError(
|
throw TypeError(
|
||||||
|
@ -168,10 +182,13 @@ exports.prototype.validateMethod = function(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disallow overriding method without override keyword (unless parent
|
// Disallow overriding method without override keyword (unless
|
||||||
// method is abstract). In the future, this will provide a warning to
|
// parent method is abstract). In the future, this will provide a
|
||||||
// default to method hiding.
|
// warning to default to method hiding. Note the check for a
|
||||||
if ( !( keywords[ 'override' ] || prev_keywords[ 'abstract' ] ) )
|
if ( !( keywords[ 'override' ]
|
||||||
|
|| prev_keywords[ 'abstract' ]
|
||||||
|
|| ( keywords[ 'abstract' ] && keywords.weak )
|
||||||
|
) )
|
||||||
{
|
{
|
||||||
throw TypeError(
|
throw TypeError(
|
||||||
"Attempting to override method '" + name +
|
"Attempting to override method '" + name +
|
||||||
|
|
|
@ -61,19 +61,31 @@ exports.extend = function()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixes in a trait
|
||||||
|
*
|
||||||
|
* @return {Object} staged abstract class
|
||||||
|
*/
|
||||||
|
exports.use = function()
|
||||||
|
{
|
||||||
|
return abstractOverride(
|
||||||
|
Class.use.apply( this, arguments )
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an abstract class implementing the given members
|
* Creates an abstract class implementing the given members
|
||||||
*
|
*
|
||||||
* Simply wraps the class module's implement() method.
|
* Simply wraps the class module's implement() method.
|
||||||
*
|
*
|
||||||
* @return {Object} abstract class
|
* @return {Object} staged abstract class
|
||||||
*/
|
*/
|
||||||
exports.implement = function()
|
exports.implement = function()
|
||||||
{
|
{
|
||||||
var impl = Class.implement.apply( this, arguments );
|
return abstractOverride(
|
||||||
|
Class.implement.apply( this, arguments )
|
||||||
abstractOverride( impl );
|
);
|
||||||
return impl;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,8 +124,8 @@ function abstractOverride( obj )
|
||||||
var extend = obj.extend,
|
var extend = obj.extend,
|
||||||
impl = obj.implement;
|
impl = obj.implement;
|
||||||
|
|
||||||
// wrap and apply the abstract flag, only if the method is defined (it may
|
// wrap and apply the abstract flag, only if the method is defined (it
|
||||||
// not be under all circumstances, e.g. after an implement())
|
// may not be under all circumstances, e.g. after an implement())
|
||||||
impl && ( obj.implement = function()
|
impl && ( obj.implement = function()
|
||||||
{
|
{
|
||||||
return abstractOverride( impl.apply( this, arguments ) );
|
return abstractOverride( impl.apply( this, arguments ) );
|
||||||
|
|
|
@ -52,6 +52,10 @@ require( 'common' ).testCase(
|
||||||
};
|
};
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// simply intended to execute test two two perspectives
|
||||||
|
this.weakab = [
|
||||||
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,9 +109,9 @@ require( 'common' ).testCase(
|
||||||
_self.testArgs( arguments, name, value, keywords );
|
_self.testArgs( arguments, name, value, keywords );
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sut.buildMethod(
|
this.assertOk( this.sut.buildMethod(
|
||||||
this.members, {}, name, value, keywords, function() {}, 1, {}
|
this.members, {}, name, value, keywords, function() {}, 1, {}
|
||||||
);
|
) );
|
||||||
|
|
||||||
this.assertEqual( true, called, 'validateMethod() was not called' );
|
this.assertEqual( true, called, 'validateMethod() was not called' );
|
||||||
},
|
},
|
||||||
|
@ -133,9 +137,9 @@ require( 'common' ).testCase(
|
||||||
_self.testArgs( arguments, name, value, keywords );
|
_self.testArgs( arguments, name, value, keywords );
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sut.buildMethod(
|
this.assertOk( this.sut.buildMethod(
|
||||||
this.members, {}, name, value, keywords, function() {}, 1, {}
|
this.members, {}, name, value, keywords, function() {}, 1, {}
|
||||||
);
|
) );
|
||||||
|
|
||||||
this.assertEqual( true, called, 'validateMethod() was not called' );
|
this.assertEqual( true, called, 'validateMethod() was not called' );
|
||||||
},
|
},
|
||||||
|
@ -159,9 +163,9 @@ require( 'common' ).testCase(
|
||||||
;
|
;
|
||||||
|
|
||||||
// build the proxy
|
// build the proxy
|
||||||
this.sut.buildMethod(
|
this.assertOk( this.sut.buildMethod(
|
||||||
this.members, {}, name, value, keywords, instCallback, cid, {}
|
this.members, {}, name, value, keywords, instCallback, cid, {}
|
||||||
);
|
) );
|
||||||
|
|
||||||
this.assertNotEqual( null, this.proxyFactoryCall,
|
this.assertNotEqual( null, this.proxyFactoryCall,
|
||||||
"Proxy factory should be used when `proxy' keyword is provided"
|
"Proxy factory should be used when `proxy' keyword is provided"
|
||||||
|
@ -181,4 +185,50 @@ require( 'common' ).testCase(
|
||||||
"Generated proxy method should be properly assigned to members"
|
"Generated proxy method should be properly assigned to members"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A weak abstract method may exist in a situation where a code
|
||||||
|
* generator is not certain whether a concrete implementation may be
|
||||||
|
* provided. In this case, we would not want to actually create an
|
||||||
|
* abstract method if a concrete one already exists.
|
||||||
|
*/
|
||||||
|
'Weak abstract methods are not processed if concrete is available':
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
called = false,
|
||||||
|
|
||||||
|
cid = 1,
|
||||||
|
name = 'foo',
|
||||||
|
cval = function() { called = true; },
|
||||||
|
aval = [],
|
||||||
|
|
||||||
|
ckeywords = {},
|
||||||
|
akeywords = { weak: true, 'abstract': true, },
|
||||||
|
|
||||||
|
instCallback = function() {}
|
||||||
|
;
|
||||||
|
|
||||||
|
// first define abstract
|
||||||
|
this.assertOk( this.sut.buildMethod(
|
||||||
|
this.members, {}, name, aval, akeywords, instCallback, cid, {}
|
||||||
|
) );
|
||||||
|
|
||||||
|
// concrete should take precedence
|
||||||
|
this.assertOk( this.sut.buildMethod(
|
||||||
|
this.members, {}, name, cval, ckeywords, instCallback, cid, {}
|
||||||
|
) );
|
||||||
|
|
||||||
|
this.members[ 'public' ].foo();
|
||||||
|
this.assertOk( called, "Concrete method did not take precedence" );
|
||||||
|
|
||||||
|
// now try abstract again to ensure this works from both directions
|
||||||
|
this.assertOk( this.sut.buildMethod(
|
||||||
|
this.members, {}, name, aval, akeywords, instCallback, cid, {}
|
||||||
|
) === false );
|
||||||
|
|
||||||
|
this.members[ 'public' ].foo();
|
||||||
|
this.assertOk( called, "Concrete method unkept" );
|
||||||
|
},
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -27,6 +27,7 @@ require( 'common' ).testCase(
|
||||||
caseSetUp: function()
|
caseSetUp: function()
|
||||||
{
|
{
|
||||||
var _self = this;
|
var _self = this;
|
||||||
|
this.util = this.require( 'util' );
|
||||||
|
|
||||||
this.quickKeywordMethodTest = function( keywords, identifier, prev )
|
this.quickKeywordMethodTest = function( keywords, identifier, prev )
|
||||||
{
|
{
|
||||||
|
@ -208,6 +209,21 @@ require( 'common' ).testCase(
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrary to the above test, an abstract method may appear after its
|
||||||
|
* concrete implementation if the `weak' keyword is provided; this
|
||||||
|
* exists to allow code generation tools to fall back to abstract
|
||||||
|
* without having to invoke the property parser directly, complicating
|
||||||
|
* their logic and duplicating work that ease.js will already do.
|
||||||
|
*/
|
||||||
|
'Concrete method may appear with weak abstract method': function()
|
||||||
|
{
|
||||||
|
this.quickKeywordMethodTest(
|
||||||
|
[ 'weak', 'abstract' ], null, []
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parameter list is part of the class interface. Changing the length
|
* The parameter list is part of the class interface. Changing the length
|
||||||
* will make the interface incompatible with that of its parent and make
|
* will make the interface incompatible with that of its parent and make
|
||||||
|
@ -267,6 +283,46 @@ require( 'common' ).testCase(
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same concept as the above test, but ensure that the logic for weak
|
||||||
|
* abstract members does not skip the valiation. Furthermore, if a weak
|
||||||
|
* abstract member is found *after* the concrete definition, the same
|
||||||
|
* restrictions should apply retroacively.
|
||||||
|
*/
|
||||||
|
'Weak abstract overrides must meet compatibility requirements':
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
var _self = this,
|
||||||
|
name = 'foo',
|
||||||
|
amethod = _self.util.createAbstractMethod( [ 'one' ] );
|
||||||
|
|
||||||
|
|
||||||
|
// abstract appears before
|
||||||
|
this.quickFailureTest( name, 'compatible', function()
|
||||||
|
{
|
||||||
|
_self.sut.validateMethod(
|
||||||
|
name,
|
||||||
|
function() {},
|
||||||
|
{},
|
||||||
|
{ member: amethod },
|
||||||
|
{ 'weak': true, 'abstract': true }
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
// abstract appears after
|
||||||
|
this.quickFailureTest( name, 'compatible', function()
|
||||||
|
{
|
||||||
|
_self.sut.validateMethod(
|
||||||
|
name,
|
||||||
|
amethod,
|
||||||
|
{ 'weak': true, 'abstract': true },
|
||||||
|
{ member: function() {} },
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* One should not be able to, for example, declare a private method it had
|
* One should not be able to, for example, declare a private method it had
|
||||||
* previously been declared protected, or declare it as protected if it has
|
* previously been declared protected, or declare it as protected if it has
|
||||||
|
|
|
@ -68,7 +68,7 @@ exports.quickFailureTest = function( name, identifier, action )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_self.fail( "Expected failure" );
|
_self.fail( false, true, "Expected failure" );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ exports.quickKeywordTest = function(
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.assertDoesNotThrow( testfunc, Error );
|
this.assertDoesNotThrow( testfunc );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue