Browse Source

Array.from()

Dmitry Panov 5 years ago
parent
commit
15eea636ca
5 changed files with 217 additions and 109 deletions
  1. 87 0
      builtin_array.go
  2. 100 0
      builtin_arrray_test.go
  3. 6 12
      builtin_weakset.go
  4. 15 97
      runtime_test.go
  5. 9 0
      tc39_test.go

+ 87 - 0
builtin_array.go

@@ -828,6 +828,92 @@ func (r *Runtime) arrayproto_values(call FunctionCall) Value {
 	return r.createArrayIterator(call.This.ToObject(r), iterationKindValue)
 	return r.createArrayIterator(call.This.ToObject(r), iterationKindValue)
 }
 }
 
 
+func (r *Runtime) checkStdArrayIter(v Value) *arrayObject {
+	if obj, ok := v.(*Object); ok {
+		if arr, ok := obj.self.(*arrayObject); ok &&
+			arr.propValueCount == 0 &&
+			arr.length == int64(len(arr.values)) &&
+			arr.getSym(symIterator) == r.global.arrayValues {
+
+			return arr
+		}
+	}
+
+	return nil
+}
+
+func (r *Runtime) array_from(call FunctionCall) Value {
+	var mapFn func(FunctionCall) Value
+	if mapFnArg := call.Argument(1); mapFnArg != _undefined {
+		if mapFnObj, ok := mapFnArg.(*Object); ok {
+			if fn, ok := mapFnObj.self.assertCallable(); ok {
+				mapFn = fn
+			}
+		}
+		if mapFn == nil {
+			panic(r.NewTypeError("%s is not a function", mapFnArg))
+		}
+	}
+	t := call.Argument(2)
+	items := call.Argument(0)
+	if mapFn == nil && call.This == r.global.Array { // mapFn may mutate the array
+		if arr := r.checkStdArrayIter(items); arr != nil {
+			items := make([]Value, len(arr.values))
+			for i, item := range arr.values {
+				if item == nil {
+					item = nilSafe(arr.get(intToValue(int64(i))))
+				}
+				items[i] = item
+			}
+			return r.newArrayValues(items)
+		}
+	}
+
+	var ctor func(args []Value) *Object
+	if o, ok := call.This.(*Object); ok {
+		if c := getConstructor(o); c != nil {
+			ctor = c
+		}
+	}
+	var arr *Object
+	if usingIterator := toMethod(r.getV(items, symIterator)); usingIterator != nil {
+		if ctor != nil {
+			arr = ctor([]Value{})
+		} else {
+			arr = r.newArrayValues(nil)
+		}
+		iter := r.getIterator(items, usingIterator)
+		k := int64(0)
+		r.iterate(iter, func(val Value) {
+			if mapFn != nil {
+				val = mapFn(FunctionCall{This: t, Arguments: []Value{val, intToValue(k)}})
+			}
+			arr.self.put(intToValue(k), val, true)
+			k++
+		})
+		arr.self.putStr("length", intToValue(k), true)
+	} else {
+		arrayLike := items.ToObject(r)
+		l := toLength(arrayLike.self.getStr("length"))
+		if ctor != nil {
+			arr = ctor([]Value{intToValue(l)})
+		} else {
+			arr = r.newArrayLength(l)
+		}
+		for k := int64(0); k < l; k++ {
+			idx := intToValue(k)
+			item := arrayLike.self.get(idx)
+			if mapFn != nil {
+				item = mapFn(FunctionCall{This: t, Arguments: []Value{item, idx}})
+			}
+			arr.self.put(idx, item, true)
+		}
+		arr.self.putStr("length", intToValue(l), true)
+	}
+
+	return arr
+}
+
 func (r *Runtime) array_isArray(call FunctionCall) Value {
 func (r *Runtime) array_isArray(call FunctionCall) Value {
 	if o, ok := call.Argument(0).(*Object); ok {
 	if o, ok := call.Argument(0).(*Object); ok {
 		if isArray(o) {
 		if isArray(o) {
@@ -888,6 +974,7 @@ func (r *Runtime) createArrayProto(val *Object) objectImpl {
 
 
 func (r *Runtime) createArray(val *Object) objectImpl {
 func (r *Runtime) createArray(val *Object) objectImpl {
 	o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.global.ArrayPrototype, 1)
 	o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.global.ArrayPrototype, 1)
+	o._putProp("from", r.newNativeFunc(r.array_from, nil, "from", nil, 1), true, false, true)
 	o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
 	o._putProp("isArray", r.newNativeFunc(r.array_isArray, nil, "isArray", nil, 1), true, false, true)
 	o.putSym(symSpecies, &valueProperty{
 	o.putSym(symSpecies, &valueProperty{
 		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),
 		getterFunc:   r.newNativeFunc(r.returnThis, nil, "get [Symbol.species]", nil, 0),

+ 100 - 0
builtin_arrray_test.go

@@ -0,0 +1,100 @@
+package goja
+
+import "testing"
+
+func TestArrayProtoProp(t *testing.T) {
+	const SCRIPT = `
+	Object.defineProperty(Array.prototype, '0', {value: 42, configurable: true, writable: false})
+	var a = []
+	a[0] = 1
+	a[0]
+	`
+
+	testScript1(SCRIPT, valueInt(42), t)
+}
+
+func TestArrayDelete(t *testing.T) {
+	const SCRIPT = `
+	var a = [1, 2];
+	var deleted = delete a[0];
+	var undef = a[0] === undefined;
+	var len = a.length;
+
+	deleted && undef && len === 2;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayDeleteNonexisting(t *testing.T) {
+	const SCRIPT = `
+	Array.prototype[0] = 42;
+	var a = [];
+	delete a[0] && a[0] === 42;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArraySetLength(t *testing.T) {
+	const SCRIPT = `
+	var a = [1, 2];
+	var assert0 = a.length == 2;
+	a.length = "1";
+	a.length = 1.0;
+	a.length = 1;
+	var assert1 = a.length == 1;
+	a.length = 2;
+	var assert2 = a.length == 2;
+	assert0 && assert1 && assert2 && a[1] === undefined;
+
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayReverseNonOptimisable(t *testing.T) {
+	const SCRIPT = `
+	var a = [];
+	Object.defineProperty(a, "0", {get: function() {return 42}, set: function(v) {Object.defineProperty(a, "0", {value: v + 1, writable: true, configurable: true})}, configurable: true})
+	a[1] = 43;
+	a.reverse();
+
+	a.length === 2 && a[0] === 44 && a[1] === 42;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArrayPushNonOptimisable(t *testing.T) {
+	const SCRIPT = `
+	Object.defineProperty(Object.prototype, "0", {value: 42});
+	var a = [];
+	var thrown = false;
+	try {
+		a.push(1);
+	} catch (e) {
+		thrown = e instanceof TypeError;
+	}
+	thrown;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestArraySetLengthWithPropItems(t *testing.T) {
+	const SCRIPT = `
+	var a = [1,2,3,4];
+	var thrown = false;
+
+	Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false});
+	try {
+		Object.defineProperty(a, "length", {value: 0, writable: false});
+	} catch (e) {
+		thrown = e instanceof TypeError;
+	}
+	thrown && a.length === 3;
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}

+ 6 - 12
builtin_weakset.go

@@ -127,20 +127,14 @@ func (r *Runtime) builtin_newWeakSet(args []Value) *Object {
 		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
 		if arg := args[0]; arg != nil && arg != _undefined && arg != _null {
 			adder := wso.getStr("add")
 			adder := wso.getStr("add")
 			if adder == r.global.weakSetAdder {
 			if adder == r.global.weakSetAdder {
-				if obj, ok := arg.(*Object); ok {
-					if arr, ok := obj.self.(*arrayObject); ok &&
-						arr.propValueCount == 0 &&
-						arr.length == int64(len(arr.values)) &&
-						arr.getSym(symIterator) == r.global.arrayValues {
-
-						for i, v := range arr.values {
-							if v == nil {
-								v = arr.get(intToValue(int64(i)))
-							}
-							wso.set.add(r.toObject(v))
+				if arr := r.checkStdArrayIter(arg); arr != nil {
+					for i, v := range arr.values {
+						if v == nil {
+							v = arr.get(intToValue(int64(i)))
 						}
 						}
-						return o
+						wso.set.add(r.toObject(v))
 					}
 					}
+					return o
 				}
 				}
 			}
 			}
 			r.populateWeakSetGeneric(o, adder, arg)
 			r.populateWeakSetGeneric(o, adder, arg)

+ 15 - 97
runtime_test.go

@@ -17,57 +17,6 @@ func TestGlobalObjectProto(t *testing.T) {
 	testScript1(SCRIPT, valueTrue, t)
 	testScript1(SCRIPT, valueTrue, t)
 }
 }
 
 
-func TestArrayProtoProp(t *testing.T) {
-	const SCRIPT = `
-	Object.defineProperty(Array.prototype, '0', {value: 42, configurable: true, writable: false})
-	var a = []
-	a[0] = 1
-	a[0]
-	`
-
-	testScript1(SCRIPT, valueInt(42), t)
-}
-
-func TestArrayDelete(t *testing.T) {
-	const SCRIPT = `
-	var a = [1, 2];
-	var deleted = delete a[0];
-	var undef = a[0] === undefined;
-	var len = a.length;
-
-	deleted && undef && len === 2;
-	`
-
-	testScript1(SCRIPT, valueTrue, t)
-}
-
-func TestArrayDeleteNonexisting(t *testing.T) {
-	const SCRIPT = `
-	Array.prototype[0] = 42;
-	var a = [];
-	delete a[0] && a[0] === 42;
-	`
-
-	testScript1(SCRIPT, valueTrue, t)
-}
-
-func TestArraySetLength(t *testing.T) {
-	const SCRIPT = `
-	var a = [1, 2];
-	var assert0 = a.length == 2;
-	a.length = "1";
-	a.length = 1.0;
-	a.length = 1;
-	var assert1 = a.length == 1;
-	a.length = 2;
-	var assert2 = a.length == 2;
-	assert0 && assert1 && assert2 && a[1] === undefined;
-
-	`
-
-	testScript1(SCRIPT, valueTrue, t)
-}
-
 func TestUnicodeString(t *testing.T) {
 func TestUnicodeString(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
 	var s = "Тест";
 	var s = "Тест";
@@ -78,52 +27,6 @@ func TestUnicodeString(t *testing.T) {
 	testScript1(SCRIPT, valueTrue, t)
 	testScript1(SCRIPT, valueTrue, t)
 }
 }
 
 
-func TestArrayReverseNonOptimisable(t *testing.T) {
-	const SCRIPT = `
-	var a = [];
-	Object.defineProperty(a, "0", {get: function() {return 42}, set: function(v) {Object.defineProperty(a, "0", {value: v + 1, writable: true, configurable: true})}, configurable: true})
-	a[1] = 43;
-	a.reverse();
-
-	a.length === 2 && a[0] === 44 && a[1] === 42;
-	`
-
-	testScript1(SCRIPT, valueTrue, t)
-}
-
-func TestArrayPushNonOptimisable(t *testing.T) {
-	const SCRIPT = `
-	Object.defineProperty(Object.prototype, "0", {value: 42});
-	var a = [];
-	var thrown = false;
-	try {
-		a.push(1);
-	} catch (e) {
-		thrown = e instanceof TypeError;
-	}
-	thrown;
-	`
-
-	testScript1(SCRIPT, valueTrue, t)
-}
-
-func TestArraySetLengthWithPropItems(t *testing.T) {
-	const SCRIPT = `
-	var a = [1,2,3,4];
-	var thrown = false;
-
-	Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false});
-	try {
-		Object.defineProperty(a, "length", {value: 0, writable: false});
-	} catch (e) {
-		thrown = e instanceof TypeError;
-	}
-	thrown && a.length === 3;
-	`
-
-	testScript1(SCRIPT, valueTrue, t)
-}
-
 func Test2TierHierarchyProp(t *testing.T) {
 func Test2TierHierarchyProp(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
 	var a = {};
 	var a = {};
@@ -1368,6 +1271,21 @@ func TestToPropertyKey(t *testing.T) {
 	assert.sameValue(o[wrapper], o[sym], "o[wrapper] === o[sym]");
 	assert.sameValue(o[wrapper], o[sym], "o[wrapper] === o[sym]");
 	assert.sameValue(o[wrapper](), "test", "o[wrapper]()");
 	assert.sameValue(o[wrapper](), "test", "o[wrapper]()");
 	assert.sameValue(o[sym](), "test", "o[sym]()");
 	assert.sameValue(o[sym](), "test", "o[sym]()");
+
+	var wrapper1 = {};
+	wrapper1[Symbol.toPrimitive] = function(hint) {
+		if (hint === "string" || hint === "default") {
+			return "1";
+		}
+		if (hint === "number") {
+			return 2;
+		}
+		$ERROR("Unknown hint value "+hint);
+	};
+	var a = [];
+	a[wrapper1] = 42;
+	assert.sameValue(a[1], 42, "a[1]");
+	assert.sameValue(a[1], a[wrapper1], "a[1] === a[wrapper1]");
 	`
 	`
 
 
 	testScript1(TESTLIB+SCRIPT, _undefined, t)
 	testScript1(TESTLIB+SCRIPT, _undefined, t)

+ 9 - 0
tc39_test.go

@@ -51,6 +51,7 @@ var (
 		"test/built-ins/Map/proto-from-ctor-realm.js":             true,
 		"test/built-ins/Map/proto-from-ctor-realm.js":             true,
 		"test/built-ins/Set/proto-from-ctor-realm.js":             true,
 		"test/built-ins/Set/proto-from-ctor-realm.js":             true,
 		"test/built-ins/Object/proto-from-ctor.js":                true,
 		"test/built-ins/Object/proto-from-ctor.js":                true,
+		"test/built-ins/Array/from/proto-from-ctor-realm.js":      true,
 
 
 		// class
 		// class
 		"test/language/statements/class/subclass/builtin-objects/Symbol/symbol-valid-as-extends-value.js": true,
 		"test/language/statements/class/subclass/builtin-objects/Symbol/symbol-valid-as-extends-value.js": true,
@@ -84,6 +85,13 @@ var (
 		"test/built-ins/RegExp/prototype/Symbol.match/get-unicode-error.js":                   true,
 		"test/built-ins/RegExp/prototype/Symbol.match/get-unicode-error.js":                   true,
 		"test/built-ins/RegExp/prototype/Symbol.match/builtin-success-u-return-val-groups.js": true,
 		"test/built-ins/RegExp/prototype/Symbol.match/builtin-success-u-return-val-groups.js": true,
 		"test/built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.js":               true,
 		"test/built-ins/RegExp/prototype/Symbol.match/builtin-infer-unicode.js":               true,
+
+		// object literals
+		"test/built-ins/Array/from/source-object-iterator-1.js": true,
+		"test/built-ins/Array/from/source-object-iterator-2.js": true,
+
+		// Typed arrays
+		"test/built-ins/Array/from/items-is-arraybuffer.js": true,
 	}
 	}
 
 
 	es6WhiteList = map[string]bool{}
 	es6WhiteList = map[string]bool{}
@@ -97,6 +105,7 @@ var (
 		"21.1.3.15",
 		"21.1.3.15",
 		"21.1.3.17",
 		"21.1.3.17",
 		"21.2.5.6",
 		"21.2.5.6",
+		"22.1.2.1",
 		"22.1.2.5",
 		"22.1.2.5",
 		//"22.1.3.1",
 		//"22.1.3.1",
 		"22.1.3.29",
 		"22.1.3.29",