1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmljsfunctioninitializer_p.h"
5
6#include <private/qqmljsmemorypool_p.h>
7
8#include <QtCore/qloggingcategory.h>
9#include <QtCore/qfileinfo.h>
10
11#include <QtQml/private/qqmlsignalnames_p.h>
12
13QT_BEGIN_NAMESPACE
14
15using namespace Qt::StringLiterals;
16
17/*!
18 * \internal
19 * \class QQmlJSFunctionInitializer
20 *
21 * QQmlJSFunctionInitializer analyzes the IR to produce an initial
22 * QQmlJSCompilePass::Function for further analysis. It only looks for the
23 * signature and the QML scope and doesn't visit the byte code.
24 */
25
26static QString bindingTypeDescription(QmlIR::Binding::Type type)
27{
28 switch (type) {
29 case QmlIR::Binding::Type_Invalid:
30 return u"invalid"_s;
31 case QmlIR::Binding::Type_Boolean:
32 return u"a boolean"_s;
33 case QmlIR::Binding::Type_Number:
34 return u"a number"_s;
35 case QmlIR::Binding::Type_String:
36 return u"a string"_s;
37 case QmlIR::Binding::Type_Null:
38 return u"null"_s;
39 case QmlIR::Binding::Type_Translation:
40 return u"a translation"_s;
41 case QmlIR::Binding::Type_TranslationById:
42 return u"a translation by id"_s;
43 case QmlIR::Binding::Type_Script:
44 return u"a script"_s;
45 case QmlIR::Binding::Type_Object:
46 return u"an object"_s;
47 case QmlIR::Binding::Type_AttachedProperty:
48 return u"an attached property"_s;
49 case QmlIR::Binding::Type_GroupProperty:
50 return u"a grouped property"_s;
51 }
52
53 return u"nothing"_s;
54}
55
56void QQmlJSFunctionInitializer::populateSignature(
57 const QV4::Compiler::Context *context, QQmlJS::AST::FunctionExpression *ast,
58 QQmlJSCompilePass::Function *function)
59{
60 const auto signatureError = [&](const QString &message) {
61 m_logger->logCompileError(message, srcLocation: ast->firstSourceLocation());
62 function->isFullyTyped = false;
63 };
64
65 if (!m_typeResolver->canCallJSFunctions()) {
66 signatureError(u"Ignoring type annotations as requested "
67 "by pragma FunctionSignatureBehavior"_s);
68 return;
69 }
70
71 QQmlJS::AST::BoundNames arguments;
72 if (ast->formals)
73 arguments = ast->formals->formals();
74
75 // If the function has no arguments and no return type annotation we assume it's untyped.
76 // You can annotate it to return void to make it typed.
77 // Otherwise we first assume it's typed and reset the flag if we detect a problem.
78 function->isFullyTyped = !arguments.isEmpty() || ast->typeAnnotation;
79
80 if (function->argumentTypes.isEmpty()) {
81 bool alreadyWarnedAboutMissingAnnotations = false;
82 for (const QQmlJS::AST::BoundName &argument : std::as_const(t&: arguments)) {
83 if (argument.typeAnnotation) {
84 if (const auto type = m_typeResolver->typeFromAST(type: argument.typeAnnotation->type)) {
85 function->argumentTypes.append(t: m_typeResolver->namedType(type));
86 } else {
87 function->argumentTypes.append(
88 t: m_typeResolver->namedType(type: m_typeResolver->varType()));
89 signatureError(u"Cannot resolve the argument type %1."_s
90 .arg(a: argument.typeAnnotation->type->toString()));
91 }
92 } else {
93 if (!alreadyWarnedAboutMissingAnnotations) {
94 alreadyWarnedAboutMissingAnnotations = true;
95 signatureError(u"Functions without type annotations won't be compiled"_s);
96 }
97 function->argumentTypes.append(
98 t: m_typeResolver->namedType(type: m_typeResolver->varType()));
99 }
100 }
101 } else {
102 for (qsizetype i = 0, end = arguments.size(); i != end; ++i) {
103 const QQmlJS::AST::BoundName &argument = arguments[i];
104 if (argument.typeAnnotation) {
105 if (const auto type = m_typeResolver->typeFromAST(type: argument.typeAnnotation->type)) {
106 if (!function->argumentTypes[i].contains(type)) {
107 signatureError(u"Type annotation %1 on signal handler "
108 "contradicts signal argument type %2"_s
109 .arg(args: argument.typeAnnotation->type->toString(),
110 args: function->argumentTypes[i].descriptiveName()));
111 }
112 }
113 }
114 }
115 }
116
117 if (!function->returnType.isValid()) {
118 if (ast->typeAnnotation) {
119 function->returnType = m_typeResolver->namedType(
120 type: m_typeResolver->typeFromAST(type: ast->typeAnnotation->type));
121 if (!function->returnType.isValid())
122 signatureError(u"Cannot resolve return type %1"_s.arg(
123 a: QmlIR::IRBuilder::asString(node: ast->typeAnnotation->type->typeId)));
124 }
125 }
126
127 for (int i = QQmlJSCompilePass::FirstArgument + function->argumentTypes.size();
128 i < context->registerCountInFunction; ++i) {
129 function->registerTypes.append(t: m_typeResolver->namedType(type: m_typeResolver->voidType()));
130 }
131
132 function->addressableScopes = m_typeResolver->objectsById();
133 function->code = context->code;
134 function->sourceLocations = context->sourceLocationTable.get();
135}
136
137static void diagnose(
138 const QString &message, const QQmlJS::SourceLocation &location, QQmlJSLogger *logger)
139{
140 logger->logCompileError(message, srcLocation: location);
141}
142
143QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
144 const QV4::Compiler::Context *context, const QString &propertyName,
145 QQmlJS::AST::Node *astNode, const QmlIR::Binding &irBinding)
146{
147 QQmlJS::SourceLocation bindingLocation;
148 bindingLocation.startLine = irBinding.location.line();
149 bindingLocation.startColumn = irBinding.location.column();
150
151 QQmlJSCompilePass::Function function;
152 function.qmlScope = m_typeResolver->registerContentPool()->createType(
153 type: m_scopeType, resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
154 variant: QQmlJSRegisterContent::ScopeObject);
155
156 // If we find a problem before we can determine whether it's a signal handler,
157 // that's bad and warrants a warning, even if it then turns out to be a signal handler.
158 const auto setIsSignalHandler = [&]() {
159 function.isSignalHandler = true;
160
161 // If the function is a signal handler and just returns a closure, it's harmless.
162 // Otherwise it's a warning.
163 m_logger->setCompileErrorLevel(context->returnsClosure ? QtDebugMsg : QtWarningMsg);
164 };
165
166 if (irBinding.type() != QmlIR::Binding::Type_Script) {
167 diagnose(message: u"Binding is not a script binding, but %1."_s.arg(
168 a: bindingTypeDescription(type: QmlIR::Binding::Type(quint32(irBinding.type())))),
169 location: bindingLocation, logger: m_logger);
170 }
171
172 function.isProperty = m_objectType->hasProperty(name: propertyName);
173 if (function.isProperty) {
174 const auto property = m_objectType->property(name: propertyName);
175 if (const QQmlJSScope::ConstPtr propertyType = property.type()) {
176 function.returnType = m_typeResolver->namedType(type: propertyType->isListProperty()
177 ? m_typeResolver->qObjectListType()
178 : QQmlJSScope::ConstPtr(property.type()));
179 } else {
180 diagnose(message: u"Cannot resolve property type %1 for binding on %2."_s
181 .arg(args: property.typeName(), args: propertyName),
182 location: bindingLocation, logger: m_logger);
183 }
184
185 if (!property.bindable().isEmpty() && !property.isPrivate())
186 function.isQPropertyBinding = true;
187 } else if (QQmlSignalNames::isHandlerName(signalName: propertyName)) {
188 if (auto actualPropertyName =
189 QQmlSignalNames::changedHandlerNameToPropertyName(handler: propertyName);
190 actualPropertyName && m_objectType->hasProperty(name: *actualPropertyName)) {
191 setIsSignalHandler();
192 } else {
193 auto signalName = QQmlSignalNames::handlerNameToSignalName(handler: propertyName);
194 const auto methods = m_objectType->methods(name: *signalName);
195 for (const auto &method : methods) {
196 if (method.isCloned())
197 continue;
198 if (method.methodType() == QQmlJSMetaMethodType::Signal) {
199 setIsSignalHandler();
200 const auto arguments = method.parameters();
201 for (qsizetype i = 0, end = arguments.size(); i < end; ++i) {
202 const auto &type = arguments[i].type();
203 if (type.isNull()) {
204 diagnose(message: u"Cannot resolve the argument type %1."_s.arg(
205 a: arguments[i].typeName()),
206 location: bindingLocation, logger: m_logger);
207 function.argumentTypes.append(
208 t: m_typeResolver->namedType(type: m_typeResolver->varType()));
209 } else {
210 function.argumentTypes.append(t: m_typeResolver->namedType(type));
211 }
212 }
213 break;
214 }
215 }
216 if (!function.isSignalHandler) {
217 diagnose(message: u"Could not find signal \"%1\"."_s.arg(a: *signalName),
218 location: bindingLocation, logger: m_logger);
219 }
220 }
221 } else {
222 QString message = u"Could not find property \"%1\"."_s.arg(a: propertyName);
223 if (m_objectType->isNameDeferred(name: propertyName)) {
224 // If the property doesn't exist but the name is deferred, then
225 // it's deferred via the presence of immediate names. Those are
226 // most commonly used to enable generalized grouped properties.
227 message += u" You may want use ID-based grouped properties here.";
228 }
229
230 diagnose(message, location: bindingLocation, logger: m_logger);
231 }
232
233 QQmlJS::MemoryPool pool;
234 auto ast = astNode->asFunctionDefinition();
235 if (!ast) {
236 QQmlJS::AST::Statement *stmt = astNode->statementCast();
237 if (!stmt) {
238 Q_ASSERT(astNode->expressionCast());
239 QQmlJS::AST::ExpressionNode *expr = astNode->expressionCast();
240 stmt = new (&pool) QQmlJS::AST::ExpressionStatement(expr);
241 }
242 auto body = new (&pool) QQmlJS::AST::StatementList(stmt);
243 body = body->finish();
244
245 QString name = u"binding for "_s; // ####
246 ast = new (&pool) QQmlJS::AST::FunctionDeclaration(
247 pool.newString(string: std::move(name)), /*formals*/ nullptr, body);
248 ast->lbraceToken = astNode->firstSourceLocation();
249 ast->functionToken = ast->lbraceToken;
250 ast->rbraceToken = astNode->lastSourceLocation();
251 }
252
253 populateSignature(context, ast, function: &function);
254 return function;
255}
256
257QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
258 const QV4::Compiler::Context *context, const QString &functionName,
259 QQmlJS::AST::Node *astNode)
260{
261 Q_UNUSED(functionName);
262
263 QQmlJSCompilePass::Function function;
264 function.qmlScope = m_typeResolver->registerContentPool()->createType(
265 type: m_scopeType, resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
266 variant: QQmlJSRegisterContent::ScopeObject);
267
268 auto ast = astNode->asFunctionDefinition();
269 Q_ASSERT(ast);
270
271 populateSignature(context, ast, function: &function);
272 return function;
273}
274
275QT_END_NAMESPACE
276

source code of qtdeclarative/src/qmlcompiler/qqmljsfunctioninitializer.cpp