[new compiler] Initial support for attached properties
authorSimon Hausmann <simon.hausmann@digia.com>
Fri, 13 Sep 2013 14:39:00 +0000 (16:39 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Fri, 20 Sep 2013 12:26:23 +0000 (14:26 +0200)
Attached properties are implemented similarly to group properties, except that
the object operated on isn't a QQmlValueType from a property (i.e. font) but the
QObject that implements the attached properties.

Change-Id: If73751162c191c65512ca1bddadd6270e6e33793
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
src/qml/compiler/qqmlcodegenerator.cpp
src/qml/compiler/qqmlcodegenerator_p.h
src/qml/compiler/qv4compileddata_p.h
src/qml/qml/qqmlobjectcreator.cpp
src/qml/qml/qqmltypeloader.cpp
src/qml/qml/qqmltypeloader_p.h

index 2f76de1..2e0ea22 100644 (file)
@@ -874,6 +874,15 @@ void QQmlCodeGenerator::collectTypeReferences()
             for (SignalParameter *param = sig->parameters->first; param; param = param->next)
                 if (!stringAt(param->customTypeNameIndex).isEmpty())
                     _typeReferences.add(param->customTypeNameIndex, param->location);
+
+        for (Binding *binding = obj->bindings->first; binding; binding = binding->next) {
+            if (binding->type != QV4::CompiledData::Binding::Type_Object)
+                continue;
+            const QString &propName = stringAt(binding->propertyNameIndex);
+            // Attached property?
+            if (propName.unicode()->isUpper())
+                _typeReferences.add(binding->propertyNameIndex, binding->location);
+        }
     }
 }
 
@@ -1070,9 +1079,10 @@ void JSCodeGen::QmlScanner::end()
     leaveEnvironment();
 }
 
-SignalHandlerConverter::SignalHandlerConverter(ParsedQML *parsedQML, const QHash<int, QQmlPropertyCache *> &resolvedPropertyCaches, QQmlCompiledData *unit)
-    : parsedQML(parsedQML)
-    , resolvedPropertyCaches(resolvedPropertyCaches)
+SignalHandlerConverter::SignalHandlerConverter(QQmlEnginePrivate *enginePrivate, ParsedQML *parsedQML,
+                                               QQmlCompiledData *unit)
+    : enginePrivate(enginePrivate)
+    , parsedQML(parsedQML)
     , unit(unit)
 {
 }
@@ -1083,114 +1093,127 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio
         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;
+        QQmlPropertyCache *cache = unit->resolvedTypes[obj->inheritedTypeNameIndex].createPropertyCache(QQmlEnginePrivate::get(enginePrivate));
+        if (!convertSignalHandlerExpressionsToFunctionDeclarations(obj, elementName, cache))
+            return false;
+    }
+    return true;
+}
 
-        for (Binding *binding = obj->bindings->first; binding; binding = binding->next) {
-            if (binding->type != QV4::CompiledData::Binding::Type_Script)
-                continue;
+bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclarations(QmlObject *obj, const QString &typeName, QQmlPropertyCache *propertyCache)
+{
+    // 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) {
+        QString propertyName = stringAt(binding->propertyNameIndex);
+        // Attached property?
+        if (propertyName.unicode()->isUpper() && binding->type == QV4::CompiledData::Binding::Type_Object) {
+            QmlObject *attachedObj = parsedQML->objects[binding->value.objectIndex];
+            QQmlType *type = unit->resolvedTypes.value(binding->propertyNameIndex).type;
+            QQmlPropertyCache *cache = enginePrivate->cache(type->attachedPropertiesType());
+            if (!convertSignalHandlerExpressionsToFunctionDeclarations(attachedObj, propertyName, cache))
+                return false;
+            continue;
+        }
 
-            QString propertyName = stringAt(binding->propertyNameIndex);
-            if (!QQmlCodeGenerator::isSignalPropertyName(propertyName))
-                continue;
+        if (binding->type != QV4::CompiledData::Binding::Type_Script)
+            continue;
 
-            if (!propertyCache)
-                propertyCache = resolvedPropertyCaches.value(obj->inheritedTypeNameIndex);
-            Q_ASSERT(propertyCache);
+        if (!QQmlCodeGenerator::isSignalPropertyName(propertyName))
+            continue;
 
-            PropertyResolver resolver(propertyCache);
+        PropertyResolver resolver(propertyCache);
 
-            Q_ASSERT(propertyName.startsWith(QStringLiteral("on")));
-            propertyName.remove(0, 2);
+        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;
-                }
+        // 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;
+        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));
-                    }
-                }
+        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;
 
-                // Try to look up the signal parameter names in the object itself
+                const QString &originalPropertyName = stringAt(binding->propertyNameIndex);
 
-                // 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));
-                    }
+                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(typeName).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(typeName).arg(originalPropertyName));
                 }
+            }
 
-                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);
-                }
+            // Try to look up the signal parameter names in the object itself
 
-                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;
+            // 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));
                 }
+            }
 
-                parameters = entry.value();
+            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);
             }
 
-            QQmlJS::Engine &jsEngine = parsedQML->jsParserEngine;
-            QQmlJS::MemoryPool *pool = jsEngine.pool();
+            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;
+            }
 
-            AST::FormalParameterList *paramList = 0;
-            foreach (const QString &param, parameters) {
-                QStringRef paramNameRef = jsEngine.newStringRef(param);
+            parameters = entry.value();
+        }
 
