diff --git a/lib/ClassBuilder.js b/lib/ClassBuilder.js index a9902d7..807ee55 100644 --- a/lib/ClassBuilder.js +++ b/lib/ClassBuilder.js @@ -298,6 +298,7 @@ exports.prototype.build = function extend( _, __ ) base = args.pop() || exports.ClassBase, prototype = this._getBase( base ), cname = '', + autoa = false, prop_init = this._memberBuilder.initMembers(), members = this._memberBuilder.initMembers( prototype ), @@ -331,6 +332,12 @@ exports.prototype.build = function extend( _, __ ) delete props.__name; } + // gobble up auto-abstract flag if present + if ( ( autoa = props.___$$auto$abstract$$ ) !== undefined ) + { + delete props.___$$auto$abstract$$; + } + // IE has problems with toString() if ( enum_bug ) { @@ -401,8 +408,7 @@ exports.prototype.build = function extend( _, __ ) new_class.___$$sinit$$ = staticInit; attachFlags( new_class, props ); - - validateAbstract( new_class, cname, abstract_methods ); + validateAbstract( new_class, cname, abstract_methods, autoa ); // We reduce the overall cost of this definition by defining it on the // prototype rather than during instantiation. While this does increase the @@ -588,13 +594,20 @@ exports.prototype.buildMembers = function buildMembers( /** * Validates abstract class requirements * + * We permit an `auto' flag for internal use only that will cause the + * abstract flag to be automatically set if the class should be marked as + * abstract, instead of throwing an error; this should be used sparingly and + * never exposed via a public API (for explicit use), as it goes against the + * self-documentation philosophy. + * * @param {function()} ctor class * @param {string} cname class name * @param {{__length}} abstract_methods object containing abstract methods + * @param {boolean} auto automatically flag as abstract * * @return {undefined} */ -function validateAbstract( ctor, cname, abstract_methods ) +function validateAbstract( ctor, cname, abstract_methods, auto ) { if ( ctor.___$$abstract$$ ) { @@ -606,15 +619,18 @@ function validateAbstract( ctor, cname, abstract_methods ) ); } } - else + else if ( abstract_methods.__length > 0 ) { - if ( abstract_methods.__length > 0 ) + if ( auto ) { - throw TypeError( - "Class " + ( cname || "(anonymous)" ) + " contains abstract " + - "members and must therefore be declared abstract" - ); + ctor.___$$abstract$$ = true; + return; } + + throw TypeError( + "Class " + ( cname || "(anonymous)" ) + " contains abstract " + + "members and must therefore be declared abstract" + ); } } diff --git a/lib/Trait.js b/lib/Trait.js index 0e5074b..a83f12f 100644 --- a/lib/Trait.js +++ b/lib/Trait.js @@ -109,9 +109,9 @@ Trait.extend = function( dfn ) } // invoked to trigger mixin - TraitType.__mixin = function( dfn ) + TraitType.__mixin = function( dfn, tc ) { - mixin( TraitType, dfn ); + mixin( TraitType, dfn, tc ); }; return TraitType; @@ -238,21 +238,25 @@ function createVirtProxy( acls, dfn ) /** * Mix trait into the given definition * - * The original object DFN is modified; it is not cloned. + * The original object DFN is modified; it is not cloned. TC should be + * initialized to an empty array; it is used to store context data for + * mixing in traits and will be encapsulated within a ctor closure (and thus + * will remain in memory). * * @param {Trait} trait trait to mix in * @param {Object} dfn definition object to merge into + * @param {Array} tc trait class context * * @return {Object} dfn */ -function mixin( trait, dfn ) +function mixin( trait, dfn, tc ) { // the abstract class hidden within the trait var acls = trait.__acls, methods = acls.___$$methods$$; // retrieve the private member name that will contain this trait object - var iname = addTraitInst( trait, dfn ); + var iname = addTraitInst( trait, dfn, tc ); mixMethods( methods['public'], dfn, 'public', iname ); mixMethods( methods['protected'], dfn, 'protected', iname ); @@ -334,13 +338,13 @@ function mixMethods( src, dest, vis, iname ) * * @param {Class} T trait * @param {Object} dfn definition object of class being mixed into + * @param {Array} tc trait class object * * @return {string} private member into which C instance shall be stored */ -function addTraitInst( T, dfn ) +function addTraitInst( T, dfn, tc ) { - var tc = ( dfn.___$$tc$$ = ( dfn.___$$tc$$ || [] ) ), - iname = '___$to$' + T.__acls.__cid; + var iname = '___$to$' + T.__acls.__cid; // the trait object array will contain two values: the destination field // and the trait to instantiate diff --git a/lib/class.js b/lib/class.js index 1096555..81f56e1 100644 --- a/lib/class.js +++ b/lib/class.js @@ -379,40 +379,67 @@ function createImplement( base, ifaces, cname ) function createUse( base, traits ) { + // invoking the partially applied class will immediately complete its + // definition and instantiate it with the provided constructor arguments var partial = function() { - return partial.extend( {} ).apply( null, arguments ); + return createMixedClass( base, traits ) + .apply( null, arguments ); }; + // otherwise, its definition is deferred until additional context is + // given during the extend operation partial.extend = function() { var args = Array.prototype.slice.call( arguments ), dfn = args.pop(), ext_base = args.pop(); - // "mix" each trait into the provided definition object - for ( var i = 0, n = traits.length; i < n; i++ ) - { - traits[ i ].__mixin( dfn ); - } - - var C = extend.call( null, ( base || ext_base ), dfn ), - meta = ClassBuilder.getMeta( C ); - - // add each trait to the list of implemented types so that the - // class is considered to be of type T in traits - for ( var i = 0, n = traits.length; i < n; i++ ) - { - meta.implemented.push( traits[ i ] ); - } - - return C; + // extend the mixed class, which ensures that all super references + // are properly resolved + return extend.call( null, + createMixedClass( ( base || ext_base ), traits ), + dfn + ); }; return partial; } +function createMixedClass( base, traits ) +{ + // generated definition for our [abstract] class that will mix in each + // of the provided traits; it will automatically be marked as abstract + // if needed + var dfn = { ___$$auto$abstract$$: true }; + + // this object is used as a class-specific context for storing trait + // data; it will be encapsulated within a ctor closure and will not be + // attached to any class + var tc = []; + + // "mix" each trait into the class definition object + for ( var i = 0, n = traits.length; i < n; i++ ) + { + traits[ i ].__mixin( dfn, tc ); + } + + // create the mixed class from the above generated definition + var C = extend.call( null, base, dfn ), + meta = ClassBuilder.getMeta( C ); + + // add each trait to the list of implemented types so that the + // class is considered to be of type T in traits + for ( var i = 0, n = traits.length; i < n; i++ ) + { + meta.implemented.push( traits[ i ] ); + } + + return C; +} + + /** * Mimics class inheritance *