qmlcompiler: Add relative index for scripts and JS functions
Store relative (to scope) index for every Script binding and JavaScript function into the corresponding data structure within QQmlJSScope. We need this at a later phase in qmltc when code generating JavaScript calls into the engine For now, ignore the logic that converts the relative indices stored into the indices pointing to the runtime function table While testing the addition, observe missing cases where we do not record bindings as such and fix that Change-Id: I85030b7937c97f83183f88ae242af3a5f223443c Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
parent
9eb8f202b6
commit
3340bdf24f
|
@ -1152,6 +1152,20 @@ void QQmlJSImportVisitor::flushPendingSignalParameters()
|
|||
m_pendingSignalHandler = QQmlJS::SourceLocation();
|
||||
}
|
||||
|
||||
/*! \internal
|
||||
|
||||
Records a JS function or a Script binding for a given \a scope. Returns an
|
||||
index of a just recorded function-or-expression.
|
||||
*/
|
||||
QQmlJSMetaMethod::RelativeFunctionIndex
|
||||
QQmlJSImportVisitor::addFunctionOrExpression(const QQmlJSScope::ConstPtr &scope,
|
||||
const QString &name)
|
||||
{
|
||||
auto &array = m_functionsAndExpressions[scope];
|
||||
array.emplaceBack(name);
|
||||
return QQmlJSMetaMethod::RelativeFunctionIndex { int(array.size() - 1) };
|
||||
}
|
||||
|
||||
bool QQmlJSImportVisitor::visit(QQmlJS::AST::ExpressionStatement *ast)
|
||||
{
|
||||
if (m_pendingSignalHandler.isValid()) {
|
||||
|
@ -1428,6 +1442,7 @@ void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExp
|
|||
else
|
||||
method.setReturnTypeName(QStringLiteral("var"));
|
||||
|
||||
method.setJsFunctionIndex(addFunctionOrExpression(m_currentScope, method.methodName()));
|
||||
m_currentScope->addOwnMethod(method);
|
||||
|
||||
if (m_currentScope->scopeType() != QQmlJSScope::QMLScope) {
|
||||
|
@ -1514,13 +1529,29 @@ void handleTranslationBinding(QQmlJSMetaPropertyBinding &binding, QStringView ba
|
|||
#endif
|
||||
}
|
||||
|
||||
QQmlJSImportVisitor::LiteralOrScriptParseResult QQmlJSImportVisitor::parseLiteralOrScriptBinding(const QString name,
|
||||
const QQmlJS::AST::Statement *statement)
|
||||
QQmlJSImportVisitor::LiteralOrScriptParseResult
|
||||
QQmlJSImportVisitor::parseLiteralOrScriptBinding(const QString name,
|
||||
const QQmlJS::AST::Statement *statement)
|
||||
{
|
||||
const auto *exprStatement = cast<const ExpressionStatement *>(statement);
|
||||
|
||||
if (exprStatement == nullptr)
|
||||
if (exprStatement == nullptr) {
|
||||
if (const auto *blockStatement = cast<const Block *>(statement)) {
|
||||
// this is a special case of script binding because:
|
||||
// 1. we are trying to parse a binding (this function's logic)
|
||||
// 2. we encounter a block statement
|
||||
Q_ASSERT(blockStatement->statements);
|
||||
auto first = blockStatement->statements->statement;
|
||||
if (first == nullptr)
|
||||
return LiteralOrScriptParseResult::Invalid;
|
||||
QQmlJSMetaPropertyBinding binding(first->firstSourceLocation(), name);
|
||||
binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
|
||||
QQmlJSMetaPropertyBinding::Script_PropertyBinding);
|
||||
m_currentScope->addOwnPropertyBinding(binding);
|
||||
return LiteralOrScriptParseResult::Script;
|
||||
}
|
||||
return LiteralOrScriptParseResult::Invalid;
|
||||
}
|
||||
|
||||
auto expr = exprStatement->expression;
|
||||
QQmlJSMetaPropertyBinding binding(expr->firstSourceLocation(), name);
|
||||
|
@ -1550,7 +1581,8 @@ QQmlJSImportVisitor::LiteralOrScriptParseResult QQmlJSImportVisitor::parseLitera
|
|||
if (templateLit->hasNoSubstitution) {
|
||||
binding.setStringLiteral(templateLit->value);
|
||||
} else {
|
||||
binding.setScriptBinding();
|
||||
binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
|
||||
QQmlJSMetaPropertyBinding::Script_PropertyBinding);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1565,8 +1597,11 @@ QQmlJSImportVisitor::LiteralOrScriptParseResult QQmlJSImportVisitor::parseLitera
|
|||
break;
|
||||
}
|
||||
|
||||
if (!binding.isValid()) // consider this to be a script binding (see IRBuilder::setBindingValue)
|
||||
binding.setScriptBinding();
|
||||
if (!binding.isValid()) {
|
||||
// consider this to be a script binding (see IRBuilder::setBindingValue)
|
||||
binding.setScriptBinding(addFunctionOrExpression(m_currentScope, name),
|
||||
QQmlJSMetaPropertyBinding::Script_PropertyBinding);
|
||||
}
|
||||
m_currentScope->addOwnPropertyBinding(binding); // always add the binding to the scope
|
||||
|
||||
if (!QQmlJSMetaPropertyBinding::isLiteralBinding(binding.bindingType()))
|
||||
|
@ -1683,6 +1718,7 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
|
|||
if (!signal.has_value()) {
|
||||
m_propertyBindings[m_currentScope].append(
|
||||
{ m_savedBindingOuterScope, group->firstSourceLocation(), name.toString() });
|
||||
// ### TODO: report Invalid parse status as a warning/error
|
||||
parseLiteralOrScriptBinding(name.toString(), scriptBinding->statement);
|
||||
} else {
|
||||
const auto statement = scriptBinding->statement;
|
||||
|
@ -1712,6 +1748,19 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding)
|
|||
m_pendingSignalHandler = firstSourceLocation;
|
||||
m_signalHandlers.insert(firstSourceLocation,
|
||||
{ scopeSignal.parameterNames(), hasMultilineStatementBody });
|
||||
|
||||
// when encountering a signal handler, add it as a script binding
|
||||
QQmlJSMetaPropertyBinding::ScriptBindingKind kind =
|
||||
QQmlJSMetaPropertyBinding::Script_Invalid;
|
||||
if (!methods.isEmpty())
|
||||
kind = QQmlJSMetaPropertyBinding::Script_SignalHandler;
|
||||
else if (propertyForChangeHandler(m_savedBindingOuterScope, *signal).has_value())
|
||||
kind = QQmlJSMetaPropertyBinding::Script_ChangeHandler;
|
||||
|
||||
QString stringName = name.toString();
|
||||
QQmlJSMetaPropertyBinding binding(firstSourceLocation, stringName);
|
||||
binding.setScriptBinding(addFunctionOrExpression(m_currentScope, stringName), kind);
|
||||
m_currentScope->addOwnPropertyBinding(binding);
|
||||
}
|
||||
|
||||
// TODO: before leaving the scopes, we must create the binding.
|
||||
|
|
|
@ -181,6 +181,12 @@ protected:
|
|||
// A set of all types that have been used during type resolution
|
||||
QSet<QString> m_usedTypes;
|
||||
|
||||
// stores JS functions and Script bindings per scope (only the name). mimics
|
||||
// the content of QmlIR::Object::functionsAndExpressions
|
||||
QHash<QQmlJSScope::ConstPtr, QList<QString>> m_functionsAndExpressions;
|
||||
QQmlJSMetaMethod::RelativeFunctionIndex
|
||||
addFunctionOrExpression(const QQmlJSScope::ConstPtr &scope, const QString &name);
|
||||
|
||||
QQmlJSImporter *m_importer;
|
||||
|
||||
void enterEnvironment(QQmlJSScope::ScopeType type, const QString &name,
|
||||
|
|
|
@ -138,6 +138,14 @@ public:
|
|||
Public
|
||||
};
|
||||
|
||||
/*! \internal
|
||||
|
||||
Represents a relative JavaScript function/expression index within a type
|
||||
in a QML document. Used as a typed alternative to int with an explicit
|
||||
invalid state.
|
||||
*/
|
||||
enum class RelativeFunctionIndex : int { Invalid = -1 };
|
||||
|
||||
QQmlJSMetaMethod() = default;
|
||||
explicit QQmlJSMetaMethod(QString name, QString returnType = QString())
|
||||
: m_name(std::move(name))
|
||||
|
@ -208,6 +216,9 @@ public:
|
|||
const QVector<QQmlJSAnnotation>& annotations() const { return m_annotations; }
|
||||
void setAnnotations(QVector<QQmlJSAnnotation> annotations) { m_annotations = annotations; }
|
||||
|
||||
void setJsFunctionIndex(RelativeFunctionIndex index) { m_jsFunctionIndex = index; }
|
||||
RelativeFunctionIndex jsFunctionIndex() const { return m_jsFunctionIndex; }
|
||||
|
||||
friend bool operator==(const QQmlJSMetaMethod &a, const QQmlJSMetaMethod &b)
|
||||
{
|
||||
return a.m_name == b.m_name
|
||||
|
@ -262,6 +273,7 @@ private:
|
|||
Type m_methodType = Signal;
|
||||
Access m_methodAccess = Public;
|
||||
int m_revision = 0;
|
||||
RelativeFunctionIndex m_jsFunctionIndex = RelativeFunctionIndex::Invalid;
|
||||
bool m_isConstructor = false;
|
||||
bool m_isJavaScriptFunction = false;
|
||||
bool m_isImplicitQmlPropertyChangeSignal = false;
|
||||
|
@ -394,6 +406,13 @@ public:
|
|||
GroupProperty,
|
||||
};
|
||||
|
||||
enum ScriptBindingKind : unsigned int {
|
||||
Script_Invalid,
|
||||
Script_PropertyBinding, // property int p: 1 + 1
|
||||
Script_SignalHandler, // onSignal: { ... }
|
||||
Script_ChangeHandler, // onXChanged: { ... }
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
// needs to be kept in sync with the BindingType enum
|
||||
|
@ -439,8 +458,14 @@ private:
|
|||
QString value;
|
||||
};
|
||||
struct Script {
|
||||
friend bool operator==(Script , Script ) { return true; }
|
||||
friend bool operator==(Script a, Script b)
|
||||
{
|
||||
return a.index == b.index && a.kind == b.kind;
|
||||
}
|
||||
friend bool operator!=(Script a, Script b) { return !(a == b); }
|
||||
QQmlJSMetaMethod::RelativeFunctionIndex index =
|
||||
QQmlJSMetaMethod::RelativeFunctionIndex::Invalid;
|
||||
ScriptBindingKind kind = Script_Invalid;
|
||||
};
|
||||
struct Object {
|
||||
friend bool operator==(Object a, Object b) { return a.value == b.value && a.typeName == b.typeName; }
|
||||
|
@ -568,11 +593,10 @@ public:
|
|||
m_bindingContent = Content::StringLiteral { value.toString() };
|
||||
}
|
||||
|
||||
void setScriptBinding()
|
||||
void setScriptBinding(QQmlJSMetaMethod::RelativeFunctionIndex value, ScriptBindingKind kind)
|
||||
{
|
||||
// ### TODO: this does not allow us to do anything interesting with the binding
|
||||
ensureSetBindingTypeOnce();
|
||||
m_bindingContent = Content::Script {};
|
||||
m_bindingContent = Content::Script { value, kind };
|
||||
}
|
||||
|
||||
void setGroupBinding(const QSharedPointer<const QQmlJSScope> &groupScope)
|
||||
|
@ -653,6 +677,22 @@ public:
|
|||
|
||||
QSharedPointer<const QQmlJSScope> literalType(const QQmlJSTypeResolver *resolver) const;
|
||||
|
||||
QQmlJSMetaMethod::RelativeFunctionIndex scriptIndex() const
|
||||
{
|
||||
if (auto *script = std::get_if<Content::Script>(&m_bindingContent))
|
||||
return script->index;
|
||||
// warn
|
||||
return QQmlJSMetaMethod::RelativeFunctionIndex::Invalid;
|
||||
}
|
||||
|
||||
ScriptBindingKind scriptKind() const
|
||||
{
|
||||
if (auto *script = std::get_if<Content::Script>(&m_bindingContent))
|
||||
return script->kind;
|
||||
// warn
|
||||
return ScriptBindingKind::Script_Invalid;
|
||||
}
|
||||
|
||||
QString objectTypeName() const
|
||||
{
|
||||
if (auto *object = std::get_if<Content::Object>(&m_bindingContent))
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import QtQml
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
id: root
|
||||
|
||||
// js func
|
||||
function jsFunc() { return true; }
|
||||
|
||||
// js func with typed params
|
||||
function jsFuncTyped(url: string) { return url + "/"; }
|
||||
|
||||
// script binding (one-line and multi-line)
|
||||
elide: Text.ElideLeft
|
||||
color: {
|
||||
if (root.truncated) return "red";
|
||||
else return "blue";
|
||||
}
|
||||
|
||||
// group prop script binding (value type and non value type)
|
||||
font.pixelSize: (40 + 2) / 2
|
||||
anchors.topMargin: 44 / 4
|
||||
|
||||
// attached prop script binding
|
||||
Keys.enabled: root.truncated ? true : false
|
||||
|
||||
// property change handler ({} and function() {} syntaxes)
|
||||
property int p0: 42
|
||||
property Item p1
|
||||
property string p2
|
||||
onP0Changed: console.log("p0 changed");
|
||||
onP1Changed: {
|
||||
console.log("p1 changed");
|
||||
}
|
||||
onP2Changed: function() {
|
||||
console.log("p2 changed:");
|
||||
console.log(p2);
|
||||
}
|
||||
|
||||
// signal handler ({} and function() {} syntaxes)
|
||||
signal mySignal0(int x)
|
||||
signal mySignal1(string x)
|
||||
signal mySignal2(bool x)
|
||||
onMySignal0: console.log("single line", x);
|
||||
onMySignal1: {
|
||||
console.log("mySignal1 emitted:", x);
|
||||
}
|
||||
onMySignal2: function (x) {
|
||||
console.log("mySignal2 emitted:", x);
|
||||
}
|
||||
|
||||
// var property assigned a js function
|
||||
property var funcHolder: function(x, y) { return x + y; }
|
||||
|
||||
component InlineType : Item {
|
||||
function jsFuncInsideInline() { return 42; }
|
||||
objectName: "inline" + " " + "component"
|
||||
Item { // inside inline component
|
||||
y: 40 / 2
|
||||
}
|
||||
}
|
||||
InlineType {
|
||||
function jsFuncInsideInlineObject(x: real) { console.log(x); }
|
||||
Item { // outside inline component
|
||||
focus: root.jsFunc();
|
||||
}
|
||||
}
|
||||
|
||||
TableView {
|
||||
delegate: Text {
|
||||
signal delegateSignal()
|
||||
onDelegateSignal: { root.jsFunc(); }
|
||||
}
|
||||
|
||||
property var prop: function(x) { return x * 2; }
|
||||
}
|
||||
|
||||
ComponentType {
|
||||
property string foo: "something"
|
||||
onFooChanged: console.log("foo changed!");
|
||||
}
|
||||
|
||||
GridView {
|
||||
delegate: ComponentType {
|
||||
function jsFuncInsideDelegate(flag: bool) { return flag ? "true" : "false"; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,18 +64,23 @@ class tst_qqmljsscope : public QQmlDataTest
|
|||
}
|
||||
|
||||
QQmlJSScope::ConstPtr run(QString url)
|
||||
{
|
||||
QmlIR::Document document(false);
|
||||
return run(url, &document);
|
||||
}
|
||||
|
||||
QQmlJSScope::ConstPtr run(QString url, QmlIR::Document *document)
|
||||
{
|
||||
url = testFile(url);
|
||||
const QString sourceCode = loadUrl(url);
|
||||
if (sourceCode.isEmpty())
|
||||
return QQmlJSScope::ConstPtr();
|
||||
|
||||
QmlIR::Document document(false);
|
||||
// NB: JS unit generated here is ignored, so use noop function
|
||||
QQmlJSSaveFunction noop([](auto &&...) { return true; });
|
||||
QQmlJSCompileError error;
|
||||
[&]() {
|
||||
QVERIFY2(qCompileQmlFile(document, url, noop, nullptr, &error),
|
||||
QVERIFY2(qCompileQmlFile(*document, url, noop, nullptr, &error),
|
||||
qPrintable(error.message));
|
||||
}();
|
||||
if (!error.message.isEmpty())
|
||||
|
@ -89,7 +94,7 @@ class tst_qqmljsscope : public QQmlDataTest
|
|||
QQmlJSScope::Ptr target = QQmlJSScope::create();
|
||||
QQmlJSImportVisitor visitor(target, &m_importer, &logger, dataDirectory());
|
||||
QQmlJSTypeResolver typeResolver { &m_importer };
|
||||
typeResolver.init(&visitor, document.program);
|
||||
typeResolver.init(&visitor, document->program);
|
||||
return visitor.result();
|
||||
}
|
||||
|
||||
|
@ -111,6 +116,7 @@ private Q_SLOTS:
|
|||
void groupedPropertiesConsistency();
|
||||
void groupedPropertySyntax();
|
||||
void attachedProperties();
|
||||
void relativeScriptIndices();
|
||||
|
||||
public:
|
||||
tst_qqmljsscope()
|
||||
|
@ -416,5 +422,99 @@ void tst_qqmljsscope::attachedProperties()
|
|||
QQmlJSMetaPropertyBinding::Script);
|
||||
}
|
||||
|
||||
inline QString getScopeName(const QQmlJSScope::ConstPtr &scope)
|
||||
{
|
||||
Q_ASSERT(scope);
|
||||
QQmlJSScope::ScopeType type = scope->scopeType();
|
||||
if (type == QQmlJSScope::GroupedPropertyScope || type == QQmlJSScope::AttachedPropertyScope)
|
||||
return scope->internalName();
|
||||
return scope->baseTypeName();
|
||||
}
|
||||
|
||||
void tst_qqmljsscope::relativeScriptIndices()
|
||||
{
|
||||
{
|
||||
QQmlEngine engine;
|
||||
QQmlComponent component(&engine);
|
||||
component.loadUrl(testFileUrl(u"functionAndBindingIndices.qml"_qs));
|
||||
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
|
||||
QScopedPointer<QObject> root(component.create());
|
||||
QVERIFY2(root, qPrintable(component.errorString()));
|
||||
}
|
||||
|
||||
QmlIR::Document document(false); // we need QmlIR information here
|
||||
QQmlJSScope::ConstPtr root = run(u"functionAndBindingIndices.qml"_qs, &document);
|
||||
QVERIFY(root);
|
||||
|
||||
using IndexedString = std::pair<QString, int>;
|
||||
// compare {property, function}Name and relative function table index
|
||||
// between QQmlJSScope and QmlIR:
|
||||
QList<IndexedString> orderedJSScopeExpressions;
|
||||
QList<IndexedString> orderedQmlIrExpressions;
|
||||
|
||||
QList<QQmlJSScope::ConstPtr> queue;
|
||||
queue.push_back(root);
|
||||
while (!queue.isEmpty()) {
|
||||
auto current = queue.front();
|
||||
queue.pop_front();
|
||||
|
||||
const auto methods = current->ownMethods();
|
||||
for (const auto &method : methods) {
|
||||
if (method.methodType() == QQmlJSMetaMethod::Signal)
|
||||
continue;
|
||||
QString name = method.methodName();
|
||||
int index = static_cast<int>(method.jsFunctionIndex());
|
||||
QVERIFY2(index >= 0,
|
||||
qPrintable(QStringLiteral("Method %1 from %2 has no index")
|
||||
.arg(name, getScopeName(current))));
|
||||
orderedJSScopeExpressions.emplaceBack(name, index);
|
||||
}
|
||||
|
||||
const auto bindings = current->ownPropertyBindings();
|
||||
for (const auto &binding : bindings) {
|
||||
if (binding.bindingType() != QQmlJSMetaPropertyBinding::Script)
|
||||
continue;
|
||||
QString name = binding.propertyName();
|
||||
int index = static_cast<int>(binding.scriptIndex());
|
||||
QVERIFY2(index >= 0,
|
||||
qPrintable(QStringLiteral("Binding on property %1 from %2 has no index")
|
||||
.arg(name, getScopeName(current))));
|
||||
orderedJSScopeExpressions.emplaceBack(name, index);
|
||||
}
|
||||
|
||||
const auto children = current->childScopes();
|
||||
for (const auto &c : children)
|
||||
queue.push_back(c);
|
||||
}
|
||||
|
||||
for (const QmlIR::Object *irObject : qAsConst(document.objects)) {
|
||||
const QString objectName = document.stringAt(irObject->inheritedTypeNameIndex);
|
||||
for (auto it = irObject->functionsBegin(); it != irObject->functionsEnd(); ++it) {
|
||||
QString name = document.stringAt(it->nameIndex);
|
||||
QVERIFY2(it->index >= 0,
|
||||
qPrintable(QStringLiteral("(qmlir) Method %1 from %2 has no index")
|
||||
.arg(name, objectName)));
|
||||
orderedQmlIrExpressions.emplaceBack(name, it->index);
|
||||
}
|
||||
for (auto it = irObject->bindingsBegin(); it != irObject->bindingsEnd(); ++it) {
|
||||
if (it->type != QmlIR::Binding::Type_Script)
|
||||
continue;
|
||||
QString name = document.stringAt(it->propertyNameIndex);
|
||||
int index = it->value.compiledScriptIndex;
|
||||
QVERIFY2(
|
||||
index >= 0,
|
||||
qPrintable(QStringLiteral("(qmlir) Binding on property %1 from %2 has no index")
|
||||
.arg(name, objectName)));
|
||||
orderedQmlIrExpressions.emplaceBack(name, index);
|
||||
}
|
||||
}
|
||||
|
||||
auto less = [](const IndexedString &x, const IndexedString &y) { return x.first < y.first; };
|
||||
std::sort(orderedJSScopeExpressions.begin(), orderedJSScopeExpressions.end(), less);
|
||||
std::sort(orderedQmlIrExpressions.begin(), orderedQmlIrExpressions.end(), less);
|
||||
|
||||
QCOMPARE(orderedJSScopeExpressions, orderedQmlIrExpressions);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_qqmljsscope)
|
||||
#include "tst_qqmljsscope.moc"
|
||||
|
|
Loading…
Reference in New Issue