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>
This commit is contained in:
Sami Shalayel 2023-08-10 09:45:37 +02:00
parent 8d6f9e716d
commit a1ce0596e5
34 changed files with 293 additions and 246 deletions

View File

@ -15,6 +15,7 @@
#include <private/qqmlvaluetype_p.h> #include <private/qqmlvaluetype_p.h>
#include <private/qqmlvmemetaobject_p.h> #include <private/qqmlvmemetaobject_p.h>
#include <private/qqmlexpression_p.h> #include <private/qqmlexpression_p.h>
#include <private/qqmlsignalnames_p.h>
#include <QtCore/qdebug.h> #include <QtCore/qdebug.h>
#include <QtCore/qmetaobject.h> #include <QtCore/qmetaobject.h>
@ -119,22 +120,14 @@ QDataStream &operator>>(QDataStream &ds,
return 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) static bool hasValidSignal(QObject *object, const QString &propertyName)
{ {
if (!isSignalPropertyName(propertyName)) auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName);
if (!signalName)
return false; return false;
QString signalName = propertyName.mid(2); int sigIdx = QQmlPropertyPrivate::findSignalByName(object->metaObject(), signalName->toLatin1())
signalName[0] = signalName.at(0).toLower(); .methodIndex();
int sigIdx = QQmlPropertyPrivate::findSignalByName(object->metaObject(), signalName.toLatin1()).methodIndex();
if (sigIdx == -1) if (sigIdx == -1)
return false; return false;
@ -305,10 +298,8 @@ void QQmlEngineDebugServiceImpl::buildObjectDump(QDataStream &message,
if (scope) { if (scope) {
const QByteArray methodName = QMetaObjectPrivate::signal(scope->metaObject(), const QByteArray methodName = QMetaObjectPrivate::signal(scope->metaObject(),
signalHandler->signalIndex()).name(); signalHandler->signalIndex()).name();
const QLatin1String methodNameStr(methodName); if (!methodName.isEmpty()) {
if (methodNameStr.size() != 0) { prop.name = QQmlSignalNames::signalNameToHandlerName(methodName);
prop.name = QLatin1String("on") + QChar(methodNameStr.at(0)).toUpper()
+ methodNameStr.mid(1);
} }
} }
} }

View File

