Bug 1069063 - Implement Array.prototype.includes. r=till
authorziyunfei <446240525@qq.com>
Thu, 20 Nov 2014 20:34:00 +0100
changeset 241131 9e692d8705783cec7d76255a1930bc78ae4642ec
parent 241130 b9fd78f08c8b411a5680f34935cc51320b40abe9
child 241132 ce06b517bd3f8709a77fcc361dac6d5f822ca187
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1069063
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1069063 - Implement Array.prototype.includes. r=till
js/src/builtin/Array.js
js/src/builtin/Utilities.js
js/src/jsarray.cpp
js/src/tests/Makefile.in
js/src/tests/ecma_7/Array/browser.js
js/src/tests/ecma_7/Array/includes.js
js/src/tests/ecma_7/Array/shell.js
js/src/tests/ecma_7/browser.js
js/src/tests/ecma_7/shell.js
js/xpconnect/tests/chrome/test_xrayToJS.xul
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -576,16 +576,60 @@ function ArrayFill(value, start = 0, end
     for (; k < final; k++) {
         O[k] = value;
     }
 
     // Step 13.
     return O;
 }
 
+// Proposed for ES7:
+// https://github.com/domenic/Array.prototype.includes/blob/master/spec.md
+function ArrayIncludes(searchElement, fromIndex = 0) {
+    // Steps 1-2.
+    var O = ToObject(this);
+
+    // Steps 3-4.
+    var len = ToLength(O.length);
+
+    // Step 5.
+    if (len === 0)
+        return false;
+
+    // Steps 6-7.
+    var n = ToInteger(fromIndex);
+
+    // Step 8.
+    var k;
+    if (n >= 0) {
+        k = n;
+    }
+    // Step 9.
+    else {
+        // Step a.
+        k = len + n;
+        // Step b.
+        if (k < 0)
+            k = 0;
+    }
+
+    // Step 10.
+    while (k < len) {
+        // Steps a-c.
+        if (SameValueZero(searchElement, O[k]))
+            return true;
+
+        // Step d.
+        k++;
+    }
+
+    // Step 11.
+    return false;
+}
+
 #define ARRAY_ITERATOR_SLOT_ITERATED_OBJECT 0
 #define ARRAY_ITERATOR_SLOT_NEXT_INDEX 1
 #define ARRAY_ITERATOR_SLOT_ITEM_KIND 2
 
 #define ITEM_KIND_VALUE 0
 #define ITEM_KIND_KEY_AND_VALUE 1
 #define ITEM_KIND_KEY 2
 
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -98,16 +98,21 @@ function ToLength(v) {
 
     if (v <= 0)
         return 0;
 
     // Math.pow(2, 53) - 1 = 0x1fffffffffffff
     return std_Math_min(v, 0x1fffffffffffff);
 }
 
+// Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4.
+function SameValueZero(x, y) {
+    return x === y || (x !== x && y !== y);
+}
+
 /********** Testing code **********/
 
 #ifdef ENABLE_PARALLEL_JS
 
 /**
  * Internal debugging tool: checks that the given `mode` permits
  * sequential execution
  */
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3222,16 +3222,20 @@ static const JSFunctionSpec array_method
     JS_SELF_HOSTED_FN("findIndex",   "ArrayFindIndex",   1,0),
     JS_SELF_HOSTED_FN("copyWithin",  "ArrayCopyWithin",  3,0),
 
     JS_SELF_HOSTED_FN("fill",        "ArrayFill",        3,0),
 
     JS_SELF_HOSTED_SYM_FN(iterator,  "ArrayValues",      0,0),
     JS_SELF_HOSTED_FN("entries",     "ArrayEntries",     0,0),
     JS_SELF_HOSTED_FN("keys",        "ArrayKeys",        0,0),
