// Copyright 2015 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. // Test the ES2015 @@species feature 'use strict'; // Subclasses of Array construct themselves under map, etc class MyArray extends Array { } assertEquals(MyArray, new MyArray().map(()=>{}).constructor); assertEquals(MyArray, new MyArray().filter(()=>{}).constructor); assertEquals(MyArray, new MyArray().slice().constructor); assertEquals(MyArray, new MyArray().splice().constructor); assertEquals(MyArray, new MyArray().concat([1]).constructor); assertEquals(1, new MyArray().concat([1])[0]); // Subclasses can override @@species to return the another class class MyOtherArray extends Array { static get [Symbol.species]() { return MyArray; } } assertEquals(MyArray, new MyOtherArray().map(()=>{}).constructor); assertEquals(MyArray, new MyOtherArray().filter(()=>{}).constructor); assertEquals(MyArray, new MyOtherArray().slice().constructor); assertEquals(MyArray, new MyOtherArray().splice().constructor); assertEquals(MyArray, new MyOtherArray().concat().constructor); // Array methods on non-arrays return arrays class MyNonArray extends Array { static get [Symbol.species]() { return MyObject; } } class MyObject { } assertEquals(MyObject, Array.prototype.map.call(new MyNonArray(), ()=>{}).constructor); assertEquals(MyObject, Array.prototype.filter.call(new MyNonArray(), ()=>{}).constructor); assertEquals(MyObject, Array.prototype.slice.call(new MyNonArray()).constructor); assertEquals(MyObject, Array.prototype.splice.call(new MyNonArray()).constructor); assertEquals(MyObject, Array.prototype.concat.call(new MyNonArray()).constructor); assertEquals(undefined, Array.prototype.map.call(new MyNonArray(), ()=>{}).length); assertEquals(undefined, Array.prototype.filter.call(new MyNonArray(), ()=>{}).length); assertEquals(undefined, Array.prototype.concat.call(new MyNonArray(), ()=>{}).length); // slice and splice actually do explicitly define the length for some reason assertEquals(0, Array.prototype.slice.call(new MyNonArray()).length); assertEquals(0, Array.prototype.splice.call(new MyNonArray()).length); // Cross-realm Arrays build same-realm arrays var realm = Realm.create(); assertEquals(Array, Array.prototype.map.call( Realm.eval(realm, "[]"), ()=>{}).constructor); assertFalse(Array === Realm.eval(realm, "[]").map(()=>{}).constructor); assertFalse(Array === Realm.eval(realm, "[].map(()=>{}).constructor")); assertEquals(Array, Array.prototype.concat.call( Realm.eval(realm, "[]")).constructor); // Defaults when constructor or @@species is missing or non-constructor class MyDefaultArray extends Array { static get [Symbol.species]() { return undefined; } } assertEquals(Array, new MyDefaultArray().map(()=>{}).constructor); class MyOtherDefaultArray extends Array { } assertEquals(MyOtherDefaultArray, new MyOtherDefaultArray().map(()=>{}).constructor); MyOtherDefaultArray.prototype.constructor = undefined; assertEquals(Array, new MyOtherDefaultArray().map(()=>{}).constructor); assertEquals(Array, new MyOtherDefaultArray().concat().constructor); // Exceptions propagated when getting constructor @@species throws class SpeciesError extends Error { } class ConstructorError extends Error { } class MyThrowingArray extends Array { static get [Symbol.species]() { throw new SpeciesError; } } assertThrows(() => new MyThrowingArray().map(()=>{}), SpeciesError); Object.defineProperty(MyThrowingArray.prototype, 'constructor', { get() { throw new ConstructorError; } }); assertThrows(() => new MyThrowingArray().map(()=>{}), ConstructorError); // Previously unexpected errors from setting properties in arrays throw class FrozenArray extends Array { constructor(...args) { super(...args); Object.freeze(this); } } assertThrows(() => new FrozenArray([1]).map(()=>0), TypeError); assertThrows(() => new FrozenArray([1]).filter(()=>true), TypeError); assertThrows(() => new FrozenArray([1]).slice(0, 1), TypeError); assertThrows(() => new FrozenArray([1]).splice(0, 1), TypeError); assertThrows(() => new FrozenArray([]).concat([1]), TypeError); // Verify call counts and constructor parameters var count; var params; class MyObservedArray extends Array { constructor(...args) { super(...args); params = args; } static get [Symbol.species]() { count++ return this; } } count = 0; params = undefined; assertEquals(MyObservedArray, new MyObservedArray().map(()=>{}).constructor); assertEquals(1, count); assertArrayEquals([0], params); count = 0; params = undefined; assertEquals(MyObservedArray, new MyObservedArray().filter(()=>{}).constructor); assertEquals(1, count); assertArrayEquals([0], params); count = 0; params = undefined; assertEquals(MyObservedArray, new MyObservedArray().concat().constructor); assertEquals(1, count); assertArrayEquals([0], params); count = 0; params = undefined; assertEquals(MyObservedArray, new MyObservedArray().slice().constructor); assertEquals(1, count); assertArrayEquals([0], params); count = 0; params = undefined; assertEquals(MyObservedArray, new MyObservedArray().splice().constructor); assertEquals(1, count); assertArrayEquals([0], params); // @@species constructor can be a Proxy, and the realm access doesn't // crash class MyProxyArray extends Array { } let ProxyArray = new Proxy(MyProxyArray, {}); MyProxyArray.constructor = ProxyArray; assertEquals(MyProxyArray, new ProxyArray().map(()=>{}).constructor);