diff --git a/lib/MethodWrappers.js b/lib/MethodWrappers.js
index c568b43..7677d13 100644
--- a/lib/MethodWrappers.js
+++ b/lib/MethodWrappers.js
@@ -89,7 +89,7 @@ exports.standard = {
// not to keep an unnecessary reference to the keywords object
var is_static = keywords && keywords[ 'static' ];
- return function()
+ var ret = function()
{
var context = getInst( this, cid ) || this,
retval = undefined,
@@ -122,6 +122,13 @@ exports.standard = {
? this
: retval;
};
+
+ // ensures that proxies can be used to provide concrete
+ // implementations of abstract methods with param requirements (we
+ // have no idea what we'll be proxying to at runtime, so we need to
+ // just power through it; see test case for more info)
+ ret.__length = Infinity;
+ return ret;
},
};
diff --git a/lib/Trait.js b/lib/Trait.js
index cff6c07..b4e2fe4 100644
--- a/lib/Trait.js
+++ b/lib/Trait.js
@@ -19,7 +19,8 @@
* along with this program. If not, see .
*/
-var AbstractClass = require( __dirname + '/class_abstract' );
+var AbstractClass = require( __dirname + '/class_abstract' ),
+ ClassBuilder = require( __dirname + '/ClassBuilder' );
function Trait()
@@ -86,12 +87,39 @@ Trait.isTrait = function( trait )
*/
function createConcrete( acls )
{
- // start by providing a concrete implementation for our dummy method
+ // start by providing a concrete implementation for our dummy method and
+ // a constructor that accepts the protected member object of the
+ // containing class
var dfn = {
'protected ___$$trait$$': function() {},
+
+ // protected member object
+ 'private ___$$pmo$$': null,
+ __construct: function( pmo )
+ {
+ this.___$$pmo$$ = pmo;
+ },
+
+ // mainly for debugging; should really never see this.
+ __name: '#ConcreteTrait#',
};
- // TODO: everything else
+ // every abstract method should be overridden with a proxy to the
+ // protected member object that will be passed in via the ctor
+ var amethods = ClassBuilder.getMeta( acls ).abstractMethods;
+ for ( var f in amethods )
+ {
+ // TODO: would be nice if this check could be for '___'; need to
+ // replace amethods.__length with something else, then
+ if ( !( Object.hasOwnProperty.call( amethods, f ) )
+ || ( f.substr( 0, 2 ) === '__' )
+ )
+ {
+ continue;
+ }
+
+ dfn[ 'public proxy ' + f ] = '___$$pmo$$';
+ }
return acls.extend( dfn );
}
@@ -135,7 +163,6 @@ function mixin( trait, dfn )
*/
function mixMethods( src, dest, vis, iname )
{
- // TODO: ignore abstract
for ( var f in src )
{
if ( !( Object.hasOwnProperty.call( src, f ) ) )
@@ -151,18 +178,30 @@ function mixMethods( src, dest, vis, iname )
continue;
}
- var pname = vis + ' proxy ' + f;
-
- // if we have already set up a proxy for a field of this name, then
- // multiple traits have defined the same concrete member
- if ( dest[ pname ] !== undefined )
+ // if abstract, then we are expected to provide the implementation;
+ // otherwise, we proxy to the trait's implementation
+ if ( src[ f ].___$$keywords$$['abstract'] )
{
- // TODO: between what traits?
- throw Error( "Trait member conflict: `" + f + "'" );
+ // copy the abstract definition (N.B. this does not copy the
+ // param names, since that is not [yet] important)
+ dest[ 'weak abstract ' + f ] = src[ f ].definition;
}
+ else
+ {
+ var pname = vis + ' proxy ' + f;
- // proxy this method to what will be the encapsulated trait object
- dest[ pname ] = iname;
+ // if we have already set up a proxy for a field of this name,
+ // then multiple traits have defined the same concrete member
+ if ( dest[ pname ] !== undefined )
+ {
+ // TODO: between what traits?
+ throw Error( "Trait member conflict: `" + f + "'" );
+ }
+
+ // proxy this method to what will be the encapsulated trait
+ // object
+ dest[ pname ] = iname;
+ }
}
}
diff --git a/test/MemberBuilderValidator/MethodTest.js b/test/MemberBuilderValidator/MethodTest.js
index b454dd3..9fe6a00 100644
--- a/test/MemberBuilderValidator/MethodTest.js
+++ b/test/MemberBuilderValidator/MethodTest.js
@@ -296,7 +296,6 @@ require( 'common' ).testCase(
name = 'foo',
amethod = _self.util.createAbstractMethod( [ 'one' ] );
-
// abstract appears before
this.quickFailureTest( name, 'compatible', function()
{
diff --git a/test/MethodWrappersTest.js b/test/MethodWrappersTest.js
index 849da43..28fc6ec 100644
--- a/test/MethodWrappersTest.js
+++ b/test/MethodWrappersTest.js
@@ -389,5 +389,27 @@ require( 'common' ).testCase(
"Should properly proxy to static membesr via static accessor method"
);
},
+
+
+ /**
+ * A proxy method should be able to be used as a concrete implementation
+ * for an abstract method; this means that it must properly expose the
+ * number of arguments of the method that it is proxying to. The problem
+ * is---it can't, because we do not have a type system and so we cannot
+ * know what we will be proxying to at runtime!
+ *
+ * As such, we have no choice (since validations are not at proxy time)
+ * but to set the length to something ridiculous so that it will never
+ * fail.
+ */
+ 'Proxy methods are able to satisfy abstract method param requirements':
+ function()
+ {
+ var f = this._sut.standard.wrapProxy(
+ {}, null, 0, function() {}, '', {}
+ );
+
+ this.assertEqual( f.__length, Infinity );
+ },
} );
diff --git a/test/Trait/AbstractTest.js b/test/Trait/AbstractTest.js
new file mode 100644
index 0000000..0859673
--- /dev/null
+++ b/test/Trait/AbstractTest.js
@@ -0,0 +1,103 @@
+/**
+ * Tests basic trait definition
+ *
+ * Copyright (C) 2014 Mike Gerwitz
+ *
+ * This file is part of GNU ease.js.
+ *
+ * ease.js is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+require( 'common' ).testCase(
+{
+ caseSetUp: function()
+ {
+ this.Sut = this.require( 'Trait' );
+ this.Class = this.require( 'class' );
+ this.AbstractClass = this.require( 'class_abstract' );
+ },
+
+
+ /**
+ * If a trait contains an abstract member, then any class that uses it
+ * should too be considered abstract if no concrete implementation is
+ * provided.
+ */
+ 'Abstract traits create abstract classes when used': function()
+ {
+ var T = this.Sut( { 'abstract foo': [] } );
+
+ var _self = this;
+ this.assertDoesNotThrow( function()
+ {
+ // no concrete `foo; should be abstract (this test is sufficient
+ // because AbstractClass will throw an error if there are no
+ // abstract members)
+ _self.AbstractClass.use( T ).extend( {} );
+ }, Error );
+ },
+
+
+ /**
+ * A class may still be concrete even if it uses abstract traits so long
+ * as it provides concrete implementations for each of the trait's
+ * abstract members.
+ */
+ 'Concrete classes may use abstract traits by definining members':
+ function()
+ {
+ var T = this.Sut( { 'abstract traitfoo': [ 'foo' ] } ),
+ C = null,
+ called = false;
+
+ var _self = this;
+ this.assertDoesNotThrow( function()
+ {
+ C = _self.Class.use( T ).extend(
+ {
+ traitfoo: function( foo ) { called = true; },
+ } );
+ } );
+
+ // sanity check
+ C().traitfoo();
+ this.assertOk( called );
+ },
+
+
+ /**
+ * The concrete methods provided by a class must be compatible with the
+ * abstract definitions of any used traits. This test ensures not only
+ * that the check is being performed, but that the abstract declaration
+ * is properly inherited from the trait.
+ *
+ * TODO: The error mentions "supertype" compatibility, which (although
+ * true) may be confusing; perhaps reference the trait that declared the
+ * method as abstract.
+ */
+ 'Concrete classes must be compatible with abstract traits': function()
+ {
+ var T = this.Sut( { 'abstract traitfoo': [ 'foo' ] } );
+
+ var _self = this;
+ this.assertThrows( function()
+ {
+ C = _self.Class.use( T ).extend(
+ {
+ // missing param in definition
+ traitfoo: function() { called = true; },
+ } );
+ } );
+ },
+} );