+
+    /* ES7 additions */
+    JS_SELF_HOSTED_FN("includes",    "ArrayIncludes",    2,0),
+
     JS_FS_END
 };
 
 static const JSFunctionSpec array_static_methods[] = {
     JS_FN("isArray",            array_isArray,      1,0),
     JS_SELF_HOSTED_FN("lastIndexOf", "ArrayStaticLastIndexOf", 2,0),
     JS_SELF_HOSTED_FN("indexOf",     "ArrayStaticIndexOf", 2,0),
     JS_SELF_HOSTED_FN("forEach",     "ArrayStaticForEach", 2,0),
--- a/js/src/tests/Makefile.in
+++ b/js/src/tests/Makefile.in
@@ -13,16 +13,17 @@ TEST_FILES = \
   js-test-driver-end.js \
   user.js \
   ecma/ \
   ecma_2/ \
   ecma_3/ \
   ecma_3_1/ \
   ecma_5/ \
   ecma_6/ \
+  ecma_7/ \
   Intl/ \
   js1_1/ \
   js1_2/ \
   js1_3/ \
   js1_4/ \
   js1_5/ \
   js1_6/ \
   js1_7/ \
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/Array/includes.js
@@ -0,0 +1,59 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var BUGNUMBER = 1069063;
+var summary = "Implement Array.prototype.includes";
+
+print(BUGNUMBER + ": " + summary);
+
+assertEq(typeof [].includes, "function");
+assertEq([].includes.length, 1);
+
+assertTrue([1, 2, 3].includes(2));
+assertTrue([1,,2].includes(2));
+assertTrue([1, 2, 3].includes(2, 1));
+assertTrue([1, 2, 3].includes(2, -2));
+assertTrue([1, 2, 3].includes(2, -100));
+assertTrue([Object, Function, Array].includes(Function));
+assertTrue([-0].includes(0));
+assertTrue([NaN].includes(NaN));
+assertTrue([,].includes());
+assertTrue(staticIncludes("123", "2"));
+assertTrue(staticIncludes({length: 3, 1: 2}, 2));
+assertTrue(staticIncludes({length: 3, 1: 2, get 3(){throw ""}}, 2));
+assertTrue(staticIncludes({length: 3, get 1() {return 2}}, 2));
+assertTrue(staticIncludes({__proto__: {1: 2}, length: 3}, 2));
+assertTrue(staticIncludes(new Proxy([1], {get(){return 2}}), 2));
+
+assertFalse([1, 2, 3].includes("2"));
+assertFalse([1, 2, 3].includes(2, 2));
+assertFalse([1, 2, 3].includes(2, -1));
+assertFalse([undefined].includes(NaN));
+assertFalse([{}].includes({}));
+assertFalse(staticIncludes({length: 3, 1: 2}, 2, 2));
+assertFalse(staticIncludes({length: 3, get 0(){delete this[1]}, 1: 2}, 2));
+assertFalse(staticIncludes({length: -100, 0: 1}, 1));
+
+assertThrowsInstanceOf(() => staticIncludes(), TypeError);
+assertThrowsInstanceOf(() => staticIncludes(null), TypeError);
+assertThrowsInstanceOf(() => staticIncludes({get length(){throw TypeError()}}), TypeError);
+assertThrowsInstanceOf(() => staticIncludes({length: 3, get 1() {throw TypeError()}}, 2), TypeError);
+assertThrowsInstanceOf(() => staticIncludes({__proto__: {get 1() {throw TypeError()}}, length: 3}, 2), TypeError);
+assertThrowsInstanceOf(() => staticIncludes(new Proxy([1], {get(){throw TypeError()}})), TypeError);
+
+function assertTrue(v) {
+    assertEq(v, true);
+}
+
+function assertFalse(v) {
+    assertEq(v, false);
+}
+
+function staticIncludes(o, v, f) {
+    return [].includes.call(o, v, f);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/shell.js
@@ -0,0 +1,205 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+if (typeof assertThrowsInstanceOf === 'undefined') {
+    var assertThrowsInstanceOf = function assertThrowsInstanceOf(f, ctor, msg) {
+        var fullmsg;
+        try {
+            f();
+        } catch (exc) {
+            if (exc instanceof ctor)
+                return;
+            fullmsg = "Assertion failed: expected exception " + ctor.name + ", got " + exc;
+        }
+        if (fullmsg === undefined)
+            fullmsg = "Assertion failed: expected exception " + ctor.name + ", no exception thrown";
+        if (msg !== undefined)
+            fullmsg += " - " + msg;
+        throw new Error(fullmsg);
+    };
+}
+
+if (typeof assertThrowsValue === 'undefined') {
+    var assertThrowsValue = function assertThrowsValue(f, val, msg) {
+        var fullmsg;
+        try {
+            f();
+        } catch (exc) {
+            if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
+                return;
+            fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
+        }
+        if (fullmsg === undefined)
+            fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
+        if (msg !== undefined)
+            fullmsg += " - " + msg;
+        throw new Error(fullmsg);
+    };
+}
+
+if (typeof assertDeepEq === 'undefined') {
+    var assertDeepEq = (function(){
+        var call = Function.prototype.call,
+            Array_isArray = Array.isArray,
+            Map_ = Map,
+            Error_ = Error,
+            Map_has = call.bind(Map.prototype.has),
+            Map_get = call.bind(Map.prototype.get),
+            Map_set = call.bind(Map.prototype.set),
+            Object_toString = call.bind(Object.prototype.toString),
+            Function_toString = call.bind(Function.prototype.toString),
+            Object_getPrototypeOf = Object.getPrototypeOf,
+            Object_hasOwnProperty = call.bind(Object.prototype.hasOwnProperty),
+            Object_getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
+            Object_isExtensible = Object.isExtensible,
+            Object_getOwnPropertyNames = Object.getOwnPropertyNames,
+            uneval_ = uneval;
+
+        // Return true iff ES6 Type(v) isn't Object.
+        // Note that `typeof document.all === "undefined"`.
+        function isPrimitive(v) {
+            return (v === null ||
+                    v === undefined ||
+                    typeof v === "boolean" ||
+                    typeof v === "number" ||
+                    typeof v === "string" ||
+                    typeof v === "symbol");
+        }
+
+        function assertSameValue(a, b, msg) {
+            try {
+                assertEq(a, b);
+            } catch (exc) {
+                throw Error_(exc.message + (msg ? " " + msg : ""));
+            }
+        }
+
+        function assertSameClass(a, b, msg) {
+            var ac = Object_toString(a), bc = Object_toString(b);
+            assertSameValue(ac, bc, msg);
+            switch (ac) {
+            case "[object Function]":
+                assertSameValue(Function_toString(a), Function_toString(b), msg);
+            }
+        }
+
+        function at(prevmsg, segment) {
+            return prevmsg ? prevmsg + segment : "at _" + segment;
+        }
+
+        // Assert that the arguments a and b are thoroughly structurally equivalent.
+        //
+        // For the sake of speed, we cut a corner:
+        //        var x = {}, y = {}, ax = [x];
+        //        assertDeepEq([ax, x], [ax, y]);  // passes (?!)
+        //
+        // Technically this should fail, since the two object graphs are different.
+        // (The graph of [ax, y] contains one more object than the graph of [ax, x].)
+        //
+        // To get technically correct behavior, pass {strictEquivalence: true}.
+        // This is slower because we have to walk the entire graph, and Object.prototype
+        // is big.
+        //
+        return function assertDeepEq(a, b, options) {
+            var strictEquivalence = options ? options.strictEquivalence : false;
+
+            function assertSameProto(a, b, msg) {
+                check(Object_getPrototypeOf(a), Object_getPrototypeOf(b), at(msg, ".__proto__"));
+            }
+
+            function failPropList(na, nb, msg) {
+                throw Error_("got own properties " + uneval_(na) + ", expected " + uneval_(nb) +
+                             (msg ? " " + msg : ""));
+            }
+
+            function assertSameProps(a, b, msg) {
+                var na = Object_getOwnPropertyNames(a),
+                    nb = Object_getOwnPropertyNames(b);
+                if (na.length !== nb.length)
+                    failPropList(na, nb, msg);
+
+                // Ignore differences in whether Array elements are stored densely.
+                if (Array_isArray(a)) {
+                    na.sort();
+                    nb.sort();
+                }
+
+                for (var i = 0; i < na.length; i++) {
+                    var name = na[i];
+                    if (name !== nb[i])
+                        failPropList(na, nb, msg);
+                    var da = Object_getOwnPropertyDescriptor(a, name),
+                        db = Object_getOwnPropertyDescriptor(b, name);
+                    var pmsg = at(msg, /^[_$A-Za-z0-9]+$/.test(name)
+                                       ? /0|[1-9][0-9]*/.test(name) ? "[" + name + "]" : "." + name
+                                       : "[" + uneval_(name) + "]");
+                    assertSameValue(da.configurable, db.configurable, at(pmsg, ".[[Configurable]]"));
+                    assertSameValue(da.enumerable, db.enumerable, at(pmsg, ".[[Enumerable]]"));
+                    if (Object_hasOwnProperty(da, "value")) {
+                        if (!Object_hasOwnProperty(db, "value"))
+                            throw Error_("got data property, expected accessor property" + pmsg);
+                        check(da.value, db.value, pmsg);
+                    } else {
+                        if (Object_hasOwnProperty(db, "value"))
+                            throw Error_("got accessor property, expected data property" + pmsg);
+                        check(da.get, db.get, at(pmsg, ".[[Get]]"));
+                        check(da.set, db.set, at(pmsg, ".[[Set]]"));
+                    }
+                }
+            };
+
+            var ab = Map_();
+            var bpath = Map_();
+
+            function check(a, b, path) {
+                if (typeof a === "symbol") {
+                    // Symbols are primitives, but they have identity.
+                    // Symbol("x") !== Symbol("x") but
+                    // assertDeepEq(Symbol("x"), Symbol("x")) should pass.
+                    if (typeof b !== "symbol") {
+                        throw Error_("got " + uneval_(a) + ", expected " + uneval_(b) + " " + path);
+                    } else if (uneval_(a) !== uneval_(b)) {
+                        // We lamely use uneval_ to distinguish well-known symbols
+                        // from user-created symbols. The standard doesn't offer
+                        // a convenient way to do it.
+                        throw Error_("got " + uneval_(a) + ", expected " + uneval_(b) + " " + path);
+                    } else if (Map_has(ab, a)) {
+                        assertSameValue(Map_get(ab, a), b, path);
+                    } else if (Map_has(bpath, b)) {
+                        var bPrevPath = Map_get(bpath, b) || "_";
+                        throw Error_("got distinct symbols " + at(path, "") + " and " +
+                                     at(bPrevPath, "") + ", expected the same symbol both places");
+                    } else {
+                        Map_set(ab, a, b);
+                        Map_set(bpath, b, path);
+                    }
+                } else if (isPrimitive(a)) {
+                    assertSameValue(a, b, path);
+                } else if (isPrimitive(b)) {
+                    throw Error_("got " + Object_toString(a) + ", expected " + uneval_(b) + " " + path);
+                } else if (Map_has(ab, a)) {
+                    assertSameValue(Map_get(ab, a), b, path);
+                } else if (Map_has(bpath, b)) {
+                    var bPrevPath = Map_get(bpath, b) || "_";
+                    throw Error_("got distinct objects " + at(path, "") + " and " + at(bPrevPath, "") +
+                                 ", expected the same object both places");
+                } else {
+                    Map_set(ab, a, b);
+                    Map_set(bpath, b, path);
+                    if (a !== b || strictEquivalence) {
+                        assertSameClass(a, b, path);
+                        assertSameProto(a, b, path);
+                        assertSameProps(a, b, path);
+                        assertSameValue(Object_isExtensible(a),
+                                        Object_isExtensible(b),
+                                        at(path, ".[[Extensible]]"));
+                    }
+                }
+            }
+
+            check(a, b, "");
+        };
+    })();
+}
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -164,17 +164,17 @@ https://bugzilla.mozilla.org/show_bug.cg
                                       the JS engine filters it out of getOwnPropertyNames */
     ["constructor", "toSource", "toString", "toLocaleString", "valueOf", "watch",
      "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",
      "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__"];
   gPrototypeProperties['Array'] =
     ["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push",
       "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf",
       "forEach", "map", "reduce", "reduceRight", "filter", "some", "every", "find",
-      "findIndex", "copyWithin", "fill", kIteratorSymbol, "entries", "keys", "constructor"];
+      "findIndex", "copyWithin", "fill", "includes", kIteratorSymbol, "entries", "keys", "constructor"];
   if (isNightlyBuild) {
     let pjsMethods = ['mapPar', 'reducePar', 'scanPar', 'scatterPar', 'filterPar'];
     gPrototypeProperties['Array'] = gPrototypeProperties['Array'].concat(pjsMethods);
   }
   for (var c of typedArrayClasses) {
     gPrototypeProperties[c] = ["constructor", "BYTES_PER_ELEMENT"];
   }
   gPrototypeProperties['TypedArray'] =