// 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 #include #include #include 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) { const auto signatureError = [&](const QString &message) { m_logger->logCompileError(message, ast->firstSourceLocation()); 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()) { bool alreadyWarnedAboutMissingAnnotations = false; 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->namedType(type)); } else { function->argumentTypes.append( m_typeResolver->namedType(m_typeResolver->varType())); signatureError(u"Cannot resolve the argument type %1."_s .arg(argument.typeAnnotation->type->toString())); } } else { if (!alreadyWarnedAboutMissingAnnotations) { alreadyWarnedAboutMissingAnnotations = true; function->argumentTypes.append( m_typeResolver->namedType(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 (!function->argumentTypes[i].contains(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->namedType( 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->namedType(m_typeResolver->voidType())); } function->addressableScopes = m_typeResolver->objectsById(); function->code = context->code; function->sourceLocations = context->sourceLocationTable.get(); } static void diagnose( const QString &message, const QQmlJS::SourceLocation &location, QQmlJSLogger *logger) { logger->logCompileError(message, location); } QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run( const QV4::Compiler::Context *context, const QString &propertyName, QQmlJS::AST::Node *astNode, const QmlIR::Binding &irBinding) { QQmlJS::SourceLocation bindingLocation; bindingLocation.startLine = irBinding.location.line(); bindingLocation.startColumn = irBinding.location.column(); QQmlJSCompilePass::Function function; function.qmlScope = m_typeResolver->registerContentPool()->createType( m_scopeType, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ScopeObject); // If we find a problem before we can determine whether it's a signal handler, // that's bad and warrants a warning, even if it then turns out to be a signal handler. const auto setIsSignalHandler = [&]() { function.isSignalHandler = true; // If the function is a signal handler and just returns a closure, it's harmless. // Otherwise it's a warning. m_logger->setCompileErrorLevel(context->returnsClosure ? QtDebugMsg : QtWarningMsg); }; 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())))), bindingLocation, m_logger); } function.isProperty = m_objectType->hasProperty(propertyName); if (function.isProperty) { const auto property = m_objectType->property(propertyName); if (const QQmlJSScope::ConstPtr propertyType = property.type()) { function.returnType = m_typeResolver->namedType(propertyType->isListProperty() ? m_typeResolver->qObjectListType() : QQmlJSScope::ConstPtr(property.type())); } else { diagnose(u"Cannot resolve property type %1 for binding on %2."_s .arg(property.typeName(), propertyName), bindingLocation, m_logger); } if (!property.bindable().isEmpty() && !property.isPrivate()) function.isQPropertyBinding = true; } else if (QQmlSignalNames::isHandlerName(propertyName)) { if (auto actualPropertyName = QQmlSignalNames::changedHandlerNameToPropertyName(propertyName); actualPropertyName && m_objectType->hasProperty(*actualPropertyName)) { setIsSignalHandler(); } 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) { setIsSignalHandler(); 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()), bindingLocation, m_logger); function.argumentTypes.append( m_typeResolver->namedType(m_typeResolver->varType())); } else { function.argumentTypes.append(m_typeResolver->namedType(type)); } } break; } } if (!function.isSignalHandler) { diagnose(u"Could not find signal \"%1\"."_s.arg(*signalName), bindingLocation, m_logger); } } } else { QString message = u"Could not find property \"%1\"."_s.arg(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, bindingLocation, m_logger); } 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(std::move(name)), /*formals*/ nullptr, body); ast->lbraceToken = astNode->firstSourceLocation(); ast->functionToken = ast->lbraceToken; ast->rbraceToken = astNode->lastSourceLocation(); } populateSignature(context, ast, &function); return function; } QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run( const QV4::Compiler::Context *context, const QString &functionName, QQmlJS::AST::Node *astNode) { Q_UNUSED(functionName); QQmlJSCompilePass::Function function; function.qmlScope = m_typeResolver->registerContentPool()->createType( m_scopeType, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ScopeObject); auto ast = astNode->asFunctionDefinition(); Q_ASSERT(ast); populateSignature(context, ast, &function); return function; } QT_END_NAMESPACE