aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSami Shalayel <sami.shalayel@qt.io>2023-08-10 09:45:37 +0200
committerSami Shalayel <sami.shalayel@qt.io>2023-08-15 12:13:31 +0000
commita1ce0596e517e84913b14ab23422137c95b8c785 (patch)
treea7a012aa8a0d260c6f464c6b5dc722806697fa4c
parent8d6f9e716d1c3fdd05ac14612583359313b9dc6e (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>
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp23
-rw-r--r--src/qml/common/qqmlsignalnames.cpp38
-rw-r--r--src/qml/common/qqmlsignalnames_p.h1
-rw-r--r--src/qml/compiler/qqmlirbuilder.cpp32
-rw-r--r--src/qml/compiler/qqmlirbuilder_p.h3
-rw-r--r--src/qml/qml/qqmlproperty.cpp43
-rw-r--r--src/qml/qml/qqmlpropertycache.cpp19
-rw-r--r--src/qml/qml/qqmlpropertycachecreator_p.h7
-rw-r--r--src/qml/qml/qqmlpropertyresolver.cpp7
-rw-r--r--src/qml/qml/qqmlpropertyvalidator.cpp3
-rw-r--r--src/qml/qml/qqmltypecompiler.cpp22
-rw-r--r--src/qml/types/qqmlconnections.cpp6
-rw-r--r--src/qmlcompiler/qqmljscompiler.cpp6
-rw-r--r--src/qmlcompiler/qqmljsfunctioninitializer.cpp67
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor.cpp12
-rw-r--r--src/qmlcompiler/qqmljsscope.cpp3
-rw-r--r--src/qmlcompiler/qqmljsutils_p.h36
-rw-r--r--src/qmldom/qqmldomelements_p.h5
-rw-r--r--src/qmlls/qqmlcompletionsupport.cpp4
-rw-r--r--src/quick/util/qquickpropertychanges.cpp3
-rw-r--r--tests/auto/qml/common/tst_qml_common.cpp6
-rw-r--r--tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp3
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/conversions2.qml2
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/methods.qml2
-rw-r--r--tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.3.qml12
-rw-r--r--tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.4.qml12
-rw-r--r--tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlots.qml13
-rw-r--r--tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp15
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp97
-rw-r--r--tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp5
-rw-r--r--tools/qmlplugindump/main.cpp10
-rw-r--r--tools/qmltc/qmltccompiler.cpp8
-rw-r--r--tools/qmltc/qmltcpropertyutils.h11
-rw-r--r--tools/qmltc/qmltcvisitor.cpp3
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, &notInRevision);
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 &current,
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 &current,
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;