diff options
author | Simon Hausmann <simon.hausmann@digia.com> | 2014-03-06 16:19:42 +0100 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2014-03-11 18:31:21 +0100 |
commit | 914b72418b7e766026f2679254fcee93fc920866 (patch) | |
tree | 202634bd203c830ddc7eeb7dab1d071ca506b8a3 | |
parent | af7ba8a6194b83fe7380b8d4ae027e2f04e21f17 (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.h | 8 | ||||
-rw-r--r-- | src/qml/compiler/qqmltypecompiler.cpp | 340 | ||||
-rw-r--r-- | src/qml/compiler/qqmltypecompiler_p.h | 110 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata.cpp | 17 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata_p.h | 12 | ||||
-rw-r--r-- | src/qml/compiler/qv4jsir_p.h | 3 | ||||
-rw-r--r-- | src/qml/qml/qqmlobjectcreator.cpp | 6 | ||||
-rw-r--r-- | src/qml/qml/qqmltypeloader_p.h | 2 | ||||
-rw-r--r-- | src/qml/types/qqmllistmodel.cpp | 2 | ||||
-rw-r--r-- | src/quick/util/qquickpropertychanges.cpp | 3 | ||||
-rw-r--r-- | tests/auto/qml/qml.pro | 4 | ||||
-rw-r--r-- | tests/auto/qml/qqmltranslation/data/translation.qml | 10 | ||||
-rw-r--r-- | tests/auto/qml/qqmltranslation/tst_qqmltranslation.cpp | 65 |
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")); |