diff options
Diffstat (limited to 'src/qml/qml/qqmlbinding.cpp')
-rw-r--r-- | src/qml/qml/qqmlbinding.cpp | 716 |
1 files changed, 408 insertions, 308 deletions
diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index 7f0442d034..4dfee0a3c6 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -1,57 +1,27 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqmlbinding_p.h" -#include "qqml.h" #include "qqmlcontext.h" -#include "qqmlinfo.h" #include "qqmldata_p.h" + +#include <private/qqmldebugserviceinterfaces_p.h> +#include <private/qqmldebugconnector_p.h> + #include <private/qqmlprofiler_p.h> #include <private/qqmlexpression_p.h> #include <private/qqmlscriptstring_p.h> #include <private/qqmlbuiltinfunctions_p.h> #include <private/qqmlvmemetaobject_p.h> #include <private/qqmlvaluetypewrapper_p.h> +#include <private/qv4qmlcontext_p.h> #include <private/qv4qobjectwrapper_p.h> #include <private/qv4variantobject_p.h> #include <private/qv4jscall_p.h> +#include <private/qjsvalue_p.h> + +#include <qtqml_tracepoints_p.h> #include <QVariant> #include <QtCore/qdebug.h> @@ -59,9 +29,12 @@ QT_BEGIN_NAMESPACE +Q_TRACE_POINT(qtqml, QQmlBinding_entry, const QQmlEngine *engine, const QString &function, const QString &fileName, int line, int column) +Q_TRACE_POINT(qtqml, QQmlBinding_exit) + QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt) { - QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); + QQmlBinding *b = newBinding(property); if (ctxt && !ctxt->isValid()) return b; @@ -73,19 +46,19 @@ QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QQmlScr QString url; QV4::Function *runtimeFunction = nullptr; - QQmlContextData *ctxtdata = QQmlContextData::get(scriptPrivate->context); + QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(scriptPrivate->context); QQmlEnginePrivate *engine = QQmlEnginePrivate::get(scriptPrivate->context->engine()); - if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit) { + if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit()) { url = ctxtdata->urlString(); if (scriptPrivate->bindingId != QQmlBinding::Invalid) - runtimeFunction = ctxtdata->typeCompilationUnit->runtimeFunctions.at(scriptPrivate->bindingId); + runtimeFunction = ctxtdata->typeCompilationUnit()->runtimeFunctions.at(scriptPrivate->bindingId); } b->setNotifyOnValueChanged(true); b->QQmlJavaScriptExpression::setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context)); b->setScopeObject(obj ? obj : scriptPrivate->scope); - QV4::ExecutionEngine *v4 = b->context()->engine->handle(); + QV4::ExecutionEngine *v4 = b->engine()->handle(); if (runtimeFunction) { QV4::Scope scope(v4); QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxtdata, b->scopeObject())); @@ -113,10 +86,11 @@ void QQmlBinding::setSourceLocation(const QQmlSourceLocation &location) } -QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QString &str, QObject *obj, - QQmlContextData *ctxt, const QString &url, quint16 lineNumber) +QQmlBinding *QQmlBinding::create( + const QQmlPropertyData *property, const QString &str, QObject *obj, + const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber) { - QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); + QQmlBinding *b = newBinding(property); b->setNotifyOnValueChanged(true); b->QQmlJavaScriptExpression::setContext(ctxt); @@ -127,10 +101,18 @@ QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, const QString return b; } -QQmlBinding *QQmlBinding::create(const QQmlPropertyData *property, QV4::Function *function, - QObject *obj, QQmlContextData *ctxt, QV4::ExecutionContext *scope) +QQmlBinding *QQmlBinding::create( + const QQmlPropertyData *property, QV4::Function *function, QObject *obj, + const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope) +{ + return create(property ? property->propType() : QMetaType(), function, obj, ctxt, scope); +} + +QQmlBinding *QQmlBinding::create(QMetaType propertyType, QV4::Function *function, QObject *obj, + const QQmlRefPointer<QQmlContextData> &ctxt, + QV4::ExecutionContext *scope) { - QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); + QQmlBinding *b = newBinding(propertyType); b->setNotifyOnValueChanged(true); b->QQmlJavaScriptExpression::setContext(ctxt); @@ -147,14 +129,9 @@ QQmlBinding::~QQmlBinding() delete m_sourceLocation; } -void QQmlBinding::setNotifyOnValueChanged(bool v) -{ - QQmlJavaScriptExpression::setNotifyOnValueChanged(v); -} - void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) { - if (!enabledFlag() || !context() || !context()->isValid()) + if (!enabledFlag() || !hasValidContext()) return; // Check that the target has not been deleted @@ -163,7 +140,7 @@ void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) // Check for a binding update loop if (Q_UNLIKELY(updatingFlag())) { - QQmlPropertyData *d = nullptr; + const QQmlPropertyData *d = nullptr; QQmlPropertyData vtd; getPropertyData(&d, &vtd); Q_ASSERT(d); @@ -175,13 +152,15 @@ void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) DeleteWatcher watcher(this); - QQmlEngine *engine = context()->engine; - QV4::Scope scope(engine->handle()); + QQmlEngine *qmlEngine = engine(); + QV4::Scope scope(qmlEngine->handle()); if (canUseAccessor()) flags.setFlag(QQmlPropertyData::BypassInterceptor); - QQmlBindingProfiler prof(QQmlEnginePrivate::get(engine)->profiler, function()); + Q_TRACE_SCOPE(QQmlBinding, qmlEngine, function() ? function()->name()->toQString() : QString(), + sourceLocation().sourceFile, sourceLocation().line, sourceLocation().column); + QQmlBindingProfiler prof(QQmlEnginePrivate::get(qmlEngine)->profiler, function()); doUpdate(watcher, flags, scope); if (!watcher.wasDeleted()) @@ -190,7 +169,7 @@ void QQmlBinding::update(QQmlPropertyData::WriteFlags flags) QV4::ReturnedValue QQmlBinding::evaluate(bool *isUndefined) { - QV4::ExecutionEngine *v4 = context()->engine->handle(); + QV4::ExecutionEngine *v4 = engine()->handle(); int argc = 0; const QV4::Value *argv = nullptr; const QV4::Value *thisObject = nullptr; @@ -204,88 +183,51 @@ QV4::ReturnedValue QQmlBinding::evaluate(bool *isUndefined) thisObject = &b->d()->boundThis; } QV4::Scope scope(v4); - QV4::JSCallData jsCall(scope, argc, argv, thisObject); + QV4::JSCallData jsCall(thisObject, argv, argc); - return QQmlJavaScriptExpression::evaluate(jsCall.callData(), isUndefined); + return QQmlJavaScriptExpression::evaluate(jsCall.callData(scope), isUndefined); } - -// QQmlBindingBinding is for target properties which are of type "binding" (instead of, say, int or -// double). The reason for being is that GenericBinding::fastWrite needs a compile-time constant -// expression for the switch for the compiler to generate the optimal code, but -// qMetaTypeId<QQmlBinding *>() needs to be used for the ID. So QQmlBinding::newBinding uses that -// to instantiate this class. -class QQmlBindingBinding: public QQmlBinding -{ -protected: - void doUpdate(const DeleteWatcher &, - QQmlPropertyData::WriteFlags flags, QV4::Scope &) override final - { - Q_ASSERT(!m_targetIndex.hasValueTypeIndex()); - QQmlPropertyData *pd = nullptr; - getPropertyData(&pd, nullptr); - QQmlBinding *thisPtr = this; - pd->writeProperty(*m_target, &thisPtr, flags); - } -}; - -// For any target that's not a binding, we have a common doUpdate. However, depending on the type -// of the target property, there is a specialized write method. -class QQmlNonbindingBinding: public QQmlBinding +template<int StaticPropType> +class GenericBinding: public QQmlBinding { protected: - void doUpdate(const DeleteWatcher &watcher, - QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) override + // Returns true if successful, false if an error description was set on expression + Q_ALWAYS_INLINE bool write(void *result, QMetaType type, bool isUndefined, + QQmlPropertyData::WriteFlags flags) override final { - auto ep = QQmlEnginePrivate::get(scope.engine); - ep->referenceScarceResources(); - - bool isUndefined = false; - - QV4::ScopedValue result(scope, evaluate(&isUndefined)); - - bool error = false; - if (!watcher.wasDeleted() && isAddedToObject() && !hasError()) - error = !write(result, isUndefined, flags); + const QQmlPropertyData *pd; + QQmlPropertyData vpd; + getPropertyData(&pd, &vpd); + Q_ASSERT(pd); - if (!watcher.wasDeleted()) { + if (isUndefined || vpd.isValid()) + return slowWrite(*pd, vpd, result, type, isUndefined, flags); - if (error) { - delayedError()->setErrorLocation(sourceLocation()); - delayedError()->setErrorObject(m_target.data()); - } - - if (hasError()) { - if (!delayedError()->addError(ep)) ep->warning(this->error(context()->engine)); - } else { - clearError(); - } + if ((StaticPropType == QMetaType::UnknownType && pd->propType() == type) + || StaticPropType == type.id()) { + Q_ASSERT(targetObject()); + return pd->writeProperty(targetObject(), result, flags); } - ep->dereferenceScarceResources(); + // If the type didn't match, we need to do JavaScript conversion. This should be rare. + return write(engine()->handle()->metaTypeToJS(type, result), isUndefined, flags); } - virtual bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) = 0; -}; - -template<int StaticPropType> -class GenericBinding: public QQmlNonbindingBinding -{ -protected: // Returns true if successful, false if an error description was set on expression Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) override final { Q_ASSERT(targetObject()); - QQmlPropertyData *pd; + const QQmlPropertyData *pd; QQmlPropertyData vpd; getPropertyData(&pd, &vpd); Q_ASSERT(pd); int propertyType = StaticPropType; // If the binding is specialized to a type, the if and switch below will be constant-folded. if (propertyType == QMetaType::UnknownType) - propertyType = pd->propType(); + propertyType = pd->propType().id(); if (Q_LIKELY(!isUndefined && !vpd.isValid())) { switch (propertyType) { @@ -297,8 +239,9 @@ protected: case QMetaType::Int: if (result.isInteger()) return doStore<int>(result.integerValue(), pd, flags); - else if (result.isNumber()) - return doStore<int>(result.doubleValue(), pd, flags); + else if (result.isNumber()) { + return doStore<int>(result.toInt32(), pd, flags); + } break; case QMetaType::Double: if (result.isNumber()) @@ -314,7 +257,7 @@ protected: break; default: if (const QV4::QQmlValueTypeWrapper *vtw = result.as<const QV4::QQmlValueTypeWrapper>()) { - if (vtw->d()->valueType->typeId == pd->propType()) { + if (vtw->d()->metaType() == pd->propType()) { return vtw->write(m_target.data(), pd->coreIndex()); } } @@ -333,19 +276,19 @@ protected: } }; -class QQmlTranslationBinding : public GenericBinding<QMetaType::QString> { +class QQmlTranslationBinding : public GenericBinding<QMetaType::QString>, public QPropertyObserver { public: - QQmlTranslationBinding(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding) + QQmlTranslationBinding(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) + : QPropertyObserver(&QQmlTranslationBinding::onLanguageChange) { setCompilationUnit(compilationUnit); - m_binding = binding; + setSource(QQmlEnginePrivate::get(compilationUnit->engine)->translationLanguage); } - QQmlSourceLocation sourceLocation() const override final - { - return QQmlSourceLocation(m_compilationUnit->fileName(), m_binding->valueLocation.line, m_binding->valueLocation.column); - } + virtual QString bindingValue() const = 0; + static void onLanguageChange(QPropertyObserver *observer, QUntypedPropertyData *) + { static_cast<QQmlTranslationBinding *>(observer)->update(); } void doUpdate(const DeleteWatcher &watcher, QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) override final @@ -356,15 +299,15 @@ public: if (!isAddedToObject() || hasError()) return; - const QString result = m_binding->valueAsString(m_compilationUnit.data()); + const QString result = this->bindingValue(); Q_ASSERT(targetObject()); - QQmlPropertyData *pd; + const QQmlPropertyData *pd; QQmlPropertyData vpd; getPropertyData(&pd, &vpd); Q_ASSERT(pd); - if (pd->propType() == QMetaType::QString) { + if (pd->propType().id() == QMetaType::QString) { doStore(result, pd, flags); } else { QV4::ScopedString value(scope, scope.engine->newString(result)); @@ -373,31 +316,163 @@ public: } bool hasDependencies() const override final { return true; } +}; -private: +class QQmlTranslationBindingFromBinding : public QQmlTranslationBinding +{ const QV4::CompiledData::Binding *m_binding; + +public: + QQmlTranslationBindingFromBinding( + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, + const QV4::CompiledData::Binding *binding) + : QQmlTranslationBinding(compilationUnit), m_binding(binding) + { + } + + QString bindingValue() const override + { + return this->m_compilationUnit->bindingValueAsString(m_binding); + } + + QQmlSourceLocation sourceLocation() const override final + { + return QQmlSourceLocation(m_compilationUnit->fileName(), m_binding->valueLocation.line(), + m_binding->valueLocation.column()); + } }; -QQmlBinding *QQmlBinding::createTranslationBinding(const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &unit, const QV4::CompiledData::Binding *binding, QObject *obj, QQmlContextData *ctxt) +class QQmlTranslationBindingFromTranslationInfo : public QQmlTranslationBinding { - QQmlTranslationBinding *b = new QQmlTranslationBinding(unit, binding); + QQmlTranslation m_translationData; + + quint16 m_line; + quint16 m_column; + +public: + QQmlTranslationBindingFromTranslationInfo( + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, + const QQmlTranslation &translationData, quint16 line, quint16 column) + : QQmlTranslationBinding(compilationUnit), + m_translationData(translationData), + m_line(line), + m_column(column) + { + } + + virtual QString bindingValue() const override { return m_translationData.translate(); } + + QQmlSourceLocation sourceLocation() const override final + { + return QQmlSourceLocation(m_compilationUnit->fileName(), m_line, m_column); + } +}; + +QQmlBinding *QQmlBinding::createTranslationBinding( + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, + const QV4::CompiledData::Binding *binding, QObject *obj, + const QQmlRefPointer<QQmlContextData> &ctxt) +{ + QQmlTranslationBinding *b = new QQmlTranslationBindingFromBinding(unit, binding); b->setNotifyOnValueChanged(true); b->QQmlJavaScriptExpression::setContext(ctxt); b->setScopeObject(obj); +#if QT_CONFIG(translation) && QT_CONFIG(qml_debug) + if (QQmlDebugTranslationService *service + = QQmlDebugConnector::service<QQmlDebugTranslationService>()) { + service->foundTranslationBinding( + TranslationBindingInformation::create(unit, binding, b->scopeObject(), ctxt)); + } +#endif + return b; +} +QQmlBinding *QQmlBinding::createTranslationBinding( + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &unit, + const QQmlRefPointer<QQmlContextData> &ctxt, const QString &propertyName, + const QQmlTranslation &translationData, const QQmlSourceLocation &location, QObject *obj) +{ + QQmlTranslationBinding *b = new QQmlTranslationBindingFromTranslationInfo( + unit, translationData, location.column, location.line); + + b->setNotifyOnValueChanged(true); + b->QQmlJavaScriptExpression::setContext(ctxt); + b->setScopeObject(obj); + +#if QT_CONFIG(translation) && QT_CONFIG(qml_debug) + QString originString; + if (QQmlDebugTranslationService *service = + QQmlDebugConnector::service<QQmlDebugTranslationService>()) { + service->foundTranslationBinding({ unit, b->scopeObject(), ctxt, + + propertyName, translationData, + + location.line, location.column }); + } +#else + Q_UNUSED(propertyName) +#endif return b; } +bool QQmlBinding::slowWrite( + const QQmlPropertyData &core, const QQmlPropertyData &valueTypeData, const void *result, + QMetaType resultType, bool isUndefined, QQmlPropertyData::WriteFlags flags) +{ + // The logic in this method is obscure. It follows what the other slowWrite does, minus the type + // conversions and the checking for binding functions. If you're writing a C++ type, and + // you're passing a binding function wrapped into QJSValue, you probably want it to be assigned. + + if (hasError()) + return false; + + QQmlEngine *qmlEngine = engine(); + const QMetaType metaType = valueTypeData.isValid() ? valueTypeData.propType() : core.propType(); + QQmlJavaScriptExpression::DeleteWatcher watcher(this); + + if (core.isVarProperty()) { + QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(m_target.data()); + Q_ASSERT(vmemo); + vmemo->setVMEProperty(core.coreIndex(), + qmlEngine->handle()->metaTypeToJS(resultType, result)); + } else if (isUndefined && core.isResettable()) { + void *args[] = { nullptr }; + QMetaObject::metacall(m_target.data(), QMetaObject::ResetProperty, core.coreIndex(), args); + } else if (isUndefined && metaType == QMetaType::fromType<QVariant>()) { + QQmlPropertyPrivate::writeValueProperty( + m_target.data(), core, valueTypeData, QVariant(), context(), flags); + } else if (metaType == QMetaType::fromType<QJSValue>()) { + QQmlPropertyPrivate::writeValueProperty( + m_target.data(), core, valueTypeData, + QVariant(resultType, result), context(), flags); + } else if (isUndefined) { + const char *name = metaType.name(); + const QString typeName = name + ? QString::fromUtf8(name) + : QStringLiteral("[unknown property type]"); + delayedError()->setErrorDescription( + QStringLiteral("Unable to assign [undefined] to ") + typeName); + return false; + } else if (!QQmlPropertyPrivate::writeValueProperty( + m_target.data(), core, valueTypeData, QVariant(resultType, result), + context(), flags)) { + if (watcher.wasDeleted()) + return true; + handleWriteError(result, resultType, metaType); + return false; + } + + return true; +} + Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core, const QQmlPropertyData &valueTypeData, const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) { - QQmlEngine *engine = context()->engine; - QV4::ExecutionEngine *v4engine = engine->handle(); - - int type = valueTypeData.isValid() ? valueTypeData.propType() : core.propType(); + const QMetaType metaType = valueTypeData.isValid() ? valueTypeData.propType() : core.propType(); + const int type = metaType.id(); QQmlJavaScriptExpression::DeleteWatcher watcher(this); @@ -406,13 +481,20 @@ Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core, if (isUndefined) { } else if (core.isQList()) { - value = v4engine->toVariant(result, qMetaTypeId<QList<QObject *> >()); + if (core.propType().flags() & QMetaType::IsQmlList) + value = QV4::ExecutionEngine::toVariant(result, QMetaType::fromType<QList<QObject*>>()); + else + value = QV4::ExecutionEngine::toVariant(result, core.propType()); } else if (result.isNull() && core.isQObject()) { value = QVariant::fromValue((QObject *)nullptr); - } else if (core.propType() == qMetaTypeId<QList<QUrl> >()) { - value = QQmlPropertyPrivate::resolvedUrlSequence(v4engine->toVariant(result, qMetaTypeId<QList<QUrl> >()), context()); - } else if (!isVarProperty && type != qMetaTypeId<QJSValue>()) { - value = v4engine->toVariant(result, type); + } else if (core.propType() == QMetaType::fromType<QList<QUrl>>()) { + const QVariant resultVariant + = QV4::ExecutionEngine::toVariant(result, QMetaType::fromType<QList<QUrl>>()); + value = QVariant::fromValue(QQmlPropertyPrivate::resolveUrlsOnAssignment() + ? QQmlPropertyPrivate::urlSequence(resultVariant, context()) + : QQmlPropertyPrivate::urlSequence(resultVariant)); + } else if (!isVarProperty && metaType != QMetaType::fromType<QJSValue>()) { + value = QV4::ExecutionEngine::toVariant(result, metaType); } if (hasError()) { @@ -429,28 +511,30 @@ Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core, QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(m_target.data()); Q_ASSERT(vmemo); vmemo->setVMEProperty(core.coreIndex(), result); - } else if (isUndefined && core.isResettable()) { - void *args[] = { nullptr }; - QMetaObject::metacall(m_target.data(), QMetaObject::ResetProperty, core.coreIndex(), args); - } else if (isUndefined && type == qMetaTypeId<QVariant>()) { + } else if (isUndefined + && (valueTypeData.isValid() ? valueTypeData.isResettable() : core.isResettable())) { + QQmlPropertyPrivate::resetValueProperty( + m_target.data(), core, valueTypeData, context(), flags); + } else if (isUndefined && type == QMetaType::QVariant) { QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, QVariant(), context(), flags); - } else if (type == qMetaTypeId<QJSValue>()) { + } else if (metaType == QMetaType::fromType<QJSValue>()) { const QV4::FunctionObject *f = result.as<QV4::FunctionObject>(); if (f && f->isBinding()) { delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration.")); return false; } - QQmlPropertyPrivate::writeValueProperty(m_target.data(), core, valueTypeData, QVariant::fromValue( - QJSValue(v4engine, result.asReturnedValue())), - context(), flags); + QQmlPropertyPrivate::writeValueProperty( + m_target.data(), core, valueTypeData, + QVariant::fromValue(QJSValuePrivate::fromReturnedValue(result.asReturnedValue())), + context(), flags); } else if (isUndefined) { - const QLatin1String typeName(QMetaType::typeName(type) - ? QMetaType::typeName(type) - : "[unknown property type]"); + const char *name = QMetaType(type).name(); + const QLatin1String typeName(name ? name : "[unknown property type]"); delayedError()->setErrorDescription(QLatin1String("Unable to assign [undefined] to ") + typeName); return false; - } else if (const QV4::FunctionObject *f = result.as<QV4::FunctionObject>()) { + } else if (const QV4::FunctionObject *f = result.as<QV4::FunctionObject>(); + f && !f->as<QV4::QQmlTypeWrapper>()) { if (f->isBinding()) delayedError()->setErrorDescription(QLatin1String("Invalid use of Qt.binding() in a binding declaration.")); else @@ -460,69 +544,61 @@ Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core, if (watcher.wasDeleted()) return true; + handleWriteError(value.constData(), value.metaType(), metaType); + return false; + } - const char *valueType = nullptr; - const char *propertyType = nullptr; - - const int userType = value.userType(); - if (userType == QMetaType::QObjectStar) { - if (QObject *o = *(QObject *const *)value.constData()) { - valueType = o->metaObject()->className(); + return true; +} - QQmlMetaObject propertyMetaObject = QQmlPropertyPrivate::rawMetaObjectForType(QQmlEnginePrivate::get(engine), type); - if (!propertyMetaObject.isNull()) - propertyType = propertyMetaObject.className(); - } - } else if (userType != QVariant::Invalid) { - if (userType == QMetaType::Nullptr || userType == QMetaType::VoidStar) - valueType = "null"; - else - valueType = QMetaType::typeName(userType); +void QQmlBinding::handleWriteError(const void *result, QMetaType resultType, QMetaType metaType) +{ + const char *valueType = nullptr; + const char *propertyType = nullptr; + + if (resultType.flags() & QMetaType::PointerToQObject) { + if (QObject *o = *(QObject *const *)result) { + valueType = o->metaObject()->className(); + QQmlMetaObject propertyMetaObject = QQmlPropertyPrivate::rawMetaObjectForType(metaType); + if (!propertyMetaObject.isNull()) + propertyType = propertyMetaObject.className(); + } + } else if (resultType.isValid()) { + if (resultType == QMetaType::fromType<std::nullptr_t>() + || resultType == QMetaType::fromType<void *>()) { + valueType = "null"; + } else { + valueType = resultType.name(); } - - if (!valueType) - valueType = "undefined"; - if (!propertyType) - propertyType = QMetaType::typeName(type); - if (!propertyType) - propertyType = "[unknown property type]"; - - delayedError()->setErrorDescription(QLatin1String("Unable to assign ") + - QLatin1String(valueType) + - QLatin1String(" to ") + - QLatin1String(propertyType)); - return false; } - return true; + if (!valueType) + valueType = "undefined"; + if (!propertyType) + propertyType = metaType.name(); + if (!propertyType) + propertyType = "[unknown property type]"; + + delayedError()->setErrorDescription(QStringLiteral("Unable to assign ") + + QString::fromUtf8(valueType) + + QStringLiteral(" to ") + + QString::fromUtf8(propertyType)); } QVariant QQmlBinding::evaluate() { - QQmlEngine *engine = context()->engine; - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); + QQmlEngine *qmlEngine = engine(); + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine); ep->referenceScarceResources(); bool isUndefined = false; - QV4::Scope scope(engine->handle()); + QV4::Scope scope(qmlEngine->handle()); QV4::ScopedValue result(scope, QQmlJavaScriptExpression::evaluate(&isUndefined)); ep->dereferenceScarceResources(); - return scope.engine->toVariant(result, qMetaTypeId<QList<QObject*> >()); -} - -QString QQmlBinding::expressionIdentifier() const -{ - if (auto f = function()) { - QString url = f->sourceFile(); - quint16 lineNumber = f->compiledFunction->location.line; - quint16 columnNumber = f->compiledFunction->location.column; - return url + QString::asprintf(":%u:%u", uint(lineNumber), uint(columnNumber)); - } - - return QStringLiteral("[native code]"); + return QV4::ExecutionEngine::toVariant(result, QMetaType::fromType<QList<QObject*> >()); } void QQmlBinding::expressionChanged() @@ -540,12 +616,7 @@ void QQmlBinding::setEnabled(bool e, QQmlPropertyData::WriteFlags flags) const bool wasEnabled = enabledFlag(); setEnabledFlag(e); setNotifyOnValueChanged(e); - - m_nextBinding.setFlag2(); // Always use accessors, only not when: - if (auto interceptorMetaObject = QQmlInterceptorMetaObject::get(targetObject())) { - if (!m_targetIndex.isValid() || interceptorMetaObject->intercepts(m_targetIndex)) - m_nextBinding.clearFlag2(); - } + updateCanUseAccessor(); if (e && !wasEnabled) update(flags); @@ -556,85 +627,6 @@ QString QQmlBinding::expression() const return QStringLiteral("function() { [native code] }"); } -void QQmlBinding::setTarget(const QQmlProperty &prop) -{ - auto pd = QQmlPropertyPrivate::get(prop); - setTarget(prop.object(), pd->core, &pd->valueTypeData); -} - -bool QQmlBinding::setTarget(QObject *object, const QQmlPropertyData &core, const QQmlPropertyData *valueType) -{ - m_target = object; - - if (!object) { - m_targetIndex = QQmlPropertyIndex(); - return false; - } - - int coreIndex = core.coreIndex(); - int valueTypeIndex = valueType ? valueType->coreIndex() : -1; - for (bool isAlias = core.isAlias(); isAlias; ) { - QQmlVMEMetaObject *vme = QQmlVMEMetaObject::getForProperty(object, coreIndex); - - int aValueTypeIndex; - if (!vme->aliasTarget(coreIndex, &object, &coreIndex, &aValueTypeIndex)) { - // can't resolve id (yet) - m_target = nullptr; - m_targetIndex = QQmlPropertyIndex(); - return false; - } - if (valueTypeIndex == -1) - valueTypeIndex = aValueTypeIndex; - - QQmlData *data = QQmlData::get(object, false); - if (!data || !data->propertyCache) { - m_target = nullptr; - m_targetIndex = QQmlPropertyIndex(); - return false; - } - QQmlPropertyData *propertyData = data->propertyCache->property(coreIndex); - Q_ASSERT(propertyData); - - m_target = object; - isAlias = propertyData->isAlias(); - coreIndex = propertyData->coreIndex(); - } - m_targetIndex = QQmlPropertyIndex(coreIndex, valueTypeIndex); - - QQmlData *data = QQmlData::get(*m_target, true); - if (!data->propertyCache) { - data->propertyCache = QQmlEnginePrivate::get(context()->engine)->cache(m_target->metaObject()); - data->propertyCache->addref(); - } - - return true; -} - -void QQmlBinding::getPropertyData(QQmlPropertyData **propertyData, QQmlPropertyData *valueTypeData) const -{ - Q_ASSERT(propertyData); - - QQmlData *data = QQmlData::get(*m_target, false); - Q_ASSERT(data); - - if (Q_UNLIKELY(!data->propertyCache)) { - data->propertyCache = QQmlEnginePrivate::get(context()->engine)->cache(m_target->metaObject()); - data->propertyCache->addref(); - } - - *propertyData = data->propertyCache->property(m_targetIndex.coreIndex()); - Q_ASSERT(*propertyData); - - if (Q_UNLIKELY(m_targetIndex.hasValueTypeIndex() && valueTypeData)) { - const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType((*propertyData)->propType()); - Q_ASSERT(valueTypeMetaObject); - QMetaProperty vtProp = valueTypeMetaObject->property(m_targetIndex.valueTypeIndex()); - valueTypeData->setFlags(QQmlPropertyData::flagsForProperty(vtProp)); - valueTypeData->setPropType(vtProp.userType()); - valueTypeData->setCoreIndex(m_targetIndex.valueTypeIndex()); - } -} - QVector<QQmlProperty> QQmlBinding::dependencies() const { QVector<QQmlProperty> dependencies; @@ -656,33 +648,132 @@ QVector<QQmlProperty> QQmlBinding::dependencies() const for (int i = 0; i < senderMeta->propertyCount(); i++) { QMetaProperty property = senderMeta->property(i); if (property.notifySignalIndex() == QMetaObjectPrivate::signal(senderMeta, guard->signalIndex()).methodIndex()) { - dependencies.push_back(QQmlProperty(senderObject, QString::fromUtf8(senderObject->metaObject()->property(i).name()))); + dependencies.push_back(QQmlProperty(senderObject, QString::fromUtf8(property.name()))); } } } + for (auto trigger = qpropertyChangeTriggers; trigger; trigger = trigger->next) { + QMetaProperty prop = trigger->property(); + if (prop.isValid()) + dependencies.push_back(QQmlProperty(trigger->target, QString::fromUtf8(prop.name()))); + } + return dependencies; } bool QQmlBinding::hasDependencies() const { - return !activeGuards.isEmpty() || translationsCaptured(); + return !activeGuards.isEmpty() || qpropertyChangeTriggers; +} + +void QQmlBinding::doUpdate(const DeleteWatcher &watcher, QQmlPropertyData::WriteFlags flags, QV4::Scope &scope) +{ + auto ep = QQmlEnginePrivate::get(scope.engine); + ep->referenceScarceResources(); + + bool error = false; + auto canWrite = [&]() { return !watcher.wasDeleted() && isAddedToObject() && !hasError(); }; + const QV4::Function *v4Function = function(); + if (v4Function && v4Function->kind == QV4::Function::AotCompiled && !hasBoundFunction()) { + const auto returnType = v4Function->aotCompiledFunction.types[0]; + if (returnType == QMetaType::fromType<QVariant>()) { + QVariant result; + const bool isUndefined = !evaluate(&result, returnType); + if (canWrite()) + error = !write(result.data(), result.metaType(), isUndefined, flags); + } else { + const auto size = returnType.sizeOf(); + if (Q_LIKELY(size > 0)) { + Q_ALLOCA_VAR(void, result, size); + if (returnType.flags() & QMetaType::NeedsConstruction) + returnType.construct(result); + const bool isUndefined = !evaluate(result, returnType); + if (canWrite()) + error = !write(result, returnType, isUndefined, flags); + if (returnType.flags() & QMetaType::NeedsDestruction) + returnType.destruct(result); + } else if (canWrite()) { + error = !write(QV4::Encode::undefined(), true, flags); + } + } + } else { + bool isUndefined = false; + QV4::ScopedValue result(scope, evaluate(&isUndefined)); + if (canWrite()) + error = !write(result, isUndefined, flags); + } + + if (!watcher.wasDeleted()) { + + if (error) { + delayedError()->setErrorLocation(sourceLocation()); + delayedError()->setErrorObject(m_target.data()); + } + + if (hasError()) { + if (!delayedError()->addError(ep)) ep->warning(this->error(engine())); + } else { + clearError(); + } + } + + ep->dereferenceScarceResources(); } -class QObjectPointerBinding: public QQmlNonbindingBinding +class QObjectPointerBinding: public QQmlBinding { QQmlMetaObject targetMetaObject; public: - QObjectPointerBinding(QQmlEnginePrivate *engine, int propertyType) - : targetMetaObject(QQmlPropertyPrivate::rawMetaObjectForType(engine, propertyType)) + QObjectPointerBinding(QMetaType propertyType) + : targetMetaObject(QQmlPropertyPrivate::rawMetaObjectForType(propertyType)) {} protected: + Q_ALWAYS_INLINE bool write(void *result, QMetaType type, bool isUndefined, + QQmlPropertyData::WriteFlags flags) override final + { + const QQmlPropertyData *pd; + QQmlPropertyData vtpd; + getPropertyData(&pd, &vtpd); + if (Q_UNLIKELY(isUndefined || vtpd.isValid())) + return slowWrite(*pd, vtpd, result, type, isUndefined, flags); + + // Check if the result is a QObject: + QObject *resultObject = nullptr; + QQmlMetaObject resultMo; + const auto typeFlags = type.flags(); + if (!result || ((typeFlags & QMetaType::IsPointer) && !*static_cast<void **>(result))) { + // Special case: we can always write a nullptr. Don't bother checking anything else. + return pd->writeProperty(targetObject(), &resultObject, flags); + } else if (typeFlags & QMetaType::PointerToQObject) { + resultObject = *static_cast<QObject **>(result); + if (!resultObject) + return pd->writeProperty(targetObject(), &resultObject, flags); + if (QQmlData *ddata = QQmlData::get(resultObject, false)) + resultMo = ddata->propertyCache; + if (resultMo.isNull()) + resultMo = resultObject->metaObject(); + } else if (type == QMetaType::fromType<QVariant>()) { + const QVariant value = *static_cast<QVariant *>(result); + resultMo = QQmlPropertyPrivate::rawMetaObjectForType(value.metaType()); + if (resultMo.isNull()) + return slowWrite(*pd, vtpd, result, type, isUndefined, flags); + resultObject = *static_cast<QObject *const *>(value.constData()); + } else { + return slowWrite(*pd, vtpd, result, type, isUndefined, flags); + } + + return compareAndSet(resultMo, resultObject, pd, flags, [&]() { + return slowWrite(*pd, vtpd, result, type, isUndefined, flags); + }); + } + Q_ALWAYS_INLINE bool write(const QV4::Value &result, bool isUndefined, QQmlPropertyData::WriteFlags flags) override final { - QQmlPropertyData *pd; + const QQmlPropertyData *pd; QQmlPropertyData vtpd; getPropertyData(&pd, &vtpd); if (Q_UNLIKELY(isUndefined || vtpd.isValid())) @@ -704,9 +795,8 @@ protected: resultMo = resultObject->metaObject(); } } else if (auto variant = result.as<QV4::VariantObject>()) { - QVariant value = variant->d()->data(); - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context()); - resultMo = QQmlPropertyPrivate::rawMetaObjectForType(ep, value.userType()); + const QVariant value = variant->d()->data(); + resultMo = QQmlPropertyPrivate::rawMetaObjectForType(value.metaType()); if (resultMo.isNull()) return slowWrite(*pd, vtpd, result, isUndefined, flags); resultObject = *static_cast<QObject *const *>(value.constData()); @@ -714,7 +804,18 @@ protected: return slowWrite(*pd, vtpd, result, isUndefined, flags); } - // Compare & set: + return compareAndSet(resultMo, resultObject, pd, flags, [&]() { + return slowWrite(*pd, vtpd, result, isUndefined, flags); + }); + } + +private: + using QQmlBinding::slowWrite; + + template<typename SlowWrite> + bool compareAndSet(const QQmlMetaObject &resultMo, QObject *resultObject, const QQmlPropertyData *pd, + QQmlPropertyData::WriteFlags flags, const SlowWrite &slowWrite) const + { if (QQmlMetaObject::canConvert(resultMo, targetMetaObject)) { return pd->writeProperty(targetObject(), &resultObject, flags); } else if (!resultObject && QQmlMetaObject::canConvert(targetMetaObject, resultMo)) { @@ -723,23 +824,22 @@ protected: // the property type. return pd->writeProperty(targetObject(), &resultObject, flags); } else { - return slowWrite(*pd, vtpd, result, isUndefined, flags); + return slowWrite(); } } }; -QQmlBinding *QQmlBinding::newBinding(QQmlEnginePrivate *engine, const QQmlPropertyData *property) +QQmlBinding *QQmlBinding::newBinding(const QQmlPropertyData *property) { - if (property && property->isQObject()) - return new QObjectPointerBinding(engine, property->propType()); - - const int type = (property && property->isFullyResolved()) ? property->propType() : QMetaType::UnknownType; + return newBinding(property ? property->propType() : QMetaType()); +} - if (type == qMetaTypeId<QQmlBinding *>()) { - return new QQmlBindingBinding; - } +QQmlBinding *QQmlBinding::newBinding(QMetaType propertyType) +{ + if (propertyType.flags() & QMetaType::PointerToQObject) + return new QObjectPointerBinding(propertyType); - switch (type) { + switch (propertyType.id()) { case QMetaType::Bool: return new GenericBinding<QMetaType::Bool>; case QMetaType::Int: |