[new compiler] Initial support for group properties
authorSimon Hausmann <simon.hausmann@digia.com>
Thu, 12 Sep 2013 12:43:56 +0000 (14:43 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Fri, 20 Sep 2013 12:26:01 +0000 (14:26 +0200)
This implements support for "font.pixelSize: 24" for example. The representation
in the compile data structure is so that font.pixelSize is short-hand for

font {
    pixelSize: 24
}

which means that inside the braces is a complete object initializer. For that
initializer we create a dedicated CompiledData::Object, which however has its
type name empty. When populating the outer instance then, the "font" property
is read as QQmlValueType (a QObject) and instead of creating a new QObject we
use that value type as instance to run the rest of the QML object initializer
(everything in braces).

Change-Id: Ic0a37ac77ab88f582546b9c09a3d06a07726420b
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/qqmlcompileddata.cpp
src/qml/qml/qqmlobjectcreator.cpp
src/qml/qml/qqmlobjectcreator_p.h
src/qml/qml/qqmltypeloader.cpp

index 6ca7078..b875067 100644 (file)
@@ -152,21 +152,38 @@ bool QQmlCodeGenerator::visit(AST::UiProgram *)
 
 bool QQmlCodeGenerator::visit(AST::UiObjectDefinition *node)
 {
-    int idx = defineQMLObject(node);
-    appendBinding(AST::SourceLocation(), registerString(QString()), idx);
+    // The grammar can't distinguish between two different definitions here:
+    //     Item { ... }
+    // versus
+    //     font { ... }
+    // The former is a new binding with no property name and "Item" as type name,
+    // and the latter is a binding to the font property with no type name but
+    // only initializer.
+
+    AST::UiQualifiedId *lastId = node->qualifiedTypeNameId;
+    while (lastId->next)
+        lastId = lastId->next;
+    bool isType = lastId->name.unicode()->isUpper();
+    if (isType) {
+        int idx = defineQMLObject(node);
+        appendBinding(AST::SourceLocation(), registerString(QString()), idx);
+    } else {
+        int idx = defineQMLObject(/*qualfied type name id*/0, node->initializer);
+        appendBinding(node->qualifiedTypeNameId, idx);
+    }
     return false;
 }
 
 bool QQmlCodeGenerator::visit(AST::UiObjectBinding *node)
 {
     int idx = defineQMLObject(node->qualifiedTypeNameId, node->initializer);
-    appendBinding(node->qualifiedId->identifierToken, registerString(asString(node->qualifiedId)), idx);
+    appendBinding(node->qualifiedId, idx);
     return false;
 }
 
 bool QQmlCodeGenerator::visit(AST::UiScriptBinding *node)
 {
-    appendBinding(node->qualifiedId->identifierToken, registerString(asString(node->qualifiedId)), node->statement);
+    appendBinding(node->qualifiedId, node->statement);
     return false;
 }
 
@@ -237,7 +254,9 @@ int QQmlCodeGenerator::defineQMLObject(AST::UiQualifiedId *qualifiedTypeNameId,
 
     _object->inheritedTypeNameIndex = registerString(asString(qualifiedTypeNameId));
 
-    AST::SourceLocation loc = qualifiedTypeNameId->firstSourceLocation();
+    AST::SourceLocation loc;
+    if (qualifiedTypeNameId)
+        loc = qualifiedTypeNameId->firstSourceLocation();
     _object->location.line = loc.startLine;
     _object->location.column = loc.startColumn;
 
@@ -664,6 +683,24 @@ void QQmlCodeGenerator::setBindingValue(QV4::CompiledData::Binding *binding, AST
     }
 }
 
+void QQmlCodeGenerator::appendBinding(AST::UiQualifiedId *name, AST::Statement *value)
+{
+    QmlObject *object = 0;
+    name = resolveQualifiedId(name, &object);
+    qSwap(_object, object);
+    appendBinding(name->identifierToken, registerString(name->name.toString()), value);
+    qSwap(_object, object);
+}
+
+void QQmlCodeGenerator::appendBinding(AST::UiQualifiedId *name, int objectIndex)
+{
+    QmlObject *object = 0;
+    name = resolveQualifiedId(name, &object);
+    qSwap(_object, object);
+    appendBinding(name->identifierToken, registerString(name->name.toString()), objectIndex);
+    qSwap(_object, object);
+}
+
 void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, int propertyNameIndex, AST::Statement *value)
 {
     if (!sanityCheckPropertyName(nameLocation, propertyNameIndex))
@@ -685,9 +722,31 @@ void QQmlCodeGenerator::appendBinding(const AST::SourceLocation &nameLocation, i
     _object->bindings->append(binding);
 }
 
+AST::UiQualifiedId *QQmlCodeGenerator::resolveQualifiedId(AST::UiQualifiedId *name, QmlObject **object)
+{
+    *object = _object;
+    while (name->next) {
+        Binding *binding = New<Binding>();
+        binding->propertyNameIndex = registerString(name->name.toString());
+        binding->value.type = QV4::CompiledData::Value::Type_Object;
+
+        int objIndex = defineQMLObject(0, 0);
+        binding->value.objectIndex = objIndex;
+
+        (*object)->bindings->append(binding);
+        *object = _objects[objIndex];
+
+        name = name->next;
+    }
+    return name;
+}
+
 bool QQmlCodeGenerator::sanityCheckPropertyName(const AST::SourceLocation &nameLocation, int nameIndex)
 {
-    QString name = jsGenerator->strings.at(nameIndex);
+    const QString &name = jsGenerator->strings.at(nameIndex);
+    if (name.isEmpty())
+        return true;
+
     if (_propertyNames.contains(name))
         COMPILE_EXCEPTION(nameLocation, tr("Duplicate property name"));
 
@@ -720,7 +779,8 @@ void QQmlCodeGenerator::recordError(const AST::SourceLocation &location, const Q
 void QQmlCodeGenerator::collectTypeReferences()
 {
     foreach (QmlObject *obj, _objects) {
-        _typeReferences.add(obj->inheritedTypeNameIndex, obj->location);
+        if (!stringAt(obj->inheritedTypeNameIndex).isEmpty())
+            _typeReferences.add(obj->inheritedTypeNameIndex, obj->location);
 
         for (QmlProperty *prop = obj->properties->first; prop; prop = prop->next) {
             if (prop->type >= QV4::CompiledData::Property::Custom)
index 97ba3ef..5344dc1 100644 (file)
@@ -215,9 +215,15 @@ public:
 
     void setBindingValue(QV4::CompiledData::Binding *binding, AST::Statement *statement);
 
+    void appendBinding(AST::UiQualifiedId *name, AST::Statement *value);
+    void appendBinding(AST::UiQualifiedId *name, int objectIndex);
     void appendBinding(const AST::SourceLocation &nameLocation, int propertyNameIndex, AST::Statement *value);
     void appendBinding(const AST::SourceLocation &nameLocation, int propertyNameIndex, int objectIndex);
 
+    // resolves qualified name (font.pixelSize for example) and returns the last name along
+    // with the object any right-hand-side of a binding should apply to.
+    AST::UiQualifiedId *resolveQualifiedId(AST::UiQualifiedId *name, QmlObject **object);
+
     bool sanityCheckPropertyName(const AST::SourceLocation &nameLocation, int nameIndex);
 
     void recordError(const AST::SourceLocation &location, const QString &description);
@@ -229,6 +235,8 @@ public:
     int registerString(const QString &str) const { return jsGenerator->registerString(str); }
     template <typename _Tp> _Tp *New() { return new (pool->allocate(sizeof(_Tp))) _Tp(); }
 
+    QString stringAt(int index) const { return jsGenerator->strings.at(index); }
+
     QList<QQmlError> errors;
 
     QList<QV4::CompiledData::Import*> _imports;
index c98d53a..9f8bd80 100644 (file)
@@ -315,6 +315,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.
     quint32 inheritedTypeNameIndex;
     quint32 idIndex;
     quint32 indexOfDefaultProperty;
index f9e36d5..35de0ac 100644 (file)
@@ -125,7 +125,8 @@ QQmlCompiledData::~QQmlCompiledData()
     }
 
     for (int ii = 0; ii < propertyCaches.count(); ++ii) 
-        propertyCaches.at(ii)->release();
+        if (propertyCaches.at(ii))
+            propertyCaches.at(ii)->release();
 
     for (int ii = 0; ii < scripts.count(); ++ii)
         scripts.at(ii)->release();
index 7ef84fe..a92bbed 100644 (file)
@@ -72,6 +72,7 @@ QQmlPropertyCacheCreator::QQmlPropertyCacheCreator(QQmlEnginePrivate *enginePriv
 
 bool QQmlPropertyCacheCreator::create(const QV4::CompiledData::Object *obj, QQmlPropertyCache **resultCache, QByteArray *vmeMetaObjectData)
 {
+    Q_ASSERT(!stringAt(obj->inheritedTypeNameIndex).isEmpty());
     QQmlType *baseType = resolvedTypes->value(obj->inheritedTypeNameIndex).type;
     Q_ASSERT(baseType);
 
@@ -475,6 +476,11 @@ QVector<QQmlAbstractBinding*> QmlObjectCreator::setupBindings(QV4::Object *qmlGl
     for (quint32 i = 0; i < _compiledObject->nBindings; ++i, ++binding) {
         QString name = stringAt(binding->propertyNameIndex);
 
+        // Child item:
+        // ...
+        //    Item {
+        //        ...
+        //    }
         if (name.isEmpty() && binding->value.type == QV4::CompiledData::Value::Type_Object) {
             create(binding->value.objectIndex, _qobject);
             continue;
@@ -482,6 +488,27 @@ QVector<QQmlAbstractBinding*> QmlObjectCreator::setupBindings(QV4::Object *qmlGl
 
         QQmlPropertyData *property = _propertyCache->property(name, _qobject, context);
 
+        // Grouped property:
+        //  ...
+        //  font {
+        //      pixelSize: 24
+        //      ...
+        //  }
+        if (binding->value.type == QV4::CompiledData::Value::Type_Object) {
+            const QV4::CompiledData::Object *obj = unit->objectAt(binding->value.objectIndex);
+            if (stringAt(obj->inheritedTypeNameIndex).isEmpty()) {
+                QQmlValueType *valueType = QQmlValueTypeFactory::valueType(property->propType);
+
+                valueType->read(_qobject, property->coreIndex);
+
+                QQmlRefPointer<QQmlPropertyCache> cache = QQmlEnginePrivate::get(engine)->cache(valueType);
+                populateInstance(binding->value.objectIndex, valueType, cache);
+
+                valueType->write(_qobject, property->coreIndex, QQmlPropertyPrivate::BypassInterceptor);
+                continue;
+            }
+        }
+
         if (_ddata->hasBindingBit(property->coreIndex))
             removeBindingOnProperty(_qobject, property->coreIndex);
 
@@ -551,23 +578,32 @@ QObject *QmlObjectCreator::create(int index, QObject *parent)
     QQmlType *type = resolvedTypes.value(obj->inheritedTypeNameIndex).type;
     Q_ASSERT(type);
 
-    QObject *result = type->create();
+    QObject *instance = type->create();
     // ### use no-event variant
     if (parent)
-        result->setParent(parent);
-
-    QQmlData *declarativeData = QQmlData::get(result, /*create*/true);
+        instance->setParent(parent);
 
     QQmlRefPointer<QQmlPropertyCache> cache = propertyCaches.value(index);
     Q_ASSERT(!cache.isNull());
 
+    context->addObject(instance);
+
+    populateInstance(index, instance, cache);
+
+    return instance;
+}
+
+void QmlObjectCreator::populateInstance(int index, QObject *instance, QQmlRefPointer<QQmlPropertyCache> cache)
+{
+    const QV4::CompiledData::Object *obj = unit->objectAt(index);
+
+    QQmlData *declarativeData = QQmlData::get(instance, /*create*/true);
+
     qSwap(_propertyCache, cache);
-    qSwap(_qobject, result);
+    qSwap(_qobject, instance);
     qSwap(_compiledObject, obj);
     qSwap(_ddata, declarativeData);
 
-    context->addObject(_qobject);
-
     const QByteArray data = vmeMetaObjectData.value(index);
     if (!data.isEmpty()) {
         // install on _object
@@ -600,9 +636,7 @@ QObject *QmlObjectCreator::create(int index, QObject *parent)
     qSwap(_propertyCache, cache);
     qSwap(_ddata, declarativeData);
     qSwap(_compiledObject, obj);
-    qSwap(_qobject, result);
-
-    return result;
+    qSwap(_qobject, instance);
 }
 
 QVariant QmlObjectCreator::variantForBinding(int expectedMetaType, const QV4::CompiledData::Binding *binding) const
index 673809d..509fda3 100644 (file)
@@ -87,6 +87,8 @@ public:
     QList<QQmlError> errors;
 
 private:
+    void populateInstance(int index, QObject *instance, QQmlRefPointer<QQmlPropertyCache> cache);
+
     QVector<QQmlAbstractBinding *> setupBindings(QV4::Object *qmlGlobal);
     void setupFunctions(QV4::Object *qmlGlobal);
 
index 810347f..652018d 100644 (file)
@@ -2218,15 +2218,19 @@ void QQmlTypeData::compile()
             QByteArray vmeMetaObjectData;
             QQmlPropertyCache *propertyCache = 0;
 
-            if (!propertyCacheBuilder.create(obj, &propertyCache, &vmeMetaObjectData)) {
-                errors << propertyCacheBuilder.errors;
-                break;
+            // If the object has no type, then it's probably a nested object definition as part
+            // of a group property.
+            const bool objectHasType = !parsedQML->jsGenerator.strings.at(obj->inheritedTypeNameIndex).isEmpty();
+            if (objectHasType) {
+                if (!propertyCacheBuilder.create(obj, &propertyCache, &vmeMetaObjectData)) {
+                    errors << propertyCacheBuilder.errors;
+                    break;
+                }
             }
 
-            Q_ASSERT(propertyCache);
-
             m_compiledData->datas << vmeMetaObjectData;
-            propertyCache->addref();
+            if (propertyCache)
+                propertyCache->addref();
             m_compiledData->propertyCaches << propertyCache;
         }