@ -14,10 +14,10 @@ using namespace Qt::Literals;
static std::optional<qsizetype> firstLetterIdx(QStringView name, qsizetype removePrefix = 0, static std::optional<qsizetype> firstLetterIdx(QStringView name, qsizetype removePrefix = 0,
qsizetype removeSuffix = 0) qsizetype removeSuffix = 0)
{ {
auto result = std::find_if(std::next(name.cbegin(), removePrefix), auto end = std::prev(name.cend(), removeSuffix);
std::prev(name.cend(), removeSuffix), auto result = std::find_if(std::next(name.cbegin(), removePrefix), end,
[](const QChar &c) { return c.isLetter(); }); [](const QChar &c) { return c.isLetter(); });
if (result != name.cend()) if (result != end)
return std::distance(name.begin(), result); return std::distance(name.begin(), result);
return {}; return {};
@ -157,23 +157,41 @@ QString QQmlSignalNames::signalNameToHandlerName(QAnyStringView signal)
return handlerName; return handlerName;
} }
/*! enum HandlerType { ChangedHandler, Handler };
\internal
Returns a signal name from \a handlerName string. static std::optional<QString> handlerNameToSignalNameHelper(QStringView handler, HandlerType type)
*/
std::optional<QString> QQmlSignalNames::handlerNameToSignalName(QStringView handler)
{ {
if (!isHandlerName(handler)) if (!QQmlSignalNames::isHandlerName(handler))
return {}; return {};
QString signalName = handler.sliced(strlen("on")).toString(); QString signalName = handler.sliced(strlen("on")).toString();
if (signalName.isEmpty()) if (signalName.isEmpty())
return {}; return {};
changeCaseOfFirstLetter(signalName, ToLower); changeCaseOfFirstLetter(signalName, ToLower, 0, type == ChangedHandler ? strlen("Changed") : 0);
return signalName; 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) bool QQmlSignalNames::isChangedSignalName(QStringView signalName)
{ {
const qsizetype smallestAllowedSize = strlen("XChanged"); const qsizetype smallestAllowedSize = strlen("XChanged");

View File

@ -42,6 +42,7 @@ public:
static std::optional<QByteArray> changedHandlerNameToPropertyName(QUtf8StringView handler); static std::optional<QByteArray> changedHandlerNameToPropertyName(QUtf8StringView handler);
static std::optional<QString> handlerNameToSignalName(QStringView handler); static std::optional<QString> handlerNameToSignalName(QStringView handler);
static std::optional<QString> changedHandlerNameToSignalName(QStringView changedHandler);
static bool isChangedHandlerName(QStringView signalName); static bool isChangedHandlerName(QStringView signalName);
static bool isChangedSignalName(QStringView signalName); static bool isChangedSignalName(QStringView signalName);

View File

@ -442,38 +442,6 @@ bool IRBuilder::generateFromQml(const QString &code, const QString &url, Documen
return errors.isEmpty(); 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) bool IRBuilder::visit(QQmlJS::AST::UiArrayMemberList *ast)
{ {
return QQmlJS::AST::Visitor::visit(ast); return QQmlJS::AST::Visitor::visit(ast);

View File

@ -506,9 +506,6 @@ public:
IRBuilder(const QSet<QString> &illegalNames); IRBuilder(const QSet<QString> &illegalNames);
bool generateFromQml(const QString &code, const QString &url, Document *output); 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::visit;
using QQmlJS::AST::Visitor::endVisit; using QQmlJS::AST::Visitor::endVisit;

View File

@ -20,6 +20,7 @@
#include <private/qqmlbuiltinfunctions_p.h> #include <private/qqmlbuiltinfunctions_p.h>
#include <private/qqmlirbuilder_p.h> #include <private/qqmlirbuilder_p.h>
#include <QtQml/private/qqmllist_p.h> #include <QtQml/private/qqmllist_p.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <QStringList> #include <QStringList>
#include <QVector> #include <QVector>
@ -363,11 +364,10 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name,
}; };
QQmlData *ddata = QQmlData::get(currentObject, false); QQmlData *ddata = QQmlData::get(currentObject, false);
auto findChangeSignal = [&](QStringView signalName) { auto findChangeSignal = [&](QStringView changedHandlerName) {
const QString changed = QStringLiteral("Changed"); if (auto propName = QQmlSignalNames::changedHandlerNameToPropertyName(changedHandlerName)) {
if (signalName.endsWith(changed)) { const QQmlPropertyData *d =
const QStringView propName = signalName.first(signalName.size() - changed.size()); ddata->propertyCache->property(*propName, currentObject, context);
const QQmlPropertyData *d = ddata->propertyCache->property(propName, currentObject, context);
while (d && d->isFunction()) while (d && d->isFunction())
d = ddata->propertyCache->overrideData(d); d = ddata->propertyCache->overrideData(d);
@ -381,19 +381,11 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name,
}; };
const QString terminalString = terminal.toString(); const QString terminalString = terminal.toString();
if (QmlIR::IRBuilder::isSignalPropertyName(terminalString)) { if (auto signalName = QQmlSignalNames::handlerNameToSignalName(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 (ddata && ddata->propertyCache) { if (ddata && ddata->propertyCache) {
// Try method // Try method
const QQmlPropertyData *d = ddata->propertyCache->property( const QQmlPropertyData *d =
signalName, currentObject, context); ddata->propertyCache->property(*signalName, currentObject, context);
// ### Qt7: This code treats methods as signals. It should use d->isSignal(). // ### 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 // 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; return;
} }
if (findChangeSignal(signalName)) if (findChangeSignal(terminalString))
return; return;
} else if (findSignalInMetaObject(signalName.toUtf8())) { } else if (findSignalInMetaObject(signalName->toUtf8())) {
return; return;
} }
} }
@ -744,15 +736,7 @@ QString QQmlProperty::name() const
d->nameCache = d->core.name(d->object) + QLatin1Char('.') + QString::fromUtf8(vtName); d->nameCache = d->core.name(d->object) + QLatin1Char('.') + QString::fromUtf8(vtName);
} else if (type() & SignalProperty) { } else if (type() & SignalProperty) {
// ### Qt7: Return the original signal name here. Do not prepend "on" // ### Qt7: Return the original signal name here. Do not prepend "on"
QString name = QStringLiteral("on") + d->core.name(d->object); d->nameCache = QQmlSignalNames::signalNameToHandlerName(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;
} else { } else {
d->nameCache = d->core.name(d->object); 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", // If no signal is found, but the signal is of the form "onBlahChanged",
// return the notify signal for the property "Blah" // return the notify signal for the property "Blah"
if (name.endsWith("Changed")) { if (auto propName = QQmlSignalNames::changedSignalNameToPropertyName(name)) {
QByteArray propName = name.mid(0, name.size() - 7); int propIdx = mo->indexOfProperty(propName->constData());
int propIdx = mo->indexOfProperty(propName.constData());
if (propIdx >= 0) { if (propIdx >= 0) {
QMetaProperty prop = mo->property(propIdx); QMetaProperty prop = mo->property(propIdx);
if (prop.hasNotifySignal()) if (prop.hasNotifySignal())

View File

@ -10,6 +10,7 @@
#include <private/qmetaobject_p.h> #include <private/qmetaobject_p.h>
#include <private/qmetaobjectbuilder_p.h> #include <private/qmetaobjectbuilder_p.h>
#include <private/qqmlpropertycachemethodarguments_p.h> #include <private/qqmlpropertycachemethodarguments_p.h>
#include <private/qqmlsignalnames_p.h>
#include <private/qv4value_p.h> #include <private/qv4value_p.h>
@ -249,8 +250,7 @@ void QQmlPropertyCache::appendSignal(const QString &name, QQmlPropertyData::Flag
int signalHandlerIndex = signalHandlerIndexCache.size(); int signalHandlerIndex = signalHandlerIndexCache.size();
signalHandlerIndexCache.append(handler); signalHandlerIndexCache.append(handler);
QString handlerName = QLatin1String("on") + name; const QString handlerName = QQmlSignalNames::signalNameToHandlerName(name);
handlerName[2] = handlerName.at(2).toUpper();
setNamedProperty(name, methodIndex + methodOffset(), methodIndexCache.data() + methodIndex); setNamedProperty(name, methodIndex + methodOffset(), methodIndexCache.data() + methodIndex);
setNamedProperty(handlerName, signalHandlerIndex + signalOffset(), setNamedProperty(handlerName, signalHandlerIndex + signalOffset(),
@ -449,7 +449,7 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject,
setNamedProperty(methodName, ii, data); setNamedProperty(methodName, ii, data);
if (data->isSignal()) { if (data->isSignal()) {
QHashedString on(QLatin1String("on") % methodName.at(0).toUpper() % QStringView{methodName}.mid(1)); QHashedString on(QQmlSignalNames::signalNameToHandlerName(methodName));
setNamedProperty(on, ii, sigdata); setNamedProperty(on, ii, sigdata);
++signalHandlerIndex; ++signalHandlerIndex;
} }
@ -462,17 +462,8 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject,
setNamedProperty(methodName, ii, data); setNamedProperty(methodName, ii, data);
if (data->isSignal()) { if (data->isSignal()) {
int length = methodName.length(); QHashedString on(QQmlSignalNames::signalNameToHandlerName(
QLatin1StringView{ methodName.constData(), 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()));
setNamedProperty(on, ii, data); setNamedProperty(on, ii, data);
++signalHandlerIndex; ++signalHandlerIndex;
} }

View File

@ -21,6 +21,7 @@
#include <private/qqmltypedata_p.h> #include <private/qqmltypedata_p.h>
#include <private/inlinecomponentutils_p.h> #include <private/inlinecomponentutils_p.h>
#include <private/qqmlsourcecoordinate_p.h> #include <private/qqmlsourcecoordinate_p.h>
#include <private/qqmlsignalnames_p.h>
#include <QScopedValueRollback> #include <QScopedValueRollback>
#include <vector> #include <vector>
@ -486,7 +487,8 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(
for ( ; p != pend; ++p) { for ( ; p != pend; ++p) {
auto flags = QQmlPropertyData::defaultSignalFlags(); auto flags = QQmlPropertyData::defaultSignalFlags();
QString changedSigName = stringAt(p->nameIndex) + QLatin1String("Changed"); const QString changedSigName =
QQmlSignalNames::propertyNameToChangedSignalName(stringAt(p->nameIndex));
seenSignals.insert(changedSigName); seenSignals.insert(changedSigName);
cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); cache->appendSignal(changedSigName, flags, effectiveMethodIndex++);
@ -497,7 +499,8 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(
for ( ; a != aend; ++a) { for ( ; a != aend; ++a) {
auto flags = QQmlPropertyData::defaultSignalFlags(); auto flags = QQmlPropertyData::defaultSignalFlags();
QString changedSigName = stringAt(a->nameIndex()) + QLatin1String("Changed"); const QString changedSigName =
QQmlSignalNames::propertyNameToChangedSignalName(stringAt(a->nameIndex()));
seenSignals.insert(changedSigName); seenSignals.insert(changedSigName);
cache->appendSignal(changedSigName, flags, effectiveMethodIndex++); cache->appendSignal(changedSigName, flags, effectiveMethodIndex++);

View File

@ -3,6 +3,7 @@
#include "qqmlpropertyresolver_p.h" #include "qqmlpropertyresolver_p.h"
#include <private/qqmlcontextdata_p.h> #include <private/qqmlcontextdata_p.h>
#include <private/qqmlsignalnames_p.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -43,10 +44,8 @@ const QQmlPropertyData *QQmlPropertyResolver::signal(const QString &name, bool *
return d; return d;
} }
if (name.endsWith(QLatin1String("Changed"))) { if (auto propName = QQmlSignalNames::changedSignalNameToPropertyName(name)) {
QString propName = name.mid(0, name.size() - static_cast<int>(strlen("Changed"))); d = property(*propName, notInRevision);
d = property(propName, notInRevision);
if (d) if (d)
return cache->signal(d->notifyIndex()); return cache->signal(d->notifyIndex());
} }

View File

@ -9,6 +9,7 @@
#include <private/qqmlpropertycachecreator_p.h> #include <private/qqmlpropertycachecreator_p.h>
#include <private/qqmlpropertyresolver_p.h> #include <private/qqmlpropertyresolver_p.h>
#include <private/qqmlstringconverters_p.h> #include <private/qqmlstringconverters_p.h>
#include <private/qqmlsignalnames_p.h>
#include <QtCore/qdatetime.h> #include <QtCore/qdatetime.h>
@ -140,7 +141,7 @@ QVector<QQmlError> QQmlPropertyValidator::validateObject(
customBindings << binding; customBindings << binding;
continue; continue;
} }
} else if (QmlIR::IRBuilder::isSignalPropertyName(name) } else if (QQmlSignalNames::isHandlerName(name)
&& !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) {
customBindings << binding; customBindings << binding;
continue; continue;

View File

@ -9,6 +9,7 @@
#include <private/qqmlcomponent_p.h> #include <private/qqmlcomponent_p.h>
#include <private/qqmlpropertyresolver_p.h> #include <private/qqmlpropertyresolver_p.h>
#include <private/qqmlcomponentandaliasresolver_p.h> #include <private/qqmlcomponentandaliasresolver_p.h>
#include <private/qqmlsignalnames_p.h>
#define COMPILE_EXCEPTION(token, desc) \ #define COMPILE_EXCEPTION(token, desc) \
{ \ { \
@ -325,18 +326,21 @@ bool SignalHandlerResolver::resolveSignalHandlerExpressions(
continue; 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; continue;
QQmlPropertyResolver resolver(propertyCache); 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; bool notInRevision = false;
const QQmlPropertyData * const signal = resolver.signal(signalName, &notInRevision); const QQmlPropertyData * const signal = resolver.signal(signalName, &notInRevision);
const QQmlPropertyData * const signalPropertyData = resolver.property(signalName, /*notInRevision ptr*/nullptr); const QQmlPropertyData * const signalPropertyData = resolver.property(signalName, /*notInRevision ptr*/nullptr);
@ -1045,7 +1049,7 @@ bool QQmlDeferredAndCustomParserBindingScanner::scanObject(
obj->flags |= Object::HasCustomParserBindings; obj->flags |= Object::HasCustomParserBindings;
continue; continue;
} }
} else if (QmlIR::IRBuilder::isSignalPropertyName(name) } else if (QQmlSignalNames::isHandlerName(name)
&& !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) {
obj->flags |= Object::HasCustomParserBindings; obj->flags |= Object::HasCustomParserBindings;
binding->setFlag(Binding::IsCustomParserBinding); binding->setFlag(Binding::IsCustomParserBinding);

View File

@ -15,6 +15,8 @@
#include <QtCore/qdebug.h> #include <QtCore/qdebug.h>
#include <QtCore/qstringlist.h> #include <QtCore/qstringlist.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qobject_p.h> #include <private/qobject_p.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -205,9 +207,7 @@ void QQmlConnectionsParser::verifyBindings(const QQmlRefPointer<QV4::ExecutableC
const QV4::CompiledData::Binding *binding = props.at(ii); const QV4::CompiledData::Binding *binding = props.at(ii);
const QString &propName = compilationUnit->stringAt(binding->propertyNameIndex); const QString &propName = compilationUnit->stringAt(binding->propertyNameIndex);
const bool thirdCharacterIsValid = (propName.size() >= 2) if (!QQmlSignalNames::isHandlerName(propName)) {
&& (propName.at(2).isUpper() || propName.at(2) == u'_');
if (!propName.startsWith(QLatin1String("on")) || !thirdCharacterIsValid) {
error(props.at(ii), QQmlConnections::tr("Cannot assign to non-existent property \"%1\"").arg(propName)); error(props.at(ii), QQmlConnections::tr("Cannot assign to non-existent property \"%1\"").arg(propName));
return; return;
} }

View File

@ -19,6 +19,8 @@
#include <QtCore/qfileinfo.h> #include <QtCore/qfileinfo.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <limits> #include <limits>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -123,9 +125,7 @@ static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc,
if (binding->type() != QV4::CompiledData::Binding::Type_Script) if (binding->type() != QV4::CompiledData::Binding::Type_Script)
continue; continue;
const QString propName = doc.stringAt(binding->propertyNameIndex); const QString propName = doc.stringAt(binding->propertyNameIndex);
if (!propName.startsWith(QLatin1String("on")) if (!QQmlSignalNames::isHandlerName(propName))
|| propName.size() < 3
|| !propName.at(2).isUpper())
continue; continue;
auto compiledFunction = doc.jsModule.functions.value(object->runtimeFunctionIndices.at(binding->value.compiledScriptIndex)); auto compiledFunction = doc.jsModule.functions.value(object->runtimeFunctionIndices.at(binding->value.compiledScriptIndex));
if (!compiledFunction) if (!compiledFunction)

View File

@ -8,6 +8,8 @@
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtCore/qfileinfo.h> #include <QtCore/qfileinfo.h>
#include <QtQml/private/qqmlsignalnames_p.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
@ -166,14 +168,15 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
} }
function.isProperty = m_objectType->hasProperty(propertyName); function.isProperty = m_objectType->hasProperty(propertyName);
if (!function.isProperty && QmlIR::IRBuilder::isSignalPropertyName(propertyName)) { if (!function.isProperty) {
const QString signalName = QmlIR::IRBuilder::signalNameFromSignalPropertyName(propertyName); if (QQmlSignalNames::isHandlerName(propertyName)) {
if (auto actualPropertyName =
if (signalName.endsWith(u"Changed"_s) QQmlSignalNames::changedHandlerNameToPropertyName(propertyName);
&& m_objectType->hasProperty(signalName.chopped(strlen("Changed")))) { actualPropertyName && m_objectType->hasProperty(*actualPropertyName)) {
function.isSignalHandler = true; function.isSignalHandler = true;
} else { } else {
const auto methods = m_objectType->methods(signalName); auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName);
const auto methods = m_objectType->methods(*signalName);
for (const auto &method : methods) { for (const auto &method : methods) {
if (method.isCloned()) if (method.isCloned())
continue; continue;
@ -197,14 +200,14 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
break; break;
} }
} }
}
if (!function.isSignalHandler) { if (!function.isSignalHandler) {
diagnose(u"Could not compile signal handler for %1: The signal does not exist"_s.arg( diagnose(u"Could not compile signal handler for %1: The signal does not exist"_s.arg(
signalName), *signalName),
QtWarningMsg, bindingLocation, error); QtWarningMsg, bindingLocation, error);
} }
} }
}
}
if (!function.isSignalHandler) { if (!function.isSignalHandler) {
if (!function.isProperty) { if (!function.isProperty) {

View File

@ -13,6 +13,7 @@
#include <QtCore/qrect.h> #include <QtCore/qrect.h>
#include <QtCore/qsize.h> #include <QtCore/qsize.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <QtQml/private/qv4codegen_p.h> #include <QtQml/private/qv4codegen_p.h>
#include <QtQml/private/qqmlstringconverters_p.h> #include <QtQml/private/qqmlstringconverters_p.h>
#include <QtQml/private/qqmlirbuilder_p.h> #include <QtQml/private/qqmlirbuilder_p.h>
@ -963,7 +964,7 @@ void QQmlJSImportVisitor::checkSignal(
const QQmlJSScope::ConstPtr &signalScope, const QQmlJS::SourceLocation &location, const QQmlJSScope::ConstPtr &signalScope, const QQmlJS::SourceLocation &location,
const QString &handlerName, const QStringList &handlerParameters) const QString &handlerName, const QStringList &handlerParameters)
{ {
const auto signal = QQmlJSUtils::signalName(handlerName); const auto signal = QQmlSignalNames::handlerNameToSignalName(handlerName);
std::optional<QQmlJSMetaMethod> signalMethod; std::optional<QQmlJSMetaMethod> signalMethod;
const auto setSignalMethod = [&](const QQmlJSScope::ConstPtr &scope, const QString &name) { const auto setSignalMethod = [&](const QQmlJSScope::ConstPtr &scope, const QString &name) {
@ -975,8 +976,7 @@ void QQmlJSImportVisitor::checkSignal(
if (signal.has_value()) { if (signal.has_value()) {
if (signalScope->hasMethod(*signal)) { if (signalScope->hasMethod(*signal)) {
setSignalMethod(signalScope, *signal); setSignalMethod(signalScope, *signal);
} else if (auto p = QQmlJSUtils::changeHandlerProperty(signalScope, *signal); } else if (auto p = QQmlJSUtils::propertyFromChangedHandler(signalScope, handlerName)) {
p.has_value()) {
// we have a change handler of the form "onXChanged" where 'X' // we have a change handler of the form "onXChanged" where 'X'
// is a property name // is a property name
@ -2025,11 +2025,11 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
prefix.clear(); prefix.clear();
} }
auto name = group->name.toString(); const auto name = group->name.toString();
// This is a preliminary check. // This is a preliminary check.
// Even if the name starts with "on", it might later turn out not to be a signal. // 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)) { if (!signal.has_value() || m_currentScope->hasProperty(name)) {
m_propertyBindings[m_currentScope].append( m_propertyBindings[m_currentScope].append(
@ -2079,7 +2079,7 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
if (!methods.isEmpty()) { if (!methods.isEmpty()) {
kind = QQmlSA::ScriptBindingKind::Script_SignalHandler; kind = QQmlSA::ScriptBindingKind::Script_SignalHandler;
checkSignal(scope, groupLocation, name, signalParameters); 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; kind = QQmlSA::ScriptBindingKind::Script_ChangeHandler;
checkSignal(scope, groupLocation, name, signalParameters); checkSignal(scope, groupLocation, name, signalParameters);
} else if (scope->hasProperty(name)) { } else if (scope->hasProperty(name)) {

View File

@ -81,7 +81,8 @@ void QQmlJSScope::insertJSIdentifier(const QString &name, const JavaScriptIdenti
void QQmlJSScope::insertPropertyIdentifier(const QQmlJSMetaProperty &property) void QQmlJSScope::insertPropertyIdentifier(const QQmlJSMetaProperty &property)
{ {
addOwnProperty(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.setMethodType(QQmlJSMetaMethodType::Signal);
method.setIsImplicitQmlPropertyChangeSignal(true); method.setIsImplicitQmlPropertyChangeSignal(true);
addOwnMethod(method); addOwnMethod(method);

View File

@ -25,6 +25,7 @@
#include <QtCore/qstring.h> #include <QtCore/qstring.h>
#include <QtCore/qstringview.h> #include <QtCore/qstringview.h>
#include <QtCore/qstringbuilder.h> #include <QtCore/qstringbuilder.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qduplicatetracker_p.h> #include <private/qduplicatetracker_p.h>
#include <optional> #include <optional>
@ -99,26 +100,6 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
return type; 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> static std::optional<QQmlJSMetaProperty>
changeHandlerProperty(const QQmlJSScope::ConstPtr &scope, QStringView signalName) changeHandlerProperty(const QQmlJSScope::ConstPtr &scope, QStringView signalName)
{ {
@ -134,6 +115,21 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
return {}; 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) static bool hasCompositeBase(const QQmlJSScope::ConstPtr &scope)
{ {
if (!scope) if (!scope)

View File

@ -22,6 +22,7 @@
#include <QtQml/private/qqmljsast_p.h> #include <QtQml/private/qqmljsast_p.h>
#include <QtQml/private/qqmljsengine_p.h> #include <QtQml/private/qqmljsengine_p.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <QtCore/QCborValue> #include <QtCore/QCborValue>
#include <QtCore/QCborMap> #include <QtCore/QCborMap>
@ -554,9 +555,7 @@ public:
bool isSignalHandler() const bool isSignalHandler() const
{ {
QString baseName = m_name.split(QLatin1Char('.')).last(); QString baseName = m_name.split(QLatin1Char('.')).last();
if (baseName.startsWith(u"on") && baseName.size() > 2 && baseName.at(2).isUpper()) return QQmlSignalNames::isHandlerName(baseName);
return true;
return false;
} }
static QString preCodeForName(QStringView n) static QString preCodeForName(QStringView n)
{ {

View File

@ -10,6 +10,7 @@
#include <QtCore/QRegularExpression> #include <QtCore/QRegularExpression>
#include <QtQmlDom/private/qqmldomexternalitems_p.h> #include <QtQmlDom/private/qqmldomexternalitems_p.h>
#include <QtQmlDom/private/qqmldomtop_p.h> #include <QtQmlDom/private/qqmldomtop_p.h>
#include <QtQml/private/qqmlsignalnames_p.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
using namespace QLspSpecification; using namespace QLspSpecification;
@ -287,8 +288,7 @@ static QList<CompletionItem> bindingsCompletions(DomItem &containingObject)
if (it.value().methodType == MethodInfo::MethodType::Signal) { if (it.value().methodType == MethodInfo::MethodType::Signal) {
CompletionItem comp; CompletionItem comp;
QString signal = it.key(); QString signal = it.key();
comp.label = comp.label = QQmlSignalNames::signalNameToHandlerName(signal).toUtf8();
(u"on"_s + signal.at(0).toUpper() + signal.mid(1)).toUtf8();
res.append(comp); res.append(comp);
} }
++it; ++it;

View File

@ -20,6 +20,7 @@
#include <private/qqmlirbuilder_p.h> #include <private/qqmlirbuilder_p.h>
#include <QtCore/qdebug.h> #include <QtCore/qdebug.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qobject_p.h> #include <private/qobject_p.h>
@ -267,7 +268,7 @@ void QQuickPropertyChangesPrivate::decodeBinding(const QString &propertyPrefix,
break; break;
} }
if (binding->isSignalHandler() || QmlIR::IRBuilder::isSignalPropertyName(propertyName)) { if (binding->isSignalHandler() || QQmlSignalNames::isHandlerName(propertyName)) {
QQmlProperty prop = property(propertyName); QQmlProperty prop = property(propertyName);
if (prop.isSignalProperty()) { if (prop.isSignalProperty()) {
QQuickReplaceSignalHandler *handler = new QQuickReplaceSignalHandler; QQuickReplaceSignalHandler *handler = new QQuickReplaceSignalHandler;

View File

@ -37,6 +37,8 @@ void tst_qml_common::tst_propertyNameToChangedHandlerName_data()
QTest::addRow("___123aProperty") << u"___123a"_s << u"on___123AChanged"_s; QTest::addRow("___123aProperty") << u"___123a"_s << u"on___123AChanged"_s;
QTest::addRow("___123Property") << u"___123"_s << u"on___123Changed"_s; QTest::addRow("___123Property") << u"___123"_s << u"on___123Changed"_s;
QTest::addRow("AProperty") << u"A"_s << u"onAChanged"_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() 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("___123aProperty") << u"___123a"_s << u"on___123A"_s;
QTest::addRow("___123Property") << u"___123"_s << u"on___123"_s; QTest::addRow("___123Property") << u"___123"_s << u"on___123"_s;
QTest::addRow("AProperty") << u"A"_s << u"onA"_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() 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("empty") << u""_s << false;
QTest::addRow("empty2") << u"onChanged"_s << false; QTest::addRow("empty2") << u"onChanged"_s << false;
QTest::addRow("on") << u"on"_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() void tst_qml_common::tst_isChangedHandlerName()
{ {

View File

@ -22,6 +22,7 @@
#include <QtQml/qqmlproperty.h> #include <QtQml/qqmlproperty.h>
#include <QtQml/qqmlincubator.h> #include <QtQml/qqmlincubator.h>
#include <QtQml/qqmlapplicationengine.h> #include <QtQml/qqmlapplicationengine.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <QtQuick/qquickitem.h> #include <QtQuick/qquickitem.h>
#include <QtNetwork/qhostaddress.h> #include <QtNetwork/qhostaddress.h>
@ -244,7 +245,7 @@ void tst_QQmlEngineDebugService::recursiveObjectTest(
QCOMPARE(p.objectDebugId, QQmlDebugService::idForObject(o)); QCOMPARE(p.objectDebugId, QQmlDebugService::idForObject(o));
// signal properties are fake - they are generated from QQmlAbstractBoundSignal children // 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(); QString signal = p.value.toString();
QQmlBoundSignalExpression *expr = QQmlPropertyPrivate::signalExpression(QQmlProperty(o, p.name)); QQmlBoundSignalExpression *expr = QQmlPropertyPrivate::signalExpression(QQmlProperty(o, p.name));
QVERIFY(expr && expr->expression() == signal); QVERIFY(expr && expr->expression() == signal);

View File

@ -94,6 +94,8 @@ Item {
} }
function qtest_signalHandlerName(sn) { 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()) if (sn.substr(0, 2) === "on" && sn[2] === sn[2].toUpperCase())
return sn return sn
return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1) return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1)

View File

@ -35,6 +35,8 @@ BirthdayParty {
} }
function stuff(sn) { 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()) if (sn.substr(0, 2) === "on" && sn[2] === sn[2].toUpperCase())
return sn return sn
return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1) return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1)

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -6,6 +6,8 @@ Item {
property bool normalName: false property bool normalName: false
property bool _nameWithUnderscore: false property bool _nameWithUnderscore: false
property bool ____nameWithUnderscores: false property bool ____nameWithUnderscores: false
property bool _6nameWithUnderscoreNumber: false
property bool $nameWithDollarsign: false
onNormalNameChanged: { onNormalNameChanged: {
changeCount = changeCount + 1; changeCount = changeCount + 1;
@ -19,9 +21,20 @@ Item {
changeCount = changeCount + 3; changeCount = changeCount + 3;
} }
on$NameWithDollarsignChanged: {
changeCount = changeCount + 4;
}
on_6NameWithUnderscoreNumberChanged: {
changeCount = changeCount + 5;
}
Component.onCompleted: { Component.onCompleted: {
normalName = true; normalName = true;
_nameWithUnderscore = true; _nameWithUnderscore = true;
____nameWithUnderscores = true; ____nameWithUnderscores = true;
$nameWithDollarsign = true;
_6nameWithUnderscoreNumber = true;
} }
} }

View File

@ -5300,6 +5300,7 @@ void tst_qqmlecmascript::propertyChangeSlots()
QQmlComponent component(&engine, testFileUrl("changeslots/propertyChangeSlots.qml")); QQmlComponent component(&engine, testFileUrl("changeslots/propertyChangeSlots.qml"));
QScopedPointer<QObject> object(component.create()); QScopedPointer<QObject> object(component.create());
QVERIFY2(object, qPrintable(component.errorString())); QVERIFY2(object, qPrintable(component.errorString()));
QCOMPARE(object->property("changeCount"), 15);
// ensure that invalid property names fail properly. // ensure that invalid property names fail properly.
QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready"); QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
@ -5315,20 +5316,6 @@ void tst_qqmlecmascript::propertyChangeSlots()
QCOMPARE(e2.errors().at(0).toString(), expectedErrorString); QCOMPARE(e2.errors().at(0).toString(), expectedErrorString);
object.reset(e2.create()); object.reset(e2.create());
QVERIFY(!object); 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() void tst_qqmlecmascript::propertyVar_data()

View File

@ -422,6 +422,10 @@ private slots:
void attachedInCtor(); void attachedInCtor();
void byteArrayConversion(); void byteArrayConversion();
void propertySignalNames_data();
void propertySignalNames();
void signalNames_data();
void signalNames();
private: private:
QQmlEngine engine; QQmlEngine engine;
@ -8140,6 +8144,99 @@ void tst_qqmllanguage::byteArrayConversion()
QCOMPARE(receiver->byteArrays[0], QByteArray("\1\2\3")); QCOMPARE(receiver->byteArrays[0], QByteArray("\1\2\3"));
QCOMPARE(receiver->byteArrays[1], QByteArray("\4\5\6")); 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) QTEST_MAIN(tst_qqmllanguage)

View File

@ -8,6 +8,7 @@
#include <QtQml/private/qqmlengine_p.h> #include <QtQml/private/qqmlengine_p.h>
#include <QtQmlModels/private/qqmllistmodel_p.h> #include <QtQmlModels/private/qqmllistmodel_p.h>
#include <QtQml/private/qqmlexpression_p.h> #include <QtQml/private/qqmlexpression_p.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <QQmlComponent> #include <QQmlComponent>
#include <QtCore/qtimer.h> #include <QtCore/qtimer.h>
@ -1098,12 +1099,12 @@ void tst_qqmllistmodel::property_changes()
expr.evaluate(); expr.evaluate();
QVERIFY2(!expr.hasError(), qPrintable(expr.error().toString())); 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" QString qml = "import QtQuick 2.0\n"
"Connections {\n" "Connections {\n"
"property bool gotSignal: false\n" "property bool gotSignal: false\n"
"target: model.get(" + QString::number(listIndex) + ")\n" "target: model.get(" + QString::number(listIndex) + ")\n"
+ signalHandler + " gotSignal = true\n" + signalHandler + ": gotSignal = true\n"
"}\n"; "}\n";
QQmlComponent component(&engine); QQmlComponent component(&engine);

View File

@ -30,6 +30,7 @@
#include <QtCore/private/qobject_p.h> #include <QtCore/private/qobject_p.h>
#include <QtCore/private/qmetaobject_p.h> #include <QtCore/private/qmetaobject_p.h>
#include <QtQmlTypeRegistrar/private/qqmljsstreamwriter_p.h> #include <QtQmlTypeRegistrar/private/qqmljsstreamwriter_p.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <QRegularExpression> #include <QRegularExpression>
#include <iostream> #include <iostream>
@ -690,10 +691,13 @@ private:
for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) {
const QMetaProperty &property = meta->property(index); const QMetaProperty &property = meta->property(index);
dump(property, metaRevision, knownAttributes); dump(property, metaRevision, knownAttributes);
const QByteArray changedSignalName =
QQmlSignalNames::propertyNameToChangedSignalName(property.name());
if (knownAttributes) if (knownAttributes)
knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"), knownAttributes->knownMethod(
0, QTypeRevision::fromEncodedVersion(property.revision())); changedSignalName, 0,
implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name()))); QTypeRevision::fromEncodedVersion(property.revision()));
implicitSignals.insert(changedSignalName);
} }
return implicitSignals; return implicitSignals;
} }

View File

@ -8,6 +8,7 @@
#include "qmltccompilerpieces.h" #include "qmltccompilerpieces.h"
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qqmljsutils_p.h> #include <private/qqmljsutils_p.h>
#include <algorithm> #include <algorithm>
@ -1734,7 +1735,7 @@ void QmltcCompiler::compileScriptBinding(QmltcType &current,
break; break;
} }
case QQmlSA::ScriptBindingKind::Script_SignalHandler: { case QQmlSA::ScriptBindingKind::Script_SignalHandler: {
const auto name = QQmlJSUtils::signalName(propertyName); const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName);
Q_ASSERT(name.has_value()); Q_ASSERT(name.has_value());
compileScriptSignal(*name); compileScriptSignal(*name);
break; break;
@ -1743,9 +1744,10 @@ void QmltcCompiler::compileScriptBinding(QmltcType &current,
const QString objectClassName = objectType->internalName(); const QString objectClassName = objectType->internalName();
const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor"); 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 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 Q_ASSERT(actualProperty.has_value()); // an error somewhere else
const auto actualPropertyType = actualProperty->type(); const auto actualPropertyType = actualProperty->type();
if (!actualPropertyType) { if (!actualPropertyType) {

View File

@ -6,6 +6,7 @@
#include <private/qqmljsmetatypes_p.h> #include <private/qqmljsmetatypes_p.h>
#include <private/qqmljsscope_p.h> #include <private/qqmljsscope_p.h>
#include <QtQml/private/qqmlsignalnames_p.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -35,13 +36,11 @@ struct QmltcPropertyData
QmltcPropertyData(const QString &propertyName) QmltcPropertyData(const QString &propertyName)
{ {
const QString nameWithUppercase = propertyName[0].toUpper() + propertyName.sliced(1);
read = propertyName; read = propertyName;
write = u"set" + nameWithUppercase; write = QQmlSignalNames::addPrefixToPropertyName(u"set", propertyName);
bindable = u"bindable" + nameWithUppercase; bindable = QQmlSignalNames::addPrefixToPropertyName(u"bindable", propertyName);
notify = propertyName + u"Changed"; notify = QQmlSignalNames::propertyNameToChangedSignalName(propertyName);
reset = u"reset" + nameWithUppercase; reset = QQmlSignalNames::addPrefixToPropertyName(u"reset", propertyName);
} }
QString read; QString read;

View File

@ -8,6 +8,7 @@
#include <QtCore/qstack.h> #include <QtCore/qstack.h>
#include <QtCore/qdir.h> #include <QtCore/qdir.h>
#include <QtCore/qloggingcategory.h> #include <QtCore/qloggingcategory.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qqmljsutils_p.h> #include <private/qqmljsutils_p.h>
@ -269,7 +270,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember)
owner->addOwnProperty(property); 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 // also check that notify is already a method of the scope
{ {
auto owningScope = m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope; auto owningScope = m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope;