diff options
Diffstat (limited to 'src/qml/qml/qqmlpropertybinding.cpp')
-rw-r--r-- | src/qml/qml/qqmlpropertybinding.cpp | 453 |
1 files changed, 237 insertions, 216 deletions
diff --git a/src/qml/qml/qqmlpropertybinding.cpp b/src/qml/qml/qqmlpropertybinding.cpp index e600748be7..c8a7e6256a 100644 --- a/src/qml/qml/qqmlpropertybinding.cpp +++ b/src/qml/qml/qqmlpropertybinding.cpp @@ -1,92 +1,44 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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) 2020 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 "qqmlpropertybinding_p.h" + +#include <private/qqmlbinding_p.h> +#include <private/qqmlglobal_p.h> +#include <private/qqmlscriptstring_p.h> #include <private/qv4functionobject_p.h> #include <private/qv4jscall_p.h> -#include <qqmlinfo.h> +#include <private/qv4qmlcontext_p.h> + +#include <QtQml/qqmlinfo.h> + +#include <QtCore/qloggingcategory.h> QT_BEGIN_NAMESPACE -namespace { -constexpr std::size_t jsExpressionOffsetLength() { - struct composite { QQmlPropertyBinding b; QQmlPropertyBindingJS js; }; - QT_WARNING_PUSH QT_WARNING_DISABLE_INVALID_OFFSETOF - return sizeof (QQmlPropertyBinding) - offsetof(composite, js); - QT_WARNING_POP -} -} +using namespace Qt::Literals::StringLiterals; -const QQmlPropertyBindingJS *QQmlPropertyBinding::jsExpression() const -{ - return std::launder(reinterpret_cast<QQmlPropertyBindingJS const *>(reinterpret_cast<std::byte const*>(this) + sizeof(QQmlPropertyBinding) + jsExpressionOffsetLength())); -} +Q_LOGGING_CATEGORY(lcQQPropertyBinding, "qt.qml.propertybinding"); QUntypedPropertyBinding QQmlPropertyBinding::create(const QQmlPropertyData *pd, QV4::Function *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex) { - if (auto aotFunction = function->aotFunction; aotFunction && aotFunction->returnType == pd->propType()) { - return QUntypedPropertyBinding(aotFunction->returnType, - [ - aotFunction, - unit = QQmlRefPointer<QV4::ExecutableCompilationUnit>(function->executableCompilationUnit()), - scopeObject = QPointer<QObject>(obj), - context = ctxt, - engine = scope->engine() - ](const QMetaType &, void *dataPtr) -> bool { - QQmlPrivate::AOTCompiledContext aotContext; - aotContext.qmlContext = context->asQQmlContext(); - aotContext.qmlScopeObject = scopeObject.data(); - aotContext.engine = engine->jsEngine(); - aotContext.compilationUnit = unit.data(); - aotFunction->functionPtr(&aotContext, dataPtr, nullptr); - // ### Fixme: The aotFunction should do the check whether old and new value are the same and - // return false in that case - return true; - }, - QPropertyBindingSourceLocation()); - } + Q_ASSERT(pd); + return create(pd->propType(), function, obj, ctxt, scope, target, targetIndex); +} - auto buffer = new std::byte[sizeof(QQmlPropertyBinding)+sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[] - auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, false); - auto js = new(buffer + sizeof(QQmlPropertyBinding) + jsExpressionOffsetLength()) QQmlPropertyBindingJS(); +QUntypedPropertyBinding QQmlPropertyBinding::create(QMetaType propertyType, QV4::Function *function, + QObject *obj, + const QQmlRefPointer<QQmlContextData> &ctxt, + QV4::ExecutionContext *scope, QObject *target, + QQmlPropertyIndex targetIndex) +{ + auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment() + + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[] + auto binding = new (buffer) QQmlPropertyBinding(propertyType, target, targetIndex, + TargetData::WithoutBoundFunction); + auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS(); Q_ASSERT(binding->jsExpression() == js); Q_ASSERT(js->asBinding() == binding); Q_UNUSED(js); @@ -99,9 +51,10 @@ QUntypedPropertyBinding QQmlPropertyBinding::create(const QQmlPropertyData *pd, QUntypedPropertyBinding QQmlPropertyBinding::createFromCodeString(const QQmlPropertyData *pd, const QString& str, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, const QString &url, quint16 lineNumber, QObject *target, QQmlPropertyIndex targetIndex) { - auto buffer = new std::byte[sizeof(QQmlPropertyBinding)+sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[] - auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, true); - auto js = new(buffer + sizeof(QQmlPropertyBinding) + jsExpressionOffsetLength()) QQmlPropertyBindingJS(); + auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment() + + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[] + auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::WithoutBoundFunction); + auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS(); Q_ASSERT(binding->jsExpression() == js); Q_ASSERT(js->asBinding() == binding); Q_UNUSED(js); @@ -111,11 +64,50 @@ QUntypedPropertyBinding QQmlPropertyBinding::createFromCodeString(const QQmlProp return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data())); } +QUntypedPropertyBinding QQmlPropertyBinding::createFromScriptString(const QQmlPropertyData *property, const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt, QObject *target, QQmlPropertyIndex targetIndex) +{ + const QQmlScriptStringPrivate *scriptPrivate = script.d.data(); + // without a valid context, we cannot create anything + if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) { + return {}; + } + + auto scopeObject = obj ? obj : scriptPrivate->scope; + + QV4::Function *runtimeFunction = nullptr; + QString url; + QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(scriptPrivate->context); + QQmlEnginePrivate *engine = QQmlEnginePrivate::get(scriptPrivate->context->engine()); + if (engine && ctxtdata && !ctxtdata->urlString().isEmpty() && ctxtdata->typeCompilationUnit()) { + url = ctxtdata->urlString(); + if (scriptPrivate->bindingId != QQmlBinding::Invalid) + runtimeFunction = ctxtdata->typeCompilationUnit()->runtimeFunctions.at(scriptPrivate->bindingId); + } + // Do we actually have a function in the script string? If not, this becomes createCodeFromString + if (!runtimeFunction) + return createFromCodeString(property, scriptPrivate->script, obj, ctxtdata, url, scriptPrivate->lineNumber, target, targetIndex); + + auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment() + + sizeof(QQmlPropertyBindingJS)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[] + auto binding = new(buffer) QQmlPropertyBinding(QMetaType(property->propType()), target, targetIndex, TargetData::WithoutBoundFunction); + auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJS(); + Q_ASSERT(binding->jsExpression() == js); + Q_ASSERT(js->asBinding() == binding); + js->setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context)); + + QV4::ExecutionEngine *v4 = engine->v4engine(); + QV4::Scope scope(v4); + QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(v4->rootContext(), ctxtdata, scopeObject)); + js->setupFunction(qmlContext, runtimeFunction); + return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data())); +} + QUntypedPropertyBinding QQmlPropertyBinding::createFromBoundFunction(const QQmlPropertyData *pd, QV4::BoundFunction *function, QObject *obj, const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope, QObject *target, QQmlPropertyIndex targetIndex) { - auto buffer = new std::byte[sizeof(QQmlPropertyBinding)+sizeof(QQmlPropertyBindingJSForBoundFunction)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[] - auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, true); - auto js = new(buffer + sizeof(QQmlPropertyBinding) + jsExpressionOffsetLength()) QQmlPropertyBindingJSForBoundFunction(); + auto buffer = new std::byte[QQmlPropertyBinding::getSizeEnsuringAlignment() + + sizeof(QQmlPropertyBindingJSForBoundFunction)+jsExpressionOffsetLength()]; // QQmlPropertyBinding uses delete[] + auto binding = new(buffer) QQmlPropertyBinding(QMetaType(pd->propType()), target, targetIndex, TargetData::HasBoundFunction); + auto js = new(buffer + QQmlPropertyBinding::getSizeEnsuringAlignment() + jsExpressionOffsetLength()) QQmlPropertyBindingJSForBoundFunction(); Q_ASSERT(binding->jsExpression() == js); Q_ASSERT(js->asBinding() == binding); Q_UNUSED(js); @@ -127,10 +119,25 @@ QUntypedPropertyBinding QQmlPropertyBinding::createFromBoundFunction(const QQmlP return QUntypedPropertyBinding(static_cast<QPropertyBindingPrivate *>(QPropertyBindingPrivatePtr(binding).data())); } +/*! + \fn bool QQmlPropertyBindingJS::hasDependencies() + \internal + + Returns true if this binding has dependencies. + Dependencies can be either QProperty dependencies or dependencies of + the JS expression (aka activeGuards). Translations end up as a QProperty + dependency, so they do not need any special handling + Note that a QQmlPropertyBinding never stores qpropertyChangeTriggers. + */ + + void QQmlPropertyBindingJS::expressionChanged() { + auto binding = asBinding(); + if (!binding->propertyDataPtr) + return; const auto currentTag = m_error.tag(); - if (currentTag & InEvaluationLoop) { + if (currentTag == InEvaluationLoop) { QQmlError err; auto location = QQmlJavaScriptExpression::sourceLocation(); err.setUrl(QUrl{location.sourceFile}); @@ -139,160 +146,142 @@ void QQmlPropertyBindingJS::expressionChanged() const auto ctxt = context(); QQmlEngine *engine = ctxt ? ctxt->engine() : nullptr; if (engine) - err.setDescription(asBinding()->createBindingLoopErrorDescription(QQmlEnginePrivate::get(engine))); + err.setDescription(asBinding()->createBindingLoopErrorDescription()); else err.setDescription(QString::fromLatin1("Binding loop detected")); err.setObject(asBinding()->target()); qmlWarning(this->scopeObject(), err); return; } - m_error.setTag(currentTag | InEvaluationLoop); - asBinding()->markDirtyAndNotifyObservers(); - m_error.setTag(currentTag); + m_error.setTag(InEvaluationLoop); + PendingBindingObserverList bindingObservers; + binding->evaluateRecursive(bindingObservers); + binding->notifyNonRecursive(bindingObservers); + m_error.setTag(NoTag); } -const QQmlPropertyBinding *QQmlPropertyBindingJS::asBinding() const -{ - return std::launder(reinterpret_cast<QQmlPropertyBinding const *>(reinterpret_cast<std::byte const*>(this) - sizeof(QQmlPropertyBinding) - jsExpressionOffsetLength())); -} - -QQmlPropertyBinding::QQmlPropertyBinding(QMetaType mt, QObject *target, QQmlPropertyIndex targetIndex, bool hasBoundFunction) +QQmlPropertyBinding::QQmlPropertyBinding(QMetaType mt, QObject *target, QQmlPropertyIndex targetIndex, TargetData::BoundFunction hasBoundFunction) : QPropertyBindingPrivate(mt, - &QtPrivate::bindingFunctionVTable<QQmlPropertyBinding>, + bindingFunctionVTableForQQmlPropertyBinding(mt), QPropertyBindingSourceLocation(), true) { static_assert (std::is_trivially_destructible_v<TargetData>); static_assert (sizeof(TargetData) + sizeof(DeclarativeErrorCallback) <= sizeof(QPropertyBindingSourceLocation)); static_assert (alignof(TargetData) <= alignof(QPropertyBindingSourceLocation)); - new (&declarativeExtraData) TargetData {target, targetIndex, hasBoundFunction}; + const auto state = hasBoundFunction ? TargetData::HasBoundFunction : TargetData::WithoutBoundFunction; + new (&declarativeExtraData) TargetData {target, targetIndex, state}; errorCallBack = bindingErrorCallback; } -bool QQmlPropertyBinding::evaluate(QMetaType metaType, void *dataPtr) +static QtPrivate::QPropertyBindingData *bindingDataFromPropertyData(QUntypedPropertyData *dataPtr, QMetaType type) { - const auto ctxt = jsExpression()->context(); - QQmlEngine *engine = ctxt ? ctxt->engine() : nullptr; - if (!engine) { - QPropertyBindingError error(QPropertyBindingError::EvaluationError); - QPropertyBindingPrivate::currentlyEvaluatingBinding()->setError(std::move(error)); - return false; - } - QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine); - ep->referenceScarceResources(); - - bool isUndefined = false; - - QV4::Scope scope(engine->handle()); - QV4::ScopedValue result(scope, hasBoundFunction() - ? static_cast<QQmlPropertyBindingJSForBoundFunction *>(jsExpression())->evaluate(&isUndefined) - : jsExpression()->evaluate(&isUndefined)); - - ep->dereferenceScarceResources(); - - if (jsExpression()->hasError()) { - QPropertyBindingError error(QPropertyBindingError::UnknownError, jsExpression()->delayedError()->error().description()); - QPropertyBindingPrivate::currentlyEvaluatingBinding()->setError(std::move(error)); - bindingErrorCallback(this); - return false; - } + // XXX Qt 7: We need a clean way to access the binding data + /* This function makes the (dangerous) assumption that if we could not get the binding data + from the binding storage, we must have been handed a QProperty. + This does hold for anything a user could write, as there the only ways of providing a bindable property + are to use the Q_X_BINDABLE macros, or to directly expose a QProperty. + As long as we can ensure that any "fancier" property we implement is not resettable, we should be fine. + We procede to calculate the address of the binding data pointer from the address of the data pointer + */ + Q_ASSERT(dataPtr); + std::byte *qpropertyPointer = reinterpret_cast<std::byte *>(dataPtr); + qpropertyPointer += type.sizeOf(); + constexpr auto alignment = alignof(QtPrivate::QPropertyBindingData *); + auto aligned = (quintptr(qpropertyPointer) + alignment - 1) & ~(alignment - 1); // ensure pointer alignment + return reinterpret_cast<QtPrivate::QPropertyBindingData *>(aligned); +} - int propertyType = metaType.id(); +void QQmlPropertyBinding::handleUndefinedAssignment(QQmlEnginePrivate *ep, void *dataPtr) +{ + const QQmlPropertyData *propertyData = nullptr; + QQmlPropertyData valueTypeData; + QQmlData *data = QQmlData::get(target(), false); + Q_ASSERT(data); + if (Q_UNLIKELY(!data->propertyCache)) + data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject()); - switch (propertyType) { - case QMetaType::Bool: { - bool b; - if (result->isBoolean()) - b = result->booleanValue(); - else - b = result->toBoolean(); - if (b == *static_cast<bool *>(dataPtr)) - return false; - *static_cast<bool *>(dataPtr) = b; - return true; - } - case QMetaType::Int: { - int i; - if (result->isInteger()) - i = result->integerValue(); - else if (result->isNumber()) { - i = QV4::StaticValue::toInteger(result->doubleValue()); - } else { - break; - } - if (i == *static_cast<int *>(dataPtr)) - return false; - *static_cast<int *>(dataPtr) = i; - return true; - } - case QMetaType::Double: - if (result->isNumber()) { - double d = result->asDouble(); - if (d == *static_cast<double *>(dataPtr)) - return false; - *static_cast<double *>(dataPtr) = d; - return true; - } - break; - case QMetaType::Float: - if (result->isNumber()) { - float d = float(result->asDouble()); - if (d == *static_cast<float *>(dataPtr)) - return false; - *static_cast<float *>(dataPtr) = d; - return true; + propertyData = data->propertyCache->property(targetIndex().coreIndex()); + Q_ASSERT(propertyData); + Q_ASSERT(!targetIndex().hasValueTypeIndex()); + QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr); + // helper function for writing back value into dataPtr + // this is necessary for QObjectCompatProperty, which doesn't give us the "real" dataPtr + // if we don't write the correct value, we would otherwise set the default constructed value + auto writeBackCurrentValue = [&](QVariant &¤tValue) { + if (currentValue.metaType() != valueMetaType()) + currentValue.convert(valueMetaType()); + auto metaType = valueMetaType(); + metaType.destruct(dataPtr); + metaType.construct(dataPtr, currentValue.constData()); + }; + if (prop.isResettable()) { + // Normally a reset would remove any existing binding; but now we need to keep the binding alive + // to handle the case where this binding becomes defined again + // We therefore detach the binding, call reset, and reattach again + const auto storage = qGetBindingStorage(target()); + auto bindingData = storage->bindingData(propertyDataPtr); + if (!bindingData) + bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType()); + QPropertyBindingDataPointer bindingDataPointer{bindingData}; + auto firstObserver = takeObservers(); + bindingData->d_ref() = 0; + if (firstObserver) { + bindingDataPointer.setObservers(firstObserver.ptr); } - break; - case QMetaType::QString: - if (result->isString()) { - QString s = result->toQStringNoThrow(); - if (s == *static_cast<QString *>(dataPtr)) - return false; - *static_cast<QString *>(dataPtr) = s; - return true; + Q_ASSERT(!bindingData->hasBinding()); + setIsUndefined(true); + //suspend binding evaluation state for reset and subsequent read + auto state = QtPrivate::suspendCurrentBindingStatus(); + prop.reset(); // May re-allocate the bindingData + QVariant currentValue = QVariant(prop.propertyMetaType(), propertyDataPtr); + QtPrivate::restoreBindingStatus(state); + writeBackCurrentValue(std::move(currentValue)); + + // Re-fetch binding data + bindingData = storage->bindingData(propertyDataPtr); + if (!bindingData) + bindingData = bindingDataFromPropertyData(propertyDataPtr, propertyData->propType()); + bindingDataPointer = QPropertyBindingDataPointer {bindingData}; + + // reattach the binding (without causing a new notification) + if (Q_UNLIKELY(bindingData->d() & QtPrivate::QPropertyBindingData::BindingBit)) { + qCWarning(lcQQPropertyBinding) << "Resetting " << prop.name() << "due to the binding becoming undefined caused a new binding to be installed\n" + << "The old binding binding will be abandoned"; + deref(); + return; } - break; - default: - break; + // reset might have changed observers (?), so refresh firstObserver + firstObserver = bindingDataPointer.firstObserver(); + bindingData->d_ref() = reinterpret_cast<quintptr>(this) | QtPrivate::QPropertyBindingData::BindingBit; + if (firstObserver) + prependObserver(firstObserver); + } else { + QQmlError qmlError; + auto location = jsExpression()->sourceLocation(); + qmlError.setColumn(location.column); + qmlError.setLine(location.line); + qmlError.setUrl(QUrl {location.sourceFile}); + const QString description = QStringLiteral(R"(QML %1: Unable to assign [undefined] to "%2")").arg(QQmlMetaType::prettyTypeName(target()) , prop.name()); + qmlError.setDescription(description); + qmlError.setObject(target()); + ep->warning(qmlError); } - - QVariant resultVariant(scope.engine->toVariant(result, metaType.id())); - resultVariant.convert(metaType); - const bool hasChanged = !metaType.equals(resultVariant.constData(), dataPtr); - metaType.destruct(dataPtr); - metaType.construct(dataPtr, resultVariant.constData()); - return hasChanged; } -QString QQmlPropertyBinding::createBindingLoopErrorDescription(QJSEnginePrivate *ep) +QString QQmlPropertyBinding::createBindingLoopErrorDescription() { - QQmlPropertyData *propertyData = nullptr; + const QQmlPropertyData *propertyData = nullptr; QQmlPropertyData valueTypeData; QQmlData *data = QQmlData::get(target(), false); Q_ASSERT(data); - if (Q_UNLIKELY(!data->propertyCache)) { - data->propertyCache = ep->cache(target()->metaObject()); - data->propertyCache->addref(); - } + if (Q_UNLIKELY(!data->propertyCache)) + data->propertyCache = QQmlMetaType::propertyCache(target()->metaObject()); + propertyData = data->propertyCache->property(targetIndex().coreIndex()); Q_ASSERT(propertyData); Q_ASSERT(!targetIndex().hasValueTypeIndex()); QQmlProperty prop = QQmlPropertyPrivate::restore(target(), *propertyData, &valueTypeData, nullptr); - return QStringLiteral(R"(QML %1: Binding loop detected for property "%2")").arg(QQmlMetaType::prettyTypeName(target()) , prop.name()); -} - -QObject *QQmlPropertyBinding::target() -{ - return std::launder(reinterpret_cast<TargetData *>(&declarativeExtraData))->target; -} - -QQmlPropertyIndex QQmlPropertyBinding::targetIndex() -{ - return std::launder(reinterpret_cast<TargetData *>(&declarativeExtraData))->targetIndex; -} - -bool QQmlPropertyBinding::hasBoundFunction() -{ - return std::launder(reinterpret_cast<TargetData *>(&declarativeExtraData))->hasBoundFunction; + return R"(QML %1: Binding loop detected for property "%2")"_L1.arg(QQmlMetaType::prettyTypeName(target()) , prop.name()); } void QQmlPropertyBinding::bindingErrorCallback(QPropertyBindingPrivate *that) @@ -302,7 +291,6 @@ void QQmlPropertyBinding::bindingErrorCallback(QPropertyBindingPrivate *that) auto engine = qmlEngine(target); if (!engine) return; - auto ep = QQmlEnginePrivate::get(engine); auto error = This->bindingError(); QQmlError qmlError; @@ -312,21 +300,24 @@ void QQmlPropertyBinding::bindingErrorCallback(QPropertyBindingPrivate *that) qmlError.setUrl(QUrl {location.sourceFile}); auto description = error.description(); if (error.type() == QPropertyBindingError::BindingLoop) { - description = This->createBindingLoopErrorDescription(ep); + description = This->createBindingLoopErrorDescription(); } qmlError.setDescription(description); qmlError.setObject(target); - ep->warning(qmlError); + QQmlEnginePrivate::get(engine)->warning(qmlError); } -QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(const QQmlPropertyData *pd, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QV4::CompiledData::Binding *binding) +template<typename TranslateWithUnit> +auto qQmlTranslationPropertyBindingCreateBinding( + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, + TranslateWithUnit &&translateWithUnit) { - auto translationBinding = [compilationUnit, binding](const QMetaType &metaType, void *dataPtr) -> bool { - // Create a dependency to the uiLanguage - QJSEnginePrivate::get(compilationUnit->engine)->uiLanguage.value(); + return [compilationUnit, translateWithUnit](QMetaType metaType, void *dataPtr) -> bool { + // Create a dependency to the translationLanguage + QQmlEnginePrivate::get(compilationUnit->engine)->translationLanguage.value(); - QVariant resultVariant(compilationUnit->bindingValueAsString(binding)); - if (metaType.id() != QMetaType::QString) + QVariant resultVariant(translateWithUnit(compilationUnit)); + if (metaType != QMetaType::fromType<QString>()) resultVariant.convert(metaType); const bool hasChanged = !metaType.equals(resultVariant.constData(), dataPtr); @@ -334,8 +325,38 @@ QUntypedPropertyBinding QQmlTranslationPropertyBinding::create(const QQmlPropert metaType.construct(dataPtr, resultVariant.constData()); return hasChanged; }; +} - return QUntypedPropertyBinding(QMetaType(pd->propType()), translationBinding, QPropertyBindingSourceLocation()); +QUntypedPropertyBinding QQmlTranslationPropertyBinding::create( + const QQmlPropertyData *pd, + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, + const QV4::CompiledData::Binding *binding) +{ + auto translationBinding = qQmlTranslationPropertyBindingCreateBinding( + compilationUnit, + [binding](const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) { + return compilationUnit->bindingValueAsString(binding); + }); + + return QUntypedPropertyBinding(QMetaType(pd->propType()), translationBinding, + QPropertyBindingSourceLocation()); +} + +QUntypedPropertyBinding QQmlTranslationPropertyBinding::create( + const QMetaType &propertyType, + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, + const QQmlTranslation &translationData) +{ + auto translationBinding = qQmlTranslationPropertyBindingCreateBinding( + compilationUnit, + [translationData]( + const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit) { + Q_UNUSED(compilationUnit); + return translationData.translate(); + }); + + return QUntypedPropertyBinding(propertyType, translationBinding, + QPropertyBindingSourceLocation()); } QV4::ReturnedValue QQmlPropertyBindingJSForBoundFunction::evaluate(bool *isUndefined) @@ -345,7 +366,7 @@ QV4::ReturnedValue QQmlPropertyBindingJSForBoundFunction::evaluate(bool *isUndef const QV4::Value *argv = nullptr; const QV4::Value *thisObject = nullptr; QV4::BoundFunction *b = nullptr; - if ((b = static_cast<QV4::BoundFunction *>(m_boundFunction.valueRef()))) { + if ((b = m_boundFunction.as<QV4::BoundFunction>())) { QV4::Heap::MemberData *args = b->boundArgs(); if (args) { argc = args->values.size; @@ -354,9 +375,9 @@ QV4::ReturnedValue QQmlPropertyBindingJSForBoundFunction::evaluate(bool *isUndef 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); } QT_END_NAMESPACE |