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, QQmlJS::DiagnosticMessage *error)
59{
60 const auto signatureError = [&](const QString &message) {
61 error->type = QtWarningMsg;
62 error->loc = ast->firstSourceLocation();
63 error->message = message;
64 function->isFullyTyped = false;
65 };
66
67 if (!m_typeResolver->canCallJSFunctions()) {
68 signatureError(u"Ignoring type annotations as requested "
69 "by pragma FunctionSignatureBehavior"_s);
70 return;
71 }
72
73 QQmlJS::AST::BoundNames arguments;
74 if (ast->formals)
75 arguments = ast->formals->formals();
76
77 // If the function has no arguments and no return type annotation we assume it's untyped.
78 // You can annotate it to return void to make it typed.
79 // Otherwise we first assume it's typed and reset the flag if we detect a problem.
80 function->isFullyTyped = !arguments.isEmpty() || ast->typeAnnotation;
81
82 if (function->argumentTypes.isEmpty()) {
83 for (const QQmlJS::AST::BoundName &argument : std::as_const(t&: arguments)) {
84 if (argument.typeAnnotation) {
85 if (const auto type = m_typeResolver->typeFromAST(type: argument.typeAnnotation->type)) {
86 function->argumentTypes.append(
87 t: m_typeResolver->tracked(
88 type: m_typeResolver->globalType(type)));
89 } else {
90 function->argumentTypes.append(
91 t: m_typeResolver->tracked(
92 type: m_typeResolver->globalType(type: m_typeResolver->varType())));
93 signatureError(u"Cannot resolve the argument type %1."_s
94 .arg(a: argument.typeAnnotation->type->toString()));
95 }
96 } else {
97 function->argumentTypes.append(
98 t: m_typeResolver->tracked(
99 type: m_typeResolver->globalType(type: m_typeResolver->varType())));
100 signatureError(u"Functions without type annotations won't be compiled"_s);
101 }
102 }
103 } else {
104 for (qsizetype i = 0, end = arguments.size(); i != end; ++i) {
105 const QQmlJS::AST::BoundName &argument = arguments[i];
106 if (argument.typeAnnotation) {
107 if (const auto type = m_typeResolver->typeFromAST(type: argument.typeAnnotation->type)) {
108 if (!m_typeResolver->registerContains(reg: function->argumentTypes[i], type)) {
109 signatureError(u"Type annotation %1 on signal handler "
110 "contradicts signal argument type %2"_s
111 .arg(args: argument.typeAnnotation->type->toString(),
112 args: function->argumentTypes[i].descriptiveName()));
113 }
114 }
115 }
116 }
117 }
118
119 if (!function->returnType.isValid()) {
120 if (ast->typeAnnotation) {
121 function->returnType = m_typeResolver->globalType(
122 type: m_typeResolver->typeFromAST(type: ast->typeAnnotation->type));
123 if (!function->returnType.isValid())
124 signatureError(u"Cannot resolve return type %1"_s.arg(
125 a: QmlIR::IRBuilder::asString(node: ast->typeAnnotation->type->typeId)));
126 }
127 }
128
129 for (int i = QQmlJSCompilePass::FirstArgument + function->argumentTypes.size();
130 i < context->registerCountInFunction; ++i) {
131 function->registerTypes.append(t: m_typeResolver->tracked(
132 type: m_typeResolver->globalType(type: m_typeResolver->voidType())));
133 }
134
135 function->addressableScopes = m_typeResolver->objectsById();
136 function->code = context->code;
137 function->sourceLocations = context->sourceLocationTable.get();
138}
139
140static void diagnose(
141 const QString &message, QtMsgType type, const QQmlJS::SourceLocation &location,
142 QQmlJS::DiagnosticMessage *error)
143{
144 *error = QQmlJS::DiagnosticMessage{
145 .message: message,
146 .type: type,
147 .loc: location
148 };
149}
150
151QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
152 const QV4::Compiler::Context *context,
153 const QString &propertyName,
154 QQmlJS::AST::Node *astNode,
155 const QmlIR::Binding &irBinding,
156 QQmlJS::DiagnosticMessage *error)
157{
158 QQmlJS::SourceLocation bindingLocation;
159 bindingLocation.startLine = irBinding.location.line();
160 bindingLocation.startColumn = irBinding.location.column();
161
162 QQmlJSCompilePass::Function function;
163 function.qmlScope = m_scopeType;
164
165 if (irBinding.type() != QmlIR::Binding::Type_Script) {
166 diagnose(message: u"Binding is not a script binding, but %1."_s.arg(
167 a: bindingTypeDescription(type: QmlIR::Binding::Type(quint32(irBinding.type())))),
168 type: QtDebugMsg, location: bindingLocation, error);
169 }
170
171 function.isProperty = m_objectType->hasProperty(name: propertyName);
172 if (!function.isProperty) {
173 if (QQmlSignalNames::isHandlerName(signalName: propertyName)) {
174 if (auto actualPropertyName =
175 QQmlSignalNames::changedHandlerNameToPropertyName(handler: propertyName);
176 actualPropertyName && m_objectType->hasProperty(name: *actualPropertyName)) {
177 function.isSignalHandler = true;
178 } else {
179 auto signalName = QQmlSignalNames::handlerNameToSignalName(handler: propertyName);
180 const auto methods = m_objectType->methods(name: *signalName);
181 for (const auto &method : methods) {
182 if (method.isCloned())
183 continue;
184 if (method.methodType() == QQmlJSMetaMethodType::Signal) {
185 function.isSignalHandler = true;
186 const auto arguments = method.parameters();
187 for (qsizetype i = 0, end = arguments.size(); i < end; ++i) {
188 const auto &type = arguments[i].type();
189 if (type.isNull()) {
190 diagnose(message: u"Cannot resolve the argument type %1."_s.arg(
191 a: arguments[i].typeName()),
192 type: QtDebugMsg, location: bindingLocation, error);
193 function.argumentTypes.append(
194 t: m_typeResolver->tracked(
195 type: m_typeResolver->globalType(type: m_typeResolver->varType())));
196 } else {
197 function.argumentTypes.append(t: m_typeResolver->tracked(
198 type: m_typeResolver->globalType(type)));
199 }
200 }
201 break;
202 }
203 }
204 if (!function.isSignalHandler) {
205 diagnose(message: u"Could not compile signal handler for %1: The signal does not exist"_s.arg(
206 a: *signalName),
207 type: QtWarningMsg, location: bindingLocation, error);
208 }
209 }
210 }
211 }
212
213 if (!function.isSignalHandler) {
214 if (!function.isProperty) {
215 diagnose(message: u"Could not compile binding for %1: The property does not exist"_s.arg(
216 a: propertyName),
217 type: QtWarningMsg, location: bindingLocation, error);
218 }
219
220 const auto property = m_objectType->property(name: propertyName);
221 if (const QQmlJSScope::ConstPtr propertyType = property.type()) {
222 function.returnType = m_typeResolver->globalType(type: propertyType->isListProperty()
223 ? m_typeResolver->qObjectListType()
224 : QQmlJSScope::ConstPtr(property.type()));
225 } else {
226 QString message = u"Cannot resolve property type %1 for binding on %2."_s
227 .arg(args: property.typeName(), args: propertyName);
228 if (m_objectType->isNameDeferred(name: propertyName)) {
229 // If the property doesn't exist but the name is deferred, then
230 // it's deferred via the presence of immediate names. Those are
231 // most commonly used to enable generalized grouped properties.
232 message += u" You may want use ID-based grouped properties here.";
233 }
234 diagnose(message, type: QtWarningMsg, location: bindingLocation, error);
235 }
236
237 if (!property.bindable().isEmpty() && !property.isPrivate())
238 function.isQPropertyBinding = true;
239 }
240
241 QQmlJS::MemoryPool pool;
242 auto ast = astNode->asFunctionDefinition();
243 if (!ast) {
244 QQmlJS::AST::Statement *stmt = astNode->statementCast();
245 if (!stmt) {
246 Q_ASSERT(astNode->expressionCast());
247 QQmlJS::AST::ExpressionNode *expr = astNode->expressionCast();
248 stmt = new (&pool) QQmlJS::AST::ExpressionStatement(expr);
249 }
250 auto body = new (&pool) QQmlJS::AST::StatementList(stmt);
251 body = body->finish();
252
253 QString name = u"binding for "_s; // ####
254 ast = new (&pool) QQmlJS::AST::FunctionDeclaration(
255 pool.newString(string: name), /*formals*/ nullptr, body);
256 ast->lbraceToken = astNode->firstSourceLocation();
257 ast->functionToken = ast->lbraceToken;
258 ast->rbraceToken = astNode->lastSourceLocation();
259 }
260
261 populateSignature(context, ast, function: &function, error);
262 return function;
263}
264
265QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run(
266 const QV4::Compiler::Context *context,
267 const QString &functionName, QQmlJS::AST::Node *astNode,
268 QQmlJS::DiagnosticMessage *error)
269{
270 Q_UNUSED(functionName);
271
272 QQmlJSCompilePass::Function function;
273 function.qmlScope = m_scopeType;
274
275 auto ast = astNode->asFunctionDefinition();
276 Q_ASSERT(ast);
277
278 populateSignature(context, ast, function: &function, error);
279 return function;
280}
281
282QT_END_NAMESPACE
283

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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