Skip to content

Commit

Permalink
Merge pull request #2844 from jgonggrijp/typed_array_eq
Browse files Browse the repository at this point in the history
  • Loading branch information
jgonggrijp committed May 4, 2020
2 parents 77dae23 + 792d5f8 commit 4334c12
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 21 deletions.
64 changes: 54 additions & 10 deletions modules/index.js
Expand Up @@ -24,11 +24,15 @@ var push = ArrayProto.push,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;

// All **ECMAScript 5** native function implementations that we hope to use
// Modern feature detection.
var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined';

// All **ECMAScript 5+** native function implementations that we hope to use
// are declared here.
var nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeCreate = Object.create;
nativeCreate = Object.create,
nativeIsView = supportsArrayBuffer && ArrayBuffer.isView;

// Create references to these builtin functions because we override them.
var _isNaN = root.isNaN,
Expand Down Expand Up @@ -152,16 +156,26 @@ function deepGet(obj, path) {
return length ? obj : void 0;
}

// Common logic for isArrayLike and isBufferLike.
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
function createSizePropertyCheck(getSizeProperty) {
return function(collection) {
var sizeProperty = getSizeProperty(collection);
return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX;
}
}

// Helper for collection methods to determine whether a collection
// should be iterated as an array or as an object.
// Related: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
// Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var getLength = shallowProperty('length');
function isArrayLike(collection) {
var length = getLength(collection);
return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
}
var isArrayLike = createSizePropertyCheck(getLength);

// Likewise to determine whether we should spend extensive checks against
// `ArrayBuffer` et al.
var getByteLength = shallowProperty('byteLength');
var isBufferLike = createSizePropertyCheck(getByteLength);

// Collection Functions
// --------------------
Expand Down Expand Up @@ -1206,10 +1220,11 @@ function deepEq(a, b, aStack, bStack) {
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className !== toString.call(b)) return false;

