1
0
Fork 0

[#19] Implemented 'virtual' keyword

- Baby steps. 'override' keyword is not yet necessary.
- Final not yet removed
closure/master
Mike Gerwitz 2011-06-08 01:11:53 -04:00
parent 24a04369ae
commit 8b83e85c43
7 changed files with 177 additions and 24 deletions

View File

@ -164,10 +164,13 @@ function validateMethod( keywords, prev_data, value, name )
);
}
// final methods cannot be overridden
if ( prev_keywords[ 'final' ] )
// Can only override virtual methods. Abstract methods are considered to
// be virtual.
if ( !( prev_keywords[ 'virtual' ] || prev_keywords[ 'abstract' ] ) )
{
throw TypeError( "Cannot override final method '" + name + "'" );
throw TypeError(
"Cannot override non-virtual method '" + name + "'"
);
}
// do not allow overriding concrete methods with abstract
@ -391,10 +394,16 @@ function scanMembers( members, name, base )
{
if ( member = members[ visibility[ i ] ][ name ] )
{
return {
member: member,
visibility: ( ( fallback ) ? 0 : i ),
};
// We need to filter out base properties (such as
// Object.prototype.toString()), but we still need to traverse the
// prototype chain. As such, we cannot use hasOwnProperty().
if ( member !== Object.prototype[ name ] )
{
return {
member: member,
visibility: ( ( fallback ) ? 0 : i ),
};
}
}
}

View File

@ -34,6 +34,7 @@ var _keywords = {
'final': true,
'abstract': true,
'const': true,
'virtual': true,
};

View File

@ -106,6 +106,28 @@ var common = require( './common' ),
} )();
/**
* Abstract methods should remain virtual until they are overridden. That is, if
* a subtype doesn't provide a concrete implementation, it should still be
* considered virtual.
*/
( function testAbstractMethodsCanBeOverriddenBySubSubTypes()
{
var AbstractFoo = AbstractClass( 'Foo',
{
'abstract foo': [],
} ),
SubAbstractFoo = AbstractClass.extend( AbstractFoo, {} ),
ConcreteFoo = Class.extend( SubAbstractFoo,
{
'foo': function() {},
} )
;
} )();
/**
* Just as Class contains an extend method, so should AbstractClass.
*/

View File

