Abstract mixin initial implementation
parent
5b6a0c0bb5
commit
96c5a702ce
30
lib/class.js
30
lib/class.js
|
@ -34,6 +34,7 @@ var _console = ( typeof console !== 'undefined' ) ? console : undefined;
|
|||
|
||||
var util = require( './util' ),
|
||||
ClassBuilder = require( './ClassBuilder' ),
|
||||
Interface = require( './interface' ),
|
||||
|
||||
warn = require( './warn' ),
|
||||
Warning = warn.Warning,
|
||||
|
@ -472,6 +473,11 @@ function createImplement( base, ifaces, cname )
|
|||
* be explicit: in this case, any instantiation attempts will result in an
|
||||
* exception being thrown.
|
||||
*
|
||||
* This staging object may be used as a base for extending. Note, however,
|
||||
* that its metadata are unavailable at the time of definition---its
|
||||
* contents are marked as "lazy" and must be processed using the mixin's
|
||||
* eventual metadata.
|
||||
*
|
||||
* @param {function()} basef returns base from which to lazily
|
||||
* extend
|
||||
* @param {Array.<Function>} traits traits to mix in
|
||||
|
@ -486,6 +492,13 @@ function createUse( basef, traits, nonbase )
|
|||
// invoking the partially applied class will immediately complete its
|
||||
// definition and instantiate it with the provided constructor arguments
|
||||
var partial = function()
|
||||
{
|
||||
return partialClass()
|
||||
.apply( null, arguments );
|
||||
};
|
||||
|
||||
|
||||
var partialClass = function()
|
||||
{
|
||||
// this argument will be set only in the case where an existing
|
||||
// (non-base) class is extended, meaning that an explict Class or
|
||||
|
@ -498,8 +511,7 @@ function createUse( basef, traits, nonbase )
|
|||
);
|
||||
}
|
||||
|
||||
return createMixedClass( basef(), traits )
|
||||
.apply( null, arguments );
|
||||
return createMixedClass( basef(), traits );
|
||||
};
|
||||
|
||||
|
||||
|
@ -545,6 +557,20 @@ function createUse( basef, traits, nonbase )
|
|||
return partial.extend( {} );
|
||||
};
|
||||
|
||||
partial.asPrototype = function()
|
||||
{
|
||||
return partialClass().asPrototype();
|
||||
};
|
||||
|
||||
partial.__isInstanceOf = Interface.isInstanceOf;
|
||||
|
||||
// allow the system to recognize this object as a viable base for
|
||||
// extending, but mark the metadata as lazy: since we defer all
|
||||
// processing for mixins, we cannot yet know all metadata
|
||||
// TODO: `_lazy' is a kluge
|
||||
ClassBuilder.masquerade( partial );
|
||||
ClassBuilder.getMeta( partial )._lazy = true;
|
||||
|
||||
return partial;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Tests abstract trait definition and use
|
||||
*
|
||||
* Copyright (C) 2014 Free Software Foundation, Inc.
|
||||
* Copyright (C) 2015 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GNU ease.js.
|
||||
*
|
||||
|
@ -360,4 +360,70 @@ require( 'common' ).testCase(
|
|||
_self.Class.use( Ta ).extend();
|
||||
} );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Before traits, the only way to make an abstract class concrete, or
|
||||
* vice versa, was by extending. Now, however, a mixing in a trait can
|
||||
* introduce abstract or concrete methods. This poses a problem, since
|
||||
* the syntax for providing self-documenting AbstractClass definitions
|
||||
* no longer works: invoking `AbstractClass.use' produces different
|
||||
* results than invoking `SomeAbstractClass.use', with the goal of
|
||||
* extending it.
|
||||
*
|
||||
* Consider this issue: we wish to mix some trait T into abstract class
|
||||
* AC. Trait T does not provide a concrete implementation of the
|
||||
* abstract methods in AT, and so the resulting class after the final
|
||||
* `#extend' call would be abstract.
|
||||
*
|
||||
* We have no choice but to allow extending the intermediate object
|
||||
* produced by a class's `#use' method; otherwise, any call to `#extend'
|
||||
* on the intermediate object would result in an error, because the
|
||||
* class would still have abstract members, but has not been declared to
|
||||
* be abstract. Handling abstract classes in this manner would be
|
||||
* consistent with all other scenarios, and would be transparent: why
|
||||
* should the user care that there is some odd intermediate object being
|
||||
* used rather than an actual class?
|
||||
*/
|
||||
'Abstract classes can be derived from intermediates': function()
|
||||
{
|
||||
var chk = [{}];
|
||||
|
||||
var AC = this.AbstractClass( { 'abstract foo': [] } ),
|
||||
T = this.Sut( { moo: function() { return chk; } } );
|
||||
|
||||
// mix trait into an abstract class
|
||||
var M = this.AbstractClass.extend(
|
||||
AC.use( T ),
|
||||
{}
|
||||
);
|
||||
|
||||
this.assertOk( this.Class.isClass( M ) );
|
||||
this.assertOk( M.isAbstract() );
|
||||
|
||||
var inst = M.extend( { foo: function() {} } )();
|
||||
|
||||
// we should not have lost the original abstract class
|
||||
this.assertOk(
|
||||
this.Class.isA( AC, inst )
|
||||
);
|
||||
|
||||
// not strictly necessary; comfort/sanity check: if this succeeds
|
||||
// but the next fails, then there's a problem marking the
|
||||
// implemented types
|
||||
this.assertStrictEqual(
|
||||
chk,
|
||||
inst.moo()
|
||||
);
|
||||
|
||||
// the trait should have been applied (see above note if this
|
||||
// fails); if this does fail, note that, without
|
||||
// AbstractClass.extend, we have (correctly):
|
||||
// isA( T, AC.use( T ).extend( ... )() )
|
||||
this.assertOk(
|
||||
this.Class.isA( T, inst ),
|
||||
'Instance is not recognized as having mixed in type T, but ' +
|
||||
'incorporates its definition; metadata bug?'
|
||||
);
|
||||
},
|
||||
} );
|
||||
|
|
Loading…
Reference in New Issue