From b4b4a646800fabbb31b4b261ea4b802d78d6501f Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 13 Sep 2013 15:03:51 +0200 Subject: [new compiler] Initial implementation of signal handler support Signal handlers start out in the parser as binding expressions. A new SignalHandlerConverter converts the bindings then so that the expression is turned into a function declaration where the function parameters match the parameters of the signal. Change-Id: I3ea5aa3b80a6ee3b095c6841c63c3e3bb0b47e4f Reviewed-by: Lars Knoll --- src/qml/compiler/qqmlcodegenerator.cpp | 215 +++++++++++++++++++++++++++++++++ src/qml/compiler/qqmlcodegenerator_p.h | 48 ++++++++ src/qml/compiler/qv4compileddata_p.h | 9 +- src/qml/qml/qqmlboundsignal.cpp | 13 ++ src/qml/qml/qqmlboundsignal_p.h | 3 + src/qml/qml/qqmlobjectcreator.cpp | 24 ++-- src/qml/qml/qqmltypeloader.cpp | 16 +++ 7 files changed, 320 insertions(+), 8 deletions(-) diff --git a/src/qml/compiler/qqmlcodegenerator.cpp b/src/qml/compiler/qqmlcodegenerator.cpp index fe57e246b0..2f76de168b 100644 --- a/src/qml/compiler/qqmlcodegenerator.cpp +++ b/src/qml/compiler/qqmlcodegenerator.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include QT_USE_NAMESPACE @@ -65,6 +66,15 @@ void QmlObject::dump(DebugStream &out) out << "}" << endl; } +QStringList Signal::parameterStringList(const QStringList &stringPool) const +{ + QStringList result; + result.reserve(parameters->count); + for (SignalParameter *param = parameters->first; param; param = param->next) + result << stringPool.at(param->nameIndex); + return result; +} + QQmlCodeGenerator::QQmlCodeGenerator() : _object(0) , jsGenerator(0) @@ -144,6 +154,20 @@ bool QQmlCodeGenerator::generateFromQml(const QString &code, const QUrl &url, co return true; } +bool QQmlCodeGenerator::isSignalPropertyName(const QString &name) +{ + if (name.length() < 3) return false; + if (!name.startsWith(QStringLiteral("on"))) return false; + int ns = name.length(); + 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. +} + bool QQmlCodeGenerator::visit(AST::UiArrayMemberList *ast) { return AST::Visitor::visit(ast); @@ -718,6 +742,9 @@ void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, i Binding *binding = New(); binding->propertyNameIndex = propertyNameIndex; + binding->location.line = nameLocation.startLine; + binding->location.column = nameLocation.startColumn; + binding->flags = 0; setBindingValue(binding, value); _object->bindings->append(binding); } @@ -728,6 +755,9 @@ void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, i return; Binding *binding = New(); binding->propertyNameIndex = propertyNameIndex; + binding->location.line = nameLocation.startLine; + binding->location.column = nameLocation.startColumn; + binding->flags = 0; binding->type = QV4::CompiledData::Binding::Type_Object; binding->value.objectIndex = objectIndex; _object->bindings->append(binding); @@ -778,6 +808,9 @@ AST::UiQualifiedId *QQmlCodeGenerator::resolveQualifiedId(AST::UiQualifiedId *na while (name->next) { Binding *binding = New(); binding->propertyNameIndex = registerString(name->name.toString()); + binding->location.line = name->identifierToken.startLine; + binding->location.column = name->identifierToken.startColumn; + binding->flags = 0; binding->type = QV4::CompiledData::Binding::Type_Object; int objIndex = defineQMLObject(0, 0); @@ -1036,3 +1069,185 @@ void JSCodeGen::QmlScanner::end() { leaveEnvironment(); } + +SignalHandlerConverter::SignalHandlerConverter(ParsedQML *parsedQML, const QHash &resolvedPropertyCaches, QQmlCompiledData *unit) + : parsedQML(parsedQML) + , resolvedPropertyCaches(resolvedPropertyCaches) + , unit(unit) +{ +} + +bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclarations() +{ + foreach (QmlObject *obj, parsedQML->objects) { + QString elementName = stringAt(obj->inheritedTypeNameIndex); + if (elementName.isEmpty()) + continue; + QQmlPropertyCache *propertyCache = 0; + // map from signal name defined in qml itself to list of parameters + QHash customSignals; + + for (Binding *binding = obj->bindings->first; binding; binding = binding->next) { + if (binding->type != QV4::CompiledData::Binding::Type_Script) + continue; + + QString propertyName = stringAt(binding->propertyNameIndex); + if (!QQmlCodeGenerator::isSignalPropertyName(propertyName)) + continue; + + if (!propertyCache) + propertyCache = resolvedPropertyCaches.value(obj->inheritedTypeNameIndex); + Q_ASSERT(propertyCache); + + PropertyResolver resolver(propertyCache); + + Q_ASSERT(propertyName.startsWith(QStringLiteral("on"))); + propertyName.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 < propertyName.size(); ++firstAlphaIndex) { + if (propertyName.at(firstAlphaIndex).isUpper()) { + propertyName[firstAlphaIndex] = propertyName.at(firstAlphaIndex).toLower(); + break; + } + } + + QList parameters; + + bool notInRevision = false; + QQmlPropertyData *signal = resolver.signal(propertyName, ¬InRevision); + if (signal) { + int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex); + foreach (const QByteArray ¶m, propertyCache->signalParameterNames(sigIndex)) + parameters << QString::fromUtf8(param); + } else { + if (notInRevision) { + // Try assinging it as a property later + if (resolver.property(propertyName, /*notInRevision ptr*/0)) + continue; + + const QString &originalPropertyName = stringAt(binding->propertyNameIndex); + + const QQmlType *type = unit->resolvedTypes.value(obj->inheritedTypeNameIndex).type; + if (type) { + COMPILE_EXCEPTION(binding->location, tr("\"%1.%2\" is not available in %3 %4.%5.").arg(elementName).arg(originalPropertyName).arg(type->module()).arg(type->majorVersion()).arg(type->minorVersion())); + } else { + COMPILE_EXCEPTION(binding->location, tr("\"%1.%2\" is not available due to component versioning.").arg(elementName).arg(originalPropertyName)); + } + } + + // Try to look up the signal parameter names in the object itself + + // build cache if necessary + if (customSignals.isEmpty()) { + for (Signal *signal = obj->qmlSignals->first; signal; signal = signal->next) { + const QString &signalName = stringAt(signal->nameIndex); + customSignals.insert(signalName, signal->parameterStringList(parsedQML->jsGenerator.strings)); + } + } + + QHash::ConstIterator entry = customSignals.find(propertyName); + if (entry == customSignals.constEnd() && propertyName.endsWith(QStringLiteral("Changed"))) { + QString alternateName = propertyName.mid(0, propertyName.length() - strlen("Changed")); + entry = customSignals.find(alternateName); + } + + if (entry == customSignals.constEnd()) { + // Can't find even a custom signal, then just don't do anything and try + // keeping the binding as a regular property assignment. + continue; + } + + parameters = entry.value(); + } + + QQmlJS::Engine &jsEngine = parsedQML->jsParserEngine; + QQmlJS::MemoryPool *pool = jsEngine.pool(); + + AST::FormalParameterList *paramList = 0; + foreach (const QString ¶m, parameters) { + QStringRef paramNameRef = jsEngine.newStringRef(param); + + if (paramList) + paramList = new (pool) AST::FormalParameterList(paramList, paramNameRef); + else + paramList = new (pool) AST::FormalParameterList(paramNameRef); + } + + if (paramList) + paramList = paramList->finish(); + + AST::Statement *statement = static_cast(parsedQML->functions[binding->value.compiledScriptIndex]); + AST::SourceElement *sourceElement = new (pool) AST::StatementSourceElement(statement); + AST::SourceElements *elements = new (pool) AST::SourceElements(sourceElement); + elements = elements->finish(); + + AST::FunctionBody *body = new (pool) AST::FunctionBody(elements); + + AST::FunctionDeclaration *functionDeclaration = new (pool) AST::FunctionDeclaration(jsEngine.newStringRef(propertyName), paramList, body); + + parsedQML->functions[binding->value.compiledScriptIndex] = functionDeclaration; + binding->flags |= QV4::CompiledData::Binding::IsSignalHandlerExpression; + binding->propertyNameIndex = parsedQML->jsGenerator.registerString(propertyName); + } + } + return true; +} + +void SignalHandlerConverter::recordError(const QV4::CompiledData::Location &location, const QString &description) +{ + QQmlError error; + error.setUrl(unit->url); + error.setLine(location.line); + error.setColumn(location.column); + error.setDescription(description); + errors << error; +} + +QQmlPropertyData *PropertyResolver::property(const QString &name, bool *notInRevision) +{ + if (notInRevision) *notInRevision = false; + + QQmlPropertyData *d = cache->property(name, 0, 0); + + // Find the first property + while (d && d->isFunction()) + d = cache->overrideData(d); + + if (d && !cache->isAllowedInRevision(d)) { + if (notInRevision) *notInRevision = true; + return 0; + } else { + return d; + } +} + + +QQmlPropertyData *PropertyResolver::signal(const QString &name, bool *notInRevision) +{ + if (notInRevision) *notInRevision = false; + + QQmlPropertyData *d = cache->property(name, 0, 0); + if (notInRevision) *notInRevision = false; + + while (d && !(d->isFunction())) + d = cache->overrideData(d); + + if (d && !cache->isAllowedInRevision(d)) { + if (notInRevision) *notInRevision = true; + return 0; + } else if (d && d->isSignal()) { + return d; + } + + if (name.endsWith(QStringLiteral("Changed"))) { + QString propName = name.mid(0, name.length() - strlen("Changed")); + + d = property(propName, notInRevision); + if (d) + return cache->signal(d->notifyIndex); + } + + return 0; +} diff --git a/src/qml/compiler/qqmlcodegenerator_p.h b/src/qml/compiler/qqmlcodegenerator_p.h index f4752506b0..a231745e25 100644 --- a/src/qml/compiler/qqmlcodegenerator_p.h +++ b/src/qml/compiler/qqmlcodegenerator_p.h @@ -114,6 +114,9 @@ struct Signal int nameIndex; QV4::CompiledData::Location location; PoolList *parameters; + + QStringList parameterStringList(const QStringList &stringPool) const; + Signal *next; }; @@ -177,6 +180,8 @@ public: QQmlCodeGenerator(); bool generateFromQml(const QString &code, const QUrl &url, const QString &urlString, ParsedQML *output); + static bool isSignalPropertyName(const QString &name); + using AST::Visitor::visit; using AST::Visitor::endVisit; @@ -274,6 +279,49 @@ private: QV4::Compiler::JSUnitGenerator *jsUnitGenerator; }; +struct PropertyResolver +{ + PropertyResolver(QQmlPropertyCache *cache) + : cache(cache) + {} + + QQmlPropertyData *property(int index) + { + return cache->property(index); + } + + QQmlPropertyData *property(const QString &name, bool *notInRevision); + + // This code must match the semantics of QQmlPropertyPrivate::findSignalByName + QQmlPropertyData *signal(const QString &name, bool *notInRevision); + + QQmlPropertyCache *cache; +}; + +// "Converts" signal expressions to full-fleged function declarations with +// parameters taken from the signal declarations +// It also updates the QV4::CompiledData::Binding objects to set the property name +// to the final signal name (onTextChanged -> textChanged) and sets the IsSignalExpression flag. +struct SignalHandlerConverter +{ + Q_DECLARE_TR_FUNCTIONS(QQmlCodeGenerator) +public: + SignalHandlerConverter(ParsedQML *parsedQML, const QHash &resolvedPropertyCaches, + QQmlCompiledData *unit); + + bool convertSignalHandlerExpressionsToFunctionDeclarations(); + + QList errors; + +private: + const QString &stringAt(int index) const { return parsedQML->jsGenerator.strings.at(index); } + void recordError(const QV4::CompiledData::Location &location, const QString &description); + + ParsedQML *parsedQML; + const QHash &resolvedPropertyCaches; + QQmlCompiledData *unit; +}; + struct Q_QML_EXPORT JSCodeGen : public QQmlJS::Codegen { JSCodeGen() diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h index e8d2a73278..0a404a454f 100644 --- a/src/qml/compiler/qv4compileddata_p.h +++ b/src/qml/compiler/qv4compileddata_p.h @@ -254,7 +254,12 @@ struct Binding Type_Object }; - quint32 type; + enum Flags { + IsSignalHandlerExpression = 0x1 + }; + + quint32 flags : 16; + quint32 type : 16; union { bool b; double d; @@ -263,6 +268,8 @@ struct Binding } value; quint32 stringIndex; // Set for Type_String and Type_Script (the latter because of script strings) + Location location; + QString valueAsString(const Unit *unit) const; double valueAsNumber() const { diff --git a/src/qml/qml/qqmlboundsignal.cpp b/src/qml/qml/qqmlboundsignal.cpp index 3242ab9831..36cbc39d40 100644 --- a/src/qml/qml/qqmlboundsignal.cpp +++ b/src/qml/qml/qqmlboundsignal.cpp @@ -86,6 +86,19 @@ QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, m_expression = expression; } +QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, QQmlContextData *ctxt, QObject *scope, const QV4::PersistentValue &function) + : QQmlJavaScriptExpression(&QQmlBoundSignalExpression_jsvtable), + m_v8function(function), + m_line(-1), + m_column(-1), + m_target(target), + m_index(index), + m_expressionFunctionValid(true), + m_invalidParameterName(false) +{ + init(ctxt, scope); +} + void QQmlBoundSignalExpression::init(QQmlContextData *ctxt, QObject *scope) { setNotifyOnValueChanged(false); diff --git a/src/qml/qml/qqmlboundsignal_p.h b/src/qml/qml/qqmlboundsignal_p.h index ffb3d06770..86e3dc20ea 100644 --- a/src/qml/qml/qqmlboundsignal_p.h +++ b/src/qml/qml/qqmlboundsignal_p.h @@ -75,6 +75,9 @@ public: const QString &handlerName = QString(), const QString ¶meterString = QString()); + QQmlBoundSignalExpression(QObject *target, int index, + QQmlContextData *ctxt, QObject *scope, const QV4::PersistentValue &function); + // "inherited" from QQmlJavaScriptExpression. static QString expressionIdentifier(QQmlJavaScriptExpression *); diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index 5853d8c04d..8ed3a13b77 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -49,6 +49,7 @@ #include #include #include +#include QT_USE_NAMESPACE @@ -515,16 +516,25 @@ QVector QmlObjectCreator::setupBindings(QV4::ExecutionCont if (binding->type == QV4::CompiledData::Binding::Type_Script) { QV4::Function *runtimeFunction = jsUnit->runtimeFunctions[binding->value.compiledScriptIndex]; - QV4::FunctionObject *function = QV4::FunctionObject::creatScriptFunction(qmlContext, runtimeFunction); + QV4::Value function = QV4::Value::fromObject(QV4::FunctionObject::creatScriptFunction(qmlContext, runtimeFunction)); - QQmlBinding *binding = new QQmlBinding(QV4::Value::fromObject(function), _qobject, context, - QString(), 0, 0); // ### + if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression) { + int signalIndex = _propertyCache->methodIndexToSignalIndex(property->coreIndex); + QQmlBoundSignal *bs = new QQmlBoundSignal(_qobject, signalIndex, _qobject, engine); + QQmlBoundSignalExpression *expr = new QQmlBoundSignalExpression(_qobject, signalIndex, + context, _qobject, function); - binding->setTarget(_qobject, *property, context); - binding->addToObject(); + bs->takeExpression(expr); + } else { + QQmlBinding *qmlBinding = new QQmlBinding(function, _qobject, context, + QString(), 0, 0); // ### + + qmlBinding->setTarget(_qobject, *property, context); + qmlBinding->addToObject(); - createdDynamicBindings[i] = binding; - binding->m_mePtr = &createdDynamicBindings[i]; + createdDynamicBindings[i] = qmlBinding; + qmlBinding->m_mePtr = &createdDynamicBindings[i]; + } continue; } diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index db68631bbf..9ca41f6071 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -2173,13 +2173,29 @@ void QQmlTypeData::compile() m_imports.populateCache(m_compiledData->importCache); m_compiledData->importCache->addref(); + QQmlEngine *engine = typeLoader()->engine(); + + QHash resolvedPropertyCaches; + for (QHash::ConstIterator resolvedType = m_resolvedTypes.constBegin(), end = m_resolvedTypes.constEnd(); resolvedType != end; ++resolvedType) { QQmlCompiledData::TypeReference ref; ref.type = resolvedType->type; + Q_ASSERT(ref.type); + resolvedPropertyCaches.insert(resolvedType.key(), ref.createPropertyCache(engine)); m_compiledData->resolvedTypes.insert(resolvedType.key(), ref); } + { + SignalHandlerConverter converter(parsedQML.data(), resolvedPropertyCaches, m_compiledData); + if (!converter.convertSignalHandlerExpressionsToFunctionDeclarations()) { + setError(converter.errors); + m_compiledData->release(); + m_compiledData = 0; + return; + } + } + JSCodeGen jsCodeGen; jsCodeGen.generateJSCodeForFunctionsAndBindings(finalUrlString(), parsedQML.data()); -- cgit v1.2.3