@ -175,7 +175,8 @@ var Type = Interface.extend( {
foo2: function() {},
}),
concrete_inst = new ConcreteFoo();
concrete_inst = ConcreteFoo()
;
assert.ok(
( concrete_inst.isInstanceOf( Type )

View File

@ -34,13 +34,13 @@ var common = require( './common' ),
Foo = Class.extend(
{
myMethod: function()
'virtual myMethod': function()
{
hitMethod = true;
return this;
},
myMethod2: function( arg )
'virtual myMethod2': function( arg )
{
hitMethod2 = true;
method2Arg = arg;

View File

@ -47,7 +47,7 @@ var common = require( './common' ),
'private privf': privf,
'public getProp': function( name )
'virtual public getProp': function( name )
{
// return property, allowing us to break encapsulation for
// protected/private properties (for testing purposes)
@ -79,7 +79,7 @@ var common = require( './common' ),
},
'public getSelfOverride': function()
'virtual public getSelfOverride': function()
{
// override me
},
@ -516,7 +516,7 @@ var common = require( './common' ),
Class(
{
'protected foo': 'bar',
'protected baz': function() {},
'virtual protected baz': function() {},
} ).extend( {
'public foo': 'bar',
'public baz': function() {},
@ -529,7 +529,7 @@ var common = require( './common' ),
Class(
{
'protected foo': 'bar',
'protected baz': function() {},
'virtual protected baz': function() {},
} ).extend( {
'protected foo': 'bar',
'protected baz': function() {},
@ -621,7 +621,7 @@ var common = require( './common' ),
{
var val = 'foobar',
result = Class( {
'protected foo': function()
'virtual protected foo': function()
{
return val;
},
@ -672,7 +672,7 @@ var common = require( './common' ),
// get the result of invoking overridden foo()
var result = Class(
{
'protected foo': function()
'virtual protected foo': function()
{
return false;
},
@ -739,7 +739,7 @@ var common = require( './common' ),
var val = 'foobar',
result = Class(
{
'protected foo': function() {},
'virtual protected foo': function() {},
} ).extend(
{
// provide concrete implementation

View File

@ -65,6 +65,122 @@ mb_common.assertCommon();
} )();
/**
* Unlike Java, PHP, Python and similar languages, methods in ease.js are *not*
* virtual by default. In order to make them override-able, the 'virtual'
* keyword must be specified for that method in the supertype.
*
* Therefore, let's ensure that non-virtual methods cannot be overridden.
*/
( function testCannotOverrideNonVirtualMethod()
{
mb_common.value = function() {};
mb_common.buildMemberQuick();
try
{
// attempt to override (should throw exception; non-virtual)
mb_common.buildMemberQuick( {}, true );
// should not get to this point
assert.fail( "Should not be able to override non-virtual method" );
}
catch ( e )
{
// ensure we have the correct error
assert.ok(
e.message.search( 'virtual' ) !== -1,
"Error message for non-virtual override should mention virtual"
);
assert.ok(
e.message.search( mb_common.name ) !== -1,
"Method name should be provided in non-virtual error message"
);
}
} )();
/**
* Working off of what was said in the test directly above, we *should* be able
* to override virtual methods.
*/
( function testCanOverrideVirtualMethods()
{
// build a virtual method
mb_common.value = function() {};
mb_common.buildMemberQuick( { 'virtual': true } );
// attempt to override it
assert.doesNotThrow( function()
{
mb_common.buildMemberQuick( {}, true );
}, Error, "Should be able to override virtual methods" );
} )();
/**
* Unlike languages like C++, ease.js does not automatically mark overridden
* methods as virtual. C# and some other languages offer a 'seal' keyword or
* similar in order to make overridden methods non-virtual. In that sense,
* ease.js will "seal" overrides by default.
*/
( function testOverriddenMethodsAreNotVirtualByDefault()
{
// build a virtual method
mb_common.value = function() {};
mb_common.buildMemberQuick( { 'virtual': true } );
// override it (non-virtual)
mb_common.buildMemberQuick( {}, true );
// attempt to override again (should fail)
assert.throws( function()
{
mb_common.buildMemberQuick( {}, true );
}, TypeError, "Overrides are not declared as virtual by default" );
} )();
/**
* Given the test directly above, we can therefore assume that it should be
* permitted to declare overridden methods as virtual.
*/
( function testCanDeclareOverridesAsVirtual()
{
// build a virtual method
mb_common.value = function() {};
mb_common.buildMemberQuick( { 'virtual': true } );
// override it (virtual)
mb_common.buildMemberQuick( { 'virtual': true }, true );
// attempt to override again
assert.doesNotThrow( function()
{
mb_common.buildMemberQuick( {}, true );
}, Error, "Can override an override if declared virtual" );
} )();
/**
* Abstract members exist to be overridden. As such, they should be considered
* virtual.
*/
( function testAbstractMethodsAreConsideredVirtual()
{
// build abstract method
mb_common.value = function() {};
mb_common.buildMemberQuick( { 'abstract': true } );
// we should be able to override it
assert.doesNotThrow( function()
{
mb_common.buildMemberQuick( {}, true );
}, Error, "Can overrde abstract methods" );
} )();
/**
* To ensure interfaces of subtypes remain compatible with that of their
* supertypes, the parameter lists must match and build upon each other.
@ -72,17 +188,17 @@ mb_common.assertCommon();
( function testMethodOverridesMustHaveEqualOrGreaterParameters()
{
mb_common.value = function( one, two ) {};
mb_common.buildMemberQuick();
mb_common.buildMemberQuick( { 'virtual': true } );
assert.doesNotThrow( function()
{
mb_common.buildMemberQuick( {}, true );
mb_common.buildMemberQuick( { 'virtual': true }, true );
}, TypeError, "Method can have equal number of parameters" );
assert.doesNotThrow( function()
{
mb_common.value = function( one, two, three ) {};
mb_common.buildMemberQuick( {}, true );
mb_common.buildMemberQuick( { 'virtual': true }, true );
}, TypeError, "Method can have greater number of parameters" );
assert.throws( function()
@ -112,7 +228,7 @@ mb_common.assertCommon();
orig_called = true;
};
mb_common.buildMemberQuick();
mb_common.buildMemberQuick( { 'virtual': true } );
// override method
mb_common.value = function()
@ -169,6 +285,10 @@ mb_common.assertCommon();
super_called = true;
};
// XXX: Bad idea. Set the keyword in another manner. This is likely to break
// in the future.
members['public'].foo.___$$keywords$$ = { 'virtual': true };
// override
builder.buildMethod( members, {}, 'foo', newfunc, {}, instCallback );
@ -236,7 +356,7 @@ mb_common.assertCommon();
return instance;
},
members = { 'public': {}, 'protected': {}, 'private': {} }
members = builder.initMembers()
;
// set instance values
@ -249,7 +369,7 @@ mb_common.assertCommon();
exports.meta,
'func',
func,
[ 'public' ],
{ 'virtual': true },
instCallback
);
@ -265,7 +385,7 @@ mb_common.assertCommon();
exports.meta,
'func',
func2,
[ 'public' ],
{},
instCallback
);