Commit 4161b54d authored by d's avatar d Committed by Commit bot

Add Array.prototype.includes

Requires adding a SameValueZero implementation.

LOG=Y
BUG=v8:3575
R=dslomov@chromium.org, arv@chromium.org
TEST=added to test262

Review URL: https://codereview.chromium.org/771863002

Cr-Commit-Position: refs/heads/master@{#25735}
parent 017eace7
......@@ -239,6 +239,7 @@ action("js2c_experimental") {
"src/generator.js",
"src/harmony-string.js",
"src/harmony-array.js",
"src/harmony-array-includes.js",
"src/harmony-typedarray.js",
"src/harmony-classes.js",
"src/harmony-tostring.js",
......
......@@ -1582,6 +1582,7 @@ EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_scoping)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_modules)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_strings)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_arrays)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_array_includes)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_classes)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_object_literals)
EMPTY_NATIVE_FUNCTIONS_FOR_FEATURE(harmony_regexps)
......@@ -1611,6 +1612,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_scoping)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_modules)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_strings)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_arrays)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_array_includes)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_classes)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_object_literals)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_arrow_functions)
......@@ -2154,6 +2156,8 @@ bool Genesis::InstallNatives() {
bool Genesis::InstallExperimentalNatives() {
static const char* harmony_arrays_natives[] = {
"native harmony-array.js", "native harmony-typedarray.js", NULL};
static const char* harmony_array_includes_natives[] = {
"native harmony-array-includes.js", NULL};
static const char* harmony_proxies_natives[] = {"native proxy.js", NULL};
static const char* harmony_strings_natives[] = {"native harmony-string.js",
NULL};
......
......@@ -175,6 +175,7 @@ DEFINE_IMPLICATION(es_staging, harmony)
#define HARMONY_INPROGRESS(V) \
V(harmony_modules, "harmony modules (implies block scoping)") \
V(harmony_arrays, "harmony array methods") \
V(harmony_array_includes, "harmony Array.prototype.includes") \
V(harmony_regexps, "harmony regular expression extensions") \
V(harmony_arrow_functions, "harmony arrow functions") \
V(harmony_proxies, "harmony proxies") \
......
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
// This file relies on the fact that the following declaration has been made
// in runtime.js:
// var $Array = global.Array;
// -------------------------------------------------------------------
// Proposed for ES7
// https://github.com/tc39/Array.prototype.includes
// 6e3b78c927aeda20b9d40e81303f9d44596cd904
function ArrayIncludes(searchElement, fromIndex) {
var array = ToObject(this);
var len = ToLength(array.length);
if (len === 0) {
return false;
}
var n = ToInteger(fromIndex);
var k;
if (n >= 0) {
k = n;
} else {
k = len + n;
if (k < 0) {
k = 0;
}
}
while (k < len) {
var elementK = array[k];
if (SameValueZero(searchElement, elementK)) {
return true;
}
++k;
}
return false;
}
// -------------------------------------------------------------------
function HarmonyArrayIncludesExtendArrayPrototype() {
%CheckIsBootstrapping();
%FunctionSetLength(ArrayIncludes, 1);
// Set up the non-enumerable functions on the Array prototype object.
InstallFunctions($Array.prototype, DONT_ENUM, $Array(
"includes", ArrayIncludes
));
}
HarmonyArrayIncludesExtendArrayPrototype();
......@@ -601,6 +601,15 @@ function SameValue(x, y) {
return x === y;
}
// ES6, section 7.2.4
function SameValueZero(x, y) {
if (typeof x != typeof y) return false;
if (IS_NUMBER(x)) {
if (NUMBER_IS_NAN(x) && NUMBER_IS_NAN(y)) return true;
}
return x === y;
}
/* ---------------------------------
- - - U t i l i t i e s - - -
......
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-array-includes
// Ported from
// https://github.com/tc39/Array.prototype.includes/blob/master/test/number-this.js
// using https://www.npmjs.org/package/test262-to-mjsunit
// Array.prototype.includes should use ToObject on this, so that when called
// with a number, it picks up numeric properties from Number.prototype
(function() {
Number.prototype[0] = "a";
Number.prototype[1] = "b";
Object.defineProperty(Number.prototype, 2, {
get: function() {
assertEquals("object", typeof this);
return "c";
}
});
Number.prototype.length = 3;
assertTrue(Array.prototype.includes.call(5, "a"));
assertTrue(Array.prototype.includes.call(5, "b"));
assertTrue(Array.prototype.includes.call(5, "c"));
assertFalse(Array.prototype.includes.call(5, "d"));
})();
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-array-includes
// Ported from
// https://github.com/tc39/Array.prototype.includes/blob/master/test/number-this.js
// using https://www.npmjs.org/package/test262-to-mjsunit
// Array.prototype.includes should use ToObject on this, so that when called
// with a number, it picks up numeric properties from Number.prototype (even in
// strict mode)
(function() {
"use strict";
Number.prototype[0] = "a";
Number.prototype[1] = "b";
Object.defineProperty(Number.prototype, 2, {
get: function() {
assertEquals("object", typeof this);
return "c";
}
});
Number.prototype.length = 3;
assertTrue(Array.prototype.includes.call(5, "a"));
assertTrue(Array.prototype.includes.call(5, "b"));
assertTrue(Array.prototype.includes.call(5, "c"));
assertFalse(Array.prototype.includes.call(5, "d"));
})();
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-array-includes
// Largely ported from
// https://github.com/tc39/Array.prototype.includes/tree/master/test
// using https://www.npmjs.org/package/test262-to-mjsunit with further edits
// Array.prototype.includes sees a new element added by a getter that is hit
// during iteration
(function() {
var arrayLike = {
length: 5,
0: "a",
get 1() {
this[2] = "c";
return "b";
}
};
assertTrue(Array.prototype.includes.call(arrayLike, "c"));
})();
// Array.prototype.includes works on array-like objects
(function() {
var arrayLike1 = {
length: 5,
0: "a",
1: "b"
};
assertTrue(Array.prototype.includes.call(arrayLike1, "a"));
assertFalse(Array.prototype.includes.call(arrayLike1, "c"));
var arrayLike2 = {
length: 2,
0: "a",
1: "b",
2: "c"
};
assertTrue(Array.prototype.includes.call(arrayLike2, "b"));
assertFalse(Array.prototype.includes.call(arrayLike2, "c"));
})();
// Array.prototype.includes should fail if used on a null or undefined this
(function() {
assertThrows(function() {
Array.prototype.includes.call(null, "a");
}, TypeError);
assertThrows(function() {
Array.prototype.includes.call(undefined, "a");
}, TypeError);
})();
// Array.prototype.includes should terminate if getting an index throws an
// exception
(function() {
function Test262Error() {}
var trappedZero = {
length: 2,
get 0() {
throw new Test262Error();
},
get 1() {
assertUnreachable("Should not try to get the first element");
}
};
assertThrows(function() {
Array.prototype.includes.call(trappedZero, "a");
}, Test262Error);
})();
// Array.prototype.includes should terminate if ToNumber ends up being called on
// a symbol fromIndex
(function() {
var trappedZero = {
length: 1,
get 0() {
assertUnreachable("Should not try to get the zeroth element");
}
};
assertThrows(function() {
Array.prototype.includes.call(trappedZero, "a", Symbol());
}, TypeError);
})();
// Array.prototype.includes should terminate if an exception occurs converting
// the fromIndex to a number
(function() {
function Test262Error() {}
var fromIndex = {
valueOf: function() {
throw new Test262Error();
}
};
var trappedZero = {
length: 1,
get 0() {
assertUnreachable("Should not try to get the zeroth element");
}
};
assertThrows(function() {
Array.prototype.includes.call(trappedZero, "a", fromIndex);
}, Test262Error);
})();
// Array.prototype.includes should terminate if an exception occurs getting the
// length
(function() {
function Test262Error() {}
var fromIndexTrap = {
valueOf: function() {
assertUnreachable("Should not try to call ToInteger on valueOf");
}
};
var throwingLength = {
get length() {
throw new Test262Error();
},
get 0() {
assertUnreachable("Should not try to get the zeroth element");
}
};
assertThrows(function() {
Array.prototype.includes.call(throwingLength, "a", fromIndexTrap);
}, Test262Error);
})();
// Array.prototype.includes should terminate if ToLength ends up being called on
// a symbol length
(function() {
var fromIndexTrap = {
valueOf: function() {
assertUnreachable("Should not try to call ToInteger on valueOf");
}
};
var badLength = {
length: Symbol(),
get 0() {
assertUnreachable("Should not try to get the zeroth element");
}
};
assertThrows(function() {
Array.prototype.includes.call(badLength, "a", fromIndexTrap);
}, TypeError);
})();
// Array.prototype.includes should terminate if an exception occurs converting
// the length to a number
(function() {
function Test262Error() {}
var fromIndexTrap = {
valueOf: function() {
assertUnreachable("Should not try to call ToInteger on valueOf");
}
};
var badLength = {
length: {
valueOf: function() {
throw new Test262Error();
}
},
get 0() {
assertUnreachable("Should not try to get the zeroth element");
}
};
assertThrows(function() {
Array.prototype.includes.call(badLength, "a", fromIndexTrap);
}, Test262Error);
})();
// Array.prototype.includes should search the whole array, as the optional
// second argument fromIndex defaults to 0
(function() {
assertTrue([10, 11].includes(10));
assertTrue([10, 11].includes(11));
var arrayLike = {
length: 2,
get 0() {
return "1";
},
get 1() {
return "2";
}
};
assertTrue(Array.prototype.includes.call(arrayLike, "1"));
assertTrue(Array.prototype.includes.call(arrayLike, "2"));
})();
// Array.prototype.includes returns false if fromIndex is greater or equal to
// the length of the array
(function() {
assertFalse([1, 2].includes(2, 3));
assertFalse([1, 2].includes(2, 2));
var arrayLikeWithTrap = {
length: 2,
get 0() {
assertUnreachable("Getter for 0 was called");
},
get 1() {
assertUnreachable("Getter for 1 was called");
}
};
assertFalse(Array.prototype.includes.call(arrayLikeWithTrap, "c", 2));
assertFalse(Array.prototype.includes.call(arrayLikeWithTrap, "c", 3));
})();
// Array.prototype.includes searches the whole array if the computed index from
// the given negative fromIndex argument is less than 0
(function() {
assertTrue([1, 3].includes(1, -4));
assertTrue([1, 3].includes(3, -4));
var arrayLike = {
length: 2,
0: "a",
get 1() {
return "b";
},
get "-1"() {
assertUnreachable("Should not try to get the element at index -1");
}
};
assertTrue(Array.prototype.includes.call(arrayLike, "a", -4));
assertTrue(Array.prototype.includes.call(arrayLike, "b", -4));
})();
// Array.prototype.includes should use a negative value as the offset from the
// end of the array to compute fromIndex
(function() {
assertTrue([12, 13].includes(13, -1));
assertFalse([12, 13].includes(12, -1));
assertTrue([12, 13].includes(12, -2));
var arrayLike = {
length: 2,
get 0() {
return "a";
},
get 1() {
return "b";
}
};
assertTrue(Array.prototype.includes.call(arrayLike, "b", -1));
assertFalse(Array.prototype.includes.call(arrayLike, "a", -1));
assertTrue(Array.prototype.includes.call(arrayLike, "a", -2));
})();
// Array.prototype.includes converts its fromIndex parameter to an integer
(function() {
assertFalse(["a", "b"].includes("a", 2.3));
var arrayLikeWithTraps = {
length: 2,
get 0() {
assertUnreachable("Getter for 0 was called");
},
get 1() {
assertUnreachable("Getter for 1 was called");
}
};
assertFalse(Array.prototype.includes.call(arrayLikeWithTraps, "c", 2.1));
assertFalse(Array.prototype.includes.call(arrayLikeWithTraps, "c", +Infinity));
assertTrue(["a", "b", "c"].includes("a", -Infinity));
assertTrue(["a", "b", "c"].includes("c", 2.9));
assertTrue(["a", "b", "c"].includes("c", NaN));
var arrayLikeWithTrapAfterZero = {
length: 2,
get 0() {
return "a";
},
get 1() {
assertUnreachable("Getter for 1 was called");
}
};
assertTrue(Array.prototype.includes.call(arrayLikeWithTrapAfterZero, "a", NaN));
var numberLike = {
valueOf: function() {
return 2;
}
};
assertFalse(["a", "b", "c"].includes("a", numberLike));
assertFalse(["a", "b", "c"].includes("a", "2"));
assertTrue(["a", "b", "c"].includes("c", numberLike));
assertTrue(["a", "b", "c"].includes("c", "2"));
})();
// Array.prototype.includes should have length 1
(function() {
assertEquals(1, Array.prototype.includes.length);
})();
// Array.prototype.includes should have name property with value 'includes'
(function() {
assertEquals("includes", Array.prototype.includes.name);
})();
// !!! Test failed to convert:
// Cannot convert tests with includes.
// !!!
// Array.prototype.includes does not skip holes; if the array has a prototype it
// gets from that
(function() {
var holesEverywhere = [,,,];
holesEverywhere.__proto__ = {
1: "a"
};
holesEverywhere.__proto__.__proto__ = Array.prototype;
assertTrue(holesEverywhere.includes("a"));
var oneHole = ["a", "b",, "d"];
oneHole.__proto__ = {
get 2() {
return "c";
}
};
assertTrue(Array.prototype.includes.call(oneHole, "c"));
})();
// Array.prototype.includes does not skip holes; instead it treates them as
// undefined
(function() {
assertTrue([,,,].includes(undefined));
assertTrue(["a", "b",, "d"].includes(undefined));
})();
// Array.prototype.includes gets length property from the prototype if it's
// available
(function() {
var proto = {
length: 1
};
var arrayLike = Object.create(proto);
arrayLike[0] = "a";
Object.defineProperty(arrayLike, "1", {
get: function() {
assertUnreachable("Getter for 1 was called");
}
});
assertTrue(Array.prototype.includes.call(arrayLike, "a"));
})();
// Array.prototype.includes treats a missing length property as zero
(function() {
var arrayLikeWithTraps = {
get 0() {
assertUnreachable("Getter for 0 was called");
},
get 1() {
assertUnreachable("Getter for 1 was called");
}
};
assertFalse(Array.prototype.includes.call(arrayLikeWithTraps, "a"));
})();
// Array.prototype.includes should always return false on negative-length
// objects
(function() {
assertFalse(Array.prototype.includes.call({
length: -1
}, 2));
assertFalse(Array.prototype.includes.call({
length: -2
}));
assertFalse(Array.prototype.includes.call({
length: -Infinity
}, undefined));
assertFalse(Array.prototype.includes.call({
length: -Math.pow(2, 53)
}, NaN));
assertFalse(Array.prototype.includes.call({
length: -1,
"-1": 2
}, 2));
assertFalse(Array.prototype.includes.call({
length: -3,
"-1": 2
}, 2));
assertFalse(Array.prototype.includes.call({
length: -Infinity,
"-1": 2
}, 2));
var arrayLikeWithTrap = {
length: -1,
get 0() {
assertUnreachable("Getter for 0 was called");
}
};
assertFalse(Array.prototype.includes.call(arrayLikeWithTrap, 2));
})();
// Array.prototype.includes should clamp positive lengths to 2^53 - 1
(function() {
var fromIndexForLargeIndexTests = 9007199254740990;
assertFalse(Array.prototype.includes.call({
length: 1
}, 2));
assertTrue(Array.prototype.includes.call({
length: 1,
0: "a"
}, "a"));
assertTrue(Array.prototype.includes.call({
length: +Infinity,
0: "a"
}, "a"));
assertFalse(Array.prototype.includes.call({
length: +Infinity
}, "a", fromIndexForLargeIndexTests));
var arrayLikeWithTrap = {
length: +Infinity,
get 9007199254740992() {
assertUnreachable("Getter for 9007199254740992 (i.e. 2^53) was called");
},
"9007199254740993": "a"
};
assertFalse(
Array.prototype.includes.call(arrayLikeWithTrap, "a", fromIndexForLargeIndexTests)
);
var arrayLikeWithTooBigLength = {
length: 9007199254740996,
"9007199254740992": "a"
};
assertFalse(
Array.prototype.includes.call(arrayLikeWithTooBigLength, "a", fromIndexForLargeIndexTests)
);
})();
// Array.prototype.includes should always return false on zero-length objects
(function() {
assertFalse([].includes(2));
assertFalse([].includes());
assertFalse([].includes(undefined));
assertFalse([].includes(NaN));
assertFalse(Array.prototype.includes.call({
length: 0
}, 2));
assertFalse(Array.prototype.includes.call({
length: 0
}));
assertFalse(Array.prototype.includes.call({
length: 0
}, undefined));
assertFalse(Array.prototype.includes.call({
length: 0
}, NaN));
assertFalse(Array.prototype.includes.call({
length: 0,
0: 2
}, 2));
assertFalse(Array.prototype.includes.call({
length: 0,
0: undefined
}));
assertFalse(Array.prototype.includes.call({
length: 0,
0: undefined
}, undefined));
assertFalse(Array.prototype.includes.call({
length: 0,
0: NaN
}, NaN));
var arrayLikeWithTrap = {
length: 0,
get 0() {
assertUnreachable("Getter for 0 was called");
}
};
Array.prototype.includes.call(arrayLikeWithTrap);
var trappedFromIndex = {
valueOf: function() {
assertUnreachable("Should not try to convert fromIndex to a number on a zero-length array");
}
};
[].includes("a", trappedFromIndex);
Array.prototype.includes.call({
length: 0
}, trappedFromIndex);
})();
// Array.prototype.includes works on objects
(function() {
assertFalse(["a", "b", "c"].includes({}));
assertFalse([{}, {}].includes({}));
var obj = {};
assertTrue([obj].includes(obj));
assertFalse([obj].includes(obj, 1));
assertTrue([obj, obj].includes(obj, 1));
var stringyObject = {
toString: function() {
return "a";
}
};
assertFalse(["a", "b", obj].includes(stringyObject));
})();
// Array.prototype.includes does not see an element removed by a getter that is
// hit during iteration
(function() {
var arrayLike = {
length: 5,
0: "a",
get 1() {
delete this[2];
return "b";
},
2: "c"
};
assertFalse(Array.prototype.includes.call(arrayLike, "c"));
})();
// Array.prototype.includes should use the SameValueZero algorithm to compare
(function() {
assertTrue([1, 2, 3].includes(2));
assertFalse([1, 2, 3].includes(4));
assertTrue([1, 2, NaN].includes(NaN));
assertTrue([1, 2, -0].includes(+0));
assertTrue([1, 2, -0].includes(-0));
assertTrue([1, 2, +0].includes(-0));
assertTrue([1, 2, +0].includes(+0));
assertFalse([1, 2, -Infinity].includes(+Infinity));
assertTrue([1, 2, -Infinity].includes(-Infinity));
assertFalse([1, 2, +Infinity].includes(-Infinity));
assertTrue([1, 2, +Infinity].includes(+Infinity));
})();
// Array.prototype.includes stops once it hits the length of an array-like, even
// if there are more after
(function() {
var arrayLike = {
length: 2,
0: "a",
1: "b",
get 2() {
assertUnreachable("Should not try to get the second element");
}
};
assertFalse(Array.prototype.includes.call(arrayLike, "c"));
})();
// Array.prototype.includes works on typed arrays
(function() {
assertTrue(Array.prototype.includes.call(new Uint8Array([1, 2, 3]), 2));
assertTrue(
Array.prototype.includes.call(new Float32Array([2.5, 3.14, Math.PI]), 3.1415927410125732)
);
assertFalse(Array.prototype.includes.call(new Uint8Array([1, 2, 3]), 4));
assertFalse(Array.prototype.includes.call(new Uint8Array([1, 2, 3]), 2, 2));
})();
......@@ -1634,6 +1634,7 @@
'../../src/generator.js',
'../../src/harmony-string.js',
'../../src/harmony-array.js',
'../../src/harmony-array-includes.js',
'../../src/harmony-tostring.js',
'../../src/harmony-typedarray.js',
'../../src/harmony-classes.js',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment