ClassBuilder.isInstanceOf now defers to type
This allows separation of concerns and makes the type system extensible. If the type does not implement the necessary API, it falls back to using instanceof.newmaster
parent
c31d1b3eb3
commit
b372fcd722
|
@ -244,10 +244,33 @@ exports.isInstanceOf = function( type, instance )
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defer check to type, falling back to a more primitive check; this
|
||||||
|
// also allows extending ease.js' type system
|
||||||
|
return !!( type.__isInstanceOf || _instChk )( type, instance );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around ECMAScript instanceof check
|
||||||
|
*
|
||||||
|
* This will not throw an error if TYPE is not a function.
|
||||||
|
*
|
||||||
|
* Note that a try/catch is used instead of checking first to see if TYPE is
|
||||||
|
* a function; this is due to the implementation of, notably, IE, which
|
||||||
|
* allows instanceof to be used on some DOM objects with typeof `object'.
|
||||||
|
* These same objects have typeof `function' in other browsers.
|
||||||
|
*
|
||||||
|
* @param {*} type constructor to check against
|
||||||
|
* @param {Object} instance instance to examine
|
||||||
|
*
|
||||||
|
* @return {boolean} whether INSTANCE is an instance of TYPE
|
||||||
|
*/
|
||||||
|
function _instChk( type, instance )
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// check prototype chain (will throw an error if type is not a
|
// check prototype chain (will throw an error if type is not a
|
||||||
// constructor (function)
|
// constructor)
|
||||||
if ( instance instanceof type )
|
if ( instance instanceof type )
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -255,28 +278,8 @@ exports.isInstanceOf = function( type, instance )
|
||||||
}
|
}
|
||||||
catch ( e ) {}
|
catch ( e ) {}
|
||||||
|
|
||||||
// if no metadata is available, then our remaining checks cannot be
|
|
||||||
// performed
|
|
||||||
if ( !instance.__cid || !( meta = exports.getMeta( instance ) ) )
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
implemented = meta.implemented;
|
|
||||||
i = implemented.length;
|
|
||||||
|
|
||||||
// check implemented interfaces et. al. (other systems may make use of
|
|
||||||
// this meta-attribute to provide references to types)
|
|
||||||
while ( i-- )
|
|
||||||
{
|
|
||||||
if ( implemented[ i ] === type )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var AbstractClass = require( './class_abstract' ),
|
var AbstractClass = require( './class_abstract' ),
|
||||||
ClassBuilder = require( './ClassBuilder' );
|
ClassBuilder = require( './ClassBuilder' ),
|
||||||
|
Interface = require( './interface' );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trait constructor / base object
|
* Trait constructor / base object
|
||||||
|
@ -151,6 +151,9 @@ Trait.extend = function( dfn )
|
||||||
mixinImpl( tclass, dest_meta );
|
mixinImpl( tclass, dest_meta );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: this and the above should use util.defineSecureProp
|
||||||
|
TraitType.__isInstanceOf = Interface.isInstanceOf;
|
||||||
|
|
||||||
return TraitType;
|
return TraitType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,8 @@ var util = require( './util' ),
|
||||||
require( './MemberBuilderValidator' )()
|
require( './MemberBuilderValidator' )()
|
||||||
),
|
),
|
||||||
|
|
||||||
Class = require( './class' )
|
Class = require( './class' ),
|
||||||
;
|
ClassBuilder = require( './ClassBuilder' );;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -269,6 +269,7 @@ var extend = ( function( extending )
|
||||||
attachExtend( new_interface );
|
attachExtend( new_interface );
|
||||||
attachStringMethod( new_interface, iname );
|
attachStringMethod( new_interface, iname );
|
||||||
attachCompat( new_interface );
|
attachCompat( new_interface );
|
||||||
|
attachInstanceOf( new_interface );
|
||||||
|
|
||||||
new_interface.prototype = prototype;
|
new_interface.prototype = prototype;
|
||||||
new_interface.constructor = new_interface;
|
new_interface.constructor = new_interface;
|
||||||
|
@ -461,3 +462,58 @@ function analyzeCompat( iface, obj )
|
||||||
return missing;
|
return missing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches instance check method
|
||||||
|
*
|
||||||
|
* This method is invoked when checking the type of a class against an
|
||||||
|
* interface.
|
||||||
|
*
|
||||||
|
* @param {Interface} iface interface that must be adhered to
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function attachInstanceOf( iface )
|
||||||
|
{
|
||||||
|
util.defineSecureProp( iface, '__isInstanceOf', function( type, obj )
|
||||||
|
{
|
||||||
|
return _isInstanceOf( type, obj );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if INSTANCE implements the interface TYPE
|
||||||
|
*
|
||||||
|
* @param {Interface} type interface to check against
|
||||||
|
* @param {Object} instance instance to examine
|
||||||
|
*
|
||||||
|
* @return {boolean} whether TYPE is implemented by INSTANCE
|
||||||
|
*/
|
||||||
|
function _isInstanceOf( type, instance )
|
||||||
|
{
|
||||||
|
// if no metadata are available, then our remaining checks cannot be
|
||||||
|
// performed
|
||||||
|
if ( !instance.__cid || !( meta = ClassBuilder.getMeta( instance ) ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
implemented = meta.implemented;
|
||||||
|
i = implemented.length;
|
||||||
|
|
||||||
|
// check implemented interfaces et. al. (other systems may make use of
|
||||||
|
// this meta-attribute to provide references to types)
|
||||||
|
while ( i-- )
|
||||||
|
{
|
||||||
|
if ( implemented[ i ] === type )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.isInstanceOf = _isInstanceOf;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* Tests treatment of class instances
|
||||||
|
*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require( 'common' ).testCase(
|
||||||
|
{
|
||||||
|
caseSetUp: function()
|
||||||
|
{
|
||||||
|
this.Sut = this.require( 'ClassBuilder' );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instance check delegation helps to keep ease.js extensible and more
|
||||||
|
* loosely coupled. If the given type implements a method
|
||||||
|
* __isInstanceOf, it will be invoked and its return value will be the
|
||||||
|
* result of the entire expression.
|
||||||
|
*/
|
||||||
|
'Delegates to type-specific instance method if present': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
|
||||||
|
// object to assert against
|
||||||
|
var obj = {};
|
||||||
|
|
||||||
|
// mock type
|
||||||
|
var type = { __isInstanceOf: function( givent, giveno )
|
||||||
|
{
|
||||||
|
_self.assertStrictEqual( givent, type );
|
||||||
|
_self.assertStrictEqual( giveno, obj );
|
||||||
|
|
||||||
|
called = true;
|
||||||
|
return true;
|
||||||
|
} };
|
||||||
|
|
||||||
|
this.assertOk( this.Sut.isInstanceOf( type, obj ) );
|
||||||
|
this.assertOk( called );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In the event that the provided type does not provide any instance
|
||||||
|
* check method, we shall fall back to ECMAScript's built-in instanceof
|
||||||
|
* operator.
|
||||||
|
*/
|
||||||
|
'Falls back to ECMAScript instanceof check lacking type method':
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
// T does not define __isInstanceOf
|
||||||
|
var T = function() {},
|
||||||
|
o = new T();
|
||||||
|
|
||||||
|
this.assertOk( this.Sut.isInstanceOf( T, o ) );
|
||||||
|
this.assertOk( !( this.Sut.isInstanceOf( T, {} ) ) );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The instanceof operator will throw an exception if the second operand
|
||||||
|
* is not a function. Our fallback shall not do that---it shall simply
|
||||||
|
* return false.
|
||||||
|
*/
|
||||||
|
'Fallback does not throw exception if type is not a constructor':
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
this.assertDoesNotThrow( function()
|
||||||
|
{
|
||||||
|
// type is not a ctor; should just return false
|
||||||
|
_self.assertOk( !( _self.Sut.isInstanceOf( {}, {} ) ) );
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
Loading…
Reference in New Issue