diff options
Diffstat (limited to 'src/declarative/qml/qmlcompiler.cpp')
-rw-r--r-- | src/declarative/qml/qmlcompiler.cpp | 2815 |
1 files changed, 2815 insertions, 0 deletions
diff --git a/src/declarative/qml/qmlcompiler.cpp b/src/declarative/qml/qmlcompiler.cpp new file mode 100644 index 0000000..10150de --- /dev/null +++ b/src/declarative/qml/qmlcompiler.cpp @@ -0,0 +1,2815 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlcompiler_p.h" + +#include "qmlcompositetypedata_p.h" +#include "qmlparser_p.h" +#include "qmlscriptparser_p.h" +#include "qmlpropertyvaluesource.h" +#include "qmlcomponent.h" +#include "qmetaobjectbuilder_p.h" +#include "qmlstringconverters_p.h" +#include "qmlengine_p.h" +#include "qmlengine.h" +#include "qmlcontext.h" +#include "qmlmetatype.h" +#include "qmlcustomparser_p_p.h" +#include "qmlcontext_p.h" +#include "qmlcomponent_p.h" +#include "parser/qmljsast_p.h" +#include "qmlvmemetaobject_p.h" +#include "qmlexpression_p.h" +#include "qmlmetaproperty_p.h" +#include "qmlrewrite_p.h" +#include "qmlscriptstring.h" +#include "qmlglobal_p.h" +#include "qmlscriptparser_p.h" +#include "qmlbinding.h" +#include "qmlcompiledbindings_p.h" + +#include <qfxperf_p_p.h> + +#include <QCoreApplication> +#include <QColor> +#include <QDebug> +#include <QPointF> +#include <QSizeF> +#include <QRectF> +#include <QAtomicInt> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +DEFINE_BOOL_CONFIG_OPTION(compilerDump, QML_COMPILER_DUMP); +DEFINE_BOOL_CONFIG_OPTION(compilerStatDump, QML_COMPILER_STATISTICS_DUMP); +DEFINE_BOOL_CONFIG_OPTION(qmlExperimental, QML_EXPERIMENTAL); + +using namespace QmlParser; + +/*! + Instantiate a new QmlCompiler. +*/ +QmlCompiler::QmlCompiler() +: output(0), engine(0), unitRoot(0), unit(0) +{ +} + +/*! + Returns true if the last call to compile() caused errors. + + \sa errors() +*/ +bool QmlCompiler::isError() const +{ + return !exceptions.isEmpty(); +} + +/*! + Return the list of errors from the last call to compile(), or an empty list + if there were no errors. +*/ +QList<QmlError> QmlCompiler::errors() const +{ + return exceptions; +} + +/*! + Returns true if \a val is a legal object id, false otherwise. + + Legal ids must start with a lower-case letter or underscore, and contain only + letters, numbers and underscores. +*/ +bool QmlCompiler::isValidId(const QString &val) +{ + if (val.isEmpty()) + return false; + + // TODO this will be enforced and return false + if (val.at(0).isLetter() && !val.at(0).isLower()) { + //return false; + qWarning().nospace() << "id '" << val << "' is invalid: ids cannot start with uppercase letters. This will be enforced in an upcoming version of QML."; + } + + QChar u(QLatin1Char('_')); + for (int ii = 0; ii < val.count(); ++ii) + if (val.at(ii) != u && + ((ii == 0 && !val.at(ii).isLetter()) || + (ii != 0 && !val.at(ii).isLetterOrNumber())) ) + return false; + + return true; +} + +/*! + Returns true if \a name refers to an attached property, false otherwise. + + Attached property names are those that start with a capital letter. +*/ +bool QmlCompiler::isAttachedPropertyName(const QByteArray &name) +{ + return !name.isEmpty() && name.at(0) >= 'A' && name.at(0) <= 'Z'; +} + +/*! + Returns true if \a name refers to a signal property, false otherwise. + + Signal property names are those that start with "on", followed by a capital + letter. +*/ +bool QmlCompiler::isSignalPropertyName(const QByteArray &name) +{ + return name.length() >= 3 && name.startsWith("on") && + 'A' <= name.at(2) && 'Z' >= name.at(2); +} + +/*! + \macro COMPILE_EXCEPTION + \internal + Inserts an error into the QmlCompiler error list, and returns false + (failure). + + \a token is used to source the error line and column, and \a desc is the + error itself. \a desc can be an expression that can be piped into QDebug. + + For example: + + \code + COMPILE_EXCEPTION(property, QCoreApplication::translate("QmlCompiler","Error for property \"%1\"").arg(QString::fromUtf8(property->name))); + \endcode +*/ +#define COMPILE_EXCEPTION(token, desc) \ + { \ + QString exceptionDescription; \ + QmlError error; \ + error.setUrl(output->url); \ + error.setLine((token)->location.start.line); \ + error.setColumn((token)->location.start.column); \ + error.setDescription(desc.trimmed()); \ + exceptions << error; \ + return false; \ + } + +/*! + \macro COMPILE_CHECK + \internal + Returns false if \a is false, otherwise does nothing. +*/ +#define COMPILE_CHECK(a) \ + { \ + if (!a) return false; \ + } + +/*! + Returns true if literal \a v can be assigned to property \a prop, otherwise + false. + + This test corresponds to action taken by genLiteralAssignment(). Any change + made here, must have a corresponding action in genLiteralAssigment(). +*/ +bool QmlCompiler::testLiteralAssignment(const QMetaProperty &prop, + QmlParser::Value *v) +{ + QString string = v->value.asScript(); + + if (!prop.isWritable()) + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: \"%1\" is a read-only property").arg(QString::fromUtf8(prop.name()))); + + if (prop.isEnumType()) { + int value; + if (prop.isFlagType()) { + value = prop.enumerator().keysToValue(string.toUtf8().constData()); + } else + value = prop.enumerator().keyToValue(string.toUtf8().constData()); + if (value == -1) + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: unknown enumeration")); + return true; + } + int type = prop.userType(); + switch(type) { + case -1: + break; + case QVariant::String: + if (!v->value.isString()) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: string expected")); + break; + case QVariant::Url: + if (!v->value.isString()) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: url expected")); + break; + case QVariant::UInt: + { + bool ok; + string.toUInt(&ok); + if (!v->value.isNumber() || !ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: unsigned int expected")); + } + break; + case QVariant::Int: + { + bool ok; + string.toInt(&ok); + if (!v->value.isNumber() || !ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: int expected")); + } + break; + case QMetaType::Float: + { + bool ok; + string.toFloat(&ok); + if (!v->value.isNumber() || !ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: float expected")); + } + break; + case QVariant::Double: + { + bool ok; + string.toDouble(&ok); + if (!v->value.isNumber() || !ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: double expected")); + } + break; + case QVariant::Color: + { + bool ok; + QmlStringConverters::colorFromString(string, &ok); + if (!ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: color expected")); + } + break; + case QVariant::Date: + { + bool ok; + QmlStringConverters::dateFromString(string, &ok); + if (!ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: date expected")); + } + break; + case QVariant::Time: + { + bool ok; + QmlStringConverters::timeFromString(string, &ok); + if (!ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: time expected")); + } + break; + case QVariant::DateTime: + { + bool ok; + QmlStringConverters::dateTimeFromString(string, &ok); + if (!ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: datetime expected")); + } + break; + case QVariant::Point: + case QVariant::PointF: + { + bool ok; + QPointF point = QmlStringConverters::pointFFromString(string, &ok); + if (!ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: point expected")); + } + break; + case QVariant::Size: + case QVariant::SizeF: + { + bool ok; + QSizeF size = QmlStringConverters::sizeFFromString(string, &ok); + if (!ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: size expected")); + } + break; + case QVariant::Rect: + case QVariant::RectF: + { + bool ok; + QRectF rect = QmlStringConverters::rectFFromString(string, &ok); + if (!ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: rect expected")); + } + break; + case QVariant::Bool: + { + if (!v->value.isBoolean()) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: boolean expected")); + } + break; + case QVariant::Vector3D: + { + bool ok; + QmlStringConverters::vector3DFromString(string, &ok); + if (!ok) COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: 3D vector expected")); + } + break; + default: + { + int t = prop.userType(); + QmlMetaType::StringConverter converter = + QmlMetaType::customStringConverter(t); + if (!converter) + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: unsupported type \"%1\"").arg(QString::fromLatin1(QVariant::typeToName(prop.type())))); + } + break; + } + return true; +} + +/*! + Generate a store instruction for assigning literal \a v to property \a prop. + + Any literal assignment that is approved in testLiteralAssignment() must have + a corresponding action in this method. +*/ +void QmlCompiler::genLiteralAssignment(const QMetaProperty &prop, + QmlParser::Value *v) +{ + QString string = v->value.asScript(); + + QmlInstruction instr; + instr.line = v->location.start.line; + if (prop.isEnumType()) { + int value; + if (prop.isFlagType()) { + value = prop.enumerator().keysToValue(string.toUtf8().constData()); + } else + value = prop.enumerator().keyToValue(string.toUtf8().constData()); + + instr.type = QmlInstruction::StoreInteger; + instr.storeInteger.propertyIndex = prop.propertyIndex(); + instr.storeInteger.value = value; + output->bytecode << instr; + return; + } + + int type = prop.userType(); + switch(type) { + case -1: + { + instr.type = QmlInstruction::StoreVariant; + instr.storeString.propertyIndex = prop.propertyIndex(); + instr.storeString.value = output->indexForString(string); + } + break; + case QVariant::String: + { + instr.type = QmlInstruction::StoreString; + instr.storeString.propertyIndex = prop.propertyIndex(); + instr.storeString.value = output->indexForString(string); + } + break; + case QVariant::Url: + { + instr.type = QmlInstruction::StoreUrl; + QUrl u = string.isEmpty() ? QUrl() : output->url.resolved(QUrl(string)); + instr.storeUrl.propertyIndex = prop.propertyIndex(); + instr.storeUrl.value = output->indexForUrl(u); + } + break; + case QVariant::UInt: + { + instr.type = QmlInstruction::StoreInteger; + instr.storeInteger.propertyIndex = prop.propertyIndex(); + instr.storeInteger.value = string.toUInt(); + } + break; + case QVariant::Int: + { + instr.type = QmlInstruction::StoreInteger; + instr.storeInteger.propertyIndex = prop.propertyIndex(); + instr.storeInteger.value = string.toInt(); + } + break; + case QMetaType::Float: + { + instr.type = QmlInstruction::StoreFloat; + instr.storeFloat.propertyIndex = prop.propertyIndex(); + instr.storeFloat.value = string.toFloat(); + } + break; + case QVariant::Double: + { + instr.type = QmlInstruction::StoreDouble; + instr.storeDouble.propertyIndex = prop.propertyIndex(); + instr.storeDouble.value = string.toDouble(); + } + break; + case QVariant::Color: + { + QColor c = QmlStringConverters::colorFromString(string); + instr.type = QmlInstruction::StoreColor; + instr.storeColor.propertyIndex = prop.propertyIndex(); + instr.storeColor.value = c.rgba(); + } + break; + case QVariant::Date: + { + QDate d = QmlStringConverters::dateFromString(string); + instr.type = QmlInstruction::StoreDate; + instr.storeDate.propertyIndex = prop.propertyIndex(); + instr.storeDate.value = d.toJulianDay(); + } + break; + case QVariant::Time: + { + QTime time = QmlStringConverters::timeFromString(string); + int data[] = { time.hour(), time.minute(), + time.second(), time.msec() }; + int index = output->indexForInt(data, 4); + instr.type = QmlInstruction::StoreTime; + instr.storeTime.propertyIndex = prop.propertyIndex(); + instr.storeTime.valueIndex = index; + } + break; + case QVariant::DateTime: + { + QDateTime dateTime = QmlStringConverters::dateTimeFromString(string); + int data[] = { dateTime.date().toJulianDay(), + dateTime.time().hour(), + dateTime.time().minute(), + dateTime.time().second(), + dateTime.time().msec() }; + int index = output->indexForInt(data, 5); + instr.type = QmlInstruction::StoreDateTime; + instr.storeDateTime.propertyIndex = prop.propertyIndex(); + instr.storeDateTime.valueIndex = index; + } + break; + case QVariant::Point: + case QVariant::PointF: + { + bool ok; + QPointF point = + QmlStringConverters::pointFFromString(string, &ok); + float data[] = { point.x(), point.y() }; + int index = output->indexForFloat(data, 2); + if (type == QVariant::PointF) + instr.type = QmlInstruction::StorePointF; + else + instr.type = QmlInstruction::StorePoint; + instr.storeRealPair.propertyIndex = prop.propertyIndex(); + instr.storeRealPair.valueIndex = index; + } + break; + case QVariant::Size: + case QVariant::SizeF: + { + bool ok; + QSizeF size = QmlStringConverters::sizeFFromString(string, &ok); + float data[] = { size.width(), size.height() }; + int index = output->indexForFloat(data, 2); + if (type == QVariant::SizeF) + instr.type = QmlInstruction::StoreSizeF; + else + instr.type = QmlInstruction::StoreSize; + instr.storeRealPair.propertyIndex = prop.propertyIndex(); + instr.storeRealPair.valueIndex = index; + } + break; + case QVariant::Rect: + case QVariant::RectF: + { + bool ok; + QRectF rect = QmlStringConverters::rectFFromString(string, &ok); + float data[] = { rect.x(), rect.y(), + rect.width(), rect.height() }; + int index = output->indexForFloat(data, 4); + if (type == QVariant::RectF) + instr.type = QmlInstruction::StoreRectF; + else + instr.type = QmlInstruction::StoreRect; + instr.storeRect.propertyIndex = prop.propertyIndex(); + instr.storeRect.valueIndex = index; + } + break; + case QVariant::Bool: + { + bool b = v->value.asBoolean(); + instr.type = QmlInstruction::StoreBool; + instr.storeBool.propertyIndex = prop.propertyIndex(); + instr.storeBool.value = b; + } + break; + case QVariant::Vector3D: + { + bool ok; + QVector3D vector = + QmlStringConverters::vector3DFromString(string, &ok); + float data[] = { vector.x(), vector.y(), vector.z() }; + int index = output->indexForFloat(data, 3); + instr.type = QmlInstruction::StoreVector3D; + instr.storeRealPair.propertyIndex = prop.propertyIndex(); + instr.storeRealPair.valueIndex = index; + } + break; + default: + { + int t = prop.userType(); + int index = output->customTypeData.count(); + instr.type = QmlInstruction::AssignCustomType; + instr.assignCustomType.propertyIndex = prop.propertyIndex(); + instr.assignCustomType.valueIndex = index; + + QmlCompiledData::CustomTypeData data; + data.index = output->indexForString(string); + data.type = t; + output->customTypeData << data; + } + break; + } + output->bytecode << instr; +} + +/*! + Resets data by clearing the lists that the QmlCompiler modifies. +*/ +void QmlCompiler::reset(QmlCompiledData *data) +{ + data->types.clear(); + data->primitives.clear(); + data->floatData.clear(); + data->intData.clear(); + data->customTypeData.clear(); + data->datas.clear(); + data->bytecode.clear(); +} + +/*! + Compile \a unit, and store the output in \a out. \a engine is the QmlEngine + with which the QmlCompiledData will be associated. + + Returns true on success, false on failure. On failure, the compile errors + are available from errors(). + + If the environment variant QML_COMPILER_DUMP is set + (eg. QML_COMPILER_DUMP=1) the compiled instructions will be dumped to stderr + on a successful compiler. +*/ +bool QmlCompiler::compile(QmlEngine *engine, + QmlCompositeTypeData *unit, + QmlCompiledData *out) +{ +#ifdef Q_ENABLE_PERFORMANCE_LOG + QmlPerfTimer<QmlPerf::Compilation> pc; +#endif + exceptions.clear(); + + Q_ASSERT(out); + reset(out); + + output = out; + + // Compile types + for (int ii = 0; ii < unit->types.count(); ++ii) { + QmlCompositeTypeData::TypeReference &tref = unit->types[ii]; + QmlCompiledData::TypeReference ref; + QmlScriptParser::TypeReference *parserRef = unit->data.referencedTypes().at(ii); + if (tref.type) + ref.type = tref.type; + else if (tref.unit) { + ref.component = tref.unit->toComponent(engine); + + if (ref.component->isError()) { + QmlError error; + error.setUrl(output->url); + error.setDescription(QLatin1String("Unable to create type ") + + parserRef->name); + if (!parserRef->refObjects.isEmpty()) { + QmlParser::Object *parserObject = parserRef->refObjects.first(); + error.setLine(parserObject->location.start.line); + error.setColumn(parserObject->location.start.column); + } + + exceptions << error; + exceptions << ref.component->errors(); + reset(out); + return false; + } + ref.ref = tref.unit; + ref.ref->addref(); + } + ref.className = parserRef->name.toUtf8(); + out->types << ref; + } + + Object *root = unit->data.tree(); + Q_ASSERT(root); + + this->engine = engine; + this->unit = unit; + this->unitRoot = root; + compileTree(root); + + if (!isError()) { + if (compilerDump()) + out->dumpInstructions(); + if (compilerStatDump()) + dumpStats(); + } else { + reset(out); + } + + compileState = ComponentCompileState(); + savedCompileStates.clear(); + output = 0; + this->engine = 0; + this->unit = 0; + this->unitRoot = 0; + + return !isError(); +} + +void QmlCompiler::compileTree(Object *tree) +{ + compileState.root = tree; + + if (!buildObject(tree, BindingContext()) || !completeComponentBuild()) + return; + + QmlInstruction init; + init.type = QmlInstruction::Init; + init.line = 0; + init.init.bindingsSize = compileState.bindings.count(); + init.init.parserStatusSize = compileState.parserStatusCount; + init.init.contextCache = genContextCache(); + if (compileState.compiledBindingData.isEmpty()) + init.init.compiledBinding = -1; + else + init.init.compiledBinding = output->indexForByteArray(compileState.compiledBindingData); + output->bytecode << init; + + genObject(tree); + + QmlInstruction def; + init.line = 0; + def.type = QmlInstruction::SetDefault; + output->bytecode << def; + + output->imports = unit->imports; + output->importCache = output->imports.cache(engine); + + Q_ASSERT(tree->metatype); + + if (tree->metadata.isEmpty()) { + output->root = tree->metatype; + } else { + static_cast<QMetaObject &>(output->rootData) = *tree->metaObject(); + output->root = &output->rootData; + } + if (!tree->metadata.isEmpty()) + QmlEnginePrivate::get(engine)->registerCompositeType(output); +} + +static bool ValuePtrLessThan(const Value *t1, const Value *t2) +{ + return t1->location.start.line < t2->location.start.line || + (t1->location.start.line == t2->location.start.line && + t1->location.start.column < t2->location.start.column); +} + +bool QmlCompiler::buildObject(Object *obj, const BindingContext &ctxt) +{ + componentStat.objects++; + + Q_ASSERT (obj->type != -1); + const QmlCompiledData::TypeReference &tr = + output->types.at(obj->type); + obj->metatype = tr.metaObject(); + + if (tr.component) + obj->url = tr.component->url(); + if (tr.type) + obj->typeName = tr.type->qmlTypeName(); + obj->className = tr.className; + + // This object is a "Component" element + if (tr.type && obj->metatype == &QmlComponent::staticMetaObject) { + COMPILE_CHECK(buildComponent(obj, ctxt)); + return true; + } + + // Build any script blocks for this type + for (int ii = 0; ii < obj->scriptBlockObjects.count(); ++ii) + COMPILE_CHECK(buildScript(obj, obj->scriptBlockObjects.at(ii))); + + // Object instantiations reset the binding context + BindingContext objCtxt(obj); + + // Create the synthesized meta object, ignoring aliases + COMPILE_CHECK(mergeDynamicMetaProperties(obj)); + COMPILE_CHECK(buildDynamicMeta(obj, IgnoreAliases)); + + // Find the native type and check for the QmlParserStatus interface + QmlType *type = toQmlType(obj); + Q_ASSERT(type); + obj->parserStatusCast = type->parserStatusCast(); + if (obj->parserStatusCast != -1) + compileState.parserStatusCount++; + + // Check if this is a custom parser type. Custom parser types allow + // assignments to non-existant properties. These assignments are then + // compiled by the type. + bool isCustomParser = output->types.at(obj->type).type && + output->types.at(obj->type).type->customParser() != 0; + QList<QmlCustomParserProperty> customProps; + + // Fetch the list of deferred properties + QStringList deferredList = deferredProperties(obj); + + // Must do id property first. This is to ensure that the id given to any + // id reference created matches the order in which the objects are + // instantiated + foreach(Property *prop, obj->properties) { + if (prop->name == "id") { + COMPILE_CHECK(buildProperty(prop, obj, objCtxt)); + break; + } + } + + // Merge + Property *defaultProperty = 0; + Property *skipProperty = 0; + if (obj->defaultProperty) { + const QMetaObject *metaObject = obj->metaObject(); + Q_ASSERT(metaObject); + QMetaProperty p = QmlMetaType::defaultProperty(metaObject); + if (p.name()) { + Property *explicitProperty = obj->getProperty(p.name(), false); + if (explicitProperty && !explicitProperty->value) { + skipProperty = explicitProperty; + + defaultProperty = new Property; + defaultProperty->parent = obj; + defaultProperty->isDefault = true; + defaultProperty->location = obj->defaultProperty->location; + defaultProperty->listValueRange = obj->defaultProperty->listValueRange; + defaultProperty->listCommaPositions = obj->defaultProperty->listCommaPositions; + + defaultProperty->values = obj->defaultProperty->values; + defaultProperty->values += explicitProperty->values; + foreach(Value *value, defaultProperty->values) + value->addref(); + qSort(defaultProperty->values.begin(), defaultProperty->values.end(), ValuePtrLessThan); + + } else { + defaultProperty = obj->defaultProperty; + defaultProperty->addref(); + } + } else { + defaultProperty = obj->defaultProperty; + defaultProperty->addref(); + } + } + + // Build all explicit properties specified + foreach(Property *prop, obj->properties) { + + if (prop == skipProperty) + continue; + if (prop->name == "id") + continue; + + bool canDefer = false; + if (isCustomParser) { + if (doesPropertyExist(prop, obj)) { + int ids = compileState.ids.count(); + COMPILE_CHECK(buildProperty(prop, obj, objCtxt)); + canDefer = ids == compileState.ids.count(); + } else { + customProps << QmlCustomParserNodePrivate::fromProperty(prop); + } + } else { + if (isSignalPropertyName(prop->name)) { + COMPILE_CHECK(buildSignal(prop,obj,objCtxt)); + } else { + int ids = compileState.ids.count(); + COMPILE_CHECK(buildProperty(prop, obj, objCtxt)); + canDefer = ids == compileState.ids.count(); + } + } + + if (canDefer && !deferredList.isEmpty() && + deferredList.contains(QString::fromUtf8(prop->name))) + prop->isDeferred = true; + + } + + // Build the default property + if (defaultProperty) { + Property *prop = defaultProperty; + + bool canDefer = false; + if (isCustomParser) { + if (doesPropertyExist(prop, obj)) { + int ids = compileState.ids.count(); + COMPILE_CHECK(buildProperty(prop, obj, objCtxt)); + canDefer = ids == compileState.ids.count(); + } else { + customProps << QmlCustomParserNodePrivate::fromProperty(prop); + } + } else { + int ids = compileState.ids.count(); + COMPILE_CHECK(buildProperty(prop, obj, objCtxt)); + canDefer = ids == compileState.ids.count(); + } + + if (canDefer && !deferredList.isEmpty() && + deferredList.contains(QString::fromUtf8(prop->name))) + prop->isDeferred = true; + } + + if (defaultProperty) + defaultProperty->release(); + + // Compile custom parser parts + if (isCustomParser && !customProps.isEmpty()) { + QmlCustomParser *cp = output->types.at(obj->type).type->customParser(); + cp->clearErrors(); + obj->custom = cp->compile(customProps); + foreach (QmlError err, cp->errors()) { + err.setUrl(output->url); + exceptions << err; + } + } + + return true; +} + +void QmlCompiler::genObject(QmlParser::Object *obj) +{ + const QmlCompiledData::TypeReference &tr = + output->types.at(obj->type); + if (tr.type && obj->metatype == &QmlComponent::staticMetaObject) { + genComponent(obj); + return; + } + + // Create the object + QmlInstruction create; + create.type = QmlInstruction::CreateObject; + create.line = obj->location.start.line; + create.create.column = obj->location.start.column; + create.create.data = -1; + if (!obj->custom.isEmpty()) + create.create.data = output->indexForByteArray(obj->custom); + create.create.type = obj->type; + if (!output->types.at(create.create.type).type && + !obj->bindingBitmask.isEmpty()) { + Q_ASSERT(obj->bindingBitmask.size() % 4 == 0); + create.create.bindingBits = + output->indexForByteArray(obj->bindingBitmask); + } else { + create.create.bindingBits = -1; + } + output->bytecode << create; + + // Setup the synthesized meta object if necessary + if (!obj->metadata.isEmpty()) { + QmlInstruction meta; + meta.type = QmlInstruction::StoreMetaObject; + meta.line = 0; + meta.storeMeta.data = output->indexForByteArray(obj->metadata); + meta.storeMeta.aliasData = output->indexForByteArray(obj->synthdata); + meta.storeMeta.propertyCache = output->propertyCaches.count(); + // ### Surely the creation of this property cache could be more efficient + QmlPropertyCache *propertyCache = 0; + if (tr.component && QmlComponentPrivate::get(tr.component)->cc->rootPropertyCache) { + propertyCache = QmlComponentPrivate::get(tr.component)->cc->rootPropertyCache->copy(); + } else { + propertyCache = QmlPropertyCache::create(engine, obj->metaObject()->superClass()); + } + propertyCache->append(engine, obj->metaObject(), QmlPropertyCache::Data::NoFlags, + QmlPropertyCache::Data::IsVMEFunction); + if (obj == unitRoot) { + propertyCache->addref(); + output->rootPropertyCache = propertyCache; + } + output->propertyCaches << propertyCache; + output->bytecode << meta; + } + + // Set the object id + if (!obj->id.isEmpty()) { + QmlInstruction id; + id.type = QmlInstruction::SetId; + id.line = 0; + id.setId.value = output->indexForString(obj->id); + id.setId.index = obj->idIndex; + output->bytecode << id; + } + + // Set any script blocks + for (int ii = 0; ii < obj->scripts.count(); ++ii) { + QmlInstruction script; + script.type = QmlInstruction::StoreScript; + script.line = 0; // ### + int idx = output->scripts.count(); + output->scripts << obj->scripts.at(ii); + script.storeScript.value = idx; + output->bytecode << script; + } + + // Begin the class + if (obj->parserStatusCast != -1) { + QmlInstruction begin; + begin.type = QmlInstruction::BeginObject; + begin.begin.castValue = obj->parserStatusCast; + begin.line = obj->location.start.line; + output->bytecode << begin; + } + + genObjectBody(obj); +} + +void QmlCompiler::genObjectBody(QmlParser::Object *obj) +{ + typedef QPair<Property *, int> PropPair; + foreach(const PropPair &prop, obj->scriptStringProperties) { + QmlInstruction ss; + ss.type = QmlInstruction::StoreScriptString; + ss.storeScriptString.propertyIndex = prop.first->index; + ss.storeScriptString.value = + output->indexForString(prop.first->values.at(0)->value.asScript()); + ss.storeScriptString.scope = prop.second; + output->bytecode << ss; + } + + bool seenDefer = false; + foreach(Property *prop, obj->valueProperties) { + if (prop->isDeferred) { + seenDefer = true; + continue; + } + genValueProperty(prop, obj); + } + if (seenDefer) { + QmlInstruction defer; + defer.type = QmlInstruction::Defer; + defer.line = 0; + defer.defer.deferCount = 0; + int deferIdx = output->bytecode.count(); + output->bytecode << defer; + + QmlInstruction init; + init.type = QmlInstruction::Init; + init.init.bindingsSize = compileState.bindings.count(); // XXX - bigger than necessary + init.init.parserStatusSize = compileState.parserStatusCount; // XXX - bigger than necessary + init.init.contextCache = -1; + init.init.compiledBinding = -1; + output->bytecode << init; + + foreach(Property *prop, obj->valueProperties) { + if (!prop->isDeferred) + continue; + genValueProperty(prop, obj); + } + + output->bytecode[deferIdx].defer.deferCount = + output->bytecode.count() - deferIdx - 1; + } + + foreach(Property *prop, obj->signalProperties) { + + QmlParser::Value *v = prop->values.at(0); + + if (v->type == Value::SignalObject) { + + genObject(v->object); + + QmlInstruction assign; + assign.type = QmlInstruction::AssignSignalObject; + assign.line = v->location.start.line; + assign.assignSignalObject.signal = + output->indexForByteArray(prop->name); + output->bytecode << assign; + + } else if (v->type == Value::SignalExpression) { + + QmlInstruction store; + store.type = QmlInstruction::StoreSignal; + store.line = v->location.start.line; + store.storeSignal.signalIndex = prop->index; + store.storeSignal.value = + output->indexForString(v->value.asScript().trimmed()); + output->bytecode << store; + + } + + } + + foreach(Property *prop, obj->attachedProperties) { + QmlInstruction fetch; + fetch.type = QmlInstruction::FetchAttached; + fetch.line = prop->location.start.line; + fetch.fetchAttached.id = prop->index; + output->bytecode << fetch; + + genObjectBody(prop->value); + + QmlInstruction pop; + pop.type = QmlInstruction::PopFetchedObject; + pop.line = prop->location.start.line; + output->bytecode << pop; + } + + foreach(Property *prop, obj->groupedProperties) { + QmlInstruction fetch; + fetch.type = QmlInstruction::FetchObject; + fetch.fetch.property = prop->index; + fetch.line = prop->location.start.line; + output->bytecode << fetch; + + genObjectBody(prop->value); + + QmlInstruction pop; + pop.type = QmlInstruction::PopFetchedObject; + pop.line = prop->location.start.line; + output->bytecode << pop; + } + + foreach(Property *prop, obj->valueTypeProperties) { + QmlInstruction fetch; + fetch.type = QmlInstruction::FetchValueType; + fetch.fetchValue.property = prop->index; + fetch.fetchValue.type = prop->type; + fetch.line = prop->location.start.line; + + output->bytecode << fetch; + + foreach(Property *vprop, prop->value->valueProperties) { + genPropertyAssignment(vprop, prop->value, prop); + } + + QmlInstruction pop; + pop.type = QmlInstruction::PopValueType; + pop.fetchValue.property = prop->index; + pop.fetchValue.type = prop->type; + pop.line = prop->location.start.line; + output->bytecode << pop; + } +} + +void QmlCompiler::genComponent(QmlParser::Object *obj) +{ + QmlParser::Object *root = obj->defaultProperty->values.at(0)->object; + Q_ASSERT(root); + + QmlInstruction create; + create.type = QmlInstruction::CreateComponent; + create.line = root->location.start.line; + create.createComponent.column = root->location.start.column; + create.createComponent.endLine = root->location.end.line; + output->bytecode << create; + int count = output->bytecode.count(); + + ComponentCompileState oldCompileState = compileState; + compileState = componentState(root); + + QmlInstruction init; + init.type = QmlInstruction::Init; + init.init.bindingsSize = compileState.bindings.count(); + init.init.parserStatusSize = compileState.parserStatusCount; + init.init.contextCache = genContextCache(); + if (compileState.compiledBindingData.isEmpty()) + init.init.compiledBinding = -1; + else + init.init.compiledBinding = output->indexForByteArray(compileState.compiledBindingData); + init.line = obj->location.start.line; + output->bytecode << init; + + genObject(root); + + QmlInstruction def; + init.line = 0; + def.type = QmlInstruction::SetDefault; + output->bytecode << def; + + output->bytecode[count - 1].createComponent.count = + output->bytecode.count() - count; + + compileState = oldCompileState; + + if (!obj->id.isEmpty()) { + QmlInstruction id; + id.type = QmlInstruction::SetId; + id.line = 0; + id.setId.value = output->indexForString(obj->id); + id.setId.index = obj->idIndex; + output->bytecode << id; + } +} + +bool QmlCompiler::buildComponent(QmlParser::Object *obj, + const BindingContext &ctxt) +{ + // The special "Component" element can only have the id property and a + // default property, that actually defines the component's tree + + // Find, check and set the "id" property (if any) + Property *idProp = 0; + if (obj->properties.count() > 1 || + (obj->properties.count() == 1 && obj->properties.begin().key() != "id")) + COMPILE_EXCEPTION(*obj->properties.begin(), QCoreApplication::translate("QmlCompiler","Invalid component specification")); + + if (!obj->scriptBlockObjects.isEmpty()) + COMPILE_EXCEPTION(obj->scriptBlockObjects.first(), QCoreApplication::translate("QmlCompiler","Invalid component specification")); + + if (obj->properties.count()) + idProp = *obj->properties.begin(); + + if (idProp && (idProp->value || idProp->values.count() > 1 || !isValidId(idProp->values.first()->primitive()))) + COMPILE_EXCEPTION(idProp, QCoreApplication::translate("QmlCompiler","Invalid component id specification")); + + if (idProp) { + QString idVal = idProp->values.first()->primitive(); + + if (compileState.ids.contains(idVal)) + COMPILE_EXCEPTION(idProp, QCoreApplication::translate("QmlCompiler","id is not unique")); + + obj->id = idVal; + addId(idVal, obj); + } + + // Check the Component tree is well formed + if (obj->defaultProperty && + (obj->defaultProperty->value || obj->defaultProperty->values.count() > 1 || + (obj->defaultProperty->values.count() == 1 && !obj->defaultProperty->values.first()->object))) + COMPILE_EXCEPTION(obj, QCoreApplication::translate("QmlCompiler","Invalid component body specification")); + + Object *root = 0; + if (obj->defaultProperty && obj->defaultProperty->values.count()) + root = obj->defaultProperty->values.first()->object; + + if (!root) + COMPILE_EXCEPTION(obj, QCoreApplication::translate("QmlCompiler","Cannot create empty component specification")); + + // Build the component tree + COMPILE_CHECK(buildComponentFromRoot(root, ctxt)); + + return true; +} + +bool QmlCompiler::buildScript(QmlParser::Object *obj, QmlParser::Object *script) +{ + Object::ScriptBlock scriptBlock; + + if (script->properties.count() == 1 && + script->properties.begin().key() == QByteArray("source")) { + + Property *source = *script->properties.begin(); + if (script->defaultProperty) + COMPILE_EXCEPTION(source, QCoreApplication::translate("QmlCompiler","Invalid Script block. Specify either the source property or inline script")); + + if (source->value || source->values.count() != 1 || + source->values.at(0)->object || !source->values.at(0)->value.isStringList()) + COMPILE_EXCEPTION(source, QCoreApplication::translate("QmlCompiler","Invalid Script source value")); + + QStringList sources = source->values.at(0)->value.asStringList(); + + for (int jj = 0; jj < sources.count(); ++jj) { + QString sourceUrl = output->url.resolved(QUrl(sources.at(jj))).toString(); + QString scriptCode; + int lineNumber = 1; + + for (int ii = 0; ii < unit->resources.count(); ++ii) { + if (unit->resources.at(ii)->url == sourceUrl) { + scriptCode = QString::fromUtf8(unit->resources.at(ii)->data); + break; + } + } + + if (!scriptCode.isEmpty()) { + scriptBlock.codes.append(scriptCode); + scriptBlock.files.append(sourceUrl); + scriptBlock.lineNumbers.append(lineNumber); + } + } + + } else if (!script->properties.isEmpty()) { + COMPILE_EXCEPTION(*script->properties.begin(), QCoreApplication::translate("QmlCompiler","Properties cannot be set on Script block")); + } else if (script->defaultProperty) { + + QString scriptCode; + int lineNumber = 1; + QString sourceUrl = output->url.toString(); + + QmlParser::Location currentLocation; + + for (int ii = 0; ii < script->defaultProperty->values.count(); ++ii) { + Value *v = script->defaultProperty->values.at(ii); + if (lineNumber == 1) + lineNumber = v->location.start.line; + if (v->object || !v->value.isString()) + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid Script block")); + + if (ii == 0) { + currentLocation = v->location.start; + scriptCode.append(QString(currentLocation.column, QLatin1Char(' '))); + } + + while (currentLocation.line < v->location.start.line) { + scriptCode.append(QLatin1Char('\n')); + currentLocation.line++; + currentLocation.column = 0; + } + + scriptCode.append(QString(v->location.start.column - currentLocation.column, QLatin1Char(' '))); + + scriptCode += v->value.asString(); + currentLocation = v->location.end; + currentLocation.column++; + } + + if (!scriptCode.isEmpty()) { + scriptBlock.codes.append(scriptCode); + scriptBlock.files.append(sourceUrl); + scriptBlock.lineNumbers.append(lineNumber); + } + } + + if (!scriptBlock.codes.isEmpty()) + obj->scripts << scriptBlock; + + return true; +} + +bool QmlCompiler::buildComponentFromRoot(QmlParser::Object *obj, + const BindingContext &ctxt) +{ + ComponentCompileState oldComponentCompileState = compileState; + ComponentStat oldComponentStat = componentStat; + + compileState = ComponentCompileState(); + compileState.root = obj; + + componentStat = ComponentStat(); + componentStat.lineNumber = obj->location.start.line; + + if (obj) + COMPILE_CHECK(buildObject(obj, ctxt)); + + COMPILE_CHECK(completeComponentBuild()); + + compileState = oldComponentCompileState; + componentStat = oldComponentStat; + + return true; +} + + +// Build a sub-object. A sub-object is one that was not created directly by +// QML - such as a grouped property object, or an attached object. Sub-object's +// can't have an id, involve a custom parser, have attached properties etc. +bool QmlCompiler::buildSubObject(Object *obj, const BindingContext &ctxt) +{ + Q_ASSERT(obj->metatype); + Q_ASSERT(!obj->defaultProperty); + Q_ASSERT(ctxt.isSubContext()); // sub-objects must always be in a binding + // sub-context + + foreach(Property *prop, obj->properties) { + if (isSignalPropertyName(prop->name)) { + COMPILE_CHECK(buildSignal(prop, obj, ctxt)); + } else { + COMPILE_CHECK(buildProperty(prop, obj, ctxt)); + } + } + + return true; +} + +int QmlCompiler::componentTypeRef() +{ + QmlType *t = QmlMetaType::qmlType("Qt/Component",4,6); + for (int ii = output->types.count() - 1; ii >= 0; --ii) { + if (output->types.at(ii).type == t) + return ii; + } + QmlCompiledData::TypeReference ref; + ref.className = "Component"; + ref.type = t; + output->types << ref; + return output->types.count() - 1; +} + +int QmlCompiler::findSignalByName(const QMetaObject *mo, const QByteArray &name) +{ + int methods = mo->methodCount(); + for (int ii = methods - 1; ii >= 0; --ii) { + QMetaMethod method = mo->method(ii); + QByteArray methodName = method.signature(); + int idx = methodName.indexOf('('); + methodName = methodName.left(idx); + + if (methodName == name) + return ii; + } + return -1; +} + +bool QmlCompiler::buildSignal(QmlParser::Property *prop, QmlParser::Object *obj, + const BindingContext &ctxt) +{ + Q_ASSERT(obj->metaObject()); + Q_ASSERT(!prop->isEmpty()); + + QByteArray name = prop->name; + Q_ASSERT(name.startsWith("on")); + name = name.mid(2); + if(name[0] >= 'A' && name[0] <= 'Z') + name[0] = name[0] - 'A' + 'a'; + + int sigIdx = findSignalByName(obj->metaObject(), name); + + if (sigIdx == -1) { + + // If the "on<Signal>" name doesn't resolve into a signal, try it as a + // property. + COMPILE_CHECK(buildProperty(prop, obj, ctxt)); + + } else { + + if (prop->value || prop->values.count() > 1) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Incorrectly specified signal")); + + prop->index = sigIdx; + obj->addSignalProperty(prop); + + if (prop->values.at(0)->object) { + COMPILE_CHECK(buildObject(prop->values.at(0)->object, ctxt)); + prop->values.at(0)->type = Value::SignalObject; + } else { + prop->values.at(0)->type = Value::SignalExpression; + + QString script = prop->values.at(0)->value.asScript().trimmed(); + if (script.isEmpty()) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Empty signal assignment")); + } + } + + return true; +} + + +/*! + Returns true if (value) property \a prop exists on obj, false otherwise. +*/ +bool QmlCompiler::doesPropertyExist(QmlParser::Property *prop, + QmlParser::Object *obj) +{ + if(isAttachedPropertyName(prop->name) || prop->name == "id") + return true; + + const QMetaObject *mo = obj->metaObject(); + if (mo) { + if (prop->isDefault) { + QMetaProperty p = QmlMetaType::defaultProperty(mo); + return p.name() != 0; + } else { + int idx = mo->indexOfProperty(prop->name.constData()); + return idx != -1; + } + } + + return false; +} + +bool QmlCompiler::buildProperty(QmlParser::Property *prop, + QmlParser::Object *obj, + const BindingContext &ctxt) +{ + if (prop->isEmpty()) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Empty property assignment")); + + const QMetaObject *metaObject = obj->metaObject(); + Q_ASSERT(metaObject); + + if (isAttachedPropertyName(prop->name)) { + // Setup attached property data + + if (ctxt.isSubContext()) { + // Attached properties cannot be used on sub-objects. Sub-objects + // always exist in a binding sub-context, which is what we test + // for here. + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Attached properties cannot be used here")); + } + + QmlType *type = 0; + QmlEnginePrivate::ImportedNamespace *typeNamespace = 0; + QmlEnginePrivate::get(engine)->resolveType(unit->imports, prop->name, + &type, 0, 0, 0, &typeNamespace); + + if (typeNamespace) { + // ### We might need to indicate that this property is a namespace + // for the DOM API + COMPILE_CHECK(buildPropertyInNamespace(typeNamespace, prop, obj, + ctxt)); + return true; + } else if (!type || !type->attachedPropertiesType()) { + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Non-existant attached object")); + } + + if (!prop->value) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Invalid attached object assignment")); + + Q_ASSERT(type->attachedPropertiesFunction()); + prop->index = type->index(); + prop->value->metatype = type->attachedPropertiesType(); + } else { + // Setup regular property data + QMetaProperty p; + + if (prop->isDefault) { + p = QmlMetaType::defaultProperty(metaObject); + + if (p.name()) { + prop->index = p.propertyIndex(); + prop->name = p.name(); + } + + } else { + prop->index = metaObject->indexOfProperty(prop->name.constData()); + + if (prop->index != -1) { + p = metaObject->property(prop->index); + Q_ASSERT(p.name()); + } + } + + // We can't error here as the "id" property does not require a + // successful index resolution + if (p.name()) { + prop->type = p.userType(); + } + } + + if (prop->index != -1) + prop->parent->setBindingBit(prop->index); + + if (!prop->isDefault && prop->name == "id" && !ctxt.isSubContext()) { + + // The magic "id" behavior doesn't apply when "id" is resolved as a + // default property or to sub-objects (which are always in binding + // sub-contexts) + COMPILE_CHECK(buildIdProperty(prop, obj)); + if (prop->type == QVariant::String && + prop->values.at(0)->value.isString()) + COMPILE_CHECK(buildPropertyAssignment(prop, obj, ctxt)); + + } else if (isAttachedPropertyName(prop->name)) { + + COMPILE_CHECK(buildAttachedProperty(prop, obj, ctxt)); + + } else if (prop->index == -1) { + + if (prop->isDefault) { + COMPILE_EXCEPTION(prop->values.first(), QCoreApplication::translate("QmlCompiler","Cannot assign to non-existant default property")); + } else { + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Cannot assign to non-existant property \"%1\"").arg(QString::fromUtf8(prop->name))); + } + + } else if (prop->value) { + + COMPILE_CHECK(buildGroupedProperty(prop, obj, ctxt)); + + } else if (QmlEnginePrivate::get(engine)->isQmlList(prop->type) || + QmlMetaType::isList(prop->type)) { + + COMPILE_CHECK(buildListProperty(prop, obj, ctxt)); + + } else if (prop->type == qMetaTypeId<QmlScriptString>()) { + + COMPILE_CHECK(buildScriptStringProperty(prop, obj, ctxt)); + + } else { + + COMPILE_CHECK(buildPropertyAssignment(prop, obj, ctxt)); + + } + + return true; +} + +bool +QmlCompiler::buildPropertyInNamespace(QmlEnginePrivate::ImportedNamespace *ns, + QmlParser::Property *nsProp, + QmlParser::Object *obj, + const BindingContext &ctxt) +{ + if (!nsProp->value) + COMPILE_EXCEPTION(nsProp, QCoreApplication::translate("QmlCompiler","Invalid use of namespace")); + + foreach (Property *prop, nsProp->value->properties) { + + if (!isAttachedPropertyName(prop->name)) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Not an attached property name")); + + // Setup attached property data + + QmlType *type = 0; + QmlEnginePrivate::get(engine)->resolveTypeInNamespace(ns, prop->name, + &type, 0, 0, 0); + + if (!type || !type->attachedPropertiesType()) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Non-existant attached object")); + + if (!prop->value) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Invalid attached object assignment")); + + Q_ASSERT(type->attachedPropertiesFunction()); + prop->index = type->index(); + prop->value->metatype = type->attachedPropertiesType(); + + COMPILE_CHECK(buildAttachedProperty(prop, obj, ctxt)); + } + + return true; +} + +void QmlCompiler::genValueProperty(QmlParser::Property *prop, + QmlParser::Object *obj) +{ + if (QmlEnginePrivate::get(engine)->isQmlList(prop->type) || + QmlMetaType::isList(prop->type)) { + genListProperty(prop, obj); + } else { + genPropertyAssignment(prop, obj); + } +} + +void QmlCompiler::genListProperty(QmlParser::Property *prop, + QmlParser::Object *obj) +{ + QmlInstruction::Type fetchType; + QmlInstruction::Type storeType; + int listType; + + if (QmlEnginePrivate::get(engine)->isQmlList(prop->type)) { + fetchType = QmlInstruction::FetchQmlList; + storeType = QmlInstruction::StoreObjectQmlList; + listType = QmlEnginePrivate::get(engine)->qmlListType(prop->type); + } else { + fetchType = QmlInstruction::FetchQList; + storeType = QmlInstruction::StoreObjectQList; + listType = QmlMetaType::listType(prop->type); + } + + QmlInstruction fetch; + fetch.type = fetchType; + fetch.line = prop->location.start.line; + fetch.fetchQmlList.property = prop->index; + bool listTypeIsInterface = QmlMetaType::isInterface(listType); + fetch.fetchQmlList.type = listType; + output->bytecode << fetch; + + for (int ii = 0; ii < prop->values.count(); ++ii) { + Value *v = prop->values.at(ii); + + if (v->type == Value::CreatedObject) { + + genObject(v->object); + if (listTypeIsInterface) { + QmlInstruction assign; + assign.type = QmlInstruction::AssignObjectList; + assign.line = prop->location.start.line; + output->bytecode << assign; + } else { + QmlInstruction store; + store.type = storeType; + store.line = prop->location.start.line; + output->bytecode << store; + } + + } else if (v->type == Value::PropertyBinding) { + + genBindingAssignment(v, prop, obj); + + } + + } + + QmlInstruction pop; + pop.type = QmlInstruction::PopQList; + pop.line = prop->location.start.line; + output->bytecode << pop; +} + +void QmlCompiler::genPropertyAssignment(QmlParser::Property *prop, + QmlParser::Object *obj, + QmlParser::Property *valueTypeProperty) +{ + for (int ii = 0; ii < prop->values.count(); ++ii) { + QmlParser::Value *v = prop->values.at(ii); + + if (v->type == Value::CreatedObject) { + + genObject(v->object); + + if (QmlMetaType::isInterface(prop->type)) { + + QmlInstruction store; + store.type = QmlInstruction::StoreInterface; + store.line = v->object->location.start.line; + store.storeObject.propertyIndex = prop->index; + output->bytecode << store; + + } else if (prop->type == -1) { + + QmlInstruction store; + store.type = QmlInstruction::StoreVariantObject; + store.line = v->object->location.start.line; + store.storeObject.propertyIndex = prop->index; + output->bytecode << store; + + } else { + + QmlInstruction store; + store.type = QmlInstruction::StoreObject; + store.line = v->object->location.start.line; + store.storeObject.propertyIndex = prop->index; + output->bytecode << store; + + } + } else if (v->type == Value::ValueSource) { + genObject(v->object); + + QmlInstruction store; + store.type = QmlInstruction::StoreValueSource; + store.line = v->object->location.start.line; + if (valueTypeProperty) { + store.assignValueSource.property = genValueTypeData(prop, valueTypeProperty); + store.assignValueSource.owner = 1; + } else { + store.assignValueSource.property = genPropertyData(prop); + store.assignValueSource.owner = 0; + } + QmlType *valueType = toQmlType(v->object); + store.assignValueSource.castValue = valueType->propertyValueSourceCast(); + output->bytecode << store; + + } else if (v->type == Value::ValueInterceptor) { + genObject(v->object); + + QmlInstruction store; + store.type = QmlInstruction::StoreValueInterceptor; + store.line = v->object->location.start.line; + if (valueTypeProperty) { + store.assignValueInterceptor.property = genValueTypeData(prop, valueTypeProperty); + store.assignValueInterceptor.owner = 1; + } else { + store.assignValueInterceptor.property = genPropertyData(prop); + store.assignValueInterceptor.owner = 0; + } + QmlType *valueType = toQmlType(v->object); + store.assignValueInterceptor.castValue = valueType->propertyValueInterceptorCast(); + output->bytecode << store; + + } else if (v->type == Value::PropertyBinding) { + + genBindingAssignment(v, prop, obj, valueTypeProperty); + + } else if (v->type == Value::Literal) { + + QMetaProperty mp = obj->metaObject()->property(prop->index); + genLiteralAssignment(mp, v); + + } + + } +} + +bool QmlCompiler::buildIdProperty(QmlParser::Property *prop, + QmlParser::Object *obj) +{ + if (prop->value || + prop->values.count() > 1 || + prop->values.at(0)->object) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Invalid use of id property")); + + QmlParser::Value *idValue = prop->values.at(0); + QString val = idValue->primitive(); + + if (!isValidId(val)) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","\"%1\" is not a valid object id").arg(val)); + + // We disallow id's that conflict with import prefixes and types + QmlEnginePrivate::ImportedNamespace *ns = 0; + QmlType *type = 0; + QmlEnginePrivate::get(engine)->resolveType(unit->imports, val.toUtf8(), + &type, 0, 0, 0, &ns); + if (type) + COMPILE_EXCEPTION(idValue, QCoreApplication::translate("QmlCompiler","id conflicts with type name")); + if (ns) + COMPILE_EXCEPTION(idValue, QCoreApplication::translate("QmlCompiler","id conflicts with namespace prefix")); + + if (compileState.ids.contains(val)) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","id is not unique")); + + prop->values.at(0)->type = Value::Id; + + obj->id = val; + addId(val, obj); + + return true; +} + +void QmlCompiler::addId(const QString &id, QmlParser::Object *obj) +{ + Q_ASSERT(!compileState.ids.contains(id)); + Q_ASSERT(obj->id == id); + obj->idIndex = compileState.ids.count(); + compileState.ids.insert(id, obj); + compileState.idIndexes.insert(obj->idIndex, obj); +} + +void QmlCompiler::addBindingReference(const BindingReference &ref) +{ + Q_ASSERT(ref.value && !compileState.bindings.contains(ref.value)); + compileState.bindings.insert(ref.value, ref); +} + +void QmlCompiler::saveComponentState() +{ + Q_ASSERT(compileState.root); + Q_ASSERT(!savedCompileStates.contains(compileState.root)); + + savedCompileStates.insert(compileState.root, compileState); + savedComponentStats.append(componentStat); +} + +QmlCompiler::ComponentCompileState +QmlCompiler::componentState(QmlParser::Object *obj) +{ + Q_ASSERT(savedCompileStates.contains(obj)); + return savedCompileStates.value(obj); +} + +// Build attached property object. In this example, +// Text { +// GridView.row: 10 +// } +// GridView is an attached property object. +bool QmlCompiler::buildAttachedProperty(QmlParser::Property *prop, + QmlParser::Object *obj, + const BindingContext &ctxt) +{ + Q_ASSERT(prop->value); + Q_ASSERT(prop->index != -1); // This is set in buildProperty() + + obj->addAttachedProperty(prop); + + COMPILE_CHECK(buildSubObject(prop->value, ctxt.incr())); + + return true; +} + + +// Build "grouped" properties. In this example: +// Text { +// font.pointSize: 12 +// font.family: "Helvetica" +// } +// font is a nested property. pointSize and family are not. +bool QmlCompiler::buildGroupedProperty(QmlParser::Property *prop, + QmlParser::Object *obj, + const BindingContext &ctxt) +{ + Q_ASSERT(prop->type != 0); + Q_ASSERT(prop->index != -1); + + if (prop->values.count()) + COMPILE_EXCEPTION(prop->values.first(), QCoreApplication::translate("QmlCompiler", "Invalid value in grouped property")); + + if (prop->type < (int)QVariant::UserType) { + QmlEnginePrivate *ep = + static_cast<QmlEnginePrivate *>(QObjectPrivate::get(engine)); + if (prop->type >= 0 /* QVariant == -1 */ && ep->valueTypes[prop->type]) { + COMPILE_CHECK(buildValueTypeProperty(ep->valueTypes[prop->type], + prop->value, obj, ctxt.incr())); + obj->addValueTypeProperty(prop); + } else { + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Invalid grouped property access")); + } + + } else { + // Load the nested property's meta type + prop->value->metatype = + QmlEnginePrivate::get(engine)->metaObjectForType(prop->type); + if (!prop->value->metatype) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Invalid grouped property access")); + + obj->addGroupedProperty(prop); + + COMPILE_CHECK(buildSubObject(prop->value, ctxt.incr())); + } + + return true; +} + +bool QmlCompiler::buildValueTypeProperty(QObject *type, + QmlParser::Object *obj, + QmlParser::Object *baseObj, + const BindingContext &ctxt) +{ + if (obj->defaultProperty) + COMPILE_EXCEPTION(obj, QCoreApplication::translate("QmlCompiler","Invalid property use")); + obj->metatype = type->metaObject(); + + foreach (Property *prop, obj->properties) { + int idx = type->metaObject()->indexOfProperty(prop->name.constData()); + if (idx == -1) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Cannot assign to non-existant property \"%1\"").arg(QString::fromUtf8(prop->name))); + QMetaProperty p = type->metaObject()->property(idx); + prop->index = idx; + prop->type = p.userType(); + + if (prop->value) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Property assignment expected")); + + if (prop->values.count() != 1) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Single property assignment expected")); + + Value *value = prop->values.at(0); + + if (value->object) { + bool isPropertyValue = output->types.at(value->object->type).type->propertyValueSourceCast() != -1; + bool isPropertyInterceptor = output->types.at(value->object->type).type->propertyValueInterceptorCast() != -1; + if (!isPropertyValue && !isPropertyInterceptor) { + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Unexpected object assignment")); + } else { + COMPILE_CHECK(buildObject(value->object, ctxt)); + + if (isPropertyInterceptor && baseObj->synthdata.isEmpty()) + buildDynamicMeta(baseObj, ForceCreation); + value->type = isPropertyValue ? Value::ValueSource : Value::ValueInterceptor; + } + } else if (value->value.isScript()) { + // ### Check for writability + BindingReference reference; + reference.expression = value->value; + reference.property = prop; + reference.value = value; + reference.bindingContext = ctxt; + reference.bindingContext.owner++; + addBindingReference(reference); + value->type = Value::PropertyBinding; + } else { + COMPILE_CHECK(testLiteralAssignment(p, value)); + value->type = Value::Literal; + } + obj->addValueProperty(prop); + } + + return true; +} + +// Build assignments to QML lists. QML lists are properties of type +// QList<T *> * and QmlList<T *> *. +// +// QList<T *> * types can accept a list of objects, or a single binding +// QmlList<T *> * types can accept a list of objects +bool QmlCompiler::buildListProperty(QmlParser::Property *prop, + QmlParser::Object *obj, + const BindingContext &ctxt) +{ + Q_ASSERT(QmlMetaType::isList(prop->type) || + QmlEnginePrivate::get(engine)->isQmlList(prop->type)); + + int t = prop->type; + + obj->addValueProperty(prop); + + if (QmlEnginePrivate::get(engine)->isQmlList(t)) { + int listType = QmlEnginePrivate::get(engine)->qmlListType(t); + bool listTypeIsInterface = QmlMetaType::isInterface(listType); + + for (int ii = 0; ii < prop->values.count(); ++ii) { + Value *v = prop->values.at(ii); + if (v->object) { + v->type = Value::CreatedObject; + COMPILE_CHECK(buildObject(v->object, ctxt)); + + // We check object coercian here. We check interface assignment + // at runtime. + if (!listTypeIsInterface) { + if (!canCoerce(listType, v->object)) { + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Cannot assign object to list")); + } + } + + } else { + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Cannot assign primitives to lists")); + } + } + + } else { + int listType = QmlMetaType::listType(t); + bool listTypeIsInterface = QmlMetaType::isInterface(listType); + + bool assignedBinding = false; + for (int ii = 0; ii < prop->values.count(); ++ii) { + Value *v = prop->values.at(ii); + if (v->object) { + v->type = Value::CreatedObject; + COMPILE_CHECK(buildObject(v->object, ctxt)); + + // We check object coercian here. We check interface assignment + // at runtime. + if (!listTypeIsInterface) { + if (!canCoerce(listType, v->object)) { + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Cannot assign object to list")); + } + } + + } else if (v->value.isScript()) { + if (assignedBinding) + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Can only assign one binding to lists")); + + assignedBinding = true; + COMPILE_CHECK(buildBinding(v, prop, ctxt)); + v->type = Value::PropertyBinding; + } else { + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Cannot assign primitives to lists")); + } + } + + } + + return true; +} + +// Compiles an assignment to a QmlScriptString property +bool QmlCompiler::buildScriptStringProperty(QmlParser::Property *prop, + QmlParser::Object *obj, + const BindingContext &ctxt) +{ + if (prop->values.count() > 1) + COMPILE_EXCEPTION(prop->values.at(1), QCoreApplication::translate("QmlCompiler", "Cannot assign multiple values to a script property")); + + if (prop->values.at(0)->object || !prop->values.at(0)->value.isScript()) + COMPILE_EXCEPTION(prop->values.at(0), QCoreApplication::translate("QmlCompiler", "Invalid property assignment: script expected")); + + obj->addScriptStringProperty(prop, ctxt.stack); + + return true; +} + +// Compile regular property assignments of the form "property: <value>" +// +// ### The following problems exist +// +// There is no distinction between how "lists" of values are specified. This +// Item { +// children: Item {} +// children: Item {} +// } +// is identical to +// Item { +// children: [ Item {}, Item {} ] +// } +// +// We allow assignming multiple values to single value properties +bool QmlCompiler::buildPropertyAssignment(QmlParser::Property *prop, + QmlParser::Object *obj, + const BindingContext &ctxt) +{ + obj->addValueProperty(prop); + + for (int ii = 0; ii < prop->values.count(); ++ii) { + Value *v = prop->values.at(ii); + if (v->object) { + + COMPILE_CHECK(buildPropertyObjectAssignment(prop, obj, v, ctxt)); + + } else { + + COMPILE_CHECK(buildPropertyLiteralAssignment(prop, obj, v, ctxt)); + + } + } + + return true; +} + +// Compile assigning a single object instance to a regular property +bool QmlCompiler::buildPropertyObjectAssignment(QmlParser::Property *prop, + QmlParser::Object *obj, + QmlParser::Value *v, + const BindingContext &ctxt) +{ + Q_ASSERT(prop->index != -1); + Q_ASSERT(v->object->type != -1); + + if (!obj->metaObject()->property(prop->index).isWritable()) + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: \"%1\" is a read-only property").arg(QString::fromUtf8(prop->name))); + + if (QmlMetaType::isInterface(prop->type)) { + + // Assigning an object to an interface ptr property + COMPILE_CHECK(buildObject(v->object, ctxt)); + + v->type = Value::CreatedObject; + + } else if (prop->type == -1) { + + // Assigning an object to a QVariant + COMPILE_CHECK(buildObject(v->object, ctxt)); + + v->type = Value::CreatedObject; + } else { + // Normally buildObject() will set this up, but we need the static + // meta object earlier to test for assignability. It doesn't matter + // that there may still be outstanding synthesized meta object changes + // on this type, as they are not relevant for assignability testing + v->object->metatype = output->types.at(v->object->type).metaObject(); + Q_ASSERT(v->object->metaObject()); + + // Will be true if the assigned type inherits QmlPropertyValueSource + bool isPropertyValue = false; + // Will be true if the assigned type inherits QmlPropertyValueInterceptor + bool isPropertyInterceptor = false; + if (QmlType *valueType = toQmlType(v->object)) { + isPropertyValue = valueType->propertyValueSourceCast() != -1; + isPropertyInterceptor = valueType->propertyValueInterceptorCast() != -1; + } + + // We want to raw metaObject here as the raw metaobject is the + // actual property type before we applied any extensions that might + // effect the properties on the type, but don't effect assignability + const QMetaObject *propertyMetaObject = + QmlEnginePrivate::get(engine)->rawMetaObjectForType(prop->type); + + // Will be true if the assgned type inherits propertyMetaObject + bool isAssignable = false; + // Determine isAssignable value + if (propertyMetaObject) { + const QMetaObject *c = v->object->metatype; + while(c) { + isAssignable |= (QmlMetaPropertyPrivate::equal(c, propertyMetaObject)); + c = c->superClass(); + } + } + + if (isAssignable) { + // Simple assignment + COMPILE_CHECK(buildObject(v->object, ctxt)); + + v->type = Value::CreatedObject; + } else if (propertyMetaObject == &QmlComponent::staticMetaObject) { + // Automatic "Component" insertion + QmlParser::Object *root = v->object; + QmlParser::Object *component = new QmlParser::Object; + component->type = componentTypeRef(); + component->typeName = "Qt/Component"; + component->metatype = &QmlComponent::staticMetaObject; + component->location = root->location; + QmlParser::Value *componentValue = new QmlParser::Value; + componentValue->object = root; + component->getDefaultProperty()->addValue(componentValue); + v->object = component; + COMPILE_CHECK(buildPropertyObjectAssignment(prop, obj, v, ctxt)); + } else if (isPropertyValue || isPropertyInterceptor) { + // Assign as a property value source + COMPILE_CHECK(buildObject(v->object, ctxt)); + + if (isPropertyInterceptor && prop->parent->synthdata.isEmpty()) + buildDynamicMeta(prop->parent, ForceCreation); + v->type = isPropertyValue ? Value::ValueSource : Value::ValueInterceptor; + } else { + COMPILE_EXCEPTION(v->object, QCoreApplication::translate("QmlCompiler","Cannot assign object to property")); + } + } + + return true; +} + +// Compile assigning a literal or binding to a regular property +bool QmlCompiler::buildPropertyLiteralAssignment(QmlParser::Property *prop, + QmlParser::Object *obj, + QmlParser::Value *v, + const BindingContext &ctxt) +{ + Q_ASSERT(prop->index != -1); + + if (v->value.isScript()) { + + //optimization for <Type>.<EnumValue> enum assignments + bool isEnumAssignment = false; + COMPILE_CHECK(testQualifiedEnumAssignment(obj->metaObject()->property(prop->index), obj, v, &isEnumAssignment)); + if (isEnumAssignment) { + v->type = Value::Literal; + return true; + } + + COMPILE_CHECK(buildBinding(v, prop, ctxt)); + + v->type = Value::PropertyBinding; + + } else { + + COMPILE_CHECK(testLiteralAssignment(obj->metaObject()->property(prop->index), v)); + + v->type = Value::Literal; + } + + return true; +} + +bool QmlCompiler::testQualifiedEnumAssignment(const QMetaProperty &prop, + QmlParser::Object *obj, + QmlParser::Value *v, + bool *isAssignment) +{ + *isAssignment = false; + if (!prop.isEnumType()) + return true; + + if (!prop.isWritable()) + COMPILE_EXCEPTION(v, QCoreApplication::translate("QmlCompiler","Invalid property assignment: \"%1\" is a read-only property").arg(QString::fromUtf8(prop.name()))); + + QString string = v->value.asString(); + if (!string.at(0).isUpper()) + return true; + + QStringList parts = string.split(QLatin1Char('.')); + if (parts.count() != 2) + return true; + + QString typeName = parts.at(0); + QmlType *type = 0; + QmlEnginePrivate::get(engine)->resolveType(unit->imports, typeName.toUtf8(), + &type, 0, 0, 0, 0); + + if (!type || obj->typeName != type->qmlTypeName()) + return true; + + QString enumValue = parts.at(1); + int value; + if (prop.isFlagType()) { + value = prop.enumerator().keysToValue(enumValue.toUtf8().constData()); + } else + value = prop.enumerator().keyToValue(enumValue.toUtf8().constData()); + if (value == -1) + return true; + + v->type = Value::Literal; + v->value = QmlParser::Variant(enumValue); + *isAssignment = true; + + return true; +} + +// Ensures that the dynamic meta specification on obj is valid +bool QmlCompiler::checkDynamicMeta(QmlParser::Object *obj) +{ + QSet<QByteArray> propNames; + QSet<QByteArray> methodNames; + bool seenDefaultProperty = false; + + // Check properties + for (int ii = 0; ii < obj->dynamicProperties.count(); ++ii) { + const QmlParser::Object::DynamicProperty &prop = + obj->dynamicProperties.at(ii); + + if (prop.isDefaultProperty) { + if (seenDefaultProperty) + COMPILE_EXCEPTION(&prop, QCoreApplication::translate("QmlCompiler","Duplicate default property")); + seenDefaultProperty = true; + } + + if (propNames.contains(prop.name)) + COMPILE_EXCEPTION(&prop, QCoreApplication::translate("QmlCompiler","Duplicate property name")); + + propNames.insert(prop.name); + } + + for (int ii = 0; ii < obj->dynamicSignals.count(); ++ii) { + QByteArray name = obj->dynamicSignals.at(ii).name; + if (methodNames.contains(name)) + COMPILE_EXCEPTION(obj, QCoreApplication::translate("QmlCompiler","Duplicate signal name")); + methodNames.insert(name); + } + for (int ii = 0; ii < obj->dynamicSlots.count(); ++ii) { + QByteArray name = obj->dynamicSlots.at(ii).name; + if (methodNames.contains(name)) + COMPILE_EXCEPTION(obj, QCoreApplication::translate("QmlCompiler","Duplicate method name")); + methodNames.insert(name); + } + + return true; +} + +bool QmlCompiler::mergeDynamicMetaProperties(QmlParser::Object *obj) +{ + for (int ii = 0; ii < obj->dynamicProperties.count(); ++ii) { + const Object::DynamicProperty &p = obj->dynamicProperties.at(ii); + + if (!p.defaultValue || p.type == Object::DynamicProperty::Alias) + continue; + + Property *property = 0; + if (p.isDefaultProperty) + property = obj->getDefaultProperty(); + else + property = obj->getProperty(p.name); + + if (property->value) + COMPILE_EXCEPTION(property, QCoreApplication::translate("QmlCompiler","Invalid property nesting")); + + for (int ii = 0; ii < p.defaultValue->values.count(); ++ii) { + Value *v = p.defaultValue->values.at(ii); + v->addref(); + property->values.append(v); + } + } + return true; +} + +Q_GLOBAL_STATIC(QAtomicInt, classIndexCounter) + +bool QmlCompiler::buildDynamicMeta(QmlParser::Object *obj, DynamicMetaMode mode) +{ + Q_ASSERT(obj); + Q_ASSERT(obj->metatype); + + if (mode != ForceCreation && + obj->dynamicProperties.isEmpty() && + obj->dynamicSignals.isEmpty() && + obj->dynamicSlots.isEmpty()) + return true; + + COMPILE_CHECK(checkDynamicMeta(obj)); + + QByteArray dynamicData(sizeof(QmlVMEMetaData), (char)0); + + QByteArray newClassName = obj->metatype->className(); + newClassName.append("_QML_"); + int idx = classIndexCounter()->fetchAndAddRelaxed(1); + newClassName.append(QByteArray::number(idx)); + if (compileState.root == obj) { + QString path = output->url.path(); + int lastSlash = path.lastIndexOf(QLatin1Char('/')); + if (lastSlash > -1) { + QString nameBase = path.mid(lastSlash + 1, path.length()-lastSlash-5); + if (!nameBase.isEmpty() && nameBase.at(0).isUpper()) + newClassName = nameBase.toUtf8() + "_QMLTYPE_" + QByteArray::number(idx); + } + } + + QMetaObjectBuilder builder; + builder.setClassName(newClassName); + builder.setFlags(QMetaObjectBuilder::DynamicMetaObject); + + bool hasAlias = false; + for (int ii = 0; ii < obj->dynamicProperties.count(); ++ii) { + const Object::DynamicProperty &p = obj->dynamicProperties.at(ii); + + int propIdx = + obj->metaObject()->indexOfProperty(p.name.constData()); + if (-1 != propIdx) { + QMetaProperty prop = obj->metaObject()->property(propIdx); + if (prop.isFinal()) + COMPILE_EXCEPTION(&p, QCoreApplication::translate("QmlCompiler","Cannot override FINAL property")); + } + + if (p.isDefaultProperty && + (p.type != Object::DynamicProperty::Alias || + mode == ResolveAliases)) + builder.addClassInfo("DefaultProperty", p.name); + + QByteArray type; + int propertyType = 0; + bool readonly = false; + switch(p.type) { + case Object::DynamicProperty::Alias: + hasAlias = true; + continue; + break; + case Object::DynamicProperty::CustomList: + case Object::DynamicProperty::Custom: + { + QByteArray customTypeName; + QmlType *qmltype = 0; + QUrl url; + QmlEnginePrivate *priv = QmlEnginePrivate::get(engine); + if (!priv->resolveType(unit->imports, p.customType, &qmltype, + &url, 0, 0, 0)) + COMPILE_EXCEPTION(&p, QCoreApplication::translate("QmlCompiler","Invalid property type")); + + if (!qmltype) { + QmlCompositeTypeData *tdata = priv->typeManager.get(url); + Q_ASSERT(tdata); + Q_ASSERT(tdata->status == QmlCompositeTypeData::Complete); + + QmlCompiledData *data = tdata->toCompiledComponent(engine); + customTypeName = data->root->className(); + } else { + customTypeName = qmltype->typeName(); + } + + if (p.type == Object::DynamicProperty::Custom) { + type = customTypeName + '*'; + propertyType = QMetaType::QObjectStar; + } else { + readonly = true; + type = "QmlList<"; + type.append(customTypeName); + type.append("*>*"); + propertyType = qMetaTypeId<QmlList<QObject*>* >(); + } + } + break; + case Object::DynamicProperty::Variant: + propertyType = -1; + type = "QVariant"; + break; + case Object::DynamicProperty::Int: + propertyType = QVariant::Int; + type = "int"; + break; + case Object::DynamicProperty::Bool: + propertyType = QVariant::Bool; + type = "bool"; + break; + case Object::DynamicProperty::Real: + propertyType = QVariant::Double; + type = "double"; + break; + case Object::DynamicProperty::String: + propertyType = QVariant::String; + type = "QString"; + break; + case Object::DynamicProperty::Url: + propertyType = QVariant::Url; + type = "QUrl"; + break; + case Object::DynamicProperty::Color: + propertyType = QVariant::Color; + type = "QColor"; + break; + case Object::DynamicProperty::Date: + propertyType = QVariant::Date; + type = "QDate"; + break; + } + + ((QmlVMEMetaData *)dynamicData.data())->propertyCount++; + QmlVMEMetaData::PropertyData propertyData = { propertyType }; + dynamicData.append((char *)&propertyData, sizeof(propertyData)); + + builder.addSignal(p.name + "Changed()"); + QMetaPropertyBuilder propBuilder = + builder.addProperty(p.name, type, builder.methodCount() - 1); + propBuilder.setScriptable(true); + propBuilder.setWritable(!readonly); + } + + if (mode == ResolveAliases) { + for (int ii = 0; ii < obj->dynamicProperties.count(); ++ii) { + const Object::DynamicProperty &p = obj->dynamicProperties.at(ii); + + if (p.type == Object::DynamicProperty::Alias) { + ((QmlVMEMetaData *)dynamicData.data())->aliasCount++; + compileAlias(builder, dynamicData, obj, p); + } + } + } + + for (int ii = 0; ii < obj->dynamicSignals.count(); ++ii) { + const Object::DynamicSignal &s = obj->dynamicSignals.at(ii); + QByteArray sig(s.name + '('); + for (int jj = 0; jj < s.parameterTypes.count(); ++jj) { + if (jj) sig.append(','); + sig.append(s.parameterTypes.at(jj)); + } + sig.append(')'); + QMetaMethodBuilder b = builder.addSignal(sig); + b.setParameterNames(s.parameterNames); + ((QmlVMEMetaData *)dynamicData.data())->signalCount++; + } + + QStringList funcScripts; + + for (int ii = 0; ii < obj->dynamicSlots.count(); ++ii) { + Object::DynamicSlot &s = obj->dynamicSlots[ii]; + QByteArray sig(s.name + '('); + QString funcScript(QLatin1String("(function(")); + + for (int jj = 0; jj < s.parameterNames.count(); ++jj) { + if (jj) { + sig.append(','); + funcScript.append(QLatin1Char(',')); + } + funcScript.append(QLatin1String(s.parameterNames.at(jj))); + sig.append("QVariant"); + } + sig.append(')'); + funcScript.append(QLatin1Char(')')); + funcScript.append(s.body); + funcScript.append(QLatin1Char(')')); + funcScripts << funcScript; + + QMetaMethodBuilder b = builder.addSlot(sig); + b.setReturnType("QVariant"); + b.setParameterNames(s.parameterNames); + + ((QmlVMEMetaData *)dynamicData.data())->methodCount++; + QmlVMEMetaData::MethodData methodData = + { s.parameterNames.count(), 0, funcScript.length(), 0 }; + + dynamicData.append((char *)&methodData, sizeof(methodData)); + } + + for (int ii = 0; ii < obj->dynamicSlots.count(); ++ii) { + const QString &funcScript = funcScripts.at(ii); + QmlVMEMetaData::MethodData *data = + ((QmlVMEMetaData *)dynamicData.data())->methodData() + ii; + + data->bodyOffset = dynamicData.size(); + + dynamicData.append((const char *)funcScript.constData(), + (funcScript.length() * sizeof(QChar))); + } + + obj->metadata = builder.toRelocatableData(); + builder.fromRelocatableData(&obj->extObject, obj->metatype, obj->metadata); + + if (mode == IgnoreAliases && hasAlias) + compileState.aliasingObjects << obj; + + obj->synthdata = dynamicData; + + return true; +} + +#include <qmljsparser_p.h> + +static QStringList astNodeToStringList(QmlJS::AST::Node *node) +{ + if (node->kind == QmlJS::AST::Node::Kind_IdentifierExpression) { + QString name = + static_cast<QmlJS::AST::IdentifierExpression *>(node)->name->asString(); + return QStringList() << name; + } else if (node->kind == QmlJS::AST::Node::Kind_FieldMemberExpression) { + QmlJS::AST::FieldMemberExpression *expr = static_cast<QmlJS::AST::FieldMemberExpression *>(node); + + QStringList rv = astNodeToStringList(expr->base); + if (rv.isEmpty()) + return rv; + rv.append(expr->name->asString()); + return rv; + } + return QStringList(); +} + +bool QmlCompiler::compileAlias(QMetaObjectBuilder &builder, + QByteArray &data, + Object *obj, + const Object::DynamicProperty &prop) +{ + if (!prop.defaultValue) + COMPILE_EXCEPTION(obj, QCoreApplication::translate("QmlCompiler","No property alias location")); + + if (prop.defaultValue->values.count() != 1 || + prop.defaultValue->values.at(0)->object || + !prop.defaultValue->values.at(0)->value.isScript()) + COMPILE_EXCEPTION(prop.defaultValue, QCoreApplication::translate("QmlCompiler","Invalid alias location")); + + QmlJS::AST::Node *node = prop.defaultValue->values.at(0)->value.asAST(); + if (!node) + COMPILE_EXCEPTION(obj, QCoreApplication::translate("QmlCompiler","No property alias location")); // ### Can this happen? + + QStringList alias = astNodeToStringList(node); + + if (alias.count() != 1 && alias.count() != 2) + COMPILE_EXCEPTION(prop.defaultValue, QCoreApplication::translate("QmlCompiler","Invalid alias reference. An alias reference must be specified as <id> or <id>.<property>")); + + if (!compileState.ids.contains(alias.at(0))) + COMPILE_EXCEPTION(prop.defaultValue, QCoreApplication::translate("QmlCompiler","Invalid alias reference. Unable to find id \"%1\"").arg(alias.at(0))); + + Object *idObject = compileState.ids[alias.at(0)]; + + QByteArray typeName; + + int propIdx = -1; + int flags = 0; + bool writable = false; + if (alias.count() == 2) { + propIdx = idObject->metaObject()->indexOfProperty(alias.at(1).toUtf8().constData()); + + if (-1 == propIdx) + COMPILE_EXCEPTION(prop.defaultValue, QCoreApplication::translate("QmlCompiler","Invalid alias location")); + + QMetaProperty aliasProperty = idObject->metaObject()->property(propIdx); + writable = aliasProperty.isWritable(); + + if (aliasProperty.isEnumType()) + typeName = "int"; // Avoid introducing a dependency on the aliased metaobject + else + typeName = aliasProperty.typeName(); + } else { + typeName = idObject->metaObject()->className(); + + //use the base type since it has been registered with metatype system + int index = typeName.indexOf("_QML_"); + if (index != -1) { + typeName = typeName.left(index); + } else { + index = typeName.indexOf("_QMLTYPE_"); + const QMetaObject *mo = idObject->metaObject(); + while (index != -1 && mo) { + typeName = mo->superClass()->className(); + index = typeName.indexOf("_QMLTYPE_"); + mo = mo->superClass(); + } + } + + typeName += '*'; + } + + if (typeName.endsWith('*')) + flags |= QML_ALIAS_FLAG_PTR; + + data.append((const char *)&idObject->idIndex, sizeof(idObject->idIndex)); + data.append((const char *)&propIdx, sizeof(propIdx)); + data.append((const char *)&flags, sizeof(flags)); + + builder.addSignal(prop.name + "Changed()"); + QMetaPropertyBuilder propBuilder = + builder.addProperty(prop.name, typeName.constData(), builder.methodCount() - 1); + propBuilder.setScriptable(true); + propBuilder.setWritable(writable); + return true; +} + +bool QmlCompiler::buildBinding(QmlParser::Value *value, + QmlParser::Property *prop, + const BindingContext &ctxt) +{ + Q_ASSERT(prop->index != -1); + Q_ASSERT(prop->parent); + Q_ASSERT(prop->parent->metaObject()); + + QMetaProperty mp = prop->parent->metaObject()->property(prop->index); + if (!mp.isWritable() && !QmlMetaType::isList(prop->type)) + COMPILE_EXCEPTION(prop, QCoreApplication::translate("QmlCompiler","Invalid property assignment: \"%1\" is a read-only property").arg(QString::fromUtf8(prop->name))); + + BindingReference reference; + reference.expression = value->value; + reference.property = prop; + reference.value = value; + reference.bindingContext = ctxt; + addBindingReference(reference); + + return true; +} + +void QmlCompiler::genBindingAssignment(QmlParser::Value *binding, + QmlParser::Property *prop, + QmlParser::Object *obj, + QmlParser::Property *valueTypeProperty) +{ + Q_UNUSED(obj); + Q_ASSERT(compileState.bindings.contains(binding)); + + const BindingReference &ref = compileState.bindings.value(binding); + if (ref.dataType == BindingReference::Experimental) { + QmlInstruction store; + store.type = QmlInstruction::StoreCompiledBinding; + store.assignBinding.value = ref.compiledIndex; + store.assignBinding.context = ref.bindingContext.stack; + store.assignBinding.owner = ref.bindingContext.owner; + if (valueTypeProperty) + store.assignBinding.property = (valueTypeProperty->index & 0xFFFF) | + ((valueTypeProperty->type & 0xFF)) << 16 | + ((prop->index & 0xFF) << 24); + else + store.assignBinding.property = prop->index; + store.line = binding->location.start.line; + output->bytecode << store; + return; + } + + QmlInstruction store; + store.type = QmlInstruction::StoreBinding; + store.assignBinding.value = output->indexForByteArray(ref.compiledData); + store.assignBinding.context = ref.bindingContext.stack; + store.assignBinding.owner = ref.bindingContext.owner; + store.line = binding->location.start.line; + + Q_ASSERT(ref.bindingContext.owner == 0 || + (ref.bindingContext.owner != 0 && valueTypeProperty)); + if (ref.bindingContext.owner) { + store.assignBinding.property = genValueTypeData(prop, valueTypeProperty); + } else { + store.assignBinding.property = genPropertyData(prop); + } + + output->bytecode << store; +} + +int QmlCompiler::genContextCache() +{ + if (compileState.ids.count() == 0) + return -1; + + QmlIntegerCache *cache = new QmlIntegerCache(engine); + + for (QHash<QString, QmlParser::Object *>::ConstIterator iter = compileState.ids.begin(); + iter != compileState.ids.end(); + ++iter) + cache->add(iter.key(), (*iter)->idIndex); + + output->contextCaches.append(cache); + return output->contextCaches.count() - 1; +} + +int QmlCompiler::genValueTypeData(QmlParser::Property *valueTypeProp, + QmlParser::Property *prop) +{ + return output->indexForByteArray(QmlMetaPropertyPrivate::saveValueType(prop->parent->metaObject(), prop->index, valueTypeProp->index, valueTypeProp->type)); +} + +int QmlCompiler::genPropertyData(QmlParser::Property *prop) +{ + return output->indexForByteArray(QmlMetaPropertyPrivate::saveProperty(prop->parent->metaObject(), prop->index)); +} + +bool QmlCompiler::completeComponentBuild() +{ + componentStat.ids = compileState.ids.count(); + + for (int ii = 0; ii < compileState.aliasingObjects.count(); ++ii) { + Object *aliasObject = compileState.aliasingObjects.at(ii); + COMPILE_CHECK(buildDynamicMeta(aliasObject, ResolveAliases)); + } + + QmlBindingCompiler::Expression expr; + expr.component = compileState.root; + expr.ids = compileState.ids; + + QmlBindingCompiler bindingCompiler; + + for (QHash<QmlParser::Value*,BindingReference>::Iterator iter = compileState.bindings.begin(); iter != compileState.bindings.end(); ++iter) { + BindingReference &binding = *iter; + + expr.context = binding.bindingContext.object; + expr.property = binding.property; + expr.expression = binding.expression; + expr.imports = unit->imports; + + if (qmlExperimental()) { + int index = bindingCompiler.compile(expr, QmlEnginePrivate::get(engine)); + if (index != -1) { + qWarning() << "Accepted for optimization:" << qPrintable(expr.expression.asScript()); + binding.dataType = BindingReference::Experimental; + binding.compiledIndex = index; + componentStat.optimizedBindings++; + continue; + } else { + qWarning() << "Rejected for optimization:" << qPrintable(expr.expression.asScript()); + } + } + + binding.dataType = BindingReference::QtScript; + + // Pre-rewrite the expression + QString expression = binding.expression.asScript(); + + // ### Optimize + QmlRewrite::SharedBindingTester sharableTest; + bool isSharable = sharableTest.isSharable(expression); + + QmlRewrite::RewriteBinding rewriteBinding; + expression = rewriteBinding(expression); + + quint32 length = expression.length(); + quint32 pc; + + if (isSharable) { + pc = output->cachedClosures.count(); + pc |= 0x80000000; + output->cachedClosures.append(0); + } else { + pc = output->cachedPrograms.length(); + output->cachedPrograms.append(0); + } + + binding.compiledData = + QByteArray((const char *)&pc, sizeof(quint32)) + + QByteArray((const char *)&length, sizeof(quint32)) + + QByteArray((const char *)expression.constData(), + expression.length() * sizeof(QChar)); + + componentStat.scriptBindings++; + } + + if (bindingCompiler.isValid()) { + compileState.compiledBindingData = bindingCompiler.program(); + QmlBindingCompiler::dump(compileState.compiledBindingData); + } + + saveComponentState(); + + return true; +} + +void QmlCompiler::dumpStats() +{ + qWarning().nospace() << "QML Document: " << output->url.toString(); + for (int ii = 0; ii < savedComponentStats.count(); ++ii) { + const ComponentStat &stat = savedComponentStats.at(ii); + qWarning().nospace() << " Component Line " << stat.lineNumber; + qWarning().nospace() << " Total Objects: " << stat.objects; + qWarning().nospace() << " IDs Used: " << stat.ids; + qWarning().nospace() << " Optimized Bindings: " << stat.optimizedBindings; + qWarning().nospace() << " QScript Bindings: " << stat.scriptBindings; + } +} + +/*! + Returns true if from can be assigned to a (QObject) property of type + to. +*/ +bool QmlCompiler::canCoerce(int to, QmlParser::Object *from) +{ + const QMetaObject *toMo = + QmlEnginePrivate::get(engine)->rawMetaObjectForType(to); + const QMetaObject *fromMo = from->metaObject(); + + while (fromMo) { + if (QmlMetaPropertyPrivate::equal(fromMo, toMo)) + return true; + fromMo = fromMo->superClass(); + } + return false; +} + +/*! + Returns true if from can be assigned to a (QObject) property of type + to. +*/ +bool QmlCompiler::canCoerce(int to, int from) +{ + const QMetaObject *toMo = + QmlEnginePrivate::get(engine)->rawMetaObjectForType(to); + const QMetaObject *fromMo = + QmlEnginePrivate::get(engine)->rawMetaObjectForType(from); + + while (fromMo) { + if (QmlMetaPropertyPrivate::equal(fromMo, toMo)) + return true; + fromMo = fromMo->superClass(); + } + return false; +} + +QmlType *QmlCompiler::toQmlType(QmlParser::Object *from) +{ + // ### Optimize + const QMetaObject *mo = from->metatype; + QmlType *type = 0; + while (!type && mo) { + type = QmlMetaType::qmlType(mo); + mo = mo->superClass(); + } + return type; +} + +QStringList QmlCompiler::deferredProperties(QmlParser::Object *obj) +{ + const QMetaObject *mo = obj->metatype; + + int idx = mo->indexOfClassInfo("DeferredPropertyNames"); + if (idx == -1) + return QStringList(); + + QMetaClassInfo classInfo = mo->classInfo(idx); + QStringList rv = QString::fromUtf8(classInfo.value()).split(QLatin1Char(',')); + return rv; +} + +QT_END_NAMESPACE |