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

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