aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@digia.com>2013-09-13 15:03:51 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-20 14:26:18 +0200
commitb4b4a646800fabbb31b4b261ea4b802d78d6501f (patch)
tree285d3553373500e4cf9b882275604002c93870ba
parent4a59984e0d024b88a1b556870b6c183816bb1d22 (diff)
[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 <lars.knoll@digia.com>
-rw-r--r--src/qml/compiler/qqmlcodegenerator.cpp215
-rw-r--r--src/qml/compiler/qqmlcodegenerator_p.h48
-rw-r--r--src/qml/compiler/qv4compileddata_p.h9
-rw-r--r--src/qml/qml/qqmlboundsignal.cpp13
-rw-r--r--src/qml/qml/qqmlboundsignal_p.h3
-rw-r--r--src/qml/qml/qqmlobjectcreator.cpp24
-rw-r--r--src/qml/qml/qqmltypeloader.cpp16
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 <private/qv4compileddata_p.h>
#include <private/qqmljsparser_p.h>
#include <private/qqmljslexer_p.h>
+#include <private/qqmlcompiler_p.h>
#include <QCoreApplication>
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>();
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>();
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>();
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<int, QQmlPropertyCache *> &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<QString, QStringList> 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<QString> parameters;
+
+ bool notInRevision = false;
+ QQmlPropertyData *signal = resolver.signal(propertyName, &notInRevision);
+ if (signal) {
+ int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex);
+ foreach (const QByteArray &param, 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<QString, QStringList>::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 &param, 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<AST::Statement*>(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<SignalParameter> *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<int, QQmlPropertyCache*> &resolvedPropertyCaches,
+ QQmlCompiledData *unit);
+
+ bool convertSignalHandlerExpressionsToFunctionDeclarations();
+
+ QList<QQmlError> 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<int, QQmlPropertyCache*> &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 &parameterString = 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 <private/qqmlcontextwrapper_p.h>
#include <private/qqmlbinding_p.h>
#include <private/qqmlstringconverters_p.h>
+#include <private/qqmlboundsignal_p.h>
QT_USE_NAMESPACE
@@ -515,16 +516,25 @@ QVector<QQmlAbstractBinding*> 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<int, QQmlPropertyCache*> resolvedPropertyCaches;
+
for (QHash<int, TypeReference>::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());