diff options
author | Robin Burchell <robin.burchell@crimson.no> | 2018-05-26 20:16:43 +0200 |
---|---|---|
committer | Robin Burchell <robin.burchell@crimson.no> | 2018-05-30 18:13:13 +0000 |
commit | b30d628c24ddb43cd7fe96e19459f4af24a9007b (patch) | |
tree | dadc6530adc92beb373bda18f9b2a561a03c294a | |
parent | e7dab999f7f662b7045e61ee080df3d00da18a92 (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.cpp | 182 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4arrayobject_p.h | 1 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4iterator.cpp | 1 | ||||
-rw-r--r-- | tests/auto/qml/ecmascripttests/TestExpectations | 37 |
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 |