diff options
author | Sami Shalayel <sami.shalayel@qt.io> | 2023-08-10 09:45:37 +0200 |
---|---|---|
committer | Sami Shalayel <sami.shalayel@qt.io> | 2023-08-15 12:13:31 +0000 |
commit | a1ce0596e517e84913b14ab23422137c95b8c785 (patch) | |
tree | a7a012aa8a0d260c6f464c6b5dc722806697fa4c | |
parent | 8d6f9e716d1c3fdd05ac14612583359313b9dc6e (diff) |
Replace signal name manipulations with QQmlSignalNames
Remove custom implementations found in qqmljs* and use the
static helper methods from qqmlsignalnames_p.h instead. This sometimes
requires to move some code around to avoid bugs with property that do
not have letters in their name.
Add a warning in the JS implementation of the SignalSpy.qml that the
used heuristic might fail on certain signal names.
Add tests in in tst_qqmllanguage to see if the property change handlers
work correctly for weird names.
Change-Id: I4dc73c34df7f77f529511fa04ab5fcc5385b59fc
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
34 files changed, 293 insertions, 246 deletions
diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp index 1fcc1e7772..3bd37878a8 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp @@ -15,6 +15,7 @@ #include <private/qqmlvaluetype_p.h> #include <private/qqmlvmemetaobject_p.h> #include <private/qqmlexpression_p.h> +#include <private/qqmlsignalnames_p.h> #include <QtCore/qdebug.h> #include <QtCore/qmetaobject.h> @@ -119,22 +120,14 @@ QDataStream &operator>>(QDataStream &ds, return ds; } -static inline bool isSignalPropertyName(const QString &signalName) -{ - // see QmlCompiler::isSignalPropertyName - return signalName.size() >= 3 && signalName.startsWith(QLatin1String("on")) && - signalName.at(2).isLetter() && signalName.at(2).isUpper(); -} - static bool hasValidSignal(QObject *object, const QString &propertyName) { - if (!isSignalPropertyName(propertyName)) + auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName); + if (!signalName) return false; - QString signalName = propertyName.mid(2); - signalName[0] = signalName.at(0).toLower(); - - int sigIdx = QQmlPropertyPrivate::findSignalByName(object->metaObject(), signalName.toLatin1()).methodIndex(); + int sigIdx = QQmlPropertyPrivate::findSignalByName(object->metaObject(), signalName->toLatin1()) + .methodIndex(); if (sigIdx == -1) return false; @@ -305,10 +298,8 @@ void QQmlEngineDebugServiceImpl::buildObjectDump(QDataStream &message, if (scope) { const QByteArray methodName = QMetaObjectPrivate::signal(scope->metaObject(), signalHandler->signalIndex()).name(); - const QLatin1String methodNameStr(methodName); - if (methodNameStr.size() != 0) { - prop.name = QLatin1String("on") + QChar(methodNameStr.at(0)).toUpper() - + methodNameStr.mid(1); + if (!methodName.isEmpty()) { + prop.name = QQmlSignalNames::signalNameToHandlerName(methodName); } } } diff --git a/src/qml/common/qqmlsignalnames.cpp b/src/qml/common/qqmlsignalnames.cpp index 5d46531dfb..96c4ee5ad7 100644 --- a/src/qml/common/qqmlsignalnames.cpp +++ b/src/qml/common/qqmlsignalnames.cpp @@ -14,10 +14,10 @@ using namespace Qt::Literals; static std::optional<qsizetype> firstLetterIdx(QStringView name, qsizetype removePrefix = 0, qsizetype removeSuffix = 0) { - auto result = std::find_if(std::next(name.cbegin(), removePrefix), - std::prev(name.cend(), removeSuffix), + auto end = std::prev(name.cend(), removeSuffix); + auto result = std::find_if(std::next(name.cbegin(), removePrefix), end, [](const QChar &c) { return c.isLetter(); }); - if (result != name.cend()) + if (result != end) return std::distance(name.begin(), result); return {}; @@ -157,23 +157,41 @@ QString QQmlSignalNames::signalNameToHandlerName(QAnyStringView signal) return handlerName; } -/*! -\internal -Returns a signal name from \a handlerName string. -*/ -std::optional<QString> QQmlSignalNames::handlerNameToSignalName(QStringView handler) +enum HandlerType { ChangedHandler, Handler }; + +static std::optional<QString> handlerNameToSignalNameHelper(QStringView handler, HandlerType type) { - if (!isHandlerName(handler)) + if (!QQmlSignalNames::isHandlerName(handler)) return {}; QString signalName = handler.sliced(strlen("on")).toString(); if (signalName.isEmpty()) return {}; - changeCaseOfFirstLetter(signalName, ToLower); + changeCaseOfFirstLetter(signalName, ToLower, 0, type == ChangedHandler ? strlen("Changed") : 0); return signalName; } +/*! +\internal +Returns a signal name from \a handlerName string. Do not use it on changed handlers, see +changedHandlerNameToSignalName for that! +*/ +std::optional<QString> QQmlSignalNames::handlerNameToSignalName(QStringView handler) +{ + return handlerNameToSignalNameHelper(handler, Handler); +} + +/*! +\internal +Returns a signal name from \a changedHandlerName string. Makes sure not to lowercase the 'C' from +Changed. +*/ +std::optional<QString> QQmlSignalNames::changedHandlerNameToSignalName(QStringView handler) +{ + return handlerNameToSignalNameHelper(handler, ChangedHandler); +} + bool QQmlSignalNames::isChangedSignalName(QStringView signalName) { const qsizetype smallestAllowedSize = strlen("XChanged"); diff --git a/src/qml/common/qqmlsignalnames_p.h b/src/qml/common/qqmlsignalnames_p.h index f777e89812..753f2ca97a 100644 --- a/src/qml/common/qqmlsignalnames_p.h +++ b/src/qml/common/qqmlsignalnames_p.h @@ -42,6 +42,7 @@ public: static std::optional<QByteArray> changedHandlerNameToPropertyName(QUtf8StringView handler); static std::optional<QString> handlerNameToSignalName(QStringView handler); + static std::optional<QString> changedHandlerNameToSignalName(QStringView changedHandler); static bool isChangedHandlerName(QStringView signalName); static bool isChangedSignalName(QStringView signalName); diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index 072894adde..2d1a617b09 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -442,38 +442,6 @@ bool IRBuilder::generateFromQml(const QString &code, const QString &url, Documen return errors.isEmpty(); } -bool IRBuilder::isSignalPropertyName(const QString &name) -{ - if (name.size() < 3) return false; - if (!name.startsWith(QLatin1String("on"))) return false; - int ns = name.size(); - for (int i = 2; i < ns; ++i) { - const QChar curr = name.at(i); - if (curr.unicode() == '_') continue; - if (curr.isUpper()) return true; - return false; - } - return false; // consists solely of underscores - invalid. -} - -QString IRBuilder::signalNameFromSignalPropertyName(const QString &signalPropertyName) -{ - Q_ASSERT(signalPropertyName.startsWith(QLatin1String("on"))); - QString signalNameCandidate = signalPropertyName; - signalNameCandidate.remove(0, 2); - - // Note that the property name could start with any alpha or '_' or '$' character, - // so we need to do the lower-casing of the first alpha character. - for (int firstAlphaIndex = 0; firstAlphaIndex < signalNameCandidate.size(); ++firstAlphaIndex) { - if (signalNameCandidate.at(firstAlphaIndex).isUpper()) { - signalNameCandidate[firstAlphaIndex] = signalNameCandidate.at(firstAlphaIndex).toLower(); - return signalNameCandidate; - } - } - - Q_UNREACHABLE_RETURN(QString()); -} - bool IRBuilder::visit(QQmlJS::AST::UiArrayMemberList *ast) { return QQmlJS::AST::Visitor::visit(ast); diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 5b9c0fce60..2935a3273b 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -506,9 +506,6 @@ public: IRBuilder(const QSet<QString> &illegalNames); bool generateFromQml(const QString &code, const QString &url, Document *output); - static bool isSignalPropertyName(const QString &name); - static QString signalNameFromSignalPropertyName(const QString &signalPropertyName); - using QQmlJS::AST::Visitor::visit; using QQmlJS::AST::Visitor::endVisit; diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 0e44df219f..7437eefa85 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -20,6 +20,7 @@ #include <private/qqmlbuiltinfunctions_p.h> #include <private/qqmlirbuilder_p.h> #include <QtQml/private/qqmllist_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <QStringList> #include <QVector> @@ -363,11 +364,10 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name, }; QQmlData *ddata = QQmlData::get(currentObject, false); - auto findChangeSignal = [&](QStringView signalName) { - const QString changed = QStringLiteral("Changed"); - if (signalName.endsWith(changed)) { - const QStringView propName = signalName.first(signalName.size() - changed.size()); - const QQmlPropertyData *d = ddata->propertyCache->property(propName, currentObject, context); + auto findChangeSignal = [&](QStringView changedHandlerName) { + if (auto propName = QQmlSignalNames::changedHandlerNameToPropertyName(changedHandlerName)) { + const QQmlPropertyData *d = + ddata->propertyCache->property(*propName, currentObject, context); while (d && d->isFunction()) d = ddata->propertyCache->overrideData(d); @@ -381,19 +381,11 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name, }; const QString terminalString = terminal.toString(); - if (QmlIR::IRBuilder::isSignalPropertyName(terminalString)) { - QString signalName = terminalString.mid(2); - int firstNon_; - int length = signalName.size(); - for (firstNon_ = 0; firstNon_ < length; ++firstNon_) - if (signalName.at(firstNon_) != u'_') - break; - signalName[firstNon_] = signalName.at(firstNon_).toLower(); - + if (auto signalName = QQmlSignalNames::handlerNameToSignalName(terminalString)) { if (ddata && ddata->propertyCache) { // Try method - const QQmlPropertyData *d = ddata->propertyCache->property( - signalName, currentObject, context); + const QQmlPropertyData *d = + ddata->propertyCache->property(*signalName, currentObject, context); // ### Qt7: This code treats methods as signals. It should use d->isSignal(). // That would be a change in behavior, though. Right now you can construct a @@ -407,9 +399,9 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name, return; } - if (findChangeSignal(signalName)) + if (findChangeSignal(terminalString)) return; - } else if (findSignalInMetaObject(signalName.toUtf8())) { + } else if (findSignalInMetaObject(signalName->toUtf8())) { return; } } @@ -744,15 +736,7 @@ QString QQmlProperty::name() const d->nameCache = d->core.name(d->object) + QLatin1Char('.') + QString::fromUtf8(vtName); } else if (type() & SignalProperty) { // ### Qt7: Return the original signal name here. Do not prepend "on" - QString name = QStringLiteral("on") + d->core.name(d->object); - for (int i = 2, end = name.size(); i != end; ++i) { - const QChar c = name.at(i); - if (c != u'_') { - name[i] = c.toUpper(); - break; - } - } - d->nameCache = name; + d->nameCache = QQmlSignalNames::signalNameToHandlerName(d->core.name(d->object)); } else { d->nameCache = d->core.name(d->object); } @@ -1956,9 +1940,8 @@ QMetaMethod QQmlPropertyPrivate::findSignalByName(const QMetaObject *mo, const Q // If no signal is found, but the signal is of the form "onBlahChanged", // return the notify signal for the property "Blah" - if (name.endsWith("Changed")) { - QByteArray propName = name.mid(0, name.size() - 7); - int propIdx = mo->indexOfProperty(propName.constData()); + if (auto propName = QQmlSignalNames::changedSignalNameToPropertyName(name)) { + int propIdx = mo->indexOfProperty(propName->constData()); if (propIdx >= 0) { QMetaProperty prop = mo->property(propIdx); if (prop.hasNotifySignal()) diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index b51fc963f7..d368433949 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -10,6 +10,7 @@ #include <private/qmetaobject_p.h> #include <private/qmetaobjectbuilder_p.h> #include <private/qqmlpropertycachemethodarguments_p.h> +#include <private/qqmlsignalnames_p.h> #include <private/qv4value_p.h> @@ -249,8 +250,7 @@ void QQmlPropertyCache::appendSignal(const QString &name, QQmlPropertyData::Flag int signalHandlerIndex = signalHandlerIndexCache.size(); signalHandlerIndexCache.append(handler); - QString handlerName = QLatin1String("on") + name; - handlerName[2] = handlerName.at(2).toUpper(); + const QString handlerName = QQmlSignalNames::signalNameToHandlerName(name); setNamedProperty(name, methodIndex + methodOffset(), methodIndexCache.data() + methodIndex); setNamedProperty(handlerName, signalHandlerIndex + signalOffset(), @@ -449,7 +449,7 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, setNamedProperty(methodName, ii, data); if (data->isSignal()) { - QHashedString on(QLatin1String("on") % methodName.at(0).toUpper() % QStringView{methodName}.mid(1)); + QHashedString on(QQmlSignalNames::signalNameToHandlerName(methodName)); setNamedProperty(on, ii, sigdata); ++signalHandlerIndex; } @@ -462,17 +462,8 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, setNamedProperty(methodName, ii, data); if (data->isSignal()) { - int length = methodName.length(); - - QVarLengthArray<char, 128> str(length+3); - str[0] = 'o'; - str[1] = 'n'; - str[2] = QtMiscUtils::toAsciiUpper(rawName[0]); - if (length > 1) - memcpy(&str[3], &rawName[1], length - 1); - str[length + 2] = '\0'; - - QHashedString on(QString::fromLatin1(str.data())); + QHashedString on(QQmlSignalNames::signalNameToHandlerName( + QLatin1StringView{ methodName.constData(), methodName.length() })); setNamedProperty(on, ii, data); ++signalHandlerIndex; } diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h index f7d27d113b..a48835f6c1 100644 --- a/src/qml/qml/qqmlpropertycachecreator_p.h +++ b/src/qml/qml/qqmlpropertycachecreator_p.h @@ -21,6 +21,7 @@ #include <private/qqmltypedata_p.h> #include <private/inlinecomponentutils_p.h> #include <private/qqmlsourcecoordinate_p.h> +#include <private/qqmlsignalnames_p.h> #include <QScopedValueRollback> #include <vector> @@ -486,7 +487,8 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject( for ( ; p != pend; ++p) { auto flags = QQmlPropertyData::defaultSignalFlags(); - QString changedSigName = stringAt(p->nameIndex) + QLatin1String("Changed"); + const QString changedSigName = + QQmlSignalNames::propertyNameToChangedSignalName(stringAt(p->nameIndex)); seenSignals.insert(changedSigName); cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); @@ -497,7 +499,8 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject( for ( ; a != aend; ++a) { auto flags = QQmlPropertyData::defaultSignalFlags(); - QString changedSigName = stringAt(a->nameIndex()) + QLatin1String("Changed"); + const QString changedSigName = + QQmlSignalNames::propertyNameToChangedSignalName(stringAt(a->nameIndex())); seenSignals.insert(changedSigName); cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); diff --git a/src/qml/qml/qqmlpropertyresolver.cpp b/src/qml/qml/qqmlpropertyresolver.cpp index ff29c38997..0217f7b7b5 100644 --- a/src/qml/qml/qqmlpropertyresolver.cpp +++ b/src/qml/qml/qqmlpropertyresolver.cpp @@ -3,6 +3,7 @@ #include "qqmlpropertyresolver_p.h" #include <private/qqmlcontextdata_p.h> +#include <private/qqmlsignalnames_p.h> QT_BEGIN_NAMESPACE @@ -43,10 +44,8 @@ const QQmlPropertyData *QQmlPropertyResolver::signal(const QString &name, bool * return d; } - if (name.endsWith(QLatin1String("Changed"))) { - QString propName = name.mid(0, name.size() - static_cast<int>(strlen("Changed"))); - - d = property(propName, notInRevision); + if (auto propName = QQmlSignalNames::changedSignalNameToPropertyName(name)) { + d = property(*propName, notInRevision); if (d) return cache->signal(d->notifyIndex()); } diff --git a/src/qml/qml/qqmlpropertyvalidator.cpp b/src/qml/qml/qqmlpropertyvalidator.cpp index b1a61d7e35..23f7516ee4 100644 --- a/src/qml/qml/qqmlpropertyvalidator.cpp +++ b/src/qml/qml/qqmlpropertyvalidator.cpp @@ -9,6 +9,7 @@ #include <private/qqmlpropertycachecreator_p.h> #include <private/qqmlpropertyresolver_p.h> #include <private/qqmlstringconverters_p.h> +#include <private/qqmlsignalnames_p.h> #include <QtCore/qdatetime.h> @@ -140,7 +141,7 @@ QVector<QQmlError> QQmlPropertyValidator::validateObject( customBindings << binding; continue; } - } else if (QmlIR::IRBuilder::isSignalPropertyName(name) + } else if (QQmlSignalNames::isHandlerName(name) && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { customBindings << binding; continue; diff --git a/src/qml/qml/qqmltypecompiler.cpp b/src/qml/qml/qqmltypecompiler.cpp index b921463eb6..85dfb68314 100644 --- a/src/qml/qml/qqmltypecompiler.cpp +++ b/src/qml/qml/qqmltypecompiler.cpp @@ -9,6 +9,7 @@ #include <private/qqmlcomponent_p.h> #include <private/qqmlpropertyresolver_p.h> #include <private/qqmlcomponentandaliasresolver_p.h> +#include <private/qqmlsignalnames_p.h> #define COMPILE_EXCEPTION(token, desc) \ { \ @@ -325,18 +326,21 @@ bool SignalHandlerResolver::resolveSignalHandlerExpressions( continue; } - if (!QmlIR::IRBuilder::isSignalPropertyName(bindingPropertyName)) + QString qPropertyName; + QString signalName; + if (auto propertyName = + QQmlSignalNames::changedHandlerNameToPropertyName(bindingPropertyName)) { + qPropertyName = *propertyName; + signalName = *QQmlSignalNames::changedHandlerNameToSignalName(bindingPropertyName); + } else { + signalName = QQmlSignalNames::handlerNameToSignalName(bindingPropertyName) + .value_or(QString()); + } + if (signalName.isEmpty()) continue; QQmlPropertyResolver resolver(propertyCache); - const QString signalName = QmlIR::IRBuilder::signalNameFromSignalPropertyName( - bindingPropertyName); - - QString qPropertyName; - if (signalName.endsWith(QLatin1String("Changed"))) - qPropertyName = signalName.mid(0, signalName.size() - static_cast<int>(strlen("Changed"))); - bool notInRevision = false; const QQmlPropertyData * const signal = resolver.signal(signalName, ¬InRevision); const QQmlPropertyData * const signalPropertyData = resolver.property(signalName, /*notInRevision ptr*/nullptr); @@ -1045,7 +1049,7 @@ bool QQmlDeferredAndCustomParserBindingScanner::scanObject( obj->flags |= Object::HasCustomParserBindings; continue; } - } else if (QmlIR::IRBuilder::isSignalPropertyName(name) + } else if (QQmlSignalNames::isHandlerName(name) && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { obj->flags |= Object::HasCustomParserBindings; binding->setFlag(Binding::IsCustomParserBinding); diff --git a/src/qml/types/qqmlconnections.cpp b/src/qml/types/qqmlconnections.cpp index e69e0087e1..de0c433152 100644 --- a/src/qml/types/qqmlconnections.cpp +++ b/src/qml/types/qqmlconnections.cpp @@ -15,6 +15,8 @@ #include <QtCore/qdebug.h> #include <QtCore/qstringlist.h> +#include <QtQml/private/qqmlsignalnames_p.h> + #include <private/qobject_p.h> QT_BEGIN_NAMESPACE @@ -205,9 +207,7 @@ void QQmlConnectionsParser::verifyBindings(const QQmlRefPointer<QV4::ExecutableC const QV4::CompiledData::Binding *binding = props.at(ii); const QString &propName = compilationUnit->stringAt(binding->propertyNameIndex); - const bool thirdCharacterIsValid = (propName.size() >= 2) - && (propName.at(2).isUpper() || propName.at(2) == u'_'); - if (!propName.startsWith(QLatin1String("on")) || !thirdCharacterIsValid) { + if (!QQmlSignalNames::isHandlerName(propName)) { error(props.at(ii), QQmlConnections::tr("Cannot assign to non-existent property \"%1\"").arg(propName)); return; } diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp index 9dfe7010b0..2f1bc7a391 100644 --- a/src/qmlcompiler/qqmljscompiler.cpp +++ b/src/qmlcompiler/qqmljscompiler.cpp @@ -19,6 +19,8 @@ #include <QtCore/qfileinfo.h> #include <QtCore/qloggingcategory.h> +#include <QtQml/private/qqmlsignalnames_p.h> + #include <limits> QT_BEGIN_NAMESPACE @@ -123,9 +125,7 @@ static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc, if (binding->type() != QV4::CompiledData::Binding::Type_Script) continue; const QString propName = doc.stringAt(binding->propertyNameIndex); - if (!propName.startsWith(QLatin1String("on")) - || propName.size() < 3 - || !propName.at(2).isUpper()) + if (!QQmlSignalNames::isHandlerName(propName)) continue; auto compiledFunction = doc.jsModule.functions.value(object->runtimeFunctionIndices.at(binding->value.compiledScriptIndex)); if (!compiledFunction) diff --git a/src/qmlcompiler/qqmljsfunctioninitializer.cpp b/src/qmlcompiler/qqmljsfunctioninitializer.cpp index 9fb83014fe..217b00c52c 100644 --- a/src/qmlcompiler/qqmljsfunctioninitializer.cpp +++ b/src/qmlcompiler/qqmljsfunctioninitializer.cpp @@ -8,6 +8,8 @@ #include <QtCore/qloggingcategory.h> #include <QtCore/qfileinfo.h> +#include <QtQml/private/qqmlsignalnames_p.h> + QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; @@ -166,44 +168,45 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run( } function.isProperty = m_objectType->hasProperty(propertyName); - if (!function.isProperty && QmlIR::IRBuilder::isSignalPropertyName(propertyName)) { - const QString signalName = QmlIR::IRBuilder::signalNameFromSignalPropertyName(propertyName); - - if (signalName.endsWith(u"Changed"_s) - && m_objectType->hasProperty(signalName.chopped(strlen("Changed")))) { - function.isSignalHandler = true; - } else { - const auto methods = m_objectType->methods(signalName); - for (const auto &method : methods) { - if (method.isCloned()) - continue; - if (method.methodType() == QQmlJSMetaMethodType::Signal) { - function.isSignalHandler = true; - const auto arguments = method.parameters(); - for (qsizetype i = 0, end = arguments.size(); i < end; ++i) { - const auto &type = arguments[i].type(); - if (type.isNull()) { - diagnose(u"Cannot resolve the argument type %1."_s.arg( - arguments[i].typeName()), - QtDebugMsg, bindingLocation, error); - function.argumentTypes.append( + if (!function.isProperty) { + if (QQmlSignalNames::isHandlerName(propertyName)) { + if (auto actualPropertyName = + QQmlSignalNames::changedHandlerNameToPropertyName(propertyName); + actualPropertyName && m_objectType->hasProperty(*actualPropertyName)) { + function.isSignalHandler = true; + } else { + auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName); + const auto methods = m_objectType->methods(*signalName); + for (const auto &method : methods) { + if (method.isCloned()) + continue; + if (method.methodType() == QQmlJSMetaMethodType::Signal) { + function.isSignalHandler = true; + const auto arguments = method.parameters(); + for (qsizetype i = 0, end = arguments.size(); i < end; ++i) { + const auto &type = arguments[i].type(); + if (type.isNull()) { + diagnose(u"Cannot resolve the argument type %1."_s.arg( + arguments[i].typeName()), + QtDebugMsg, bindingLocation, error); + function.argumentTypes.append( m_typeResolver->tracked( - m_typeResolver->globalType(m_typeResolver->varType()))); - } else { - function.argumentTypes.append(m_typeResolver->tracked( - m_typeResolver->globalType(type))); + m_typeResolver->globalType(m_typeResolver->varType()))); + } else { + function.argumentTypes.append(m_typeResolver->tracked( + m_typeResolver->globalType(type))); + } } + break; } - break; + } + if (!function.isSignalHandler) { + diagnose(u"Could not compile signal handler for %1: The signal does not exist"_s.arg( + *signalName), + QtWarningMsg, bindingLocation, error); } } } - - if (!function.isSignalHandler) { - diagnose(u"Could not compile signal handler for %1: The signal does not exist"_s.arg( - signalName), - QtWarningMsg, bindingLocation, error); - } } if (!function.isSignalHandler) { diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index a772f1b5a2..181607d42e 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -13,6 +13,7 @@ #include <QtCore/qrect.h> #include <QtCore/qsize.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <QtQml/private/qv4codegen_p.h> #include <QtQml/private/qqmlstringconverters_p.h> #include <QtQml/private/qqmlirbuilder_p.h> @@ -963,7 +964,7 @@ void QQmlJSImportVisitor::checkSignal( const QQmlJSScope::ConstPtr &signalScope, const QQmlJS::SourceLocation &location, const QString &handlerName, const QStringList &handlerParameters) { - const auto signal = QQmlJSUtils::signalName(handlerName); + const auto signal = QQmlSignalNames::handlerNameToSignalName(handlerName); std::optional<QQmlJSMetaMethod> signalMethod; const auto setSignalMethod = [&](const QQmlJSScope::ConstPtr &scope, const QString &name) { @@ -975,8 +976,7 @@ void QQmlJSImportVisitor::checkSignal( if (signal.has_value()) { if (signalScope->hasMethod(*signal)) { setSignalMethod(signalScope, *signal); - } else if (auto p = QQmlJSUtils::changeHandlerProperty(signalScope, *signal); - p.has_value()) { + } else if (auto p = QQmlJSUtils::propertyFromChangedHandler(signalScope, handlerName)) { // we have a change handler of the form "onXChanged" where 'X' // is a property name @@ -2025,11 +2025,11 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding) prefix.clear(); } - auto name = group->name.toString(); + const auto name = group->name.toString(); // This is a preliminary check. // Even if the name starts with "on", it might later turn out not to be a signal. - const auto signal = QQmlJSUtils::signalName(name); + const auto signal = QQmlSignalNames::handlerNameToSignalName(name); if (!signal.has_value() || m_currentScope->hasProperty(name)) { m_propertyBindings[m_currentScope].append( @@ -2079,7 +2079,7 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding) if (!methods.isEmpty()) { kind = QQmlSA::ScriptBindingKind::Script_SignalHandler; checkSignal(scope, groupLocation, name, signalParameters); - } else if (QQmlJSUtils::changeHandlerProperty(scope, signalName).has_value()) { + } else if (QQmlJSUtils::propertyFromChangedHandler(scope, name).has_value()) { kind = QQmlSA::ScriptBindingKind::Script_ChangeHandler; checkSignal(scope, groupLocation, name, signalParameters); } else if (scope->hasProperty(name)) { diff --git a/src/qmlcompiler/qqmljsscope.cpp b/src/qmlcompiler/qqmljsscope.cpp index 8e5e66675a..4664e62c54 100644 --- a/src/qmlcompiler/qqmljsscope.cpp +++ b/src/qmlcompiler/qqmljsscope.cpp @@ -81,7 +81,8 @@ void QQmlJSScope::insertJSIdentifier(const QString &name, const JavaScriptIdenti void QQmlJSScope::insertPropertyIdentifier(const QQmlJSMetaProperty &property) { addOwnProperty(property); - QQmlJSMetaMethod method(property.propertyName() + u"Changed"_s, u"void"_s); + QQmlJSMetaMethod method( + QQmlSignalNames::propertyNameToChangedSignalName(property.propertyName()), u"void"_s); method.setMethodType(QQmlJSMetaMethodType::Signal); method.setIsImplicitQmlPropertyChangeSignal(true); addOwnMethod(method); diff --git a/src/qmlcompiler/qqmljsutils_p.h b/src/qmlcompiler/qqmljsutils_p.h index 5e4f6004f8..87e75e5cfa 100644 --- a/src/qmlcompiler/qqmljsutils_p.h +++ b/src/qmlcompiler/qqmljsutils_p.h @@ -25,6 +25,7 @@ #include <QtCore/qstring.h> #include <QtCore/qstringview.h> #include <QtCore/qstringbuilder.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <private/qduplicatetracker_p.h> #include <optional> @@ -99,26 +100,6 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils return type; } - /*! \internal - Returns a signal name from \a handlerName string. - */ - static std::optional<QString> signalName(QStringView handlerName) - { - if (handlerName.startsWith(u"on") && handlerName.size() > 2) { - QString signal = handlerName.mid(2).toString(); - for (int i = 0; i < signal.size(); ++i) { - QChar &ch = signal[i]; - if (ch.isLower()) - return {}; - if (ch.isUpper()) { - ch = ch.toLower(); - return signal; - } - } - } - return {}; - } - static std::optional<QQmlJSMetaProperty> changeHandlerProperty(const QQmlJSScope::ConstPtr &scope, QStringView signalName) { @@ -134,6 +115,21 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils return {}; } + static std::optional<QQmlJSMetaProperty> + propertyFromChangedHandler(const QQmlJSScope::ConstPtr &scope, QStringView changedHandler) + { + auto signalName = QQmlSignalNames::changedHandlerNameToPropertyName(changedHandler); + if (!signalName) + return {}; + + auto p = scope->property(*signalName); + const bool isBindable = !p.bindable().isEmpty(); + const bool canNotify = !p.notify().isEmpty(); + if (p.isValid() && (isBindable || canNotify)) + return p; + return {}; + } + static bool hasCompositeBase(const QQmlJSScope::ConstPtr &scope) { if (!scope) diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h index 4cc1cd016e..a26b624a1b 100644 --- a/src/qmldom/qqmldomelements_p.h +++ b/src/qmldom/qqmldomelements_p.h @@ -22,6 +22,7 @@ #include <QtQml/private/qqmljsast_p.h> #include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <QtCore/QCborValue> #include <QtCore/QCborMap> @@ -554,9 +555,7 @@ public: bool isSignalHandler() const { QString baseName = m_name.split(QLatin1Char('.')).last(); - if (baseName.startsWith(u"on") && baseName.size() > 2 && baseName.at(2).isUpper()) - return true; - return false; + return QQmlSignalNames::isHandlerName(baseName); } static QString preCodeForName(QStringView n) { diff --git a/src/qmlls/qqmlcompletionsupport.cpp b/src/qmlls/qqmlcompletionsupport.cpp index 2545e4abc0..93b4a472df 100644 --- a/src/qmlls/qqmlcompletionsupport.cpp +++ b/src/qmlls/qqmlcompletionsupport.cpp @@ -10,6 +10,7 @@ #include <QtCore/QRegularExpression> #include <QtQmlDom/private/qqmldomexternalitems_p.h> #include <QtQmlDom/private/qqmldomtop_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> QT_BEGIN_NAMESPACE using namespace QLspSpecification; @@ -287,8 +288,7 @@ static QList<CompletionItem> bindingsCompletions(DomItem &containingObject) if (it.value().methodType == MethodInfo::MethodType::Signal) { CompletionItem comp; QString signal = it.key(); - comp.label = - (u"on"_s + signal.at(0).toUpper() + signal.mid(1)).toUtf8(); + comp.label = QQmlSignalNames::signalNameToHandlerName(signal).toUtf8(); res.append(comp); } ++it; diff --git a/src/quick/util/qquickpropertychanges.cpp b/src/quick/util/qquickpropertychanges.cpp index fcb6e71e17..f1c01e3a0b 100644 --- a/src/quick/util/qquickpropertychanges.cpp +++ b/src/quick/util/qquickpropertychanges.cpp @@ -20,6 +20,7 @@ #include <private/qqmlirbuilder_p.h> #include <QtCore/qdebug.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <private/qobject_p.h> @@ -267,7 +268,7 @@ void QQuickPropertyChangesPrivate::decodeBinding(const QString &propertyPrefix, break; } - if (binding->isSignalHandler() || QmlIR::IRBuilder::isSignalPropertyName(propertyName)) { + if (binding->isSignalHandler() || QQmlSignalNames::isHandlerName(propertyName)) { QQmlProperty prop = property(propertyName); if (prop.isSignalProperty()) { QQuickReplaceSignalHandler *handler = new QQuickReplaceSignalHandler; diff --git a/tests/auto/qml/common/tst_qml_common.cpp b/tests/auto/qml/common/tst_qml_common.cpp index 67d72500e0..13d7ac0f72 100644 --- a/tests/auto/qml/common/tst_qml_common.cpp +++ b/tests/auto/qml/common/tst_qml_common.cpp @@ -37,6 +37,8 @@ void tst_qml_common::tst_propertyNameToChangedHandlerName_data() QTest::addRow("___123aProperty") << u"___123a"_s << u"on___123AChanged"_s; QTest::addRow("___123Property") << u"___123"_s << u"on___123Changed"_s; QTest::addRow("AProperty") << u"A"_s << u"onAChanged"_s; + QTest::addRow("_Property") << u"_"_s << u"on_Changed"_s; + QTest::addRow("$Property") << u"$"_s << u"on$Changed"_s; } void tst_qml_common::tst_propertyNameToChangedHandlerName() { @@ -67,6 +69,8 @@ void tst_qml_common::tst_signalNameToHandlerName_data() QTest::addRow("___123aProperty") << u"___123a"_s << u"on___123A"_s; QTest::addRow("___123Property") << u"___123"_s << u"on___123"_s; QTest::addRow("AProperty") << u"A"_s << u"onA"_s; + QTest::addRow("_Property") << u"_"_s << u"on_"_s; + QTest::addRow("$Property") << u"$"_s << u"on$"_s; } void tst_qml_common::tst_signalNameToHandlerName() @@ -150,6 +154,8 @@ void tst_qml_common::tst_isChangedHandlerName_data() QTest::addRow("empty") << u""_s << false; QTest::addRow("empty2") << u"onChanged"_s << false; QTest::addRow("on") << u"on"_s << false; + QTest::addRow("on_Changed") << u"on_Changed"_s << true; + QTest::addRow("on$Changed") << u"on$Changed"_s << true; } void tst_qml_common::tst_isChangedHandlerName() { diff --git a/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp b/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp index 97507f2512..586ed8ea8e 100644 --- a/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp +++ b/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp @@ -22,6 +22,7 @@ #include <QtQml/qqmlproperty.h> #include <QtQml/qqmlincubator.h> #include <QtQml/qqmlapplicationengine.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <QtQuick/qquickitem.h> #include <QtNetwork/qhostaddress.h> @@ -244,7 +245,7 @@ void tst_QQmlEngineDebugService::recursiveObjectTest( QCOMPARE(p.objectDebugId, QQmlDebugService::idForObject(o)); // signal properties are fake - they are generated from QQmlAbstractBoundSignal children - if (p.name.startsWith("on") && p.name.size() > 2 && p.name[2].isUpper()) { + if (QQmlSignalNames::isHandlerName(p.name)) { QString signal = p.value.toString(); QQmlBoundSignalExpression *expr = QQmlPropertyPrivate::signalExpression(QQmlProperty(o, p.name)); QVERIFY(expr && expr->expression() == signal); diff --git a/tests/auto/qml/qmlcppcodegen/data/conversions2.qml b/tests/auto/qml/qmlcppcodegen/data/conversions2.qml index c3a9414ae2..d0d1fc9b8f 100644 --- a/tests/auto/qml/qmlcppcodegen/data/conversions2.qml +++ b/tests/auto/qml/qmlcppcodegen/data/conversions2.qml @@ -94,6 +94,8 @@ Item { } function qtest_signalHandlerName(sn) { + // Warning: to not test for signal handlers like this in actual code. + // Use the helper methods in QQmlSignalNames instead. if (sn.substr(0, 2) === "on" && sn[2] === sn[2].toUpperCase()) return sn return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1) diff --git a/tests/auto/qml/qmlcppcodegen/data/methods.qml b/tests/auto/qml/qmlcppcodegen/data/methods.qml index 3abd14c9c1..c045c2249b 100644 --- a/tests/auto/qml/qmlcppcodegen/data/methods.qml +++ b/tests/auto/qml/qmlcppcodegen/data/methods.qml @@ -35,6 +35,8 @@ BirthdayParty { } function stuff(sn) { + // Warning: to not test for signal handlers like this in actual code. + // Use the helper methods in QQmlSignalNames instead. if (sn.substr(0, 2) === "on" && sn[2] === sn[2].toUpperCase()) return sn return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1) diff --git a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.3.qml b/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.3.qml deleted file mode 100644 index d611e0fe30..0000000000 --- a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.3.qml +++ /dev/null @@ -1,12 +0,0 @@ -import QtQuick 2.0 - -Item { - property int changeCount: 0 - - // invalid property name - we don't allow $ - property bool $nameWithDollarsign: false - - on$NameWithDollarsignChanged: { - changeCount = changeCount + 4; - } -} diff --git a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.4.qml b/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.4.qml deleted file mode 100644 index a6862517c6..0000000000 --- a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.4.qml +++ /dev/null @@ -1,12 +0,0 @@ -import QtQuick 2.0 - -Item { - property int changeCount: 0 - - property bool _6nameWithUnderscoreNumber: false - - // invalid property name - the first character after an underscore must be a letter - on_6NameWithUnderscoreNumberChanged: { - changeCount = changeCount + 3; - } -} diff --git a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlots.qml b/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlots.qml index f91fb71f1f..9a3141e15a 100644 --- a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlots.qml +++ b/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlots.qml @@ -6,6 +6,8 @@ Item { property bool normalName: false property bool _nameWithUnderscore: false property bool ____nameWithUnderscores: false + property bool _6nameWithUnderscoreNumber: false + property bool $nameWithDollarsign: false onNormalNameChanged: { changeCount = changeCount + 1; @@ -19,9 +21,20 @@ Item { changeCount = changeCount + 3; } + on$NameWithDollarsignChanged: { + changeCount = changeCount + 4; + } + + on_6NameWithUnderscoreNumberChanged: { + changeCount = changeCount + 5; + } + Component.onCompleted: { normalName = true; _nameWithUnderscore = true; ____nameWithUnderscores = true; + $nameWithDollarsign = true; + _6nameWithUnderscoreNumber = true; } + } diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 69a342bcb9..ca578e1f83 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -5300,6 +5300,7 @@ void tst_qqmlecmascript::propertyChangeSlots() QQmlComponent component(&engine, testFileUrl("changeslots/propertyChangeSlots.qml")); QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); + QCOMPARE(object->property("changeCount"), 15); // ensure that invalid property names fail properly. QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready"); @@ -5315,20 +5316,6 @@ void tst_qqmlecmascript::propertyChangeSlots() QCOMPARE(e2.errors().at(0).toString(), expectedErrorString); object.reset(e2.create()); QVERIFY(!object); - - QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready"); - QQmlComponent e3(&engine, testFileUrl("changeslots/propertyChangeSlotErrors.3.qml")); - expectedErrorString = e3.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on$NameWithDollarsignChanged\""); - QCOMPARE(e3.errors().at(0).toString(), expectedErrorString); - object.reset(e3.create()); - QVERIFY(!object); - - QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready"); - QQmlComponent e4(&engine, testFileUrl("changeslots/propertyChangeSlotErrors.4.qml")); - expectedErrorString = e4.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on_6NameWithUnderscoreNumberChanged\""); - QCOMPARE(e4.errors().at(0).toString(), expectedErrorString); - object.reset(e4.create()); - QVERIFY(!object); } void tst_qqmlecmascript::propertyVar_data() diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 462082ed03..fbc09f35e0 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -422,6 +422,10 @@ private slots: void attachedInCtor(); void byteArrayConversion(); + void propertySignalNames_data(); + void propertySignalNames(); + void signalNames_data(); + void signalNames(); private: QQmlEngine engine; @@ -8140,6 +8144,99 @@ void tst_qqmllanguage::byteArrayConversion() QCOMPARE(receiver->byteArrays[0], QByteArray("\1\2\3")); QCOMPARE(receiver->byteArrays[1], QByteArray("\4\5\6")); } +void tst_qqmllanguage::propertySignalNames_data() +{ + QTest::addColumn<QString>("propertyName"); + QTest::addColumn<QString>("propertyChangedSignal"); + QTest::addColumn<QString>("propertyChangedHandler"); + QTest::addRow("helloWorld") << u"helloWorld"_s << u"helloWorldChanged"_s + << u"onHelloWorldChanged"_s; + QTest::addRow("$helloWorld") << u"$helloWorld"_s << u"$helloWorldChanged"_s + << u"on$HelloWorldChanged"_s; + QTest::addRow("_helloWorld") << u"_helloWorld"_s << u"_helloWorldChanged"_s + << u"on_HelloWorldChanged"_s; + QTest::addRow("_") << u"_"_s << u"_Changed"_s << u"on_Changed"_s; + QTest::addRow("$") << u"$"_s << u"$Changed"_s << u"on$Changed"_s; + QTest::addRow("ä") << u"ä"_s << u"äChanged"_s << u"onÄChanged"_s; + QTest::addRow("___123a") << u"___123a"_s << u"___123aChanged"_s << u"on___123AChanged"_s; +} +void tst_qqmllanguage::propertySignalNames() +{ + QFETCH(QString, propertyName); + QFETCH(QString, propertyChangedSignal); + QFETCH(QString, propertyChangedHandler); + QQmlEngine e; + QQmlComponent c(&e); + c.setData(uR"( +import QtQuick +Item { + property int %1: 456 + property bool success: false + function f() { %1 = 123; } + function g() { %2(); } + %3: success = true +})"_s.arg(propertyName, propertyChangedSignal, propertyChangedHandler) + .toUtf8(), + QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o != nullptr); + const QMetaObject *metaObject = o->metaObject(); + auto signalIndex = + metaObject->indexOfSignal(propertyChangedSignal.append("()").toStdString().c_str()); + QVERIFY(signalIndex > -1); + auto signal = metaObject->method(signalIndex); + QVERIFY(signal.isValid()); + QSignalSpy changeSignal(o.data(), signal); + QMetaObject::invokeMethod(o.data(), "f"); + QCOMPARE(o->property(propertyName.toStdString().c_str()), 123); + QVERIFY(changeSignal.size() == 1); + QCOMPARE(o->property("success"), true); + QMetaObject::invokeMethod(o.data(), "g"); + QVERIFY(changeSignal.size() == 2); +} +void tst_qqmllanguage::signalNames_data() +{ + QTest::addColumn<QString>("signalName"); + QTest::addColumn<QString>("handlerName"); + QTest::addRow("helloWorld") << u"helloWorld"_s << u"onHelloWorld"_s; + QTest::addRow("$helloWorld") << u"$helloWorld"_s << u"on$HelloWorld"_s; + QTest::addRow("_helloWorld") << u"_helloWorld"_s << u"on_HelloWorld"_s; + QTest::addRow("_") << u"_"_s << u"on_"_s; + QTest::addRow("aUmlaut") << u"ä"_s << u"onÄ"_s; + QTest::addRow("___123a") << u"___123a"_s << u"on___123A"_s; +} +void tst_qqmllanguage::signalNames() +{ + QFETCH(QString, signalName); + QFETCH(QString, handlerName); + QQmlEngine e; + QQmlComponent c(&e); + c.setData(uR"( +import QtQuick +Item { + signal %1() + property bool success: false + function f() { %1(); } + %2: success = true +})"_s.arg(signalName, handlerName) + .toUtf8(), + QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o != nullptr); + const QMetaObject *metaObject = o->metaObject(); + auto signalIndex = metaObject->indexOfSignal(signalName.append("()").toStdString().c_str()); + QVERIFY(signalIndex > -1); + auto signal = metaObject->method(signalIndex); + QVERIFY(signal.isValid()); + QSignalSpy changeSignal(o.data(), signal); + signal.invoke(o.data()); + QVERIFY(changeSignal.size() == 1); + QCOMPARE(o->property("success"), true); + QMetaObject::invokeMethod(o.data(), "f"); + QVERIFY(changeSignal.size() == 2); +} QTEST_MAIN(tst_qqmllanguage) diff --git a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp index a84ffb87b4..8df8e12dc7 100644 --- a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp +++ b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp @@ -8,6 +8,7 @@ #include <QtQml/private/qqmlengine_p.h> #include <QtQmlModels/private/qqmllistmodel_p.h> #include <QtQml/private/qqmlexpression_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <QQmlComponent> #include <QtCore/qtimer.h> @@ -1098,12 +1099,12 @@ void tst_qqmllistmodel::property_changes() expr.evaluate(); QVERIFY2(!expr.hasError(), qPrintable(expr.error().toString())); - QString signalHandler = "on" + QString(roleName[0].toUpper()) + roleName.mid(1, roleName.size()) + "Changed:"; + QString signalHandler = QQmlSignalNames::propertyNameToChangedHandlerName(roleName); QString qml = "import QtQuick 2.0\n" "Connections {\n" "property bool gotSignal: false\n" "target: model.get(" + QString::number(listIndex) + ")\n" - + signalHandler + " gotSignal = true\n" + + signalHandler + ": gotSignal = true\n" "}\n"; QQmlComponent component(&engine); diff --git a/tools/qmlplugindump/main.cpp b/tools/qmlplugindump/main.cpp index 42b9544b04..4031bf91d9 100644 --- a/tools/qmlplugindump/main.cpp +++ b/tools/qmlplugindump/main.cpp @@ -30,6 +30,7 @@ #include <QtCore/private/qobject_p.h> #include <QtCore/private/qmetaobject_p.h> #include <QtQmlTypeRegistrar/private/qqmljsstreamwriter_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <QRegularExpression> #include <iostream> @@ -690,10 +691,13 @@ private: for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { const QMetaProperty &property = meta->property(index); dump(property, metaRevision, knownAttributes); + const QByteArray changedSignalName = + QQmlSignalNames::propertyNameToChangedSignalName(property.name()); if (knownAttributes) - knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"), - 0, QTypeRevision::fromEncodedVersion(property.revision())); - implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name()))); + knownAttributes->knownMethod( + changedSignalName, 0, + QTypeRevision::fromEncodedVersion(property.revision())); + implicitSignals.insert(changedSignalName); } return implicitSignals; } diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 34077a29d8..6bdbdcf25d 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -8,6 +8,7 @@ #include "qmltccompilerpieces.h" #include <QtCore/qloggingcategory.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <private/qqmljsutils_p.h> #include <algorithm> @@ -1734,7 +1735,7 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, break; } case QQmlSA::ScriptBindingKind::Script_SignalHandler: { - const auto name = QQmlJSUtils::signalName(propertyName); + const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName); Q_ASSERT(name.has_value()); compileScriptSignal(*name); break; @@ -1743,9 +1744,10 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, const QString objectClassName = objectType->internalName(); const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor"); - const auto signalName = QQmlJSUtils::signalName(propertyName); + const auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName); Q_ASSERT(signalName.has_value()); // an error somewhere else - const auto actualProperty = QQmlJSUtils::changeHandlerProperty(objectType, *signalName); + const auto actualProperty = + QQmlJSUtils::propertyFromChangedHandler(objectType, propertyName); Q_ASSERT(actualProperty.has_value()); // an error somewhere else const auto actualPropertyType = actualProperty->type(); if (!actualPropertyType) { diff --git a/tools/qmltc/qmltcpropertyutils.h b/tools/qmltc/qmltcpropertyutils.h index db26c498dd..8a69e5ef09 100644 --- a/tools/qmltc/qmltcpropertyutils.h +++ b/tools/qmltc/qmltcpropertyutils.h @@ -6,6 +6,7 @@ #include <private/qqmljsmetatypes_p.h> #include <private/qqmljsscope_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> QT_BEGIN_NAMESPACE @@ -35,13 +36,11 @@ struct QmltcPropertyData QmltcPropertyData(const QString &propertyName) { - const QString nameWithUppercase = propertyName[0].toUpper() + propertyName.sliced(1); - read = propertyName; - write = u"set" + nameWithUppercase; - bindable = u"bindable" + nameWithUppercase; - notify = propertyName + u"Changed"; - reset = u"reset" + nameWithUppercase; + write = QQmlSignalNames::addPrefixToPropertyName(u"set", propertyName); + bindable = QQmlSignalNames::addPrefixToPropertyName(u"bindable", propertyName); + notify = QQmlSignalNames::propertyNameToChangedSignalName(propertyName); + reset = QQmlSignalNames::addPrefixToPropertyName(u"reset", propertyName); } QString read; diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp index 3acee13bbb..b15c9cdae8 100644 --- a/tools/qmltc/qmltcvisitor.cpp +++ b/tools/qmltc/qmltcvisitor.cpp @@ -8,6 +8,7 @@ #include <QtCore/qstack.h> #include <QtCore/qdir.h> #include <QtCore/qloggingcategory.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <private/qqmljsutils_p.h> @@ -269,7 +270,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember) owner->addOwnProperty(property); } - const QString notifyName = name + u"Changed"_s; + const QString notifyName = QQmlSignalNames::propertyNameToChangedSignalName(name); // also check that notify is already a method of the scope { auto owningScope = m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope; |