-                if (paramList)
-                    paramList = new (pool) AST::FormalParameterList(paramList, paramNameRef);
-                else
-                    paramList = new (pool) AST::FormalParameterList(paramNameRef);
-            }
+        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 = paramList->finish();
+                paramList = new (pool) AST::FormalParameterList(paramList, paramNameRef);
+            else
+                paramList = new (pool) AST::FormalParameterList(paramNameRef);
+        }
 
-            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();
+        if (paramList)
+            paramList = paramList->finish();
 
-            AST::FunctionBody *body = new (pool) AST::FunctionBody(elements);
+        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::FunctionDeclaration *functionDeclaration = new (pool) AST::FunctionDeclaration(jsEngine.newStringRef(propertyName), paramList, body);
+        AST::FunctionBody *body = new (pool) AST::FunctionBody(elements);
 
-            parsedQML->functions[binding->value.compiledScriptIndex] = functionDeclaration;
-            binding->flags |= QV4::CompiledData::Binding::IsSignalHandlerExpression;
-            binding->propertyNameIndex = parsedQML->jsGenerator.registerString(propertyName);
-        }
+        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;
 }
index a231745..7569b9c 100644 (file)
@@ -306,7 +306,7 @@ struct SignalHandlerConverter
 {
     Q_DECLARE_TR_FUNCTIONS(QQmlCodeGenerator)
 public:
-    SignalHandlerConverter(ParsedQML *parsedQML, const QHash<int, QQmlPropertyCache*> &resolvedPropertyCaches,
+    SignalHandlerConverter(QQmlEnginePrivate *enginePrivate, ParsedQML *parsedQML,
                            QQmlCompiledData *unit);
 
     bool convertSignalHandlerExpressionsToFunctionDeclarations();
@@ -314,11 +314,13 @@ public:
     QList<QQmlError> errors;
 
 private:
+    bool convertSignalHandlerExpressionsToFunctionDeclarations(QmlObject *obj, const QString &typeName, QQmlPropertyCache *propertyCache);
+
     const QString &stringAt(int index) const { return parsedQML->jsGenerator.strings.at(index); }
     void recordError(const QV4::CompiledData::Location &location, const QString &description);
 
+    QQmlEnginePrivate *enginePrivate;
     ParsedQML *parsedQML;
-    const QHash<int, QQmlPropertyCache*> &resolvedPropertyCaches;
     QQmlCompiledData *unit;
 };
 
index 0a404a4..e11cddd 100644 (file)
@@ -334,9 +334,9 @@ struct Property
 
 struct Object
 {
-    // An empty inherited type name suggests that this object doesn't require to be instantiated
-    // by itself but is merely used for grouped properties. It can therefore only have bindings,
-    // so nProperties, nFunctions and nSignals must be zero.
+    // Depending on the use, this may be the type name to instantiate before instantiating this
+    // object. For grouped properties the type name will be empty and for attached properties
+    // it will be the name of the attached type.
     quint32 inheritedTypeNameIndex;
     quint32 idIndex;
     quint32 indexOfDefaultProperty;
index 2272977..63fc88e 100644 (file)
@@ -503,6 +503,19 @@ QVector<QQmlAbstractBinding*> QmlObjectCreator::setupBindings(QV4::ExecutionCont
             continue;
         }
 
+        // Attached property
+        if (binding->type == QV4::CompiledData::Binding::Type_Object
+            && name.unicode()->isUpper()) {
+            const QV4::CompiledData::Object *obj = unit->objectAt(binding->value.objectIndex);
+            Q_ASSERT(stringAt(obj->inheritedTypeNameIndex).isEmpty());
+            QQmlType *attachedType = resolvedTypes.value(binding->propertyNameIndex).type;
+            const int id = attachedType->attachedPropertiesId();
+            QObject *qmlObject = qmlAttachedPropertiesObjectById(id, _qobject);
+            QQmlRefPointer<QQmlPropertyCache> cache = QQmlEnginePrivate::get(engine)->cache(qmlObject);
+            populateInstance(binding->value.objectIndex, qmlObject, cache);
+            continue;
+        }
+
         QQmlPropertyData *property = _propertyCache->property(name, _qobject, context);
 
         // Grouped property:
index 9ca41f6..8c8f8c9 100644 (file)
@@ -49,6 +49,7 @@
 #include <private/qqmlcomponent_p.h>
 #include <private/qqmlprofilerservice_p.h>
 #include <private/qqmlmemoryprofiler_p.h>
+#include <private/qqmlcodegenerator_p.h>
 
 #include <QtCore/qdir.h>
 #include <QtCore/qfile.h>
@@ -2175,19 +2176,18 @@ void QQmlTypeData::compile()
 
         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);
+            SignalHandlerConverter converter(QQmlEnginePrivate::get(engine),
+                                             parsedQML.data(),
+                                             m_compiledData);
             if (!converter.convertSignalHandlerExpressionsToFunctionDeclarations()) {
                 setError(converter.errors);
                 m_compiledData->release();
index f86c037..fee09d3 100644 (file)
@@ -68,7 +68,6 @@
 #include <private/qqmldirparser_p.h>
 #include <private/qqmlbundle_p.h>
 #include <private/qflagpointer_p.h>
-#include <private/qqmlcodegenerator_p.h>
 
 #include <private/qv4value_p.h>
 #include <private/qv4script_p.h>
@@ -85,6 +84,10 @@ class QQmlTypeData;
 class QQmlDataLoader;
 class QQmlExtensionInterface;
 
+namespace QtQml {
+struct ParsedQML;
+}
+
 class Q_QML_PRIVATE_EXPORT QQmlDataBlob : public QQmlRefCount
 {
 public: