QmlCompiler: Inline translation methods

We hardcode them into QQmlJSTypePropagator and QQmlJSCodegenerator for
now. This is OK for builtins.

Task-number: QTBUG-101387
Change-Id: Ifab46083b3a782f009859ce969c283d5bb2b4e8b
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2022-05-25 15:46:13 +02:00
parent 1b6069798a
commit 23fdccf7f3
13 changed files with 347 additions and 60 deletions

View File

@ -19,6 +19,7 @@
#include <private/qv4qobjectwrapper_p.h>
#include <private/qv4identifiertable_p.h>
#include <private/qv4errorobject_p.h>
#include <private/qqmlbuiltinfunctions_p.h>
#include <private/qqmlfinalizer_p.h>
#include <QtCore/qmutex.h>
@ -763,18 +764,26 @@ void AOTCompiledContext::setReturnValueUndefined() const
}
}
static void captureFallbackProperty(
QObject *object, int coreIndex, int notifyIndex, bool isConstant,
QQmlContextData *qmlContext)
static QQmlPropertyCapture *propertyCapture(const QQmlContextData *qmlContext)
{
if (!qmlContext || isConstant)
return;
if (!qmlContext)
return nullptr;
QQmlEngine *engine = qmlContext->engine();
Q_ASSERT(engine);
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
Q_ASSERT(ep);
if (QQmlPropertyCapture *capture = ep->propertyCapture)
return ep->propertyCapture;
}
static void captureFallbackProperty(
QObject *object, int coreIndex, int notifyIndex, bool isConstant,
const QQmlContextData *qmlContext)
{
if (isConstant)
return;
if (QQmlPropertyCapture *capture = propertyCapture(qmlContext))
capture->captureProperty(object, coreIndex, notifyIndex);
}
@ -782,14 +791,10 @@ static void captureObjectProperty(
QObject *object, const QQmlPropertyCache *propertyCache,
const QQmlPropertyData *property, QQmlContextData *qmlContext)
{
if (!qmlContext || property->isConstant())
if (property->isConstant())
return;
QQmlEngine *engine = qmlContext->engine();
Q_ASSERT(engine);
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine);
Q_ASSERT(ep);
if (QQmlPropertyCapture *capture = ep->propertyCapture)
if (QQmlPropertyCapture *capture = propertyCapture(qmlContext))
capture->captureProperty(object, propertyCache, property);
}
@ -1059,6 +1064,21 @@ bool AOTCompiledContext::captureQmlContextPropertyLookup(uint index) const
return false;
}
void AOTCompiledContext::captureTranslation() const
{
if (QQmlPropertyCapture *capture = propertyCapture(qmlContext))
capture->captureTranslation();
}
QString AOTCompiledContext::translationContext() const
{
#if QT_CONFIG(translation)
return QV4::GlobalExtensions::currentTranslationContext(engine->handle());
#else
return QString();
#endif
}
QMetaType AOTCompiledContext::lookupResultMetaType(uint index) const
{
QV4::Lookup *l = compilationUnit->runtimeLookups + index;

View File

@ -1942,6 +1942,42 @@ ReturnedValue GlobalExtensions::method_qsTranslateNoOp(const FunctionObject *b,
return argv[1].asReturnedValue();
}
QString GlobalExtensions::currentTranslationContext(ExecutionEngine *engine)
{
QString context;
CppStackFrame *frame = engine->currentStackFrame;
// The first non-empty source URL in the call stack determines the translation context.
while (frame && context.isEmpty()) {
if (CompiledData::CompilationUnitBase *baseUnit = frame->v4Function->compilationUnit) {
const auto *unit = static_cast<const CompiledData::CompilationUnit *>(baseUnit);
QString fileName = unit->fileName();
QUrl url(unit->fileName());
if (url.isValid() && url.isRelative()) {
context = url.fileName();
} else {
context = QQmlFile::urlToLocalFileOrQrc(fileName);
if (context.isEmpty() && fileName.startsWith(QLatin1String(":/")))
context = fileName;
}
context = QFileInfo(context).baseName();
}
frame = frame->parentFrame();
}
if (context.isEmpty()) {
if (QQmlRefPointer<QQmlContextData> ctxt = engine->callingQmlContext()) {
QString path = ctxt->urlString();
int lastSlash = path.lastIndexOf(QLatin1Char('/'));
int lastDot = path.lastIndexOf(QLatin1Char('.'));
int length = lastDot - (lastSlash + 1);
context = (lastSlash > -1) ? path.mid(lastSlash + 1, (length > -1) ? length : -1) : QString();
}
}
return context;
}
/*!
\qmlmethod string Qt::qsTr(string sourceText, string disambiguation, int n)
@ -1971,43 +2007,10 @@ ReturnedValue GlobalExtensions::method_qsTr(const FunctionObject *b, const Value
if ((argc > 2) && !argv[2].isNumber())
THROW_GENERIC_ERROR("qsTr(): third argument (n) must be a number");
QString context;
CppStackFrame *frame = scope.engine->currentStackFrame;
// The first non-empty source URL in the call stack determines the translation context.
while (frame && context.isEmpty()) {
if (CompiledData::CompilationUnitBase *baseUnit = frame->v4Function->compilationUnit) {
const auto *unit = static_cast<const CompiledData::CompilationUnit *>(baseUnit);
QString fileName = unit->fileName();
QUrl url(unit->fileName());
if (url.isValid() && url.isRelative()) {
context = url.fileName();
} else {
context = QQmlFile::urlToLocalFileOrQrc(fileName);
if (context.isEmpty() && fileName.startsWith(QLatin1String(":/")))
context = fileName;
}
context = QFileInfo(context).baseName();
}
frame = frame->parentFrame();
}
if (context.isEmpty()) {
if (QQmlRefPointer<QQmlContextData> ctxt = scope.engine->callingQmlContext()) {
QString path = ctxt->urlString();
int lastSlash = path.lastIndexOf(QLatin1Char('/'));
int lastDot = path.lastIndexOf(QLatin1Char('.'));
int length = lastDot - (lastSlash + 1);
context = (lastSlash > -1) ? path.mid(lastSlash + 1, (length > -1) ? length : -1) : QString();
}
}
QString text = argv[0].toQStringNoThrow();
QString comment;
if (argc > 1)
comment = argv[1].toQStringNoThrow();
int n = -1;
if (argc > 2)
n = argv[2].toInt32();
const QString context = currentTranslationContext(scope.engine);
const QString text = argv[0].toQStringNoThrow();
const QString comment = argc > 1 ? argv[1].toQStringNoThrow() : QString();
const int n = argc > 2 ? argv[2].toInt32() : -1;
if (QQmlEnginePrivate *ep = (scope.engine->qmlEngine() ? QQmlEnginePrivate::get(scope.engine->qmlEngine()) : nullptr))
if (ep->propertyCapture)

View File

@ -209,6 +209,7 @@ struct Q_QML_PRIVATE_EXPORT GlobalExtensions {
static void init(Object *globalObject, QJSEngine::Extensions extensions);
#if QT_CONFIG(translation)
static QString currentTranslationContext(ExecutionEngine *engine);
static ReturnedValue method_qsTranslate(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_qsTranslateNoOp(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc);
static ReturnedValue method_qsTr(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc);

View File

@ -600,6 +600,8 @@ namespace QQmlPrivate
// Run QQmlPropertyCapture::captureProperty() without retrieving the value.
bool captureLookup(uint index, QObject *object) const;
bool captureQmlContextPropertyLookup(uint index) const;
void captureTranslation() const;
QString translationContext() const;
QMetaType lookupResultMetaType(uint index) const;
void storeNameSloppy(uint nameIndex, void *value, QMetaType type) const;
QJSValue javaScriptGlobalProperty(uint nameIndex) const;

View File

@ -1166,6 +1166,81 @@ void QQmlJSCodeGenerator::generate_CallProperty(int nameIndex, int baseReg, int
reject(u"CallProperty"_s);
}
bool QQmlJSCodeGenerator::inlineTranslateMethod(const QString &name, int argc, int argv)
{
addInclude(u"qcoreapplication.h"_s);
const auto arg = [&](int i, const QQmlJSScope::ConstPtr &type) {
Q_ASSERT(i < argc);
return conversion(registerType(argv + i).storedType(), type, registerVariable(argv + i));
};
const auto stringArg = [&](int i) {
return i < argc
? (arg(i, m_typeResolver->stringType()) + u".toUtf8().constData()"_s)
: u"\"\""_s;
};
const auto intArg = [&](int i) {
return i < argc ? arg(i, m_typeResolver->intType()) : u"-1"_s;
};
const auto stringRet = [&](const QString &expression) {
return conversion(
m_typeResolver->stringType(), m_state.accumulatorOut().storedType(), expression);
};
const auto capture = [&]() {
m_body += u"aotContext->captureTranslation();\n"_s;
};
if (name == u"QT_TRID_NOOP"_s || name == u"QT_TR_NOOP"_s) {
Q_ASSERT(argc > 0);
m_body += m_state.accumulatorVariableOut + u" = "_s
+ stringRet(arg(0, m_typeResolver->stringType())) + u";\n"_s;
return true;
}
if (name == u"QT_TRANSLATE_NOOP"_s) {
Q_ASSERT(argc > 1);
m_body += m_state.accumulatorVariableOut + u" = "_s
+ stringRet(arg(1, m_typeResolver->stringType())) + u";\n"_s;
return true;
}
if (name == u"qsTrId"_s) {
capture();
// We inline qtTrId() here because in the !QT_CONFIG(translation) case it's unavailable.
// QCoreApplication::translate() is always available in some primitive form.
// Also, this saves a function call.
m_body += m_state.accumulatorVariableOut + u" = "_s
+ stringRet(u"QCoreApplication::translate(nullptr, "_s + stringArg(0) +
u", nullptr, "_s + intArg(1) + u")"_s) + u";\n"_s;
return true;
}
if (name == u"qsTr"_s) {
capture();
m_body += m_state.accumulatorVariableOut + u" = "_s
+ stringRet(u"QCoreApplication::translate("_s
+ u"aotContext->translationContext().toUtf8().constData(), "_s
+ stringArg(0) + u", "_s + stringArg(1) + u", "_s
+ intArg(2) + u")"_s) + u";\n"_s;
return true;
}
if (name == u"qsTranslate"_s) {
capture();
m_body += m_state.accumulatorVariableOut + u" = "_s
+ stringRet(u"QCoreApplication::translate("_s
+ stringArg(0) + u", "_s + stringArg(1) + u", "_s
+ stringArg(2) + u", "_s + intArg(3) + u")"_s) + u";\n"_s;
return true;
}
return false;
}
bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int argv)
{
addInclude(u"cmath"_s);
@ -1372,6 +1447,14 @@ void QQmlJSCodeGenerator::generate_CallQmlContextPropertyLookup(int index, int a
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptReturnValue)
reject(u"call to untyped JavaScript function"_s);
if (m_typeResolver->equals(m_state.accumulatorOut().scopeType(),
m_typeResolver->jsGlobalObject())) {
const QString name = m_jsUnitGenerator->stringForIndex(
m_jsUnitGenerator->lookupNameIndex(index));
if (inlineTranslateMethod(name, argc, argv))
return;
}
AccumulatorConverter registers(this);
const QString indexString = QString::number(index);

View File

@ -261,7 +261,9 @@ private:
QString argumentsList(int argc, int argv, QString *outVar);
QString castTargetName(const QQmlJSScope::ConstPtr &type) const;
bool inlineTranslateMethod(const QString &name, int argc, int argv);
bool inlineMathMethod(const QString &name, int argc, int argv);
QQmlJSScope::ConstPtr mathObject() const
{
using namespace Qt::StringLiterals;

View File

@ -1027,7 +1027,7 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
}
addReadRegister(base, callBase);
propagateCall(member.method(), argc, argv);
propagateCall(member.method(), argc, argv, member.scopeType());
}
QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMethod> &methods,
@ -1149,7 +1149,9 @@ void QQmlJSTypePropagator::addReadRegister(int index, const QQmlJSRegisterConten
m_state.addReadRegister(index, m_typeResolver->convert(m_state.registers[index], convertTo));
}
void QQmlJSTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv)
void QQmlJSTypePropagator::propagateCall(
const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
const QQmlJSScope::ConstPtr &scope)
{
QStringList errors;
const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
@ -1167,9 +1169,11 @@ void QQmlJSTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods,
? m_typeResolver->jsValueType()
: QQmlJSScope::ConstPtr(match.returnType());
setAccumulator(m_typeResolver->returnType(
returnType ? QQmlJSScope::ConstPtr(returnType) : m_typeResolver->voidType(),
match.isJavaScriptFunction() ? QQmlJSRegisterContent::JavaScriptReturnValue
: QQmlJSRegisterContent::MethodReturnValue));
returnType ? QQmlJSScope::ConstPtr(returnType) : m_typeResolver->voidType(),
match.isJavaScriptFunction()
? QQmlJSRegisterContent::JavaScriptReturnValue
: QQmlJSRegisterContent::MethodReturnValue,
scope));
if (!m_state.accumulatorOut().isValid())
setError(u"Cannot store return type of method %1()."_s.arg(match.methodName()));
@ -1189,6 +1193,114 @@ void QQmlJSTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods,
}
}
bool QQmlJSTypePropagator::propagateTranslationMethod(
const QList<QQmlJSMetaMethod> &methods, int argc, int argv)
{
if (methods.length() != 1)
return false;
const QQmlJSMetaMethod method = methods.front();
const QQmlJSRegisterContent intType
= m_typeResolver->globalType(m_typeResolver->intType());
const QQmlJSRegisterContent stringType
= m_typeResolver->globalType(m_typeResolver->stringType());
const QQmlJSRegisterContent returnType
= m_typeResolver->returnType(
m_typeResolver->stringType(), QQmlJSRegisterContent::MethodReturnValue,
m_typeResolver->jsGlobalObject());
if (method.methodName() == u"qsTranslate"_s) {
switch (argc) {
case 4:
addReadRegister(argv + 3, intType); // n
Q_FALLTHROUGH();
case 3:
addReadRegister(argv + 2, stringType); // disambiguation
Q_FALLTHROUGH();
case 2:
addReadRegister(argv + 1, stringType); // sourceText
addReadRegister(argv, stringType); // context
setAccumulator(returnType);
return true;
default:
return false;
}
}
if (method.methodName() == u"QT_TRANSLATE_NOOP"_s) {
switch (argc) {
case 3:
addReadRegister(argv + 2, stringType); // disambiguation
Q_FALLTHROUGH();
case 2:
addReadRegister(argv + 1, stringType); // sourceText
addReadRegister(argv, stringType); // context
setAccumulator(returnType);
return true;
default:
return false;
}
}
if (method.methodName() == u"qsTr"_s) {
switch (argc) {
case 3:
addReadRegister(argv + 2, intType); // n
Q_FALLTHROUGH();
case 2:
addReadRegister(argv + 1, stringType); // disambiguation
Q_FALLTHROUGH();
case 1:
addReadRegister(argv, stringType); // sourceText
setAccumulator(returnType);
return true;
default:
return false;
}
}
if (method.methodName() == u"QT_TR_NOOP"_s) {
switch (argc) {
case 2:
addReadRegister(argv + 1, stringType); // disambiguation
Q_FALLTHROUGH();
case 1:
addReadRegister(argv, stringType); // sourceText
setAccumulator(returnType);
return true;
default:
return false;
}
}
if (method.methodName() == u"qsTrId"_s) {
switch (argc) {
case 2:
addReadRegister(argv + 1, intType); // n
Q_FALLTHROUGH();
case 1:
addReadRegister(argv, stringType); // id
setAccumulator(returnType);
return true;
default:
return false;
}
}
if (method.methodName() == u"QT_TRID_NOOP"_s) {
switch (argc) {
case 1:
addReadRegister(argv, stringType); // id
setAccumulator(returnType);
return true;
default:
return false;
}
}
return false;
}
void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc,
int argv)
{
@ -1224,8 +1336,13 @@ void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName,
= m_typeResolver->scopedType(m_function->qmlScope, functionName);
if (resolvedContent.isMethod()) {
const auto methods = resolvedContent.method();
if (resolvedContent.variant() == QQmlJSRegisterContent::JavaScriptGlobal) {
if (propagateTranslationMethod(methods, argc, argv))
return;
}
if (!methods.isEmpty()) {
propagateCall(methods, argc, argv);
propagateCall(methods, argc, argv, resolvedContent.scopeType());
return;
}
}

View File

@ -196,7 +196,10 @@ private:
QQmlJS::SourceLocation getCurrentBindingSourceLocation() const;
QQmlJSRegisterContent propagateBinaryOperation(QSOperator::Op op, int lhs);
void propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv);
void propagateCall(
const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
const QQmlJSScope::ConstPtr &scope);
bool propagateTranslationMethod(const QList<QQmlJSMetaMethod> &methods, int argc, int argv);
void propagatePropertyLookup(const QString &name);
void propagateScopeLookupCall(const QString &functionName, int argc, int argv);
void saveRegisterStateForJump(int offset);

View File

@ -1200,11 +1200,12 @@ QQmlJSRegisterContent QQmlJSTypeResolver::valueType(const QQmlJSRegisterContent
}
QQmlJSRegisterContent QQmlJSTypeResolver::returnType(
const QQmlJSScope::ConstPtr &type, QQmlJSRegisterContent::ContentVariant variant) const
const QQmlJSScope::ConstPtr &type, QQmlJSRegisterContent::ContentVariant variant,
const QQmlJSScope::ConstPtr &scope) const
{
Q_ASSERT(variant == QQmlJSRegisterContent::MethodReturnValue
|| variant == QQmlJSRegisterContent::JavaScriptReturnValue);
return QQmlJSRegisterContent::create(storedType(type), type, variant);
return QQmlJSRegisterContent::create(storedType(type), type, variant, scope);
}
bool QQmlJSTypeResolver::registerIsStoredIn(

View File

@ -99,8 +99,9 @@ public:
QQmlJSRegisterContent scopedType(const QQmlJSScope::ConstPtr &scope, const QString &name) const;
QQmlJSRegisterContent memberType(const QQmlJSRegisterContent &type, const QString &name) const;
QQmlJSRegisterContent valueType(const QQmlJSRegisterContent &list) const;
QQmlJSRegisterContent returnType(const QQmlJSScope::ConstPtr &type,
QQmlJSRegisterContent::ContentVariant variant) const;
QQmlJSRegisterContent returnType(
const QQmlJSScope::ConstPtr &type, QQmlJSRegisterContent::ContentVariant variant,
const QQmlJSScope::ConstPtr &scope) const;
bool registerIsStoredIn(const QQmlJSRegisterContent &reg,
const QQmlJSScope::ConstPtr &type) const;

View File

@ -136,6 +136,7 @@ set(qml_files
themergood.qml
throwObjectName.qml
toString.qml
translation.qml
typePropertyClash.qml
typedArray.qml
undefinedResets.qml

View File

@ -0,0 +1,25 @@
pragma Strict
import QtQml
QtObject {
// Force the engine to generate script bindings by appending an empty string to each one.
property string translate2: qsTranslate("c", "s") + ""
property string translate3: qsTranslate("c", "s", "d") + ""
property string translate4: qsTranslate("c", "s", "d", 4) + ""
property string translateNoop2: QT_TRANSLATE_NOOP("c", "s") + ""
property string translateNoop3: QT_TRANSLATE_NOOP("c", "s", "d") + ""
property string tr1: qsTr("s") + ""
property string tr2: qsTr("s", "d") + ""
property string tr3: qsTr("s", "d", 4) + ""
property string trNoop1: QT_TR_NOOP("s") + ""
property string trNoop2: QT_TR_NOOP("s", "d") + ""
property string trId1: qsTrId("s") + ""
property string trId2: qsTrId("s", 4) + ""
property string trIdNoop1: QT_TRID_NOOP("s") + ""
}

View File

@ -123,6 +123,7 @@ private slots:
void objectToString();
void throwObjectName();
void javaScriptArgument();
void translation();
};
void tst_QmlCppCodegen::simpleBinding()
@ -2242,6 +2243,33 @@ void tst_QmlCppCodegen::javaScriptArgument()
QCOMPARE(o->property("d").toString(), u"9"_qs);
}
void tst_QmlCppCodegen::translation()
{
QQmlEngine engine;
QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/translation.qml"_s));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
QCOMPARE(o->property("translate2"), u"s"_s);
QCOMPARE(o->property("translate3"), u"s"_s);
QCOMPARE(o->property("translate4"), u"s"_s);
QCOMPARE(o->property("translateNoop2"), u"s"_s);
QCOMPARE(o->property("translateNoop3"), u"s"_s);
QCOMPARE(o->property("tr1"), u"s"_s);
QCOMPARE(o->property("tr2"), u"s"_s);
QCOMPARE(o->property("tr3"), u"s"_s);
QCOMPARE(o->property("trNoop1"), u"s"_s);
QCOMPARE(o->property("trNoop2"), u"s"_s);
QCOMPARE(o->property("trId1"), u"s"_s);
QCOMPARE(o->property("trId2"), u"s"_s);
QCOMPARE(o->property("trIdNoop1"), u"s"_s);
}
void tst_QmlCppCodegen::runInterpreted()
{
#ifdef Q_OS_ANDROID