switch (className) {
// Strings, numbers, regular expressions, dates, and booleans are compared by value.
// These types are compared by value.
case '[object RegExp]':
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
Expand All @@ -1228,6 +1243,25 @@ function deepEq(a, b, aStack, bStack) {
return +a === +b;
case '[object Symbol]':
return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
case '[object ArrayBuffer]':
// Coerce to `DataView` so we can fall through to the next case.
return deepEq(new DataView(a), new DataView(b), aStack, bStack);
case '[object DataView]':
var byteLength = getByteLength(a);
if (byteLength !== getByteLength(b)) {
return false;
}
while (byteLength--) {
if (a.getUint8(byteLength) !== b.getUint8(byteLength)) {
return false;
}
}
return true;
}

if (isTypedArray(a)) {
// Coerce typed arrays to `DataView`.
return deepEq(new DataView(a.buffer), new DataView(b.buffer), aStack, bStack);
}

var areArrays = className === '[object Array]';
Expand Down Expand Up @@ -1325,7 +1359,7 @@ export function isObject(obj) {
return type === 'function' || type === 'object' && !!obj;
}

// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet.
// Add some isType methods.
export var isArguments = tagTester('Arguments');
export var isFunction = tagTester('Function');
export var isString = tagTester('String');
Expand All @@ -1338,6 +1372,8 @@ export var isMap = tagTester('Map');
export var isWeakMap = tagTester('WeakMap');
export var isSet = tagTester('Set');
export var isWeakSet = tagTester('WeakSet');
export var isArrayBuffer = tagTester('ArrayBuffer');
export var isDataView = tagTester('DataView');

// Define a fallback version of the method in browsers (ahem, IE < 9), where
// there isn't any inspectable "Arguments" type.
Expand Down Expand Up @@ -1383,6 +1419,14 @@ export function isUndefined(obj) {
return obj === void 0;
}

// Is a given value a typed array?
var typedArrayPattern = /\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;
export var isTypedArray = supportsArrayBuffer ? function(obj) {
// `ArrayBuffer.isView` is the most future-proof, so use it when available.
// Otherwise, fall back on the above regular expression.
return nativeIsView ? (nativeIsView(obj) && !isDataView(obj)) : isBufferLike(obj) && typedArrayPattern.test(toString.call(obj));
} : constant(false);

// Shortcut function for checking if an object has a given property directly
// on itself (in other words, not on a prototype).
export function has(obj, path) {
Expand Down
66 changes: 66 additions & 0 deletions test/objects.js
Expand Up @@ -578,6 +578,36 @@
var sameStringSymbol = Symbol('x');
assert.strictEqual(_.isEqual(symbol, sameStringSymbol), false, 'Different symbols of same string are not equal');
}



// typed arrays
if (typeof ArrayBuffer !== 'undefined') {
var u8 = new Uint8Array([1, 2]);
var u8b = new Uint8Array([1, 2]);
var i8 = new Int8Array([1, 2]);
var u16 = new Uint16Array([1, 2]);
var u16one = new Uint16Array([3]);

assert.ok(_.isEqual(u8, u8b), 'Identical typed array data are equal');
assert.ok(_.isEqual(u8.buffer, u8b.buffer), 'Identical ArrayBuffers are equal');
assert.ok(_.isEqual(new DataView(u8.buffer), new DataView(u8b.buffer)), 'Identical DataViews are equal');
assert.ok(_.isEqual(new DataView(u8.buffer), new DataView(i8.buffer)), 'Identical DataViews of different typed arrays are equal');
assert.ok(_.isEqual(u8.buffer, i8.buffer), 'Identical ArrayBuffers of different typed arrays are equal');

assert.notOk(_.isEqual({a: 1, buffer: u8.buffer}, {a: 2, buffer: u8b.buffer}), 'Unequal objects with similar buffer properties are not equals');

assert.notOk(_.isEqual(u8, i8), 'Different types of typed arrays with the same byte data are not equal');
assert.notOk(_.isEqual(u8, u16), 'Typed arrays with different types and different byte length are not equal');
assert.notOk(_.isEqual(u8, u16one), 'Typed arrays with different types, same byte length but different byte data are not equal');
assert.notOk(_.isEqual(new DataView(u8.buffer), new DataView(u16.buffer)), 'Different DataViews with different length are not equal');
assert.notOk(_.isEqual(new DataView(u8.buffer), new DataView(u16one.buffer)), 'Different DataViews with different byte data are not equal');
assert.notOk(_.isEqual(u8.buffer, u16.buffer), 'Different ArrayBuffers with different length are not equal');
assert.notOk(_.isEqual(u8.buffer, u16one.buffer), 'Different ArrayBuffers with different byte data are not equal');
}

//assert.ok(_.isEqual(new DataView(u8.buffer)), new DataView(u8b.buffer))
//assert.notOk(_.isEqual(new DataView((new Uint8Array([1,2])).buffer), new DataView((new Uint8Array([5,6,10])).buffer));
});

QUnit.test('isEmpty', function(assert) {
Expand Down Expand Up @@ -881,6 +911,42 @@
assert.ok(_.isError(new URIError()), 'URIErrors are Errors');
});

if (typeof ArrayBuffer != 'undefined') {
QUnit.test('isArrayBuffer, isDataView and isTypedArray', function(assert) {
var buffer = new ArrayBuffer(16);
var checkValues = {
'null': null,
'a string': '',
'an array': [],
'an ArrayBuffer': buffer,
'a DataView': new DataView(buffer),
'a TypedArray': new Uint8Array(buffer)
};
var types = ['an ArrayBuffer', 'a DataView', 'a TypedArray'];
_.each(types, function(type) {
var typeCheck = _['is' + type.split(' ')[1]];
_.each(checkValues, function(value, description) {
if (description === type) {
assert.ok(typeCheck(value), description + ' is ' + type);
} else {
assert.ok(!typeCheck(value), description + ' is not ' + type);
}
});
});
});

QUnit.test('isTypedArray', function(assert) {
var buffer = new ArrayBuffer(16);
_.each([Uint8ClampedArray, Int8Array, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array], function(ctor) {
assert.ok(_.isTypedArray(new ctor(buffer)), ctor.name + ' is a typed array');
});

if (typeof BigInt64Array != 'undefined') {
assert.ok(_.isTypedArray(new BigInt64Array(buffer)), 'BigInt64Array is a typed array');
}
});
}

QUnit.test('tap', function(assert) {
var intercepted = null;
var interceptor = function(obj) { intercepted = obj; };
Expand Down
67 changes: 57 additions & 10 deletions underscore.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion underscore.js.map

Large diffs are not rendered by default.

0 comments on commit 4334c12

Please sign in to comment.