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:
parent
8d6f9e716d
commit
a1ce0596e5
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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++);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <private/qqmlcomponent_p.h>
|
||||
#include <private/qqmlpropertyresolver_p.h>
|
||||
#include <private/qqmlcomponentandaliasresolver_p.h>
|
||||
#include <private/qqmlsignalnames_p.h>
|
||||
|
||||
#define COMPILE_EXCEPTION(token, desc) \
|
||||
{ \
|
||||
|
@ -325,18 +326,21 @@ bool SignalHandlerResolver::resolveSignalHandlerExpressions(
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!QmlIR::IRBuilder::isSignalPropertyName(bindingPropertyName))
|
||||
QString qPropertyName;
|
||||
QString signalName;
|
||||
if (auto propertyName =
|
||||
QQmlSignalNames::changedHandlerNameToPropertyName(bindingPropertyName)) {
|
||||
qPropertyName = *propertyName;
|
||||
signalName = *QQmlSignalNames::changedHandlerNameToSignalName(bindingPropertyName);
|
||||
} else {
|
||||
signalName = QQmlSignalNames::handlerNameToSignalName(bindingPropertyName)
|
||||
.value_or(QString());
|
||||
}
|
||||
if (signalName.isEmpty())
|
||||
continue;
|
||||
|
||||
QQmlPropertyResolver resolver(propertyCache);
|
||||
|
||||
const QString signalName = QmlIR::IRBuilder::signalNameFromSignalPropertyName(
|
||||
bindingPropertyName);
|
||||
|
||||
QString qPropertyName;
|
||||
if (signalName.endsWith(QLatin1String("Changed")))
|
||||
qPropertyName = signalName.mid(0, signalName.size() - static_cast<int>(strlen("Changed")));
|
||||
|
||||
bool notInRevision = false;
|
||||
const QQmlPropertyData * const signal = resolver.signal(signalName, ¬InRevision);
|
||||
const QQmlPropertyData * const signalPropertyData = resolver.property(signalName, /*notInRevision ptr*/nullptr);
|
||||
|
@ -1045,7 +1049,7 @@ bool QQmlDeferredAndCustomParserBindingScanner::scanObject(
|
|||
obj->flags |= Object::HasCustomParserBindings;
|
||||
continue;
|
||||
}
|
||||
} else if (QmlIR::IRBuilder::isSignalPropertyName(name)
|
||||
} else if (QQmlSignalNames::isHandlerName(name)
|
||||
&& !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) {
|
||||
obj->flags |= Object::HasCustomParserBindings;
|
||||
binding->setFlag(Binding::IsCustomParserBinding);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "qmltccompilerpieces.h"
|
||||
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
#include <QtQml/private/qqmlsignalnames_p.h>
|
||||
#include <private/qqmljsutils_p.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -1734,7 +1735,7 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t,
|
|||
break;
|
||||
}
|
||||
case QQmlSA::ScriptBindingKind::Script_SignalHandler: {
|
||||
const auto name = QQmlJSUtils::signalName(propertyName);
|
||||
const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName);
|
||||
Q_ASSERT(name.has_value());
|
||||
compileScriptSignal(*name);
|
||||
break;
|
||||
|
@ -1743,9 +1744,10 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t,
|
|||
const QString objectClassName = objectType->internalName();
|
||||
const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor");
|
||||
|
||||
const auto signalName = QQmlJSUtils::signalName(propertyName);
|
||||
const auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName);
|
||||
Q_ASSERT(signalName.has_value()); // an error somewhere else
|
||||
const auto actualProperty = QQmlJSUtils::changeHandlerProperty(objectType, *signalName);
|
||||
const auto actualProperty =
|
||||
QQmlJSUtils::propertyFromChangedHandler(objectType, propertyName);
|
||||
Q_ASSERT(actualProperty.has_value()); // an error somewhere else
|
||||
const auto actualPropertyType = actualProperty->type();
|
||||
if (!actualPropertyType) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue