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 | QT_BEGIN_NAMESPACE |
12 | |
13 | using 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 | |
24 | static 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 | |
54 | void 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 | |
131 | static 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 | |
142 | QQmlJSCompilePass::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 | |
255 | QQmlJSCompilePass::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 | |
272 | QT_END_NAMESPACE |
273 | |