aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@digia.com>2014-03-06 16:19:42 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2014-03-11 18:31:21 +0100
commit914b72418b7e766026f2679254fcee93fc920866 (patch)
tree202634bd203c830ddc7eeb7dab1d071ca506b8a3
parentaf7ba8a6194b83fe7380b8d4ae027e2f04e21f17 (diff)
Add support for resolving translation bindings at compile time
Simple calls to qsTr and qsTrId are detected at type compile time and reduced to a special Translation and TranslationById binding type, which avoids allocating a QML binding at type instantiation type just to perform a translation. Change-Id: I61e4f2db2a8092b5e6870e174b832d9c20cd62b5 Reviewed-by: Lars Knoll <lars.knoll@digia.com>
-rw-r--r--src/qml/compiler/qqmlcodegenerator_p.h8
-rw-r--r--src/qml/compiler/qqmltypecompiler.cpp340
-rw-r--r--src/qml/compiler/qqmltypecompiler_p.h110
-rw-r--r--src/qml/compiler/qv4compileddata.cpp17
-rw-r--r--src/qml/compiler/qv4compileddata_p.h12
-rw-r--r--src/qml/compiler/qv4jsir_p.h3
-rw-r--r--src/qml/qml/qqmlobjectcreator.cpp6
-rw-r--r--src/qml/qml/qqmltypeloader_p.h2
-rw-r--r--src/qml/types/qqmllistmodel.cpp2
-rw-r--r--src/quick/util/qquickpropertychanges.cpp3
-rw-r--r--tests/auto/qml/qml.pro4
-rw-r--r--tests/auto/qml/qqmltranslation/data/translation.qml10
-rw-r--r--tests/auto/qml/qqmltranslation/tst_qqmltranslation.cpp65
13 files changed, 549 insertions, 33 deletions
diff --git a/src/qml/compiler/qqmlcodegenerator_p.h b/src/qml/compiler/qqmlcodegenerator_p.h
index 3b72e24bd2..583f8e1ed5 100644
--- a/src/qml/compiler/qqmlcodegenerator_p.h
+++ b/src/qml/compiler/qqmlcodegenerator_p.h
@@ -171,8 +171,8 @@ template <typename T>
class FixedPoolArray
{
T *data;
- int count;
public:
+ int count;
void init(QQmlJS::MemoryPool *pool, const QVector<T> &vector)
{
@@ -192,6 +192,12 @@ public:
return data[index];
}
+ T &operator[](int index) {
+ Q_ASSERT(index >= 0 && index < count);
+ return data[index];
+ }
+
+
int indexOf(const T &value) const {
for (int i = 0; i < count; ++i)
if (data[i] == value)
diff --git a/src/qml/compiler/qqmltypecompiler.cpp b/src/qml/compiler/qqmltypecompiler.cpp
index 4e5901f8b3..37728407f3 100644
--- a/src/qml/compiler/qqmltypecompiler.cpp
+++ b/src/qml/compiler/qqmltypecompiler.cpp
@@ -47,6 +47,7 @@
#include <private/qqmlvmemetaobject_p.h>
#include <private/qqmlcomponent_p.h>
#include <private/qqmlstringconverters_p.h>
+#include <private/qv4ssa_p.h>
#define COMPILE_EXCEPTION(token, desc) \
{ \
@@ -133,6 +134,13 @@ bool QQmlTypeCompiler::compile()
// Build property caches and VME meta object data
+ for (QHash<int, QQmlCompiledData::TypeReference*>::ConstIterator it = compiledData->resolvedTypes.constBegin(), end = compiledData->resolvedTypes.constEnd();
+ it != end; ++it) {
+ QQmlCustomParser *customParser = (*it)->type ? (*it)->type->customParser() : 0;
+ if (customParser)
+ customParsers.insert(it.key(), customParser);
+ }
+
compiledData->datas.reserve(parsedQML->objects.count());
compiledData->propertyCaches.reserve(parsedQML->objects.count());
@@ -202,6 +210,11 @@ bool QQmlTypeCompiler::compile()
return false;
}
+ {
+ QQmlJavaScriptBindingExpressionSimplificationPass pass(this);
+ pass.reduceTranslationBindings();
+ }
+
QV4::ExecutionEngine *v4 = engine->v4engine();
QScopedPointer<EvalInstructionSelection> isel(v4->iselFactory->create(engine, v4->executableAllocator, &parsedQML->jsModule, &parsedQML->jsGenerator));
@@ -291,6 +304,11 @@ int QQmlTypeCompiler::registerString(const QString &str)
return parsedQML->jsGenerator.registerString(str);
}
+QV4::IR::Module *QQmlTypeCompiler::jsIRModule() const
+{
+ return &parsedQML->jsModule;
+}
+
const QV4::CompiledData::QmlUnit *QQmlTypeCompiler::qmlUnit() const
{
return compiledData->qmlUnit;
@@ -883,6 +901,7 @@ bool QQmlPropertyCacheCreator::createMetaObject(int objectIndex, const QtQml::Qm
SignalHandlerConverter::SignalHandlerConverter(QQmlTypeCompiler *typeCompiler)
: QQmlCompilePass(typeCompiler)
, qmlObjects(*typeCompiler->qmlObjects())
+ , customParsers(typeCompiler->customParserCache())
, resolvedTypes(*typeCompiler->resolvedTypes())
, illegalNames(QV8Engine::get(QQmlEnginePrivate::get(typeCompiler->enginePrivate()))->illegalNames())
, propertyCaches(typeCompiler->propertyCaches())
@@ -896,13 +915,11 @@ bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclaratio
QQmlPropertyCache *cache = propertyCaches.at(objectIndex);
if (!cache)
continue;
- QString elementName = stringAt(obj->inheritedTypeNameIndex);
- if (!elementName.isEmpty()) {
- QQmlCompiledData::TypeReference *tr = resolvedTypes.value(obj->inheritedTypeNameIndex);
- QQmlCustomParser *customParser = (tr && tr->type) ? tr->type->customParser() : 0;
- if (customParser && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers))
+ if (QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex)) {
+ if (!(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers))
continue;
}
+ const QString elementName = stringAt(obj->inheritedTypeNameIndex);
if (!convertSignalHandlerExpressionsToFunctionDeclarations(obj, elementName, cache))
return false;
}
@@ -1603,12 +1620,12 @@ bool QQmlComponentAndAliasResolver::resolveAliases()
return true;
}
-
QQmlPropertyValidator::QQmlPropertyValidator(QQmlTypeCompiler *typeCompiler)
: QQmlCompilePass(typeCompiler)
, enginePrivate(typeCompiler->enginePrivate())
, qmlUnit(typeCompiler->qmlUnit())
, resolvedTypes(*typeCompiler->resolvedTypes())
+ , customParsers(typeCompiler->customParserCache())
, propertyCaches(typeCompiler->propertyCaches())
, objectIndexToIdPerComponent(*typeCompiler->objectIndexToIdPerComponent())
, customParserData(typeCompiler->customParserData())
@@ -1677,10 +1694,7 @@ bool QQmlPropertyValidator::validateObject(int objectIndex, const QV4::CompiledD
}
}
- QQmlCustomParser *customParser = 0;
- QQmlCompiledData::TypeReference *objectType = resolvedTypes.value(obj->inheritedTypeNameIndex);
- if (objectType && objectType->type)
- customParser = objectType->type->customParser();
+ QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex);
QList<const QV4::CompiledData::Binding*> customBindings;
struct GroupPropertyFinder {
@@ -1778,6 +1792,7 @@ bool QQmlPropertyValidator::validateObject(int objectIndex, const QV4::CompiledD
if (notInRevision) {
QString typeName = stringAt(obj->inheritedTypeNameIndex);
+ QQmlCompiledData::TypeReference *objectType = resolvedTypes.value(obj->inheritedTypeNameIndex);
if (objectType && objectType->type) {
COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5.").arg(typeName).arg(name).arg(objectType->type->module()).arg(objectType->majorVersion).arg(objectType->minorVersion));
} else {
@@ -1953,14 +1968,14 @@ bool QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache *propertyCa
case QMetaType::QVariant:
break;
case QVariant::String: {
- if (binding->type != QV4::CompiledData::Binding::Type_String) {
+ if (!binding->evaluatesToString()) {
recordError(binding->valueLocation, tr("Invalid property assignment: string expected"));
return false;
}
}
break;
case QVariant::StringList: {
- if (binding->type != QV4::CompiledData::Binding::Type_String) {
+ if (!binding->evaluatesToString()) {
recordError(binding->valueLocation, tr("Invalid property assignment: string or string list expected"));
return false;
}
@@ -2172,7 +2187,7 @@ bool QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache *propertyCa
}
break;
} else if (property->propType == qMetaTypeId<QList<QString> >()) {
- if (binding->type != QV4::CompiledData::Binding::Type_String) {
+ if (!binding->evaluatesToString()) {
recordError(binding->valueLocation, tr("Invalid property assignment: string or array of strings expected"));
return false;
}
@@ -2301,6 +2316,7 @@ QQmlJSCodeGenerator::QQmlJSCodeGenerator(QQmlTypeCompiler *typeCompiler, QtQml::
: QQmlCompilePass(typeCompiler)
, objectIndexToIdPerComponent(*typeCompiler->objectIndexToIdPerComponent())
, resolvedTypes(*typeCompiler->resolvedTypes())
+ , customParsers(typeCompiler->customParserCache())
, qmlObjects(*typeCompiler->qmlObjects())
, propertyCaches(typeCompiler->propertyCaches())
, v4CodeGen(v4CodeGen)
@@ -2365,16 +2381,12 @@ bool QQmlJSCodeGenerator::compileJavaScriptCodeInObjectsRecursively(int objectIn
QtQml::QmlObject *object = qmlObjects.at(objectIndex);
if (object->functionsAndExpressions->count > 0) {
- bool haveCustomParser = false;
- QQmlCompiledData::TypeReference *objectType = resolvedTypes.value(object->inheritedTypeNameIndex);
- if (objectType && objectType->type)
- haveCustomParser = objectType->type->customParser() != 0;
-
QQmlPropertyCache *scopeObject = propertyCaches.at(scopeObjectIndex);
v4CodeGen->beginObjectScope(scopeObject);
QList<QtQml::CompiledFunctionOrExpression> functionsToCompile;
for (QtQml::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe; foe = foe->next) {
+ const bool haveCustomParser = customParsers.contains(object->inheritedTypeNameIndex);
if (haveCustomParser)
foe->disableAcceleratedLookups = true;
functionsToCompile << *foe;
@@ -2462,4 +2474,296 @@ void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex)
}
}
+QQmlJavaScriptBindingExpressionSimplificationPass::QQmlJavaScriptBindingExpressionSimplificationPass(QQmlTypeCompiler *typeCompiler)
+ : QQmlCompilePass(typeCompiler)
+ , qmlObjects(*typeCompiler->qmlObjects())
+ , customParsers(typeCompiler->customParserCache())
+ , jsModule(typeCompiler->jsIRModule())
+{
+
+}
+
+void QQmlJavaScriptBindingExpressionSimplificationPass::reduceTranslationBindings()
+{
+ for (int i = 0; i < qmlObjects.count(); ++i)
+ reduceTranslationBindings(i);
+ if (!irFunctionsToRemove.isEmpty()) {
+ QQmlIRFunctionCleanser cleanser(compiler, irFunctionsToRemove);
+ cleanser.clean();
+ }
+}
+
+void QQmlJavaScriptBindingExpressionSimplificationPass::reduceTranslationBindings(int objectIndex)
+{
+ const QmlObject *obj = qmlObjects.at(objectIndex);
+ // Don't feed QV4::CompiledData::Binding::Type_Translation into custom parsers.
+ const bool allowTranslations = !customParsers.contains(obj->inheritedTypeNameIndex);
+ if (!allowTranslations)
+ return;
+
+ for (Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
+ if (binding->type != QV4::CompiledData::Binding::Type_Script)
+ continue;
+
+ const int irFunctionIndex = obj->runtimeFunctionIndices->at(binding->value.compiledScriptIndex);
+ QV4::IR::Function *irFunction = jsModule->functions.at(irFunctionIndex);
+ if (simplifyBinding(irFunction, binding)) {
+ irFunctionsToRemove.append(irFunctionIndex);
+ jsModule->functions[irFunctionIndex] = 0;
+ }
+ }
+}
+
+void QQmlJavaScriptBindingExpressionSimplificationPass::visitMove(IR::Move *move)
+{
+ IR::Temp *target = move->target->asTemp();
+ if (!target || target->kind != IR::Temp::VirtualRegister) {
+ discard();
+ return;
+ }
+
+ if (IR::Call *call = move->source->asCall()) {
+ if (IR::Name *n = call->base->asName()) {
+ if (n->builtin == IR::Name::builtin_invalid) {
+ visitFunctionCall(n->id, call->args, target);
+ return;
+ }
+ }
+ discard();
+ return;
+ }
+
+ if (IR::Name *n = move->source->asName()) {
+ if (n->builtin == IR::Name::builtin_qml_id_array
+ || n->builtin == IR::Name::builtin_qml_imported_scripts_object
+ || n->builtin == IR::Name::builtin_qml_context_object
+ || n->builtin == IR::Name::builtin_qml_scope_object) {
+ // these are free of side-effects
+ return;
+ }
+ discard();
+ return;
+ }
+
+ if (!move->source->asTemp() && !move->source->asString() && !move->source->asConst()) {
+ discard();
+ return;
+ }
+
+ _temps[target->index] = move->source;
+}
+
+void QQmlJavaScriptBindingExpressionSimplificationPass::visitFunctionCall(const QString *name, IR::ExprList *args, IR::Temp *target)
+{
+ // more than one function call?
+ if (_nameOfFunctionCalled) {
+ discard();
+ return;
+ }
+
+ _nameOfFunctionCalled = name;
+
+ _functionParameters.clear();
+ while (args) {
+ int slot;
+ if (IR::Temp *param = args->expr->asTemp()) {
+ if (param->kind != IR::Temp::VirtualRegister) {
+ discard();
+ return;
+ }
+ slot = param->index;
+ } else if (IR::Const *param = args->expr->asConst()) {
+ slot = --_synthesizedConsts;
+ Q_ASSERT(!_temps.contains(slot));
+ _temps[slot] = param;
+ }
+ _functionParameters.append(slot);
+ args = args->next;
+ }
+
+ _functionCallReturnValue = target->index;
+}
+
+void QQmlJavaScriptBindingExpressionSimplificationPass::visitRet(QV4::IR::Ret *ret)
+{
+ // nothing initialized earlier?
+ if (_returnValueOfBindingExpression != -1) {
+ discard();
+ return;
+ }
+ IR::Temp *target = ret->expr->asTemp();
+ if (!target || target->kind != IR::Temp::VirtualRegister) {
+ discard();
+ return;
+ }
+ _returnValueOfBindingExpression = target->index;
+}
+
+bool QQmlJavaScriptBindingExpressionSimplificationPass::simplifyBinding(QV4::IR::Function *function, Binding *binding)
+{
+ _canSimplify = true;
+ _nameOfFunctionCalled = 0;
+ _functionParameters.clear();
+ _functionCallReturnValue = -1;
+ _temps.clear();
+ _returnValueOfBindingExpression = -1;
+ _synthesizedConsts = 0;
+
+ // It would seem unlikely that function with some many basic blocks (after optimization)
+ // consists merely of a qsTr call or a constant value return ;-)
+ if (function->basicBlocks.count() > 10)
+ return false;
+
+ foreach (QV4::IR::BasicBlock *bb, function->basicBlocks) {
+ foreach (QV4::IR::Stmt *s, bb->statements) {
+ s->accept(this);
+ if (!_canSimplify)
+ return false;
+ }
+ if (!_canSimplify)
+ return false;
+ }
+
+ if (_returnValueOfBindingExpression == -1)
+ return false;
+
+ if (_canSimplify) {
+ if (_nameOfFunctionCalled) {
+ if (_functionCallReturnValue != _returnValueOfBindingExpression)
+ return false;
+ return detectTranslationCallAndConvertBinding(binding);
+ }
+ }
+
+ return false;
+}
+
+bool QQmlJavaScriptBindingExpressionSimplificationPass::detectTranslationCallAndConvertBinding(Binding *binding)
+{
+ if (*_nameOfFunctionCalled == QStringLiteral("qsTr")) {
+ QString translation;
+ QV4::CompiledData::TranslationData translationData;
+ translationData.number = -1;
+ translationData.commentIndex = 0; // empty string
+
+ QVector<int>::ConstIterator param = _functionParameters.constBegin();
+ QVector<int>::ConstIterator end = _functionParameters.constEnd();
+ if (param == end)
+ return false;
+
+ IR::String *stringParam = _temps[*param]->asString();
+ if (!stringParam)
+ return false;
+
+ translation = *stringParam->value;
+
+ ++param;
+ if (param != end) {
+ stringParam = _temps[*param]->asString();
+ if (!stringParam)
+ return false;
+ translationData.commentIndex = compiler->registerString(*stringParam->value);
+ ++param;
+
+ if (param != end) {
+ IR::Const *constParam = _temps[*param]->asConst();
+ if (!constParam || constParam->type != IR::SInt32Type)
+ return false;
+
+ translationData.number = int(constParam->value);
+ ++param;
+ }
+ }
+
+ if (param != end)
+ return false;
+
+ binding->type = QV4::CompiledData::Binding::Type_Translation;
+ binding->stringIndex = compiler->registerString(translation);
+ binding->value.translationData = translationData;
+ return true;
+ } else if (*_nameOfFunctionCalled == QStringLiteral("qsTrId")) {
+ QString id;
+ QV4::CompiledData::TranslationData translationData;
+ translationData.number = -1;
+ translationData.commentIndex = 0; // empty string, but unused
+
+ QVector<int>::ConstIterator param = _functionParameters.constBegin();
+ QVector<int>::ConstIterator end = _functionParameters.constEnd();
+ if (param == end)
+ return false;
+
+ IR::String *stringParam = _temps[*param]->asString();
+ if (!stringParam)
+ return false;
+
+ id = *stringParam->value;
+
+ ++param;
+ if (param != end) {
+ IR::Const *constParam = _temps[*param]->asConst();
+ if (!constParam || constParam->type != IR::SInt32Type)
+ return false;
+
+ translationData.number = int(constParam->value);
+ ++param;
+ }
+
+ if (param != end)
+ return false;
+
+ binding->type = QV4::CompiledData::Binding::Type_TranslationById;
+ binding->stringIndex = compiler->registerString(id);
+ binding->value.translationData = translationData;
+ return true;
+ }
+ return false;
+}
+
+QQmlIRFunctionCleanser::QQmlIRFunctionCleanser(QQmlTypeCompiler *typeCompiler, const QVector<int> &functionsToRemove)
+ : QQmlCompilePass(typeCompiler)
+ , module(typeCompiler->jsIRModule())
+ , functionsToRemove(functionsToRemove)
+{
+}
+
+void QQmlIRFunctionCleanser::clean()
+{
+ QVector<QV4::IR::Function*> newFunctions;
+ newFunctions.reserve(module->functions.count() - functionsToRemove.count());
+
+ newFunctionIndices.resize(module->functions.count());
+
+ for (int i = 0; i < module->functions.count(); ++i) {
+ QV4::IR::Function *f = module->functions.at(i);
+ Q_ASSERT(f || functionsToRemove.contains(i));
+ if (f) {
+ newFunctionIndices[i] = newFunctions.count();
+ newFunctions << f;
+ }
+ }
+
+ module->functions = newFunctions;
+
+ foreach (IR::Function *function, module->functions) {
+ foreach (IR::BasicBlock *block, function->basicBlocks) {
+ foreach (IR::Stmt *s, block->statements) {
+ s->accept(this);
+ }
+ }
+ }
+
+ foreach (QmlObject *obj, *compiler->qmlObjects()) {
+ if (!obj->runtimeFunctionIndices)
+ continue;
+ for (int i = 0; i < obj->runtimeFunctionIndices->count; ++i)
+ (*obj->runtimeFunctionIndices)[i] = newFunctionIndices[obj->runtimeFunctionIndices->at(i)];
+ }
+}
+
+void QQmlIRFunctionCleanser::visitClosure(QV4::IR::Closure *closure)
+{
+ closure->value = newFunctionIndices.at(closure->value);
+}
+
QT_END_NAMESPACE
diff --git a/src/qml/compiler/qqmltypecompiler_p.h b/src/qml/compiler/qqmltypecompiler_p.h
index 73ca9de8b8..74168fee41 100644
--- a/src/qml/compiler/qqmltypecompiler_p.h
+++ b/src/qml/compiler/qqmltypecompiler_p.h
@@ -79,6 +79,8 @@ public:
QString stringAt(int idx) const;
int registerString(const QString &str);
+ QV4::IR::Module *jsIRModule() const;
+
const QV4::CompiledData::QmlUnit *qmlUnit() const;
QUrl url() const { return compiledData->url; }
@@ -100,12 +102,16 @@ public:
void setCustomParserBindings(const QVector<int> &bindings);
void setDeferredBindingsPerObject(const QHash<int, QBitArray> &deferredBindingsPerObject);
+ const QHash<int, QQmlCustomParser*> &customParserCache() const { return customParsers; }
+
private:
QList<QQmlError> errors;
QQmlEnginePrivate *engine;
QQmlCompiledData *compiledData;
QQmlTypeData *typeData;
QtQml::ParsedQML *parsedQML;
+ // index is string index of type name (use obj->inheritedTypeNameIndex)
+ QHash<int, QQmlCustomParser*> customParsers;
};
struct QQmlCompilePass
@@ -158,6 +164,7 @@ private:
bool convertSignalHandlerExpressionsToFunctionDeclarations(const QmlObject *obj, const QString &typeName, QQmlPropertyCache *propertyCache);
const QList<QtQml::QmlObject*> &qmlObjects;
+ const QHash<int, QQmlCustomParser*> &customParsers;
const QHash<int, QQmlCompiledData::TypeReference*> &resolvedTypes;
const QSet<QString> &illegalNames;
const QVector<QQmlPropertyCache*> &propertyCaches;
@@ -261,6 +268,7 @@ private:
QQmlEnginePrivate *enginePrivate;
const QV4::CompiledData::QmlUnit *qmlUnit;
const QHash<int, QQmlCompiledData::TypeReference*> &resolvedTypes;
+ const QHash<int, QQmlCustomParser*> &customParsers;
const QVector<QQmlPropertyCache *> &propertyCaches;
const QHash<int, QHash<int, int> > objectIndexToIdPerComponent;
QHash<int, QQmlCompiledData::CustomParserData> *customParserData;
@@ -286,6 +294,7 @@ private:
const QHash<int, QHash<int, int> > &objectIndexToIdPerComponent;
const QHash<int, QQmlCompiledData::TypeReference*> &resolvedTypes;
+ const QHash<int, QQmlCustomParser*> &customParsers;
const QList<QtQml::QmlObject*> &qmlObjects;
const QVector<QQmlPropertyCache *> &propertyCaches;
QtQml::JSCodeGen * const v4CodeGen;
@@ -305,6 +314,107 @@ private:
const QVector<QQmlPropertyCache*> &propertyCaches;
};
+class QQmlJavaScriptBindingExpressionSimplificationPass : public QQmlCompilePass, public QV4::IR::StmtVisitor
+{
+public:
+ QQmlJavaScriptBindingExpressionSimplificationPass(QQmlTypeCompiler *typeCompiler);
+
+ void reduceTranslationBindings();
+
+private:
+ void reduceTranslationBindings(int objectIndex);
+
+ virtual void visitMove(QV4::IR::Move *move);
+ virtual void visitJump(QV4::IR::Jump *) {}
+ virtual void visitCJump(QV4::IR::CJump *) { discard(); }
+ virtual void visitExp(QV4::IR::Exp *) { discard(); }
+ virtual void visitPhi(IR::Phi *) {}
+ virtual void visitRet(QV4::IR::Ret *ret);
+
+ void visitFunctionCall(const QString *name, IR::ExprList *args, IR::Temp *target);
+
+ void discard() { _canSimplify = false; }
+
+ bool simplifyBinding(QV4::IR::Function *function, Binding *binding);
+ bool detectTranslationCallAndConvertBinding(Binding *binding);
+
+ const QList<QtQml::QmlObject*> &qmlObjects;
+ const QHash<int, QQmlCustomParser*> &customParsers;
+ QV4::IR::Module *jsModule;
+
+ bool _canSimplify;
+ const QString *_nameOfFunctionCalled;
+ QVector<int> _functionParameters;
+ int _functionCallReturnValue;
+
+ QHash<int, IR::Expr*> _temps;
+ int _returnValueOfBindingExpression;
+ int _synthesizedConsts;
+
+ QVector<int> irFunctionsToRemove;
+};
+
+class QQmlIRFunctionCleanser : public QQmlCompilePass, public QV4::IR::StmtVisitor,
+ public QV4::IR::ExprVisitor
+{
+public:
+ QQmlIRFunctionCleanser(QQmlTypeCompiler *typeCompiler, const QVector<int> &functionsToRemove);
+
+ void clean();
+
+private:
+ virtual void visitClosure(QV4::IR::Closure *closure);
+
+ virtual void visitTemp(QV4::IR::Temp *) {}
+
+ virtual void visitMove(QV4::IR::Move *s) {
+ s->source->accept(this);
+ s->target->accept(this);
+ }
+
+ virtual void visitConvert(QV4::IR::Convert *e) { e->expr->accept(this); }
+ virtual void visitPhi(QV4::IR::Phi *) { }
+
+ virtual void visitExp(QV4::IR::Exp *s) { s->expr->accept(this); }
+
+ virtual void visitJump(QV4::IR::Jump *) {}
+ virtual void visitCJump(QV4::IR::CJump *s) { s->cond->accept(this); }
+ virtual void visitRet(QV4::IR::Ret *s) { s->expr->accept(this); }
+
+ virtual void visitConst(QV4::IR::Const *) {}
+ virtual void visitString(QV4::IR::String *) {}
+ virtual void visitRegExp(QV4::IR::RegExp *) {}
+ virtual void visitName(QV4::IR::Name *) {}
+ virtual void visitUnop(QV4::IR::Unop *e) { e->expr->accept(this); }
+ virtual void visitBinop(QV4::IR::Binop *e) { e->left->accept(this); e->right->accept(this); }
+ virtual void visitCall(QV4::IR::Call *e) {
+ e->base->accept(this);
+ for (QV4::IR::ExprList *it = e->args; it; it = it->next)
+ it->expr->accept(this);
+ }
+
+ virtual void visitNew(QV4::IR::New *e) {
+ e->base->accept(this);
+ for (QV4::IR::ExprList *it = e->args; it; it = it->next)
+ it->expr->accept(this);
+ }
+
+ virtual void visitSubscript(QV4::IR::Subscript *e) {
+ e->base->accept(this);
+ e->index->accept(this);
+ }
+
+ virtual void visitMember(QV4::IR::Member *e) {
+ e->base->accept(this);
+ }
+
+private:
+ QV4::IR::Module *module;
+ const QVector<int> &functionsToRemove;
+
+ QVector<int> newFunctionIndices;
+};
+
QT_END_NAMESPACE
#endif // QQMLTYPECOMPILER_P_H
diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp
index 0ddc75488c..38c84140d3 100644
--- a/src/qml/compiler/qv4compileddata.cpp
+++ b/src/qml/compiler/qv4compileddata.cpp
@@ -46,6 +46,7 @@
#include <private/qv4objectproto_p.h>
#include <private/qv4lookup_p.h>
#include <private/qv4regexpobject_p.h>
+#include <QCoreApplication>
#include <algorithm>
@@ -190,6 +191,22 @@ QString Binding::valueAsString(const Unit *unit) const
return QString::number(value.d);
case Type_Invalid:
return QString();
+ case Type_TranslationById: {
+ QByteArray id = unit->stringAt(stringIndex).toUtf8();
+ return qtTrId(id.constData(), value.translationData.number);
+ }
+ case Type_Translation: {
+ // This code must match that in the qsTr() implementation
+ const QString &path = unit->stringAt(unit->sourceFileIndex);
+ int lastSlash = path.lastIndexOf(QLatin1Char('/'));
+ QString context = (lastSlash > -1) ? path.mid(lastSlash + 1, path.length()-lastSlash-5) :
+ QString();
+ QByteArray contextUtf8 = context.toUtf8();
+ QByteArray comment = unit->stringAt(value.translationData.commentIndex).toUtf8();
+ QByteArray text = unit->stringAt(stringIndex).toUtf8();
+ return QCoreApplication::translate(contextUtf8.constData(), text.constData(),
+ comment.constData(), value.translationData.number);
+ }
default:
break;
}
diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h
index 2d6e767ff3..186363dcde 100644
--- a/src/qml/compiler/qv4compileddata_p.h
+++ b/src/qml/compiler/qv4compileddata_p.h
@@ -280,6 +280,11 @@ struct Function
// Qml data structures
+struct Q_QML_EXPORT TranslationData {
+ quint32 commentIndex;
+ int number;
+};
+
struct Q_QML_EXPORT Binding
{
quint32 propertyNameIndex;
@@ -289,6 +294,8 @@ struct Q_QML_EXPORT Binding
Type_Boolean,
Type_Number,
Type_String,
+ Type_Translation,
+ Type_TranslationById,
Type_Script,
Type_Object,
Type_AttachedProperty,
@@ -312,8 +319,9 @@ struct Q_QML_EXPORT Binding
double d;
quint32 compiledScriptIndex; // used when Type_Script
quint32 objectIndex;
+ TranslationData translationData; // used when Type_Translation
} value;
- quint32 stringIndex; // Set for Type_String and Type_Script (the latter because of script strings)
+ quint32 stringIndex; // Set for Type_String, Type_Translation and Type_Script (the latter because of script strings)
Location location;
Location valueLocation;
@@ -365,6 +373,8 @@ struct Q_QML_EXPORT Binding
return false;
}
+ bool evaluatesToString() const { return type == Type_String || type == Type_Translation || type == Type_TranslationById; }
+
QString valueAsString(const Unit *unit) const;
QString valueAsScriptString(const Unit *unit) const;
double valueAsNumber() const
diff --git a/src/qml/compiler/qv4jsir_p.h b/src/qml/compiler/qv4jsir_p.h
index 9b720ba6a2..a333214a8b 100644
--- a/src/qml/compiler/qv4jsir_p.h
+++ b/src/qml/compiler/qv4jsir_p.h
@@ -422,6 +422,9 @@ struct Q_AUTOTEST_EXPORT Temp: Expr {
inline bool operator==(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW
{ return t1.index == t2.index && t1.scope == t2.scope && t1.kind == t2.kind && t1.type == t2.type; }
+inline bool operator!=(const Temp &t1, const Temp &t2) Q_DECL_NOTHROW
+{ return !(t1 == t2); }
+
inline uint qHash(const Temp &t, uint seed = 0) Q_DECL_NOTHROW
{ return t.index ^ (t.kind | (t.scope << 3)) ^ seed; }
diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp
index 150401a358..264ada2507 100644
--- a/src/qml/qml/qqmlobjectcreator.cpp
+++ b/src/qml/qml/qqmlobjectcreator.cpp
@@ -359,14 +359,14 @@ void QQmlObjectCreator::setPropertyValue(QQmlPropertyData *property, const QV4::
}
break;
case QVariant::String: {
- Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_String);
+ Q_ASSERT(binding->evaluatesToString());
QString value = binding->valueAsString(&qmlUnit->header);
argv[0] = &value;
QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, property->coreIndex, argv);
}
break;
case QVariant::StringList: {
- Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_String);
+ Q_ASSERT(binding->evaluatesToString());
QStringList value(binding->valueAsString(&qmlUnit->header));
argv[0] = &value;
QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, property->coreIndex, argv);
@@ -586,7 +586,7 @@ void QQmlObjectCreator::setPropertyValue(QQmlPropertyData *property, const QV4::
QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, property->coreIndex, argv);
break;
} else if (property->propType == qMetaTypeId<QList<QString> >()) {
- Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_String);
+ Q_ASSERT(binding->evaluatesToString());
QList<QString> value;
value.append(binding->valueAsString(&qmlUnit->header));
argv[0] = reinterpret_cast<void *>(&value);
diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h
index a42412754f..9cd28a03ce 100644
--- a/src/qml/qml/qqmltypeloader_p.h
+++ b/src/qml/qml/qqmltypeloader_p.h
@@ -264,7 +264,7 @@ public:
QString fileName;
};
-class QQmlTypeLoader : public QQmlDataLoader
+class Q_AUTOTEST_EXPORT QQmlTypeLoader : public QQmlDataLoader
{
Q_DECLARE_TR_FUNCTIONS(QQmlTypeLoader)
public:
diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp
index dd52dfc1d4..37742a3667 100644
--- a/src/qml/types/qqmllistmodel.cpp
+++ b/src/qml/types/qqmllistmodel.cpp
@@ -2369,6 +2369,8 @@ bool QQmlListModelParser::compileProperty(const QV4::CompiledData::QmlUnit *qmlU
d += QByteArray::number(v);
}
}
+ } else {
+ Q_UNREACHABLE();
}
d.append('\0');
diff --git a/src/quick/util/qquickpropertychanges.cpp b/src/quick/util/qquickpropertychanges.cpp
index 6ff98b8582..39a1c1b861 100644
--- a/src/quick/util/qquickpropertychanges.cpp
+++ b/src/quick/util/qquickpropertychanges.cpp
@@ -287,6 +287,9 @@ QByteArray QQuickPropertyChangesParser::compile(const QV4::CompiledData::QmlUnit
case QV4::CompiledData::Binding::Type_Boolean:
var = binding->valueAsBoolean();
break;
+ case QV4::CompiledData::Binding::Type_Translation:
+ case QV4::CompiledData::Binding::Type_TranslationById:
+ Q_UNREACHABLE();
default:
break;
}
diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro
index 60c83a11b2..74369685f5 100644
--- a/tests/auto/qml/qml.pro
+++ b/tests/auto/qml/qml.pro
@@ -21,7 +21,6 @@ PUBLICTESTS += \
qqmlmoduleplugin \
qqmlnotifier \
qqmlqt \
- qqmltranslation \
qqmlxmlhttprequest \
qtqmlmodules \
qquickfolderlistmodel \
@@ -58,7 +57,8 @@ PRIVATETESTS += \
qqmlinstantiator \
qv4debugger \
qqmlenginecleanup \
- v4misc
+ v4misc \
+ qqmltranslation
qtHaveModule(widgets) {
PUBLICTESTS += \
diff --git a/tests/auto/qml/qqmltranslation/data/translation.qml b/tests/auto/qml/qqmltranslation/data/translation.qml
index 8435bedb28..b96471c9dd 100644
--- a/tests/auto/qml/qqmltranslation/data/translation.qml
+++ b/tests/auto/qml/qqmltranslation/data/translation.qml
@@ -1,13 +1,15 @@
import QtQuick 2.0
QtObject {
+ objectName: "test"
+
property string basic: qsTr("hello")
property string basic2: qsTranslate("CustomContext", "goodbye")
- property string basic3: if (1) qsTr("hello")
+ property string basic3: if (objectName.length > 0) qsTr("hello")
property string disambiguation: qsTr("hi", "informal 'hello'")
property string disambiguation2: qsTranslate("CustomContext", "see ya", "informal 'goodbye'")
- property string disambiguation3: if (1) qsTr("hi", "informal 'hello'")
+ property string disambiguation3: if (objectName.length > 0) qsTr("hi", "informal 'hello'")
property string _noop: QT_TR_NOOP("hello")
property string _noop2: QT_TRANSLATE_NOOP("CustomContext", "goodbye")
@@ -15,7 +17,7 @@ QtObject {
property string noop2: qsTranslate("CustomContext", _noop2)
property string singular: qsTr("%n duck(s)", "", 1)
- property string singular2: if (1) qsTr("%n duck(s)", "", 1)
+ property string singular2: if (objectName.length > 0) qsTr("%n duck(s)", "", 1)
property string plural: qsTr("%n duck(s)", "", 2)
- property string plural2: if (1) qsTr("%n duck(s)", "", 2)
+ property string plural2: if (objectName.length > 0) qsTr("%n duck(s)", "", 2)
}
diff --git a/tests/auto/qml/qqmltranslation/tst_qqmltranslation.cpp b/tests/auto/qml/qqmltranslation/tst_qqmltranslation.cpp
index 01e1cf85fb..d8e3d99e84 100644
--- a/tests/auto/qml/qqmltranslation/tst_qqmltranslation.cpp
+++ b/tests/auto/qml/qqmltranslation/tst_qqmltranslation.cpp
@@ -43,6 +43,9 @@
#include <QQmlEngine>
#include <QQmlComponent>
#include <QTranslator>
+#include <QQmlContext>
+#include <private/qqmlcompiler_p.h>
+#include <private/qqmlengine_p.h>
#include "../../shared/util.h"
class tst_qqmltranslation : public QQmlDataTest
@@ -61,16 +64,18 @@ void tst_qqmltranslation::translation_data()
{
QTest::addColumn<QString>("translation");
QTest::addColumn<QUrl>("testFile");
+ QTest::addColumn<bool>("verifyCompiledData");
- QTest::newRow("qml") << QStringLiteral("qml_fr") << testFileUrl("translation.qml");
- QTest::newRow("qrc") << QStringLiteral(":/qml_fr.qm") << QUrl("qrc:/translation.qml");
- QTest::newRow("js") << QStringLiteral("qml_fr") << testFileUrl("jstranslation.qml");
+ QTest::newRow("qml") << QStringLiteral("qml_fr") << testFileUrl("translation.qml") << true;
+ QTest::newRow("qrc") << QStringLiteral(":/qml_fr.qm") << QUrl("qrc:/translation.qml") << true;
+ QTest::newRow("js") << QStringLiteral("qml_fr") << testFileUrl("jstranslation.qml") << false;
}
void tst_qqmltranslation::translation()
{
QFETCH(QString, translation);
QFETCH(QUrl, testFile);
+ QFETCH(bool, verifyCompiledData);
QTranslator translator;
translator.load(translation, dataDirectory());
@@ -81,6 +86,38 @@ void tst_qqmltranslation::translation()
QObject *object = component.create();
QVERIFY(object != 0);
+ if (verifyCompiledData) {
+ QQmlContext *context = qmlContext(object);
+ QQmlEnginePrivate *engine = QQmlEnginePrivate::get(context->engine());
+ QQmlTypeData *typeData = engine->typeLoader.getType(context->baseUrl());
+ QQmlCompiledData *cdata = typeData->compiledData();
+ QVERIFY(cdata);
+
+ QSet<QString> compiledTranslations;
+ compiledTranslations << QStringLiteral("basic")
+ << QStringLiteral("disambiguation")
+ << QStringLiteral("singular") << QStringLiteral("plural");
+
+ const QV4::CompiledData::QmlUnit *unit = cdata->qmlUnit;
+ const QV4::CompiledData::Object *rootObject = unit->objectAt(unit->indexOfRootObject);
+ const QV4::CompiledData::Binding *binding = rootObject->bindingTable();
+ for (quint32 i = 0; i < rootObject->nBindings; ++i, ++binding) {
+ const QString propertyName = unit->header.stringAt(binding->propertyNameIndex);
+
+ const bool expectCompiledTranslation = compiledTranslations.contains(propertyName);
+
+ if (expectCompiledTranslation) {
+ if (binding->type != QV4::CompiledData::Binding::Type_Translation)
+ qDebug() << "binding for property" << propertyName << "is not a compiled translation";
+ QCOMPARE(binding->type, quint32(QV4::CompiledData::Binding::Type_Translation));
+ } else {
+ if (binding->type == QV4::CompiledData::Binding::Type_Translation)
+ qDebug() << "binding for property" << propertyName << "is not supposed to be a compiled translation";
+ QVERIFY(binding->type != QV4::CompiledData::Binding::Type_Translation);
+ }
+ }
+ }
+
QCOMPARE(object->property("basic").toString(), QLatin1String("bonjour"));
QCOMPARE(object->property("basic2").toString(), QLatin1String("au revoir"));
QCOMPARE(object->property("basic3").toString(), QLatin1String("bonjour"));
@@ -109,6 +146,28 @@ void tst_qqmltranslation::idTranslation()
QObject *object = component.create();
QVERIFY(object != 0);
+ {
+ QQmlContext *context = qmlContext(object);
+ QQmlEnginePrivate *engine = QQmlEnginePrivate::get(context->engine());
+ QQmlTypeData *typeData = engine->typeLoader.getType(context->baseUrl());
+ QQmlCompiledData *cdata = typeData->compiledData();
+ QVERIFY(cdata);
+
+ const QV4::CompiledData::QmlUnit *unit = cdata->qmlUnit;
+ const QV4::CompiledData::Object *rootObject = unit->objectAt(unit->indexOfRootObject);
+ const QV4::CompiledData::Binding *binding = rootObject->bindingTable();
+ for (quint32 i = 0; i < rootObject->nBindings; ++i, ++binding) {
+ const QString propertyName = unit->header.stringAt(binding->propertyNameIndex);
+ if (propertyName == "idTranslation") {
+ if (binding->type != QV4::CompiledData::Binding::Type_TranslationById)
+ qDebug() << "binding for property" << propertyName << "is not a compiled translation";
+ QCOMPARE(binding->type, quint32(QV4::CompiledData::Binding::Type_TranslationById));
+ } else {
+ QVERIFY(binding->type != QV4::CompiledData::Binding::Type_Translation);
+ }
+ }
+ }
+
QCOMPARE(object->property("idTranslation").toString(), QLatin1String("bonjour tout le monde"));
QCOMPARE(object->property("idTranslation2").toString(), QLatin1String("bonjour tout le monde"));
QCOMPARE(object->property("idTranslation3").toString(), QLatin1String("bonjour tout le monde"));