1
0
Fork 0

Added support for final classes

- This commit was originally many. Unfortunately, certain Git objects became
  corrupt shortly after my 500th commit due to HDD issues. Due to the scope, I
  was unable to recover the set of commits I needed (after an hour of trying
  every method).
  - Fortunately, vim's swap files came to the rescue. Had I been able to
    properly shut down my PC, I would have been rather frustrated.
closure/master
Mike Gerwitz 2011-05-22 11:11:18 -04:00
parent 0ef016600c
commit 9690663d1c
5 changed files with 155 additions and 9 deletions

View File

@ -200,6 +200,15 @@ exports.build = function extend()
|| { __length: 0 }
;
// prevent extending final classes
if ( base.___$$final$$ === true )
{
throw Error(
"Cannot extend final class " +
( base.___$$meta$$.name || '(anonymous)' )
);
}
// grab the name, if one was provided
if ( cname = props.__name )
{
@ -252,6 +261,8 @@ exports.build = function extend()
new_class.___$$methods$$ = members;
new_class.___$$sinit$$ = staticInit;
attachFlags( new_class, props );
// We reduce the overall cost of this definition by defining it on the
// prototype rather than during instantiation. While this does increase the
// amount of time it takes to access the property through the prototype
@ -1032,3 +1043,21 @@ function attachId( ctor, id )
util.defineSecureProp( ctor.prototype, '__cid', id );
}
/**
* Sets class flags
*
* @param {Class} ctor class to flag
* @param {Object} props class properties
*
* @return {undefined}
*/
function attachFlags( ctor, props )
{
ctor.___$$final$$ = !!( props.___$$final$$ );
// The properties are no longer needed. Set to undefined rather than delete
// (v8 performance)
props.___$$final$$ = undefined;
}

41
lib/class_final.js 100644
View File

@ -0,0 +1,41 @@
/**
* Wrapper permitting the definition of final classes
*
* Copyright (C) 2010 Mike Gerwitz
*
* This file is part of ease.js.
*
* ease.js is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @author Mike Gerwitz
* @package core
*/
var Class = require( __dirname + '/class' );
module.exports = function()
{
// the last argument _should_ be the definition
var dfn = arguments[ arguments.length - 1 ];
if ( typeof dfn === 'object' )
{
// mark it as final
dfn.___$$final$$ = true;
}
// forward everything to Class
return Class.apply( this, arguments );
};

View File

@ -81,15 +81,26 @@ exports.buildMethod = function(
: {}
;
if ( keywords[ 'abstract' ] )
{
// do not permit private abstract methods (doesn't make sense, since
// they cannot be inherited/overridden)
if ( keywords[ 'abstract' ] && keywords[ 'private' ] )
if ( keywords[ 'private' ] )
{
throw TypeError(
"Method '" + name + "' cannot be both private and abstract"
);
}
// abstract final also does not make sense
if ( keywords[ 'final' ] )
{
throw TypeError(
"Method '" + name + "' cannot be both abstract and final"
);
}
}
// const doesn't make sense for methods; they're always immutable
if ( keywords[ 'const' ] )
{

View File

@ -24,7 +24,10 @@
var common = require( './common' ),
assert = require( 'assert' ),
builder = common.require( 'class_builder' )
builder = common.require( 'class_builder' ),
Class = common.require( 'class' )
FinalClass = common.require( 'class_final' )
;
@ -83,7 +86,7 @@ var common = require( './common' ),
{
assert.ok(
e.message.search( 'foo' ) !== -1,
"Final property error message contains name of method"
"Final property error message contains name of property"
);
return;
@ -92,3 +95,65 @@ var common = require( './common' ),
assert.fail( "Should not be able to use final keyword with properties" );
} )();
/**
* The 'abstract' keyword's very point is to state that no definition is
* provided and that a subtype must provide one. Therefore, declaring something
* 'abstract final' is rather contradictory and should not be permitted.
*/
( function testFinalyKeywordCannotBeUsedWithAbstract()
{
try
{
// should fail
builder.build( { 'abstract final foo': [] } );
}
catch ( e )
{
assert.ok(
e.message.search( 'foo' ) !== -1,
"Abstract final error message contains name of method"
);
return;
}
assert.fail( "Should not be able to use final keyword with abstract" );
} )();
/**
* Ensure that FinalClass properly forwards data to create a new Class.
*/
( function testFinalClassesAreValidClasses()
{
assert.ok( Class.isClass( FinalClass( {} ) ),
"Final classes should generate valid classes"
);
} )();
/**
* When a class is declared as final, it should prevent it from ever being
* extended. Ever.
*/
( function testFinalClassesCannotBeExtended()
{
try
{
// this should fail
FinalClass( 'Foo', {} ).extend( {} );
}
catch ( e )
{
assert.ok(
e.message.search( 'Foo' ) !== -1,
"Final class error message should contain name of class"
);
return;
}
assert.fail( "Should not be able to extend final classes" );
} )();

View File

@ -29,7 +29,7 @@ RMTRAIL="$PATH_TOOLS/rmtrail"
# order matters
CAT_MODULES="prop_parser util propobj member_builder class_builder"
CAT_MODULES="$CAT_MODULES class interface"
CAT_MODULES="$CAT_MODULES class class_final interface"
##
# Output template header