1
0
Fork 0

Abstract mixin initial implementation

master
Mike Gerwitz 2015-05-24 00:29:55 -04:00
parent 5b6a0c0bb5
commit 96c5a702ce
2 changed files with 95 additions and 3 deletions

View File

@ -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;
}

View File

@ -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?'
);
},
} );