qml: Override the new Object::instanceOf hook to allow QML type checking
[ChangeLog][QtQml] The instanceof keyword in JavaScript has been extended to work on QML types and instances. This means that you are now able to use it to verify that a var is indeed the type you expect (e.g. someVar instanceof Rectangle). Note that one of the added tests revealed a slight shortcoming in the QML type system (QTBUG-58477). For now, we should keep consistency and work to address the problem universally in the future. Change-Id: I7d9bf9b64cfd037908de1ae51b01065eacb95abe Task-number: QTBUG-24799 Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
This commit is contained in:
parent
212ccd5962
commit
34ff6c40c1
|
@ -74,6 +74,19 @@ Note that QML makes the following modifications to native objects:
|
|||
\li Locale-aware conversion functions are added to the \l Date and \l Number prototypes.
|
||||
\endlist
|
||||
|
||||
In addition, QML also extends the behavior of the instanceof function to
|
||||
allow for type checking against QML types. This means that you may use it to
|
||||
verify that a variable is indeed the type you expect, for example:
|
||||
|
||||
\qml
|
||||
var v = something();
|
||||
if (!v instanceof Item) {
|
||||
throw new TypeError("I need an Item type!");
|
||||
}
|
||||
|
||||
...
|
||||
\endqml
|
||||
|
||||
|
||||
\section1 JavaScript Environment Restrictions
|
||||
|
||||
|
|
|
@ -342,4 +342,44 @@ bool QmlTypeWrapper::isEqualTo(Managed *a, Managed *b)
|
|||
return false;
|
||||
}
|
||||
|
||||
ReturnedValue QmlTypeWrapper::instanceOf(const Object *typeObject, const Value &var)
|
||||
{
|
||||
Q_ASSERT(typeObject->as<QV4::QmlTypeWrapper>());
|
||||
const QV4::QmlTypeWrapper *typeWrapper = static_cast<const QV4::QmlTypeWrapper *>(typeObject);
|
||||
QV4::ExecutionEngine *engine = typeObject->internalClass()->engine;
|
||||
QQmlEnginePrivate *qenginepriv = QQmlEnginePrivate::get(engine->qmlEngine());
|
||||
|
||||
// can only compare a QObject* against a QML type
|
||||
const QObjectWrapper *wrapper = var.as<QObjectWrapper>();
|
||||
if (!wrapper)
|
||||
return engine->throwTypeError();
|
||||
|
||||
// in case the wrapper outlived the QObject*
|
||||
const QObject *wrapperObject = wrapper->object();
|
||||
if (!wrapperObject)
|
||||
return engine->throwTypeError();
|
||||
|
||||
const int myTypeId = typeWrapper->d()->type->typeId();
|
||||
QQmlMetaObject myQmlType;
|
||||
if (myTypeId == 0) {
|
||||
// we're a composite type; a composite type cannot be equal to a
|
||||
// non-composite object instance (Rectangle{} is never an instance of
|
||||
// CustomRectangle)
|
||||
QQmlData *theirDData = QQmlData::get(wrapperObject, /*create=*/false);
|
||||
Q_ASSERT(theirDData); // must exist, otherwise how do we have a QObjectWrapper for it?!
|
||||
if (!theirDData->compilationUnit)
|
||||
return Encode(false);
|
||||
|
||||
QQmlTypeData *td = qenginepriv->typeLoader.getType(typeWrapper->d()->type->sourceUrl());
|
||||
CompiledData::CompilationUnit *cu = td->compilationUnit();
|
||||
myQmlType = qenginepriv->metaObjectForType(cu->metaTypeId);
|
||||
} else {
|
||||
myQmlType = qenginepriv->metaObjectForType(myTypeId);
|
||||
}
|
||||
|
||||
const QMetaObject *theirType = wrapperObject->metaObject();
|
||||
|
||||
return QV4::Encode(QQmlMetaObject::canConvert(theirType, myQmlType));
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
@ -103,7 +103,7 @@ struct Q_QML_EXPORT QmlTypeWrapper : Object
|
|||
static bool put(Managed *m, String *name, const Value &value);
|
||||
static PropertyAttributes query(const Managed *, String *name);
|
||||
static bool isEqualTo(Managed *that, Managed *o);
|
||||
|
||||
static ReturnedValue instanceOf(const Object *typeObject, const Value &var);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -8170,6 +8170,8 @@ void tst_qqmlecmascript::stringify_qtbug_50592()
|
|||
QCOMPARE(obj->property("source").toString(), QString::fromLatin1("http://example.org/some_nonexistant_image.png"));
|
||||
}
|
||||
|
||||
// Tests for the JS-only instanceof. Tests for the QML extensions for
|
||||
// instanceof belong in tst_qqmllanguage!
|
||||
void tst_qqmlecmascript::instanceof_data()
|
||||
{
|
||||
QTest::addColumn<QString>("setupCode");
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
MouseArea {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
Rectangle {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
Rectangle {
|
||||
property int somethingCustom: 0
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
CustomRectangle 1.0 CustomRectangle.qml
|
||||
CustomMouseArea 1.0 CustomMouseArea.qml
|
|
@ -0,0 +1,13 @@
|
|||
import QtQml 2.0
|
||||
|
||||
QtObject {
|
||||
id: qtobjectInstance
|
||||
|
||||
property Timer aTimer: Timer {
|
||||
id: timerInstance
|
||||
}
|
||||
|
||||
property Connections aConnections: Connections {
|
||||
id: connectionsInstance
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import QtQml 2.0 as QmlImport
|
||||
|
||||
QmlImport.QtObject {
|
||||
id: qtobjectInstance
|
||||
|
||||
property QmlImport.Timer aTimer: QmlImport.Timer {
|
||||
id: timerInstance
|
||||
}
|
||||
|
||||
property QmlImport.Connections aConnections: QmlImport.Connections {
|
||||
id: connectionsInstance
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Item {
|
||||
id: itemInstance
|
||||
|
||||
Rectangle {
|
||||
id: rectangleInstance
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaInstance
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import QtQuick 2.0
|
||||
import "instanceOf"
|
||||
|
||||
Item {
|
||||
id: itemInstance
|
||||
|
||||
Rectangle {
|
||||
id: rectangleInstance
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaInstance
|
||||
}
|
||||
|
||||
CustomRectangle {
|
||||
id: customRectangleInstance
|
||||
}
|
||||
CustomRectangleWithProp {
|
||||
id: customRectangleWithPropInstance
|
||||
}
|
||||
CustomMouseArea {
|
||||
id: customMouseAreaInstance
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import QtQuick 2.0 as QuickImport
|
||||
import "instanceOf" as CustomImport
|
||||
|
||||
QuickImport.Item {
|
||||
id: itemInstance
|
||||
|
||||
QuickImport.Rectangle {
|
||||
id: rectangleInstance
|
||||
}
|
||||
|
||||
QuickImport.MouseArea {
|
||||
id: mouseAreaInstance
|
||||
}
|
||||
|
||||
CustomImport.CustomRectangle {
|
||||
id: customRectangleInstance
|
||||
}
|
||||
CustomImport.CustomRectangleWithProp {
|
||||
id: customRectangleWithPropInstance
|
||||
}
|
||||
CustomImport.CustomMouseArea {
|
||||
id: customMouseAreaInstance
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -263,6 +263,9 @@ private slots:
|
|||
void qmlTypeCanBeResolvedByName_data();
|
||||
void qmlTypeCanBeResolvedByName();
|
||||
|
||||
void instanceof_data();
|
||||
void instanceof();
|
||||
|
||||
private:
|
||||
QQmlEngine engine;
|
||||
QStringList defaultImportPathList;
|
||||
|
@ -309,7 +312,7 @@ private:
|
|||
if (!errorfile) { \
|
||||
if (qgetenv("DEBUG") != "" && !component.errors().isEmpty()) \
|
||||
qWarning() << "Unexpected Errors:" << component.errors(); \
|
||||
QVERIFY(!component.isError()); \
|
||||
QVERIFY2(!component.isError(), qPrintable(component.errorString())); \
|
||||
QVERIFY(component.errors().isEmpty()); \
|
||||
} else { \
|
||||
DETERMINE_ERRORS(errorfile,expected,actual);\
|
||||
|
@ -4338,6 +4341,200 @@ void tst_qqmllanguage::qmlTypeCanBeResolvedByName()
|
|||
QVERIFY(!o.isNull());
|
||||
}
|
||||
|
||||
// Tests for the QML-only extensions of instanceof. Tests for the regular JS
|
||||
// instanceof belong in tst_qqmlecmascript!
|
||||
void tst_qqmllanguage::instanceof_data()
|
||||
{
|
||||
QTest::addColumn<QUrl>("documentToTestIn");
|
||||
QTest::addColumn<QVariant>("expectedValue");
|
||||
|
||||
// so the way this works is that the name of the test tag defines the test
|
||||
// to run.
|
||||
//
|
||||
// the expectedValue is either a boolean true or false for whether the two
|
||||
// operands are indeed an instanceof each other, or a string for the
|
||||
// expected error message.
|
||||
|
||||
// assert that basic types don't convert to QObject
|
||||
QTest::newRow("1 instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant("TypeError: Type error");
|
||||
QTest::newRow("true instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant("TypeError: Type error");
|
||||
QTest::newRow("\"foobar\" instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant("TypeError: Type error");
|
||||
|
||||
// assert that Managed don't either
|
||||
QTest::newRow("new String(\"foobar\") instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant("TypeError: Type error");
|
||||
QTest::newRow("new Object() instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant("TypeError: Type error");
|
||||
QTest::newRow("new Date() instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant("TypeError: Type error");
|
||||
|
||||
// test that simple QtQml comparisons work
|
||||
QTest::newRow("qtobjectInstance instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("qtobjectInstance instanceof Timer")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("timerInstance instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("timerInstance instanceof Timer")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("connectionsInstance instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("connectionsInstance instanceof Timer")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("connectionsInstance instanceof Connections")
|
||||
<< testFileUrl("instanceof_qtqml.qml")
|
||||
<< QVariant(true);
|
||||
|
||||
// make sure they still work when imported with a qualifier
|
||||
QTest::newRow("qtobjectInstance instanceof QmlImport.QtObject")
|
||||
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("qtobjectInstance instanceof QmlImport.Timer")
|
||||
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("timerInstance instanceof QmlImport.QtObject")
|
||||
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("timerInstance instanceof QmlImport.Timer")
|
||||
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("connectionsInstance instanceof QmlImport.QtObject")
|
||||
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("connectionsInstance instanceof QmlImport.Timer")
|
||||
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("connectionsInstance instanceof QmlImport.Connections")
|
||||
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
||||
<< QVariant(true);
|
||||
|
||||
// test that Quick C++ types work ok
|
||||
QTest::newRow("itemInstance instanceof QtObject")
|
||||
<< testFileUrl("instanceof_qtquick.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("itemInstance instanceof Timer")
|
||||
<< testFileUrl("instanceof_qtquick.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("itemInstance instanceof Rectangle")
|
||||
<< testFileUrl("instanceof_qtquick.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("rectangleInstance instanceof Item")
|
||||
<< testFileUrl("instanceof_qtquick.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("rectangleInstance instanceof Rectangle")
|
||||
<< testFileUrl("instanceof_qtquick.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("rectangleInstance instanceof MouseArea")
|
||||
<< testFileUrl("instanceof_qtquick.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("mouseAreaInstance instanceof Item")
|
||||
<< testFileUrl("instanceof_qtquick.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("mouseAreaInstance instanceof Rectangle")
|
||||
<< testFileUrl("instanceof_qtquick.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("mouseAreaInstance instanceof MouseArea")
|
||||
<< testFileUrl("instanceof_qtquick.qml")
|
||||
<< QVariant(true);
|
||||
|
||||
// test that unqualified quick composite types work ok
|
||||
QTest::newRow("rectangleInstance instanceof CustomRectangle")
|
||||
<< testFileUrl("instanceof_qtquick_composite.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("customRectangleInstance instanceof Rectangle")
|
||||
<< testFileUrl("instanceof_qtquick_composite.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("customRectangleInstance instanceof Item")
|
||||
<< testFileUrl("instanceof_qtquick_composite.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("customRectangleWithPropInstance instanceof CustomRectangleWithProp")
|
||||
<< testFileUrl("instanceof_qtquick_composite.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("customRectangleWithPropInstance instanceof CustomRectangle")
|
||||
<< testFileUrl("instanceof_qtquick_composite.qml")
|
||||
<< QVariant(false); // ### XXX: QTBUG-58477
|
||||
QTest::newRow("customRectangleWithPropInstance instanceof Rectangle")
|
||||
<< testFileUrl("instanceof_qtquick_composite.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("customRectangleInstance instanceof MouseArea")
|
||||
<< testFileUrl("instanceof_qtquick_composite.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("customMouseAreaInstance instanceof MouseArea")
|
||||
<< testFileUrl("instanceof_qtquick_composite.qml")
|
||||
<< QVariant(true);
|
||||
|
||||
// test that they still work when qualified
|
||||
QTest::newRow("rectangleInstance instanceof CustomImport.CustomRectangle")
|
||||
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("customRectangleInstance instanceof QuickImport.Rectangle")
|
||||
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("customRectangleInstance instanceof QuickImport.Item")
|
||||
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("customRectangleWithPropInstance instanceof CustomImport.CustomRectangleWithProp")
|
||||
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("customRectangleWithPropInstance instanceof CustomImport.CustomRectangle")
|
||||
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
||||
<< QVariant(false); // ### XXX: QTBUG-58477
|
||||
QTest::newRow("customRectangleWithPropInstance instanceof QuickImport.Rectangle")
|
||||
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
||||
<< QVariant(true);
|
||||
QTest::newRow("customRectangleInstance instanceof QuickImport.MouseArea")
|
||||
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
||||
<< QVariant(false);
|
||||
QTest::newRow("customMouseAreaInstance instanceof QuickImport.MouseArea")
|
||||
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
||||
<< QVariant(true);
|
||||
}
|
||||
|
||||
void tst_qqmllanguage::instanceof()
|
||||
{
|
||||
QFETCH(QUrl, documentToTestIn);
|
||||
QFETCH(QVariant, expectedValue);
|
||||
|
||||
QQmlEngine engine;
|
||||
QQmlComponent component(&engine, documentToTestIn);
|
||||
VERIFY_ERRORS(0);
|
||||
|
||||
QScopedPointer<QObject> o(component.create());
|
||||
QVERIFY(o != 0);
|
||||
|
||||
QQmlExpression expr(engine.contextForObject(o.data()), 0, QString::fromLatin1(QTest::currentDataTag()));
|
||||
QVariant ret = expr.evaluate();
|
||||
|
||||
if (expectedValue.type() == QVariant::Bool) {
|
||||
// no error expected
|
||||
QVERIFY2(!expr.hasError(), qPrintable(expr.error().description()));
|
||||
bool returnValue = ret.toBool();
|
||||
|
||||
if (QTest::currentDataTag() == QLatin1String("customRectangleWithPropInstance instanceof CustomRectangle") ||
|
||||
QTest::currentDataTag() == QLatin1String("customRectangleWithPropInstance instanceof CustomImport.CustomRectangle"))
|
||||
QEXPECT_FAIL("", "QTBUG-58477: QML type rules are a little lax", Continue);
|
||||
QCOMPARE(returnValue, expectedValue.toBool());
|
||||
} else {
|
||||
QVERIFY(expr.hasError());
|
||||
QCOMPARE(expr.error().description(), expectedValue.toString());
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_qqmllanguage)
|
||||
|
||||
#include "tst_qqmllanguage.moc"
|
||||
|
|
Loading…
Reference in New Issue