aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobin Burchell <robin.burchell@crimson.no>2018-05-26 20:16:43 +0200
committerRobin Burchell <robin.burchell@crimson.no>2018-05-30 18:13:13 +0000
commitb30d628c24ddb43cd7fe96e19459f4af24a9007b (patch)
treedadc6530adc92beb373bda18f9b2a561a03c294a
parente7dab999f7f662b7045e61ee080df3d00da18a92 (diff)
qv4arrayobject: Implement Array.from from ES7
The remaining failures look to be down to the constructor hack, but this gets us a good part of the way there already. Change-Id: I6e57828a56edddd5cb70560b6f50dfc6311c88ae Reviewed-by: Lars Knoll <lars.knoll@qt.io>
-rw-r--r--src/qml/jsruntime/qv4arrayobject.cpp182
-rw-r--r--src/qml/jsruntime/qv4arrayobject_p.h1
-rw-r--r--src/qml/jsruntime/qv4iterator.cpp1
-rw-r--r--tests/auto/qml/ecmascripttests/TestExpectations37
4 files changed, 169 insertions, 52 deletions
diff --git a/src/qml/jsruntime/qv4arrayobject.cpp b/src/qml/jsruntime/qv4arrayobject.cpp
index 4a78bbcd9a..c106d5531f 100644
--- a/src/qml/jsruntime/qv4arrayobject.cpp
+++ b/src/qml/jsruntime/qv4arrayobject.cpp
@@ -39,6 +39,7 @@
****************************************************************************/
#include "qv4arrayobject_p.h"
+#include "qv4objectiterator_p.h"
#include "qv4arrayiterator_p.h"
#include "qv4sparsearray_p.h"
#include "qv4objectproto_p.h"
@@ -96,6 +97,7 @@ void ArrayPrototype::init(ExecutionEngine *engine, Object *ctor)
ctor->defineReadonlyProperty(engine->id_prototype(), (o = this));
ctor->defineDefaultProperty(QStringLiteral("isArray"), method_isArray, 1);
ctor->defineDefaultProperty(QStringLiteral("of"), method_of, 0);
+ ctor->defineDefaultProperty(QStringLiteral("from"), method_from, 1);
ctor->addSymbolSpecies();
defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
@@ -140,48 +142,198 @@ ReturnedValue ArrayPrototype::method_isArray(const FunctionObject *, const Value
return Encode(isArray);
}
-ReturnedValue ArrayPrototype::method_of(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc)
+ScopedObject createObjectFromCtorOrArray(Scope &scope, ScopedFunctionObject ctor, bool useLen, int len)
{
- Scope scope(builtin);
- ScopedObject object(scope, Primitive::undefinedValue());
- ScopedFunctionObject that(scope, thisObject);
+ ScopedObject a(scope, Primitive::undefinedValue());
- if (that) {
+ if (ctor) {
// ### the spec says that we should only call constructors if
// IsConstructor(that), but we have no way of knowing if a builtin is a
// constructor. so for the time being, just try call it, and silence any
// exceptions-- this is not ideal, as the spec also says that we should
// return on exception.
- ScopedValue argument(scope, QV4::Encode(argc));
- object = ScopedObject(scope, that->callAsConstructor(argument, 1));
- if (scope.engine->hasException) {
+ //
+ // this also isn't completely kosher. for instance:
+ // Array.from.call(Object, []).constructor == Object
+ // is expected by the tests, but naturally, we get Number.
+ ScopedValue argument(scope, useLen ? QV4::Encode(len) : Primitive::undefinedValue());
+ a = ctor->callAsConstructor(argument, useLen ? 1 : 0);
+ if (scope.engine->hasException)
scope.engine->catchException(); // probably not a constructor, then.
+ }
+
+ if (!a) {
+ a = scope.engine->newArrayObject(len);
+ }
+
+ return a;
+}
+
+ReturnedValue ArrayPrototype::method_from(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc)
+{
+ Scope scope(builtin);
+ ScopedFunctionObject thatCtor(scope, thisObject);
+ ScopedObject itemsObject(scope, argv[0]);
+ bool usingIterator = false;
+
+ if (itemsObject) {
+ // If the object claims to support iterators, then let's try use them.
+ ScopedValue it(scope, itemsObject->get(scope.engine->symbol_iterator()));
+ if (!it->isNullOrUndefined()) {
+ ScopedFunctionObject itfunc(scope, it);
+ if (!itfunc)
+ return scope.engine->throwTypeError();
+ usingIterator = true;
}
}
- if (!object) {
- object = ScopedObject(scope, scope.engine->newArrayObject());
+ ScopedFunctionObject mapfn(scope, Primitive::undefinedValue());
+ Value *mapArguments = nullptr;
+ if (argc > 1) {
+ mapfn = ScopedFunctionObject(scope, argv[1]);
+ if (!mapfn)
+ return scope.engine->throwTypeError(QString::fromLatin1("%1 is not a function").arg(argv[1].toQStringNoThrow()));
+ mapArguments = scope.alloc(2);
+ }
+
+ ScopedValue thisArg(scope);
+ if (argc > 2)
+ thisArg = argv[2];
+
+ if (usingIterator) {
+ // Item iteration supported, so let's go ahead and try use that.
+ ScopedObject a(createObjectFromCtorOrArray(scope, thatCtor, false, 0));
+ CHECK_EXCEPTION();
+ ScopedObject iterator(scope, Runtime::method_getIterator(scope.engine, itemsObject, true));
+ CHECK_EXCEPTION(); // symbol_iterator threw; whoops.
+ if (!iterator) {
+ return scope.engine->throwTypeError(); // symbol_iterator wasn't an object.
+ }
+
+ qint64 k = 0;
+ ScopedValue mappedValue(scope);
+ Value *nextValue = scope.alloc(1);
+ ScopedValue done(scope);
+
+ // The loop below pulls out all the properties using the iterator, and
+ // sets them into the created array.
+ forever {
+ if (k > (static_cast<qint64>(1) << 53) - 1) {
+ ScopedValue falsey(scope, Encode(false));
+ ScopedValue error(scope, scope.engine->throwTypeError());
+ return Runtime::method_iteratorClose(scope.engine, iterator, falsey);
+ }
+
+ // Retrieve the next value. If the iteration ends, we're done here.
+ done = Value::fromReturnedValue(Runtime::method_iteratorNext(scope.engine, iterator, nextValue));
+ CHECK_EXCEPTION();
+ if (done->toBoolean()) {
+ if (ArrayObject *ao = a->as<ArrayObject>()) {
+ ao->setArrayLengthUnchecked(k);
+ } else {
+ a->set(scope.engine->id_length(), Primitive::fromDouble(k), QV4::Object::DoThrowOnRejection);
+ CHECK_EXCEPTION();
+ }
+ return a.asReturnedValue();
+ }
+
+ if (mapfn) {
+ mapArguments[0] = *nextValue;
+ mapArguments[1] = Primitive::fromDouble(k);
+ mappedValue = mapfn->call(thisArg, mapArguments, 2);
+ if (scope.engine->hasException)
+ return Runtime::method_iteratorClose(scope.engine, iterator, Primitive::fromBoolean(false));
+ } else {
+ mappedValue = *nextValue;
+ }
+
+ if (!a->hasOwnProperty(k)) {
+ a->arraySet(k, mappedValue);
+ } else {
+ // Don't return: we need to close the iterator.
+ scope.engine->throwTypeError(QString::fromLatin1("Cannot redefine property: %1").arg(k));
+ }
+
+ if (scope.engine->hasException) {
+ ScopedValue falsey(scope, Encode(false));
+ return Runtime::method_iteratorClose(scope.engine, iterator, falsey);
+ }
+
+ k++;
+ }
+
+ // the return is hidden up in the loop above, when iteration finishes.
+ } else {
+ // Array-like fallback. We request properties by index, and set them on
+ // the return object.
+ ScopedObject arrayLike(scope, argv[0].toObject(scope.engine));
+ if (!arrayLike)
+ return scope.engine->throwTypeError(QString::fromLatin1("Cannot convert %1 to object").arg(argv[0].toQStringNoThrow()));
+ qint64 len = arrayLike->getLength();
+ ScopedObject a(createObjectFromCtorOrArray(scope, thatCtor, true, len));
CHECK_EXCEPTION();
+
+ qint64 k = 0;
+ ScopedValue mappedValue(scope, Primitive::undefinedValue());
+ ScopedValue kValue(scope);
+ while (k < len) {
+ kValue = arrayLike->getIndexed(k);
+ CHECK_EXCEPTION();
+
+ if (mapfn) {
+ mapArguments[0] = kValue;
+ mapArguments[1] = Primitive::fromDouble(k);
+ mappedValue = mapfn->call(thisArg, mapArguments, 2);
+ CHECK_EXCEPTION();
+ } else {
+ mappedValue = kValue;
+ }
+
+ if (a->hasOwnProperty(k))
+ return scope.engine->throwTypeError(QString::fromLatin1("Cannot redefine property: %1").arg(k));
+
+ a->arraySet(k, mappedValue);
+ CHECK_EXCEPTION();
+
+ k++;
+ }
+
+ if (ArrayObject *ao = a->as<ArrayObject>()) {
+ ao->setArrayLengthUnchecked(k);
+ } else {
+ a->set(scope.engine->id_length(), Primitive::fromDouble(k), QV4::Object::DoThrowOnRejection);
+ CHECK_EXCEPTION();
+ }
+ return a.asReturnedValue();
}
+}
+
+ReturnedValue ArrayPrototype::method_of(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc)
+{
+ Scope scope(builtin);
+ ScopedFunctionObject that(scope, thisObject);
+ ScopedObject a(createObjectFromCtorOrArray(scope, that, true, argc));
+ CHECK_EXCEPTION();
+
int k = 0;
while (k < argc) {
- if (object->hasOwnProperty(k)) {
+ if (a->hasOwnProperty(k)) {
return scope.engine->throwTypeError(QString::fromLatin1("Cannot redefine property: %1").arg(k));
}
- object->arraySet(k, argv[k]);
+ a->arraySet(k, argv[k]);
CHECK_EXCEPTION();
k++;
}
// ArrayObject updates its own length, and will throw if we try touch it.
- if (!object->as<ArrayObject>()) {
- object->set(scope.engine->id_length(), ScopedValue(scope, Primitive::fromDouble(argc)), QV4::Object::DoThrowOnRejection);
+ if (!a->as<ArrayObject>()) {
+ a->set(scope.engine->id_length(), Primitive::fromDouble(argc), QV4::Object::DoThrowOnRejection);
CHECK_EXCEPTION();
}
- return object.asReturnedValue();
+ return a.asReturnedValue();
}
ReturnedValue ArrayPrototype::method_toString(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc)
diff --git a/src/qml/jsruntime/qv4arrayobject_p.h b/src/qml/jsruntime/qv4arrayobject_p.h
index 975007034f..f1acfed7e7 100644
--- a/src/qml/jsruntime/qv4arrayobject_p.h
+++ b/src/qml/jsruntime/qv4arrayobject_p.h
@@ -79,6 +79,7 @@ struct ArrayPrototype: ArrayObject
void init(ExecutionEngine *engine, Object *ctor);
static ReturnedValue method_isArray(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
+ static ReturnedValue method_from(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_of(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_toString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_toLocaleString(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
diff --git a/src/qml/jsruntime/qv4iterator.cpp b/src/qml/jsruntime/qv4iterator.cpp
index 3e2c2582f6..df8000a8f7 100644
--- a/src/qml/jsruntime/qv4iterator.cpp
+++ b/src/qml/jsruntime/qv4iterator.cpp
@@ -61,3 +61,4 @@ ReturnedValue IteratorPrototype::createIterResultObject(ExecutionEngine *engine,
obj->set(ScopedString(scope, engine->newString(QStringLiteral("done"))), Primitive::fromBoolean(done), Object::DoNotThrow);
return obj->asReturnedValue();
}
+
diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations
index 8eaa4c8b6b..966e30e08e 100644
--- a/tests/auto/qml/ecmascripttests/TestExpectations
+++ b/tests/auto/qml/ecmascripttests/TestExpectations
@@ -20,45 +20,8 @@ language/statements/with/let-identifier-with-newline.js sloppyFails
# ----- test failures that should be fixed
-built-ins/Array/from/Array.from-descriptor.js fails
-built-ins/Array/from/Array.from-name.js fails
-built-ins/Array/from/Array.from_arity.js fails
-built-ins/Array/from/Array.from_forwards-length-for-array-likes.js fails
-built-ins/Array/from/calling-from-valid-1-noStrict.js sloppyFails
-built-ins/Array/from/calling-from-valid-1-onlyStrict.js strictFails
-built-ins/Array/from/calling-from-valid-2.js fails
-built-ins/Array/from/elements-added-after.js fails
-built-ins/Array/from/elements-deleted-after.js fails
-built-ins/Array/from/elements-updated-after.js fails
-built-ins/Array/from/from-array.js fails
-built-ins/Array/from/from-string.js fails
-built-ins/Array/from/get-iter-method-err.js fails
-built-ins/Array/from/items-is-arraybuffer.js fails
-built-ins/Array/from/iter-adv-err.js fails
built-ins/Array/from/iter-cstm-ctor-err.js fails
-built-ins/Array/from/iter-cstm-ctor.js fails
-built-ins/Array/from/iter-get-iter-err.js fails
-built-ins/Array/from/iter-get-iter-val-err.js fails
-built-ins/Array/from/iter-map-fn-args.js fails
-built-ins/Array/from/iter-map-fn-err.js fails
-built-ins/Array/from/iter-map-fn-return.js fails
-built-ins/Array/from/iter-map-fn-this-arg.js fails
-built-ins/Array/from/iter-map-fn-this-non-strict.js sloppyFails
-built-ins/Array/from/iter-map-fn-this-strict.js strictFails
-built-ins/Array/from/iter-set-elem-prop-err.js fails
-built-ins/Array/from/iter-set-elem-prop.js fails
-built-ins/Array/from/iter-set-length-err.js fails
-built-ins/Array/from/iter-set-length.js fails
-built-ins/Array/from/mapfn-throws-exception.js fails
built-ins/Array/from/proto-from-ctor-realm.js fails
-built-ins/Array/from/source-array-boundary.js fails
-built-ins/Array/from/source-object-constructor.js fails
-built-ins/Array/from/source-object-iterator-1.js fails
-built-ins/Array/from/source-object-iterator-2.js fails
-built-ins/Array/from/source-object-length.js fails
-built-ins/Array/from/source-object-missing.js fails
-built-ins/Array/from/source-object-without.js fails
-built-ins/Array/from/this-null.js fails
built-ins/Array/isArray/proxy-revoked.js fails
built-ins/Array/isArray/proxy.js fails
built-ins/Array/length/define-own-prop-length-overflow-realm.js fails