aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljsfunctioninitializer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler/qqmljsfunctioninitializer.cpp')
-rw-r--r--src/qmlcompiler/qqmljsfunctioninitializer.cpp282
1 files changed, 282 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljsfunctioninitializer.cpp b/src/qmlcompiler/qqmljsfunctioninitializer.cpp
new file mode 100644
index 0000000000..09928364b1
--- /dev/null
+++ b/src/qmlcompiler/qqmljsfunctioninitializer.cpp
@@ -0,0 +1,282 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qqmljsfunctioninitializer_p.h"
+
+#include <private/qqmljsmemorypool_p.h>
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qfileinfo.h>
+
+#include <QtQml/private/qqmlsignalnames_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*!
+ * \internal
+ * \class QQmlJSFunctionInitializer
+ *
+ * QQmlJSFunctionInitializer analyzes the IR to produce an initial
+ * QQmlJSCompilePass::Function for further analysis. It only looks for the
+ * signature and the QML scope and doesn't visit the byte code.
+ */
+
+static QString bindingTypeDescription(QmlIR::Binding::Type type)
+{
+ switch (type) {
+ case QmlIR::Binding::Type_Invalid:
+ return u"invalid"_s;
+ case QmlIR::Binding::Type_Boolean:
+ return u"a boolean"_s;
+ case QmlIR::Binding::Type_Number:
+ return u"a number"_s;
+ case QmlIR::Binding::Type_String:
+ return u"a string"_s;
+ case QmlIR::Binding::Type_Null:
+ return u"null"_s;
+ case QmlIR::Binding::Type_Translation:
+ return u"a translation"_s;
+ case QmlIR::Binding::Type_TranslationById:
+ return u"a translation by id"_s;
+ case QmlIR::Binding::Type_Script:
+ return u"a script"_s;
+ case QmlIR::Binding::Type_Object:
+ return u"an object"_s;
+ case QmlIR::Binding::Type_AttachedProperty:
+ return u"an attached property"_s;
+ case QmlIR::Binding::Type_GroupProperty:
+ return u"a grouped property"_s;
+ }
+
+ return u"nothing"_s;
+}
+
+void QQmlJSFunctionInitializer::populateSignature(
+ const QV4::Compiler::Context *context, QQmlJS::AST::FunctionExpression *ast,
+ QQmlJSCompilePass::Function *function, QQmlJS::DiagnosticMessage *error)
+{
+ const auto signatureError = [&](const QString &message) {
+ error->type = QtWarningMsg;
+ error->loc = ast->firstSourceLocation();
+ error->message = message;
+ function->isFullyTyped = false;
+ };
+
+ if (!m_typeResolver->canCallJSFunctions()) {
+ signatureError(u"Ignoring type annotations as requested "
+ "by pragma FunctionSignatureBehavior"_s);
+ return;
+ }
+
+ QQmlJS::AST::BoundNames arguments;
+ if (ast->formals)
+ arguments = ast->formals->formals();
+
+ // If the function has no arguments and no return type annotation we assume it's untyped.
+ // You can annotate it to return void to make it typed.
+ // Otherwise we first assume it's typed and reset the flag if we detect a problem.
+ function->isFullyTyped = !arguments.isEmpty() || ast->typeAnnotation;
+
+ if (function->argumentTypes.isEmpty()) {
+ for (const QQmlJS::AST::BoundName &argument : std::as_const(arguments)) {
+ if (argument.typeAnnotation) {
+ if (const auto type = m_typeResolver->typeFromAST(argument.typeAnnotation->type)) {
+ function->argumentTypes.append(
+ m_typeResolver->tracked(
+ m_typeResolver->globalType(type)));
+ } else {
+ function->argumentTypes.append(
+ m_typeResolver->tracked(
+ m_typeResolver->globalType(m_typeResolver->varType())));
+ signatureError(u"Cannot resolve the argument type %1."_s
+ .arg(argument.typeAnnotation->type->toString()));
+ }
+ } else {
+ function->argumentTypes.append(
+ m_typeResolver->tracked(
+ m_typeResolver->globalType(m_typeResolver->varType())));
+ signatureError(u"Functions without type annotations won't be compiled"_s);
+ }
+ }
+ } else {
+ for (qsizetype i = 0, end = arguments.size(); i != end; ++i) {
+ const QQmlJS::AST::BoundName &argument = arguments[i];
+ if (argument.typeAnnotation) {
+ if (const auto type = m_typeResolver->typeFromAST(argument.typeAnnotation->type)) {
+ if (!m_typeResolver->registerContains(function->argumentTypes[i], type)) {
+ signatureError(u"Type annotation %1 on signal handler "
+ "contradicts signal argument type %2"_s
+ .arg(argument.typeAnnotation->type->toString(),
+ function->argumentTypes[i].descriptiveName()));
+ }
+ }
+ }
+ }
+ }
+
+ if (!function->returnType.isValid()) {
+ if (ast->typeAnnotation) {
+ function->returnType = m_typeResolver->globalType(
+ m_typeResolver->typeFromAST(ast->typeAnnotation->type));
+ if (!function->returnType.isValid())
+ signatureError(u"Cannot resolve return type %1"_s.arg(
+ QmlIR::IRBuilder::asString(ast->typeAnnotation->type->typeId)));
+ }
+ }
+
+ for (int i = QQmlJSCompilePass::FirstArgument + function->argumentTypes.size();
+ i < context->registerCountInFunction; ++i) {
+ function->registerTypes.append(m_typeResolver->tracked(
+ m_typeResolver->globalType(m_typeResolver->voidType())));
+ }
+
+ function->addressableScopes = m_typeResolver->objectsById();
+ function->code = context->code;
+ function->sourceLocations = context->sourceLocationTable.get();
+}
+
+static void diagnose(
+ const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location,
+ QQmlJS::DiagnosticMessage *error)
+{
+ *error = QQmlJS::DiagnosticMessage{
+ message,
+ type,
+ location
+ };
+}
+
+QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
+ const QV4::Compiler::Context *context,
+ const QString &propertyName,
+ QQmlJS::AST::Node *astNode,
+ const QmlIR::Binding &irBinding,
+ QQmlJS::DiagnosticMessage *error)
+{
+ QQmlJS::SourceLocation bindingLocation;
+ bindingLocation.startLine = irBinding.location.line();
+ bindingLocation.startColumn = irBinding.location.column();
+
+ QQmlJSCompilePass::Function function;
+ function.qmlScope = m_scopeType;
+
+ if (irBinding.type() != QmlIR::Binding::Type_Script) {
+ diagnose(u"Binding is not a script binding, but %1."_s.arg(
+ bindingTypeDescription(QmlIR::Binding::Type(quint32(irBinding.type())))),
+ QtDebugMsg, bindingLocation, error);
+ }
+
+ function.isProperty = m_objectType->hasProperty(propertyName);
+ if (!function.isProperty) {
+ if (QQmlSignalNames::isHandlerName(propertyName)) {
+ if (auto actualPropertyName =
+ QQmlSignalNames::changedHandlerNameToPropertyName(propertyName);
+ actualPropertyName && m_objectType->hasProperty(*actualPropertyName)) {
+ function.isSignalHandler = true;
+ } else {
+ auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName);
+ const auto methods = m_objectType->methods(*signalName);
+ for (const auto &method : methods) {
+ if (method.isCloned())
+ continue;
+ if (method.methodType() == QQmlJSMetaMethodType::Signal) {
+ function.isSignalHandler = true;
+ const auto arguments = method.parameters();
+ for (qsizetype i = 0, end = arguments.size(); i < end; ++i) {
+ const auto &type = arguments[i].type();
+ if (type.isNull()) {
+ diagnose(u"Cannot resolve the argument type %1."_s.arg(
+ arguments[i].typeName()),
+ QtDebugMsg, bindingLocation, error);
+ function.argumentTypes.append(
+ m_typeResolver->tracked(
+ m_typeResolver->globalType(m_typeResolver->varType())));
+ } else {
+ function.argumentTypes.append(m_typeResolver->tracked(
+ m_typeResolver->globalType(type)));
+ }
+ }
+ break;
+ }
+ }
+ if (!function.isSignalHandler) {
+ diagnose(u"Could not compile signal handler for %1: The signal does not exist"_s.arg(
+ *signalName),
+ QtWarningMsg, bindingLocation, error);
+ }
+ }
+ }
+ }
+
+ if (!function.isSignalHandler) {
+ if (!function.isProperty) {
+ diagnose(u"Could not compile binding for %1: The property does not exist"_s.arg(
+ propertyName),
+ QtWarningMsg, bindingLocation, error);
+ }
+
+ const auto property = m_objectType->property(propertyName);
+ if (const QQmlJSScope::ConstPtr propertyType = property.type()) {
+ function.returnType = m_typeResolver->globalType(propertyType->isListProperty()
+ ? m_typeResolver->qObjectListType()
+ : QQmlJSScope::ConstPtr(property.type()));
+ } else {
+ QString message = u"Cannot resolve property type %1 for binding on %2."_s
+ .arg(property.typeName(), propertyName);
+ if (m_objectType->isNameDeferred(propertyName)) {
+ // If the property doesn't exist but the name is deferred, then
+ // it's deferred via the presence of immediate names. Those are
+ // most commonly used to enable generalized grouped properties.
+ message += u" You may want use ID-based grouped properties here.";
+ }
+ diagnose(message, QtWarningMsg, bindingLocation, error);
+ }
+
+ if (!property.bindable().isEmpty() && !property.isPrivate())
+ function.isQPropertyBinding = true;
+ }
+
+ QQmlJS::MemoryPool pool;
+ auto ast = astNode->asFunctionDefinition();
+ if (!ast) {
+ QQmlJS::AST::Statement *stmt = astNode->statementCast();
+ if (!stmt) {
+ Q_ASSERT(astNode->expressionCast());
+ QQmlJS::AST::ExpressionNode *expr = astNode->expressionCast();
+ stmt = new (&pool) QQmlJS::AST::ExpressionStatement(expr);
+ }
+ auto body = new (&pool) QQmlJS::AST::StatementList(stmt);
+ body = body->finish();
+
+ QString name = u"binding for "_s; // ####
+ ast = new (&pool) QQmlJS::AST::FunctionDeclaration(
+ pool.newString(name), /*formals*/ nullptr, body);
+ ast->lbraceToken = astNode->firstSourceLocation();
+ ast->functionToken = ast->lbraceToken;
+ ast->rbraceToken = astNode->lastSourceLocation();
+ }
+
+ populateSignature(context, ast, &function, error);
+ return function;
+}
+
+QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
+ const QV4::Compiler::Context *context,
+ const QString &functionName, QQmlJS::AST::Node *astNode,
+ QQmlJS::DiagnosticMessage *error)
+{
+ Q_UNUSED(functionName);
+
+ QQmlJSCompilePass::Function function;
+ function.qmlScope = m_scopeType;
+
+ auto ast = astNode->asFunctionDefinition();
+ Q_ASSERT(ast);
+
+ populateSignature(context, ast, &function, error);
+ return function;
+}
+
+QT_END_NAMESPACE