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' ),
|
var util = require( './util' ),
|
||||||
ClassBuilder = require( './ClassBuilder' ),
|
ClassBuilder = require( './ClassBuilder' ),
|
||||||
|
Interface = require( './interface' ),
|
||||||
|
|
||||||
warn = require( './warn' ),
|
warn = require( './warn' ),
|
||||||
Warning = warn.Warning,
|
Warning = warn.Warning,
|
||||||
|
@ -472,6 +473,11 @@ function createImplement( base, ifaces, cname )
|
||||||
* be explicit: in this case, any instantiation attempts will result in an
|
* be explicit: in this case, any instantiation attempts will result in an
|
||||||
* exception being thrown.
|
* 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
|
* @param {function()} basef returns base from which to lazily
|
||||||
* extend
|
* extend
|
||||||
* @param {Array.<Function>} traits traits to mix in
|
* @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
|
// invoking the partially applied class will immediately complete its
|
||||||
// definition and instantiate it with the provided constructor arguments
|
// definition and instantiate it with the provided constructor arguments
|
||||||
var partial = function()
|
var partial = function()
|
||||||
|
{
|
||||||
|
return partialClass()
|
||||||
|
.apply( null, arguments );
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var partialClass = function()
|
||||||
{
|
{
|
||||||
// this argument will be set only in the case where an existing
|
// this argument will be set only in the case where an existing
|
||||||
// (non-base) class is extended, meaning that an explict Class or
|
// (non-base) class is extended, meaning that an explict Class or
|
||||||
|
@ -498,8 +511,7 @@ function createUse( basef, traits, nonbase )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return createMixedClass( basef(), traits )
|
return createMixedClass( basef(), traits );
|
||||||
.apply( null, arguments );
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -545,6 +557,20 @@ function createUse( basef, traits, nonbase )
|
||||||
return partial.extend( {} );
|
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;
|
return partial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Tests abstract trait definition and use
|
* 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.
|
* This file is part of GNU ease.js.
|
||||||
*
|
*
|
||||||
|
@ -360,4 +360,70 @@ require( 'common' ).testCase(
|
||||||
_self.Class.use( Ta ).extend();
|
_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