diff options
Diffstat (limited to 'src/qml/qml/qqmlbinding.cpp')
-rw-r--r-- | src/qml/qml/qqmlbinding.cpp | 628 |
1 files changed, 361 insertions, 267 deletions
diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index 69ac63b0ad..47f8e5c429 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -1,47 +1,9 @@ -/**************************************************************************** -** -** 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> @@ -67,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; @@ -125,7 +90,7 @@ 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); @@ -140,7 +105,14 @@ QQmlBinding *QQmlBinding::create( const QQmlPropertyData *property, QV4::Function *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope) { - QQmlBinding *b = newBinding(QQmlEnginePrivate::get(ctxt), property); + 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(propertyType); b->setNotifyOnValueChanged(true); b->QQmlJavaScriptExpression::setContext(ctxt); @@ -168,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); @@ -211,81 +183,44 @@ 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.data(), &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); - - if (!watcher.wasDeleted()) { + const QQmlPropertyData *pd; + QQmlPropertyData vpd; + getPropertyData(&pd, &vpd); + Q_ASSERT(pd); - if (error) { - delayedError()->setErrorLocation(sourceLocation()); - delayedError()->setErrorObject(m_target.data()); - } + if (isUndefined || vpd.isValid()) + return slowWrite(*pd, vpd, result, type, isUndefined, flags); - if (hasError()) { - if (!delayedError()->addError(ep)) ep->warning(this->error(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); @@ -305,7 +240,7 @@ protected: if (result.isInteger()) return doStore<int>(result.integerValue(), pd, flags); else if (result.isNumber()) { - return doStore<int>(QV4::StaticValue::toInteger(result.doubleValue()), pd, flags); + return doStore<int>(result.toInt32(), pd, flags); } break; case QMetaType::Double: @@ -322,7 +257,7 @@ protected: break; default: if (const QV4::QQmlValueTypeWrapper *vtw = result.as<const QV4::QQmlValueTypeWrapper>()) { - if (vtw->d()->valueType()->metaType == pd->propType()) { + if (vtw->d()->metaType() == pd->propType()) { return vtw->write(m_target.data(), pd->coreIndex()); } } @@ -341,19 +276,19 @@ protected: } }; -class QQmlTranslationBinding : public GenericBinding<QMetaType::QString> { +class QQmlTranslationBinding : public GenericBinding<QMetaType::QString>, public QPropertyObserver { public: - QQmlTranslationBinding(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &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 @@ -364,11 +299,11 @@ public: if (!isAddedToObject() || hasError()) return; - const QString result = m_compilationUnit->bindingValueAsString(m_binding); + const QString result = this->bindingValue(); Q_ASSERT(targetObject()); - QQmlPropertyData *pd; + const QQmlPropertyData *pd; QQmlPropertyData vpd; getPropertyData(&pd, &vpd); Q_ASSERT(pd); @@ -381,9 +316,56 @@ 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()); + } +}; + +class QQmlTranslationBindingFromTranslationInfo : public QQmlTranslationBinding +{ + 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( @@ -391,29 +373,106 @@ QQmlBinding *QQmlBinding::createTranslationBinding( const QV4::CompiledData::Binding *binding, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt) { - QQmlTranslationBinding *b = new QQmlTranslationBinding(unit, binding); + 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(b, obj, ctxt); + 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 *qmlEngine = engine(); - QV4::ExecutionEngine *v4engine = qmlEngine->handle(); - - const int type = valueTypeData.isValid() ? valueTypeData.propType().id() : core.propType().id(); + const QMetaType metaType = valueTypeData.isValid() ? valueTypeData.propType() : core.propType(); + const int type = metaType.id(); QQmlJavaScriptExpression::DeleteWatcher watcher(this); @@ -422,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().id() == qMetaTypeId<QList<QUrl> >()) { - value = QQmlPropertyPrivate::urlSequence(v4engine->toVariant(result, qMetaTypeId<QList<QUrl>>())); - } 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()) { @@ -445,12 +511,13 @@ 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.")); @@ -476,41 +543,45 @@ 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(qmlEngine), type); - if (!propertyMetaObject.isNull()) - propertyType = propertyMetaObject.className(); - } - } else if (userType != QMetaType::UnknownType) { - if (userType == QMetaType::Nullptr || userType == QMetaType::VoidStar) - valueType = "null"; - else - valueType = QMetaType(userType).name(); +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(type).name(); - 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() @@ -526,7 +597,7 @@ QVariant QQmlBinding::evaluate() ep->dereferenceScarceResources(); - return scope.engine->toVariant(result, qMetaTypeId<QList<QObject*> >()); + return QV4::ExecutionEngine::toVariant(result, QMetaType::fromType<QList<QObject*> >()); } void QQmlBinding::expressionChanged() @@ -544,12 +615,7 @@ void QQmlBinding::setEnabled(bool e, QQmlPropertyData::WriteFlags flags) const bool wasEnabled = enabledFlag(); setEnabledFlag(e); setNotifyOnValueChanged(e); - - m_nextBinding.setTag(m_nextBinding.tag().setFlag(CanUseAccessor)); // Always use accessors, only not when: - if (auto interceptorMetaObject = QQmlInterceptorMetaObject::get(targetObject())) { - if (!m_targetIndex.isValid() || interceptorMetaObject->intercepts(m_targetIndex)) - m_nextBinding.setTag(m_nextBinding.tag().setFlag(CanUseAccessor, false)); - } + updateCanUseAccessor(); if (e && !wasEnabled) update(flags); @@ -560,85 +626,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.data(), true); - if (!data->propertyCache) { - data->propertyCache = QQmlEnginePrivate::get(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.data(), false); - Q_ASSERT(data); - - if (Q_UNLIKELY(!data->propertyCache)) { - data->propertyCache = QQmlEnginePrivate::get(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 = QQmlMetaType::metaObjectForMetaType((*propertyData)->propType()); - Q_ASSERT(valueTypeMetaObject); - QMetaProperty vtProp = valueTypeMetaObject->property(m_targetIndex.valueTypeIndex()); - valueTypeData->setFlags(QQmlPropertyData::flagsForProperty(vtProp)); - valueTypeData->setPropType(vtProp.metaType()); - valueTypeData->setCoreIndex(m_targetIndex.valueTypeIndex()); - } -} - QVector<QQmlProperty> QQmlBinding::dependencies() const { QVector<QQmlProperty> dependencies; @@ -660,33 +647,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() || qpropertyChangeTriggers; + return !activeGuards.isEmpty() || qpropertyChangeTriggers; } -class QObjectPointerBinding: public QQmlNonbindingBinding +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 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())) @@ -708,10 +794,8 @@ protected: resultMo = resultObject->metaObject(); } } else if (auto variant = result.as<QV4::VariantObject>()) { - QVariant value = variant->d()->data(); - QQmlEngine *qmlEngine = engine(); - resultMo = QQmlPropertyPrivate::rawMetaObjectForType( - qmlEngine ? QQmlEnginePrivate::get(qmlEngine) : nullptr, 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()); @@ -719,7 +803,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)) { @@ -728,23 +823,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().id()); - - const int type = property ? property->propType().id() : 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: |