1
0
Fork 0

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
Mike Gerwitz 2014-04-22 00:24:21 -04:00
parent c31d1b3eb3
commit b372fcd722
4 changed files with 179 additions and 26 deletions

View File

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

View File

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

View File

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

View File

@ -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( {}, {} ) ) );
} );
},
} );