var BUGNUMBER = 1165052; var summary = 'Use ArraySpeciesCreate in Array.prototype.{concat,filter,map,slice,splice}.'; print(BUGNUMBER + ": " + summary); function test(funcName, args, expectedLength, expectedLogs) { // modified @@species function FakeArray(n) { this.length = n; } var a = [1, 2, 3, 4, 5]; a.constructor = { [Symbol.species]: FakeArray }; var b = a[funcName](...args); assertEq(b.constructor, FakeArray); function FakeArrayWithSpecies(n) { this.length = n; } FakeArrayWithSpecies[Symbol.species] = FakeArrayWithSpecies; a = [1, 2, 3, 4, 5]; a.constructor = FakeArrayWithSpecies; b = a[funcName](...args); assertEq(b.constructor, FakeArrayWithSpecies); function FakeArrayWithHook(n) { return new Proxy(new FakeArray(n), { set(that, name, value) { logs += "set:" + name + ":" + value + ","; return true; }, defineProperty(that, name, desc) { logs += "define:" + name + ":" + desc.value + ":" + desc.configurable + ":" + desc.enumerable + ":" + desc.writable + ","; return true; } }); } var logs = ""; var ctorProxy = new Proxy({}, { get(that, name) { logs += "c-get:" + name.toString() + ","; if (name == Symbol.species) return FakeArrayWithHook; return undefined; } }); a = new Proxy([1, 2, 3, 4, 5], { get(that, name) { logs += "get:" + name.toString() + ","; if (name == "constructor") return ctorProxy; return that[name]; } }); b = a[funcName](...args); assertEq(b.constructor, FakeArray); assertEq(Object.keys(b).sort().join(","), "length"); assertEq(b.length, expectedLength); assertEq(logs, expectedLogs); // no @@species a = [1, 2, 3, 4, 5]; a.constructor = FakeArray; b = a[funcName](...args); assertEq(b.constructor, Array); a = [1, 2, 3, 4, 5]; a.constructor = { [Symbol.species]: undefined }; b = a[funcName](...args); assertEq(b.constructor, Array); a = [1, 2, 3, 4, 5]; a.constructor = { [Symbol.species]: null }; b = a[funcName](...args); assertEq(b.constructor, Array); // invalid @@species for (var species of [0, 1.1, true, false, "a", /a/, Symbol.iterator, [], {}]) { a = [1, 2, 3, 4, 5]; a.constructor = { [Symbol.species]: species }; assertThrowsInstanceOf(() => a[funcName](...args), TypeError); } // undefined constructor a = [1, 2, 3, 4, 5]; a.constructor = undefined; b = a[funcName](...args); assertEq(b.constructor, Array); // invalid constructor for (var ctor of [null, 0, 1.1, true, false, "a", Symbol.iterator]) { a = [1, 2, 3, 4, 5]; a.constructor = ctor; assertThrowsInstanceOf(() => a[funcName](...args), TypeError); } // not an array a = new Proxy({ 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, length: 5, [funcName]: Array.prototype[funcName] }, { get(that, name) { assertEq(name !== "constructor", true); return that[name]; } }); b = a[funcName](...args); assertEq(b.constructor, Array); // @@species from different global var g = newGlobal(); g.eval("function FakeArray(n) { this.length = n; }"); a = [1, 2, 3, 4, 5]; a.constructor = { [Symbol.species]: g.FakeArray }; b = a[funcName](...args); assertEq(b.constructor, g.FakeArray); a = [1, 2, 3, 4, 5]; a.constructor = { [Symbol.species]: g.Array }; b = a[funcName](...args); assertEq(b.constructor, g.Array); // constructor from different global g.eval("function FakeArrayWithSpecies(n) { this.length = n; }"); g.eval("FakeArrayWithSpecies[Symbol.species] = FakeArrayWithSpecies;"); a = [1, 2, 3, 4, 5]; a.constructor = g.FakeArrayWithSpecies; b = a[funcName](...args); assertEq(b.constructor, g.FakeArrayWithSpecies); g.eval("var a = [1, 2, 3, 4, 5];"); b = Array.prototype[funcName].apply(g.a, args); assertEq(b.constructor, Array); // running in different global b = g.a[funcName](...args); assertEq(b.constructor, g.Array); // subclasses // not-modified @@species eval(` class ${funcName}Class extends Array { } a = new ${funcName}Class(1, 2, 3, 4, 5); b = a[funcName](...args); assertEq(b.constructor, ${funcName}Class); `); // modified @@species eval(` class ${funcName}Class2 extends Array { static get [Symbol.species]() { return Date; } } a = new ${funcName}Class2(1, 2, 3, 4, 5); b = a[funcName](...args); assertEq(b.constructor, Date); `); } test("concat", [], 0, "get:concat,get:constructor,c-get:Symbol(Symbol.species),get:Symbol(Symbol.isConcatSpreadable),get:length,get:0,define:0:1:true:true:true,get:1,define:1:2:true:true:true,get:2,define:2:3:true:true:true,get:3,define:3:4:true:true:true,get:4,define:4:5:true:true:true,set:length:5,"); test("filter", [x => x % 2], 0, "get:filter,get:length,get:constructor,c-get:Symbol(Symbol.species),get:0,define:0:1:true:true:true,get:1,get:2,define:1:3:true:true:true,get:3,get:4,define:2:5:true:true:true,"); test("map", [x => x * 2], 5, "get:map,get:length,get:constructor,c-get:Symbol(Symbol.species),get:0,define:0:2:true:true:true,get:1,define:1:4:true:true:true,get:2,define:2:6:true:true:true,get:3,define:3:8:true:true:true,get:4,define:4:10:true:true:true,"); test("slice", [], 5, "get:slice,get:length,get:constructor,c-get:Symbol(Symbol.species),get:0,define:0:1:true:true:true,get:1,define:1:2:true:true:true,get:2,define:2:3:true:true:true,get:3,define:3:4:true:true:true,get:4,define:4:5:true:true:true,set:length:5,"); test("splice", [0, 5], 5, "get:splice,get:length,get:constructor,c-get:Symbol(Symbol.species),get:0,define:0:1:true:true:true,get:1,define:1:2:true:true:true,get:2,define:2:3:true:true:true,get:3,define:3:4:true:true:true,get:4,define:4:5:true:true:true,set:length:5,"); if (typeof reportCompare === "function") reportCompare(true, true);