[#19] Implemented 'virtual' keyword
- Baby steps. 'override' keyword is not yet necessary. - Final not yet removedclosure/master
parent
24a04369ae
commit
8b83e85c43
|
@ -164,10 +164,13 @@ function validateMethod( keywords, prev_data, value, name )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// final methods cannot be overridden
|
// Can only override virtual methods. Abstract methods are considered to
|
||||||
if ( prev_keywords[ 'final' ] )
|
// 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
|
// do not allow overriding concrete methods with abstract
|
||||||
|
@ -391,10 +394,16 @@ function scanMembers( members, name, base )
|
||||||
{
|
{
|
||||||
if ( member = members[ visibility[ i ] ][ name ] )
|
if ( member = members[ visibility[ i ] ][ name ] )
|
||||||
{
|
{
|
||||||
return {
|
// We need to filter out base properties (such as
|
||||||
member: member,
|
// Object.prototype.toString()), but we still need to traverse the
|
||||||
visibility: ( ( fallback ) ? 0 : i ),
|
// prototype chain. As such, we cannot use hasOwnProperty().
|
||||||
};
|
if ( member !== Object.prototype[ name ] )
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
member: member,
|
||||||
|
visibility: ( ( fallback ) ? 0 : i ),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ var _keywords = {
|
||||||
'final': true,
|
'final': true,
|
||||||
'abstract': true,
|
'abstract': true,
|
||||||
'const': true,
|
'const': true,
|
||||||
|
'virtual': true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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.
|
* Just as Class contains an extend method, so should AbstractClass.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -175,7 +175,8 @@ var Type = Interface.extend( {
|
||||||
foo2: function() {},
|
foo2: function() {},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
concrete_inst = new ConcreteFoo();
|
concrete_inst = ConcreteFoo()
|
||||||
|
;
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
( concrete_inst.isInstanceOf( Type )
|
( concrete_inst.isInstanceOf( Type )
|
||||||
|
|
|
@ -34,13 +34,13 @@ var common = require( './common' ),
|
||||||
|
|
||||||
Foo = Class.extend(
|
Foo = Class.extend(
|
||||||
{
|
{
|
||||||
myMethod: function()
|
'virtual myMethod': function()
|
||||||
{
|
{
|
||||||
hitMethod = true;
|
hitMethod = true;
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
myMethod2: function( arg )
|
'virtual myMethod2': function( arg )
|
||||||
{
|
{
|
||||||
hitMethod2 = true;
|
hitMethod2 = true;
|
||||||
method2Arg = arg;
|
method2Arg = arg;
|
||||||
|
|
|
@ -47,7 +47,7 @@ var common = require( './common' ),
|
||||||
'private privf': privf,
|
'private privf': privf,
|
||||||
|
|
||||||
|
|
||||||
'public getProp': function( name )
|
'virtual public getProp': function( name )
|
||||||
{
|
{
|
||||||
// return property, allowing us to break encapsulation for
|
// return property, allowing us to break encapsulation for
|
||||||
// protected/private properties (for testing purposes)
|
// protected/private properties (for testing purposes)
|
||||||
|
@ -79,7 +79,7 @@ var common = require( './common' ),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
'public getSelfOverride': function()
|
'virtual public getSelfOverride': function()
|
||||||
{
|
{
|
||||||
// override me
|
// override me
|
||||||
},
|
},
|
||||||
|
@ -516,7 +516,7 @@ var common = require( './common' ),
|
||||||
Class(
|
Class(
|
||||||
{
|
{
|
||||||
'protected foo': 'bar',
|
'protected foo': 'bar',
|
||||||
'protected baz': function() {},
|
'virtual protected baz': function() {},
|
||||||
} ).extend( {
|
} ).extend( {
|
||||||
'public foo': 'bar',
|
'public foo': 'bar',
|
||||||
'public baz': function() {},
|
'public baz': function() {},
|
||||||
|
@ -529,7 +529,7 @@ var common = require( './common' ),
|
||||||
Class(
|
Class(
|
||||||
{
|
{
|
||||||
'protected foo': 'bar',
|
'protected foo': 'bar',
|
||||||
'protected baz': function() {},
|
'virtual protected baz': function() {},
|
||||||
} ).extend( {
|
} ).extend( {
|
||||||
'protected foo': 'bar',
|
'protected foo': 'bar',
|
||||||
'protected baz': function() {},
|
'protected baz': function() {},
|
||||||
|
@ -621,7 +621,7 @@ var common = require( './common' ),
|
||||||
{
|
{
|
||||||
var val = 'foobar',
|
var val = 'foobar',
|
||||||
result = Class( {
|
result = Class( {
|
||||||
'protected foo': function()
|
'virtual protected foo': function()
|
||||||
{
|
{
|
||||||
return val;
|
return val;
|
||||||
},
|
},
|
||||||
|
@ -672,7 +672,7 @@ var common = require( './common' ),
|
||||||
// get the result of invoking overridden foo()
|
// get the result of invoking overridden foo()
|
||||||
var result = Class(
|
var result = Class(
|
||||||
{
|
{
|
||||||
'protected foo': function()
|
'virtual protected foo': function()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
@ -739,7 +739,7 @@ var common = require( './common' ),
|
||||||
var val = 'foobar',
|
var val = 'foobar',
|
||||||
result = Class(
|
result = Class(
|
||||||
{
|
{
|
||||||
'protected foo': function() {},
|
'virtual protected foo': function() {},
|
||||||
} ).extend(
|
} ).extend(
|
||||||
{
|
{
|
||||||
// provide concrete implementation
|
// provide concrete implementation
|
||||||
|
|
|
@ -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
|
* To ensure interfaces of subtypes remain compatible with that of their
|
||||||
* supertypes, the parameter lists must match and build upon each other.
|
* supertypes, the parameter lists must match and build upon each other.
|
||||||
|
@ -72,17 +188,17 @@ mb_common.assertCommon();
|
||||||
( function testMethodOverridesMustHaveEqualOrGreaterParameters()
|
( function testMethodOverridesMustHaveEqualOrGreaterParameters()
|
||||||
{
|
{
|
||||||
mb_common.value = function( one, two ) {};
|
mb_common.value = function( one, two ) {};
|
||||||
mb_common.buildMemberQuick();
|
mb_common.buildMemberQuick( { 'virtual': true } );
|
||||||
|
|
||||||
assert.doesNotThrow( function()
|
assert.doesNotThrow( function()
|
||||||
{
|
{
|
||||||
mb_common.buildMemberQuick( {}, true );
|
mb_common.buildMemberQuick( { 'virtual': true }, true );
|
||||||
}, TypeError, "Method can have equal number of parameters" );
|
}, TypeError, "Method can have equal number of parameters" );
|
||||||
|
|
||||||
assert.doesNotThrow( function()
|
assert.doesNotThrow( function()
|
||||||
{
|
{
|
||||||
mb_common.value = function( one, two, three ) {};
|
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" );
|
}, TypeError, "Method can have greater number of parameters" );
|
||||||
|
|
||||||
assert.throws( function()
|
assert.throws( function()
|
||||||
|
@ -112,7 +228,7 @@ mb_common.assertCommon();
|
||||||
orig_called = true;
|
orig_called = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
mb_common.buildMemberQuick();
|
mb_common.buildMemberQuick( { 'virtual': true } );
|
||||||
|
|
||||||
// override method
|
// override method
|
||||||
mb_common.value = function()
|
mb_common.value = function()
|
||||||
|
@ -169,6 +285,10 @@ mb_common.assertCommon();
|
||||||
super_called = true;
|
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
|
// override
|
||||||
builder.buildMethod( members, {}, 'foo', newfunc, {}, instCallback );
|
builder.buildMethod( members, {}, 'foo', newfunc, {}, instCallback );
|
||||||
|
|
||||||
|
@ -236,7 +356,7 @@ mb_common.assertCommon();
|
||||||
return instance;
|
return instance;
|
||||||
},
|
},
|
||||||
|
|
||||||
members = { 'public': {}, 'protected': {}, 'private': {} }
|
members = builder.initMembers()
|
||||||
;
|
;
|
||||||
|
|
||||||
// set instance values
|
// set instance values
|
||||||
|
@ -249,7 +369,7 @@ mb_common.assertCommon();
|
||||||
exports.meta,
|
exports.meta,
|
||||||
'func',
|
'func',
|
||||||
func,
|
func,
|
||||||
[ 'public' ],
|
{ 'virtual': true },
|
||||||
instCallback
|
instCallback
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -265,7 +385,7 @@ mb_common.assertCommon();
|
||||||
exports.meta,
|
exports.meta,
|
||||||
'func',
|
'func',
|
||||||
func2,
|
func2,
|
||||||
[ 'public' ],
|
{},
|
||||||
instCallback
|
instCallback
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue