From b841b9cc5ee1705e71f43c57d6c0c8dc4b7e5a80 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Mon, 2 Jan 2017 23:32:42 -0500 Subject: [PATCH] Fix trait extending of supertype with constructor Supertypes that extend constructors may now be extended by traits without completely blowing up. Good feature. * lib/Trait.js (__tconstruct): Add function. (createVirtProxy): Use it. * test/Trait/ClassExtendTest.js: Add test. --- lib/Trait.js | 48 ++++++++++++++++++++++++++++------- test/Trait/ClassExtendTest.js | 43 ++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/lib/Trait.js b/lib/Trait.js index fb76e92..ddc5259 100644 --- a/lib/Trait.js +++ b/lib/Trait.js @@ -1,7 +1,7 @@ /** * Provides system for code reuse via traits * - * Copyright (C) 2014, 2015, 2016 Free Software Foundation, Inc. + * Copyright (C) 2014, 2015, 2016, 2017 Free Software Foundation, Inc. * * This file is part of GNU ease.js. * @@ -498,14 +498,12 @@ function createConcrete( acls ) // parent ACLS has access to it (!), which is not prohibited since // JS does not provide a strict typing mechanism...this is a kluge) // and target supertype---that is, what __super calls should - // referene - 'protected ___$$pmo$$': null, - 'protected ___$$super$$': null, - __construct: function( base, pmo ) - { - this.___$$super$$ = base; - this.___$$pmo$$ = pmo; - }, + // reference + 'protected ___$$pmo$$': null, + 'protected ___$$super$$': null, + + 'weak virtual __construct': _fvoid, + 'override __construct': __tconstruct, // mainly for debugging; should really never see this. __name: '#ConcreteTrait#', @@ -544,6 +542,30 @@ function createConcrete( acls ) } +/** + * Trait class __construct method + * + * This is to be used for the `__construct' method of a concrete trait + * class. This simply performs low-level trait initialization. + * + * Just below this function's definition, its `__length' property is set to + * Infinity---this avoids supertype compatibility issues based on argument + * count (see argument length check in `MemberBuilderValidator'). + * + * @param {Class} base supertype + * @param {Object} pmo protected member object + */ +function __tconstruct( base, pmo ) +{ + this.___$$super$$ = base; + this.___$$pmo$$ = pmo; +} + +// prevent any issues related to supertype compatability (__length is +// recognized by easejs to store argument count) +__tconstruct.__length = Infinity; + + /** * Create virtual method proxies for all virtual members * @@ -567,6 +589,14 @@ function createVirtProxy( acls, dfn ) // f = `field' for ( var f in vmembers ) { + // constructors may exist when extending a class; they require + // special treatment and it makes no sense to create a proxy for + // them + if ( f === '__construct' ) + { + continue; + } + var vis = ( acls.___$$methods$$['public'][ f ] !== undefined ) ? 'public' : 'protected'; diff --git a/test/Trait/ClassExtendTest.js b/test/Trait/ClassExtendTest.js index cfcc0cf..b538449 100644 --- a/test/Trait/ClassExtendTest.js +++ b/test/Trait/ClassExtendTest.js @@ -1,7 +1,7 @@ /** * Tests extending traits from classes * - * Copyright (C) 2015 Free Software Foundation, Inc. + * Copyright (C) 2015, 2017 Free Software Foundation, Inc. * * This file is part of GNU ease.js. * @@ -189,6 +189,47 @@ require( 'common' ).testCase( }, + /** + * Also an implementation detail: when a constructor is present on the + * supertype, special care is needed to make sure that we have no errors + * in an override---the trait itself has its own constructor. + * + * Another subtle detail is that our constructor override needs to take + * into account that the supertype constructor could have any number of + * arguments. Since easejs enforces argument length for overrides, we + * need to make sure that the trait will handle this. (In actuality, + * the implementation just sets the argument length of the trait class + * `__construct' to Infinity.) + */ + 'Trait mixin handles supertype constructor': function() + { + var ctor_called = 0; + + var C = this.Class( + { + // notice that this isn't virtual (another implementation quirk + // to handle), and notice the argument count (which creates + // quite the rainbow if you have semantic coloring for your + // editor!) + __construct: function( a, b, c, d, e, f, g, h, i, j, k, l ) + { + ctor_called++; + } + } ); + + var T = this.Sut.extend( C, {} ); + + this.assertDoesNotThrow( function() + { + C.use( T )(); + } ); + + // the supertype's constructor should be invoked only _once_ (we + // were mixed into an object that should have already invoked it) + this.assertEqual( 1, ctor_called ); + }, + + /** * This is a corollary, but is still worth testing for assurance. *