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 "qqmljsscope_p.h"
5#include "qqmljstypepropagator_p.h"
6
7#include "qqmljsutils_p.h"
8#include "qqmlsa_p.h"
9
10#include <private/qv4compilerscanfunctions_p.h>
11
12#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h>
13
14QT_BEGIN_NAMESPACE
15
16using namespace Qt::StringLiterals;
17
18/*!
19 * \internal
20 * \class QQmlJSTypePropagator
21 *
22 * QQmlJSTypePropagator is the initial pass that performs the type inference and
23 * annotates every register in use at any instruction with the possible types it
24 * may hold. This includes information on how and in what scope the values are
25 * retrieved. These annotations may be used by further compile passes for
26 * refinement or code generation.
27 */
28
29QQmlJSTypePropagator::QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator,
30 const QQmlJSTypeResolver *typeResolver,
31 QQmlJSLogger *logger, QQmlSA::PassManager *passManager)
32 : QQmlJSCompilePass(unitGenerator, typeResolver, logger),
33 m_passManager(passManager)
34{
35}
36
37QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run(
38 const Function *function, QQmlJS::DiagnosticMessage *error)
39{
40 m_function = function;
41 m_error = error;
42 m_returnType = m_typeResolver->globalType(type: m_function->returnType);
43
44 do {
45 // Reset the error if we need to do another pass
46 if (m_state.needsMorePasses)
47 *m_error = QQmlJS::DiagnosticMessage();
48
49 m_prevStateAnnotations = m_state.annotations;
50 m_state = PassState();
51 m_state.State::operator=(initialState(function: m_function));
52
53 reset();
54 decode(code: m_function->code.constData(), len: static_cast<uint>(m_function->code.size()));
55
56 // If we have found unresolved backwards jumps, we need to start over with a fresh state.
57 // Mind that m_jumpOriginRegisterStateByTargetInstructionOffset is retained in that case.
58 // This means that we won't start over for the same reason again.
59 } while (m_state.needsMorePasses);
60
61 return m_state.annotations;
62}
63
64#define INSTR_PROLOGUE_NOT_IMPLEMENTED() \
65 setError(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__))); \
66 return;
67
68#define INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE() \
69 m_logger->log(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__)), \
70 qmlCompiler, QQmlJS::SourceLocation()); \
71 return;
72
73void QQmlJSTypePropagator::generate_Ret()
74{
75 if (m_passManager != nullptr && m_function->isProperty) {
76 m_passManager->d_func()->analyzeBinding(
77 element: QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
78 value: QQmlJSScope::createQQmlSAElement(
79 m_typeResolver->containedType(container: m_state.accumulatorIn())),
80 location: QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
81 jsLocation: getCurrentBindingSourceLocation()));
82 }
83
84 if (m_function->isSignalHandler) {
85 // Signal handlers cannot return anything.
86 } else if (m_typeResolver->registerContains(
87 reg: m_state.accumulatorIn(), type: m_typeResolver->voidType())) {
88 // You can always return undefined.
89 } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) {
90 setError(u"function without type annotation returns %1"_s
91 .arg(a: m_state.accumulatorIn().descriptiveName()));
92 return;
93 } else if (!canConvertFromTo(from: m_state.accumulatorIn(), to: m_returnType)) {
94 setError(u"cannot convert from %1 to %2"_s
95 .arg(args: m_state.accumulatorIn().descriptiveName(),
96 args: m_returnType.descriptiveName()));
97
98 m_logger->log(message: u"Cannot assign binding of type %1 to %2"_s.arg(
99 args: m_typeResolver->containedTypeName(container: m_state.accumulatorIn(), useFancyName: true),
100 args: m_typeResolver->containedTypeName(container: m_returnType, useFancyName: true)),
101 id: qmlIncompatibleType, srcLocation: getCurrentBindingSourceLocation());
102 return;
103 }
104
105 if (m_returnType.isValid()) {
106 // We need to preserve any possible undefined value as that resets the property.
107 if (m_typeResolver->canHoldUndefined(content: m_state.accumulatorIn()))
108 addReadAccumulator(convertTo: m_state.accumulatorIn());
109 else
110 addReadAccumulator(convertTo: m_returnType);
111 }
112
113 m_state.setHasSideEffects(true);
114 m_state.skipInstructionsUntilNextJumpTarget = true;
115}
116
117void QQmlJSTypePropagator::generate_Debug()
118{
119 INSTR_PROLOGUE_NOT_IMPLEMENTED();
120}
121
122void QQmlJSTypePropagator::generate_LoadConst(int index)
123{
124 auto encodedConst = m_jsUnitGenerator->constant(idx: index);
125 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->typeForConst(rv: encodedConst)));
126}
127
128void QQmlJSTypePropagator::generate_LoadZero()
129{
130 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->int32Type()));
131}
132
133void QQmlJSTypePropagator::generate_LoadTrue()
134{
135 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->boolType()));
136}
137
138void QQmlJSTypePropagator::generate_LoadFalse()
139{
140 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->boolType()));
141}
142
143void QQmlJSTypePropagator::generate_LoadNull()
144{
145 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->nullType()));
146}
147
148void QQmlJSTypePropagator::generate_LoadUndefined()
149{
150 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->voidType()));
151}
152
153void QQmlJSTypePropagator::generate_LoadInt(int)
154{
155 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->int32Type()));
156}
157
158void QQmlJSTypePropagator::generate_MoveConst(int constIndex, int destTemp)
159{
160 auto encodedConst = m_jsUnitGenerator->constant(idx: constIndex);
161 setRegister(index: destTemp, content: m_typeResolver->globalType(type: m_typeResolver->typeForConst(rv: encodedConst)));
162}
163
164void QQmlJSTypePropagator::generate_LoadReg(int reg)
165{
166 // Do not re-track the register. We're not manipulating it.
167 m_state.setIsRename(true);
168 const QQmlJSRegisterContent content = checkedInputRegister(reg);
169 m_state.addReadRegister(registerIndex: reg, reg: content);
170 m_state.setRegister(registerIndex: Accumulator, content);
171}
172
173void QQmlJSTypePropagator::generate_StoreReg(int reg)
174{
175 // Do not re-track the register. We're not manipulating it.
176 m_state.setIsRename(true);
177 m_state.addReadAccumulator(reg: m_state.accumulatorIn());
178 m_state.setRegister(registerIndex: reg, content: m_state.accumulatorIn());
179}
180
181void QQmlJSTypePropagator::generate_MoveReg(int srcReg, int destReg)
182{
183 Q_ASSERT(destReg != InvalidRegister);
184 // Do not re-track the register. We're not manipulating it.
185 m_state.setIsRename(true);
186 const QQmlJSRegisterContent content = checkedInputRegister(reg: srcReg);
187 m_state.addReadRegister(registerIndex: srcReg, reg: content);
188 m_state.setRegister(registerIndex: destReg, content);
189}
190
191void QQmlJSTypePropagator::generate_LoadImport(int index)
192{
193 Q_UNUSED(index)
194 INSTR_PROLOGUE_NOT_IMPLEMENTED();
195}
196
197void QQmlJSTypePropagator::generate_LoadLocal(int index)
198{
199 Q_UNUSED(index);
200 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->jsValueType()));
201}
202
203void QQmlJSTypePropagator::generate_StoreLocal(int index)
204{
205 Q_UNUSED(index)
206 INSTR_PROLOGUE_NOT_IMPLEMENTED();
207}
208
209void QQmlJSTypePropagator::generate_LoadScopedLocal(int scope, int index)
210{
211 Q_UNUSED(scope)
212 Q_UNUSED(index)
213 INSTR_PROLOGUE_NOT_IMPLEMENTED();
214}
215
216void QQmlJSTypePropagator::generate_StoreScopedLocal(int scope, int index)
217{
218 Q_UNUSED(scope)
219 Q_UNUSED(index)
220 INSTR_PROLOGUE_NOT_IMPLEMENTED();
221}
222
223void QQmlJSTypePropagator::generate_LoadRuntimeString(int stringId)
224{
225 Q_UNUSED(stringId)
226 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->stringType()));
227 // m_state.accumulatorOut.m_state.value = m_jsUnitGenerator->stringForIndex(stringId);
228}
229
230void QQmlJSTypePropagator::generate_MoveRegExp(int regExpId, int destReg)
231{
232 Q_UNUSED(regExpId)
233 Q_UNUSED(destReg)
234 INSTR_PROLOGUE_NOT_IMPLEMENTED();
235}
236
237void QQmlJSTypePropagator::generate_LoadClosure(int value)
238{
239 Q_UNUSED(value)
240 // TODO: Check the function at index and see whether it's a generator to return another type
241 // instead.
242 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->functionType()));
243}
244
245void QQmlJSTypePropagator::generate_LoadName(int nameIndex)
246{
247 const QString name = m_jsUnitGenerator->stringForIndex(index: nameIndex);
248 setAccumulator(m_typeResolver->scopedType(scope: m_function->qmlScope, name));
249 if (!m_state.accumulatorOut().isValid())
250 setError(u"Cannot find name "_s + name);
251}
252
253void QQmlJSTypePropagator::generate_LoadGlobalLookup(int index)
254{
255 generate_LoadName(nameIndex: m_jsUnitGenerator->lookupNameIndex(index));
256}
257
258QQmlJS::SourceLocation QQmlJSTypePropagator::getCurrentSourceLocation() const
259{
260 Q_ASSERT(m_function->sourceLocations);
261 const auto &entries = m_function->sourceLocations->entries;
262
263 auto item = std::lower_bound(first: entries.begin(), last: entries.end(), val: currentInstructionOffset(),
264 comp: [](auto entry, uint offset) { return entry.offset < offset; });
265 Q_ASSERT(item != entries.end());
266 auto location = item->location;
267
268 return location;
269}
270
271QQmlJS::SourceLocation QQmlJSTypePropagator::getCurrentBindingSourceLocation() const
272{
273 Q_ASSERT(m_function->sourceLocations);
274 const auto &entries = m_function->sourceLocations->entries;
275
276 Q_ASSERT(!entries.isEmpty());
277 return combine(l1: entries.constFirst().location, l2: entries.constLast().location);
278}
279
280void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
281{
282 auto location = getCurrentSourceLocation();
283
284 if (m_function->qmlScope->isInCustomParserParent()) {
285 // Only ignore custom parser based elements if it's not Connections.
286 if (m_function->qmlScope->baseType().isNull()
287 || m_function->qmlScope->baseType()->internalName() != u"QQmlConnections"_s)
288 return;
289 }
290
291 if (isMethod) {
292 if (isCallingProperty(scope: m_function->qmlScope, name))
293 return;
294 } else if (propertyResolution(scope: m_function->qmlScope, type: name) != PropertyMissing) {
295 return;
296 }
297
298 std::optional<QQmlJSFixSuggestion> suggestion;
299
300 auto childScopes = m_function->qmlScope->childScopes();
301 for (qsizetype i = 0; i < m_function->qmlScope->childScopes().size(); i++) {
302 auto &scope = childScopes[i];
303 if (location.offset > scope->sourceLocation().offset) {
304 if (i + 1 < childScopes.size()
305 && childScopes.at(i: i + 1)->sourceLocation().offset < location.offset)
306 continue;
307 if (scope->childScopes().size() == 0)
308 continue;
309
310 const auto jsId = scope->childScopes().first()->findJSIdentifier(id: name);
311
312 if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
313 const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
314
315 QQmlJS::SourceLocation fixLocation = id.location;
316 Q_UNUSED(fixLocation)
317 fixLocation.length = 0;
318
319 const auto handler = m_typeResolver->signalHandlers()[id.location];
320
321 QString fixString = handler.isMultiline ? u"function("_s : u"("_s;
322 const auto parameters = handler.signalParameters;
323 for (int numParams = parameters.size(); numParams > 0; --numParams) {
324 fixString += parameters.at(i: parameters.size() - numParams);
325 if (numParams > 1)
326 fixString += u", "_s;
327 }
328
329 fixString += handler.isMultiline ? u") "_s : u") => "_s;
330
331 suggestion = QQmlJSFixSuggestion {
332 name + u" is accessible in this scope because you are handling a signal"
333 " at %1:%2. Use a function instead.\n"_s
334 .arg(a: id.location.startLine)
335 .arg(a: id.location.startColumn),
336 fixLocation,
337 fixString
338 };
339 suggestion->setAutoApplicable();
340 }
341 break;
342 }
343 }
344
345 // Might be a delegate just missing a required property.
346 // This heuristic does not recognize all instances of this occurring but should be sufficient
347 // protection against wrongly suggesting to add an id to the view to access the model that way
348 // which is very misleading
349 if (name == u"model" || name == u"index") {
350 if (QQmlJSScope::ConstPtr parent = m_function->qmlScope->parentScope(); !parent.isNull()) {
351 const auto bindings = parent->ownPropertyBindings(name: u"delegate"_s);
352
353 for (auto it = bindings.first; it != bindings.second; it++) {
354 if (!it->hasObject())
355 continue;
356 if (it->objectType() == m_function->qmlScope) {
357 suggestion = QQmlJSFixSuggestion {
358 name + " is implicitly injected into this delegate."
359 " Add a required property instead."_L1,
360 m_function->qmlScope->sourceLocation()
361 };
362 };
363
364 break;
365 }
366 }
367 }
368
369 if (!suggestion.has_value()) {
370 for (QQmlJSScope::ConstPtr scope = m_function->qmlScope; !scope.isNull();
371 scope = scope->parentScope()) {
372 if (scope->hasProperty(name)) {
373 const QString id = m_function->addressableScopes.id(scope, referrer: m_function->qmlScope);
374
375 QQmlJS::SourceLocation fixLocation = location;
376 fixLocation.length = 0;
377 suggestion = QQmlJSFixSuggestion{
378 name
379 + " is a member of a parent element.\n You can qualify the access "
380 "with its id to avoid this warning.\n"_L1,
381 fixLocation, (id.isEmpty() ? u"<id>."_s : (id + u'.'))
382 };
383
384 if (id.isEmpty())
385 suggestion->setHint("You first have to give the element an id"_L1);
386 else
387 suggestion->setAutoApplicable();
388 }
389 }
390 }
391
392 if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
393 && m_function->addressableScopes.existsAnywhereInDocument(id: name)) {
394 const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
395 QQmlJSFixSuggestion bindComponents {
396 "Set \"%1\" in order to use IDs from outer components in nested components."_L1
397 .arg(args: replacement),
398 QQmlJS::SourceLocation(0, 0, 1, 1),
399 replacement + '\n'_L1
400 };
401 bindComponents.setAutoApplicable();
402 suggestion = bindComponents;
403 }
404
405 if (!suggestion.has_value()) {
406 if (auto didYouMean =
407 QQmlJSUtils::didYouMean(userInput: name,
408 candidates: m_function->qmlScope->properties().keys()
409 + m_function->qmlScope->methods().keys(),
410 location);
411 didYouMean.has_value()) {
412 suggestion = didYouMean;
413 }
414 }
415
416 m_logger->log(message: QLatin1String("Unqualified access"), id: qmlUnqualified, srcLocation: location, showContext: true, showFileName: true,
417 suggestion);
418}
419
420void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
421 bool isMethod) const
422{
423 Q_ASSERT(!scope.isNull());
424 auto qmlScope = QQmlJSScope::findCurrentQMLScope(scope);
425 if (qmlScope.isNull())
426 return;
427
428 QList<QQmlJSAnnotation> annotations;
429
430 QQmlJSMetaMethod method;
431
432 if (isMethod) {
433 const QVector<QQmlJSMetaMethod> methods = qmlScope->methods(name);
434 if (methods.isEmpty())
435 return;
436 method = methods.constFirst();
437 annotations = method.annotations();
438 } else {
439 QQmlJSMetaProperty property = qmlScope->property(name);
440 if (!property.isValid())
441 return;
442 annotations = property.annotations();
443 }
444
445 auto deprecationAnn = std::find_if(
446 first: annotations.constBegin(), last: annotations.constEnd(),
447 pred: [](const QQmlJSAnnotation &annotation) { return annotation.isDeprecation(); });
448
449 if (deprecationAnn == annotations.constEnd())
450 return;
451
452 QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
453
454 QString descriptor = name;
455 if (isMethod)
456 descriptor += u'(' + method.parameterNames().join(sep: u", "_s) + u')';
457
458 QString message = QStringLiteral("%1 \"%2\" is deprecated")
459 .arg(a: isMethod ? u"Method"_s : u"Property"_s)
460 .arg(a: descriptor);
461
462 if (!deprecation.reason.isEmpty())
463 message.append(QStringLiteral(" (Reason: %1)").arg(a: deprecation.reason));
464
465 m_logger->log(message, id: qmlDeprecated, srcLocation: getCurrentSourceLocation());
466}
467
468// Only to be called once a lookup has already failed
469QQmlJSTypePropagator::PropertyResolution QQmlJSTypePropagator::propertyResolution(
470 QQmlJSScope::ConstPtr scope, const QString &propertyName) const
471{
472 auto property = scope->property(name: propertyName);
473 if (!property.isValid())
474 return PropertyMissing;
475
476 QString errorType;
477 if (property.type().isNull())
478 errorType = u"found"_s;
479 else if (!property.type()->isFullyResolved())
480 errorType = u"fully resolved"_s;
481 else
482 return PropertyFullyResolved;
483
484 Q_ASSERT(!errorType.isEmpty());
485
486 m_logger->log(
487 message: u"Type \"%1\" of property \"%2\" not %3. This is likely due to a missing dependency entry or a type not being exposed declaratively."_s
488 .arg(args: property.typeName(), args: propertyName, args&: errorType),
489 id: qmlUnresolvedType, srcLocation: getCurrentSourceLocation());
490
491 return PropertyTypeUnresolved;
492}
493
494bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const
495{
496 auto property = scope->property(name);
497 if (!property.isValid())
498 return false;
499
500 QString propertyType = u"Property"_s;
501
502 auto methods = scope->methods(name);
503
504 QString errorType;
505 if (!methods.isEmpty()) {
506 errorType = u"shadowed by a property."_s;
507 switch (methods.first().methodType()) {
508 case QQmlJSMetaMethodType::Signal:
509 propertyType = u"Signal"_s;
510 break;
511 case QQmlJSMetaMethodType::Slot:
512 propertyType = u"Slot"_s;
513 break;
514 case QQmlJSMetaMethodType::Method:
515 propertyType = u"Method"_s;
516 break;
517 default:
518 Q_UNREACHABLE();
519 }
520 } else if (m_typeResolver->equals(a: property.type(), b: m_typeResolver->varType())) {
521 errorType =
522 u"a variant property. It may or may not be a method. Use a regular function instead."_s;
523 } else if (m_typeResolver->equals(a: property.type(), b: m_typeResolver->jsValueType())) {
524 errorType =
525 u"a QJSValue property. It may or may not be a method. Use a regular Q_INVOKABLE instead."_s;
526 } else {
527 errorType = u"not a method"_s;
528 }
529
530 m_logger->log(message: u"%1 \"%2\" is %3"_s.arg(args&: propertyType, args: name, args&: errorType), id: qmlUseProperFunction,
531 srcLocation: getCurrentSourceLocation(), showContext: true, showFileName: true, suggestion: {});
532
533 return true;
534}
535
536void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
537{
538 // LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope.
539 // Any import namespaces etc. are handled via LoadProperty or GetLookup.
540
541 const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
542 const QString name = m_jsUnitGenerator->stringForIndex(index: nameIndex);
543
544 setAccumulator(m_typeResolver->scopedType(scope: m_function->qmlScope, name));
545
546 if (!m_state.accumulatorOut().isValid() && m_typeResolver->isPrefix(name)) {
547 const QQmlJSRegisterContent inType = m_typeResolver->globalType(type: m_function->qmlScope);
548 setAccumulator(QQmlJSRegisterContent::create(
549 storedType: m_typeResolver->voidType(), importNamespaceStringId: nameIndex, variant: QQmlJSRegisterContent::ScopeModulePrefix,
550 scope: m_typeResolver->containedType(container: inType)));
551 return;
552 }
553
554 checkDeprecated(scope: m_function->qmlScope, name, isMethod: false);
555
556 if (!m_state.accumulatorOut().isValid()) {
557 setError(u"Cannot access value for name "_s + name);
558 handleUnqualifiedAccess(name, isMethod: false);
559 return;
560 }
561
562 const QQmlJSScope::ConstPtr outStored
563 = m_typeResolver->genericType(type: m_state.accumulatorOut().storedType());
564
565 if (outStored.isNull()) {
566 // It should really be valid.
567 // We get the generic type from aotContext->loadQmlContextPropertyIdLookup().
568 setError(u"Cannot determine generic type for "_s + name);
569 return;
570 }
571
572 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectById
573 && !outStored->isReferenceType()) {
574 setError(u"Cannot retrieve a non-object type by ID: "_s + name);
575 return;
576 }
577
578 if (m_passManager != nullptr) {
579 m_passManager->d_func()->analyzeRead(
580 element: QQmlJSScope::createQQmlSAElement(m_function->qmlScope), propertyName: name,
581 readScope: QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
582 location: QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
583 jsLocation: getCurrentBindingSourceLocation()));
584 }
585
586 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ScopeAttached)
587 m_attachedContext = QQmlJSScope::ConstPtr();
588}
589
590/*!
591 \internal
592 As far as type propagation is involved, StoreNameSloppy and
593 StoreNameStrict are completely the same
594 StoreNameStrict is rejecting a few writes (where the variable was not
595 defined before) that would work in a sloppy context in JS, but the
596 compiler would always reject this. And for type propagation, this does
597 not matter at all.
598 \a nameIndex is the index in the string table corresponding to
599 the name which we are storing
600 */
601void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex)
602{
603 const QString name = m_jsUnitGenerator->stringForIndex(index: nameIndex);
604 const QQmlJSRegisterContent type = m_typeResolver->scopedType(scope: m_function->qmlScope, name);
605 const QQmlJSRegisterContent in = m_state.accumulatorIn();
606
607 if (!type.isValid()) {
608 handleUnqualifiedAccess(name, isMethod: false);
609 setError(u"Cannot find name "_s + name);
610 return;
611 }
612
613 if (!type.isProperty()) {
614 QString message = type.isMethod() ? u"Cannot assign to method %1"_s
615 : u"Cannot assign to non-property %1"_s;
616 // The interpreter treats methods as read-only properties in its error messages
617 // and we lack a better fitting category. We might want to revisit this later.
618 m_logger->log(message: message.arg(a: name), id: qmlReadOnlyProperty,
619 srcLocation: getCurrentSourceLocation());
620 setError(u"Cannot assign to non-property "_s + name);
621 return;
622 }
623
624 if (!type.isWritable() && !m_function->qmlScope->hasOwnProperty(name)) {
625 setError(u"Can't assign to read-only property %1"_s.arg(a: name));
626
627 m_logger->log(message: u"Cannot assign to read-only property %1"_s.arg(a: name), id: qmlReadOnlyProperty,
628 srcLocation: getCurrentSourceLocation());
629
630 return;
631 }
632
633 if (!canConvertFromTo(from: in, to: type)) {
634 setError(u"cannot convert from %1 to %2"_s
635 .arg(args: in.descriptiveName(), args: type.descriptiveName()));
636 }
637
638 if (m_passManager != nullptr) {
639 m_passManager->d_func()->analyzeWrite(
640 element: QQmlJSScope::createQQmlSAElement(m_function->qmlScope), propertyName: name,
641 value: QQmlJSScope::createQQmlSAElement(m_typeResolver->containedType(container: in)),
642 writeScope: QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
643 location: QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
644 jsLocation: getCurrentBindingSourceLocation()));
645 }
646
647 m_state.setHasSideEffects(true);
648
649 if (m_typeResolver->canHoldUndefined(content: in) && !m_typeResolver->canHoldUndefined(content: type)) {
650 if (type.property().reset().isEmpty())
651 setError(u"Cannot assign potential undefined to %1"_s.arg(a: type.descriptiveName()));
652 else if (m_typeResolver->registerIsStoredIn(reg: in, type: m_typeResolver->voidType()))
653 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->varType()));
654 else
655 addReadAccumulator(convertTo: in);
656 } else {
657 addReadAccumulator(convertTo: type);
658 }
659}
660
661void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
662{
663 return generate_StoreNameCommon(nameIndex);
664}
665
666void QQmlJSTypePropagator::generate_StoreNameStrict(int name)
667{
668 return generate_StoreNameCommon(nameIndex: name);
669}
670
671bool QQmlJSTypePropagator::checkForEnumProblems(
672 const QQmlJSRegisterContent &base, const QString &propertyName) const
673{
674 if (base.isEnumeration()) {
675 const auto metaEn = base.enumeration();
676 if (!metaEn.isScoped()) {
677 m_logger->log(message: u"You cannot access unscoped enum \"%1\" from here."_s.arg(a: propertyName),
678 id: qmlRestrictedType, srcLocation: getCurrentSourceLocation());
679 return true;
680 } else if (!metaEn.hasKey(key: propertyName)) {
681 auto fixSuggestion = QQmlJSUtils::didYouMean(userInput: propertyName, candidates: metaEn.keys(),
682 location: getCurrentSourceLocation());
683 m_logger->log(message: u"\"%1\" is not an entry of enum \"%2\"."_s.arg(a: propertyName)
684 .arg(a: metaEn.name()),
685 id: qmlMissingEnumEntry, srcLocation: getCurrentSourceLocation(), showContext: true, showFileName: true,
686 suggestion: fixSuggestion);
687 return true;
688 }
689 }
690 return false;
691}
692
693void QQmlJSTypePropagator::generate_LoadElement(int base)
694{
695 const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
696
697 if ((!baseRegister.isList()
698 && !m_typeResolver->registerContains(reg: baseRegister, type: m_typeResolver->stringType()))
699 || !m_typeResolver->isNumeric(type: m_state.accumulatorIn())) {
700 const auto jsValue = m_typeResolver->globalType(type: m_typeResolver->jsValueType());
701 addReadAccumulator(convertTo: jsValue);
702 addReadRegister(index: base, convertTo: jsValue);
703 setAccumulator(jsValue);
704 return;
705 }
706
707 const auto contained = m_typeResolver->containedType(container: m_state.accumulatorIn());
708 if (m_typeResolver->isSignedInteger(type: contained))
709 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->int32Type()));
710 else if (m_typeResolver->isUnsignedInteger(type: contained))
711 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->uint32Type()));
712 else
713 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->realType()));
714
715 addReadRegister(index: base, convertTo: baseRegister);
716 // We can end up with undefined.
717 setAccumulator(m_typeResolver->merge(
718 a: m_typeResolver->valueType(list: baseRegister),
719 b: m_typeResolver->globalType(type: m_typeResolver->voidType())));
720}
721
722void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
723{
724 const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
725 const QQmlJSRegisterContent indexRegister = checkedInputRegister(reg: index);
726
727 if (!baseRegister.isList()
728 || !m_typeResolver->isNumeric(type: indexRegister)) {
729 const auto jsValue = m_typeResolver->globalType(type: m_typeResolver->jsValueType());
730 addReadAccumulator(convertTo: jsValue);
731 addReadRegister(index: base, convertTo: jsValue);
732 addReadRegister(index, convertTo: jsValue);
733
734 // Writing to a JS array can have side effects all over the place since it's
735 // passed by reference.
736 m_state.setHasSideEffects(true);
737 return;
738 }
739
740 const auto contained = m_typeResolver->containedType(container: indexRegister);
741 if (m_typeResolver->isSignedInteger(type: contained))
742 addReadRegister(index, convertTo: m_typeResolver->globalType(type: m_typeResolver->int32Type()));
743 else if (m_typeResolver->isUnsignedInteger(type: contained))
744 addReadRegister(index, convertTo: m_typeResolver->globalType(type: m_typeResolver->uint32Type()));
745 else
746 addReadRegister(index, convertTo: m_typeResolver->globalType(type: m_typeResolver->realType()));
747
748 addReadRegister(index: base, convertTo: baseRegister);
749 addReadAccumulator(convertTo: m_typeResolver->valueType(list: baseRegister));
750
751 // If we're writing a QQmlListProperty backed by a container somewhere else,
752 // that has side effects.
753 // If we're writing to a list retrieved from a property, that _should_ have side effects,
754 // but currently the QML engine doesn't implement them.
755 // TODO: Figure out the above and accurately set the flag.
756 m_state.setHasSideEffects(true);
757}
758
759void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
760{
761 setAccumulator(
762 m_typeResolver->memberType(
763 type: m_state.accumulatorIn(),
764 name: m_state.accumulatorIn().isImportNamespace()
765 ? m_jsUnitGenerator->stringForIndex(index: m_state.accumulatorIn().importNamespace())
766 + u'.' + propertyName
767 : propertyName));
768
769 if (!m_state.accumulatorOut().isValid()) {
770 if (m_typeResolver->isPrefix(name: propertyName)) {
771 Q_ASSERT(m_state.accumulatorIn().isValid());
772 addReadAccumulator(convertTo: m_state.accumulatorIn());
773 setAccumulator(QQmlJSRegisterContent::create(
774 storedType: m_state.accumulatorIn().storedType(),
775 importNamespaceStringId: m_jsUnitGenerator->getStringId(string: propertyName),
776 variant: QQmlJSRegisterContent::ObjectModulePrefix,
777 scope: m_typeResolver->containedType(container: m_state.accumulatorIn())));
778 return;
779 }
780 if (m_state.accumulatorIn().isImportNamespace())
781 m_logger->log(message: u"Type not found in namespace"_s, id: qmlUnresolvedType,
782 srcLocation: getCurrentSourceLocation());
783 } else if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::Singleton
784 && m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectModulePrefix) {
785 m_logger->log(
786 message: u"Cannot access singleton as a property of an object. Did you want to access an attached object?"_s,
787 id: qmlAccessSingleton, srcLocation: getCurrentSourceLocation());
788 setAccumulator(QQmlJSRegisterContent());
789 }
790
791 if (checkForEnumProblems(base: m_state.accumulatorIn(), propertyName))
792 return;
793
794 if (!m_state.accumulatorOut().isValid()) {
795 setError(u"Cannot load property %1 from %2."_s
796 .arg(args: propertyName, args: m_state.accumulatorIn().descriptiveName()));
797
798 const QString typeName = m_typeResolver->containedTypeName(container: m_state.accumulatorIn(), useFancyName: true);
799
800 if (typeName == u"QVariant")
801 return;
802 if (m_state.accumulatorIn().isList() && propertyName == u"length")
803 return;
804
805 auto baseType = m_typeResolver->containedType(container: m_state.accumulatorIn());
806 // Warn separately when a property is only not found because of a missing type
807
808 if (propertyResolution(scope: baseType, propertyName) != PropertyMissing)
809 return;
810
811 std::optional<QQmlJSFixSuggestion> fixSuggestion;
812
813 if (auto suggestion = QQmlJSUtils::didYouMean(userInput: propertyName, candidates: baseType->properties().keys(),
814 location: getCurrentSourceLocation());
815 suggestion.has_value()) {
816 fixSuggestion = suggestion;
817 }
818
819 if (!fixSuggestion.has_value()
820 && m_state.accumulatorIn().variant() == QQmlJSRegisterContent::MetaType) {
821 QStringList enumKeys;
822 for (const QQmlJSMetaEnum &metaEnum :
823 m_state.accumulatorIn().scopeType()->enumerations())
824 enumKeys << metaEnum.keys();
825
826 if (auto suggestion =
827 QQmlJSUtils::didYouMean(userInput: propertyName, candidates: enumKeys, location: getCurrentSourceLocation());
828 suggestion.has_value()) {
829 fixSuggestion = suggestion;
830 }
831 }
832
833 m_logger->log(message: u"Member \"%1\" not found on type \"%2\""_s.arg(a: propertyName).arg(a: typeName),
834 id: qmlMissingProperty, srcLocation: getCurrentSourceLocation(), showContext: true, showFileName: true, suggestion: fixSuggestion);
835 return;
836 }
837
838 if (m_state.accumulatorOut().isMethod() && m_state.accumulatorOut().method().size() != 1) {
839 setError(u"Cannot determine overloaded method on loadProperty"_s);
840 return;
841 }
842
843 if (m_state.accumulatorOut().isProperty()) {
844 if (m_typeResolver->registerContains(
845 reg: m_state.accumulatorOut(), type: m_typeResolver->voidType())) {
846 setError(u"Type %1 does not have a property %2 for reading"_s
847 .arg(args: m_state.accumulatorIn().descriptiveName(), args: propertyName));
848 return;
849 }
850
851 if (!m_state.accumulatorOut().property().type()) {
852 m_logger->log(
853 message: QString::fromLatin1(ba: "Type of property \"%2\" not found").arg(a: propertyName),
854 id: qmlMissingType, srcLocation: getCurrentSourceLocation());
855 }
856 }
857
858 if (m_passManager != nullptr) {
859 const bool isAttached =
860 m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectAttached;
861
862 m_passManager->d_func()->analyzeRead(
863 element: QQmlJSScope::createQQmlSAElement(
864 m_typeResolver->containedType(container: m_state.accumulatorIn())),
865 propertyName,
866 readScope: QQmlJSScope::createQQmlSAElement(isAttached ? m_attachedContext
867 : m_function->qmlScope),
868 location: QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
869 jsLocation: getCurrentBindingSourceLocation()));
870 }
871
872 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectAttached)
873 m_attachedContext = m_typeResolver->containedType(container: m_state.accumulatorIn());
874
875 switch (m_state.accumulatorOut().variant()) {
876 case QQmlJSRegisterContent::ObjectEnum:
877 case QQmlJSRegisterContent::ExtensionObjectEnum:
878 case QQmlJSRegisterContent::Singleton:
879 // For reading enums or singletons, we don't need to access anything, unless it's an
880 // import namespace. Then we need the name.
881 if (m_state.accumulatorIn().isImportNamespace())
882 addReadAccumulator(convertTo: m_state.accumulatorIn());
883 break;
884 default:
885 addReadAccumulator(convertTo: m_state.accumulatorIn());
886 break;
887 }
888}
889
890void QQmlJSTypePropagator::generate_LoadProperty(int nameIndex)
891{
892 propagatePropertyLookup(propertyName: m_jsUnitGenerator->stringForIndex(index: nameIndex));
893}
894
895void QQmlJSTypePropagator::generate_LoadOptionalProperty(int name, int offset)
896{
897 Q_UNUSED(name);
898 Q_UNUSED(offset);
899 INSTR_PROLOGUE_NOT_IMPLEMENTED();
900}
901
902void QQmlJSTypePropagator::generate_GetLookup(int index)
903{
904 propagatePropertyLookup(propertyName: m_jsUnitGenerator->lookupName(index));
905}
906
907void QQmlJSTypePropagator::generate_GetOptionalLookup(int index, int offset)
908{
909 Q_UNUSED(index);
910 Q_UNUSED(offset);
911 INSTR_PROLOGUE_NOT_IMPLEMENTED();
912}
913
914void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
915{
916 auto callBase = m_state.registers[base].content;
917 const QString propertyName = m_jsUnitGenerator->stringForIndex(index: nameIndex);
918
919 QQmlJSRegisterContent property = m_typeResolver->memberType(type: callBase, name: propertyName);
920 if (!property.isProperty()) {
921 setError(u"Type %1 does not have a property %2 for writing"_s
922 .arg(args: callBase.descriptiveName(), args: propertyName));
923 return;
924 }
925
926 if (!property.isWritable()) {
927 setError(u"Can't assign to read-only property %1"_s.arg(a: propertyName));
928
929 m_logger->log(message: u"Cannot assign to read-only property %1"_s.arg(a: propertyName),
930 id: qmlReadOnlyProperty, srcLocation: getCurrentSourceLocation());
931
932 return;
933 }
934
935 if (!canConvertFromTo(from: m_state.accumulatorIn(), to: property)) {
936 setError(u"cannot convert from %1 to %2"_s
937 .arg(args: m_state.accumulatorIn().descriptiveName(), args: property.descriptiveName()));
938 return;
939 }
940
941 if (m_passManager != nullptr) {
942 const bool isAttached = callBase.variant() == QQmlJSRegisterContent::ObjectAttached;
943
944 m_passManager->d_func()->analyzeWrite(
945 element: QQmlJSScope::createQQmlSAElement(m_typeResolver->containedType(container: callBase)),
946 propertyName,
947 value: QQmlJSScope::createQQmlSAElement(
948 m_typeResolver->containedType(container: m_state.accumulatorIn())),
949 writeScope: QQmlJSScope::createQQmlSAElement(isAttached ? m_attachedContext
950 : m_function->qmlScope),
951 location: QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
952 jsLocation: getCurrentBindingSourceLocation()));
953 }
954
955 m_state.setHasSideEffects(true);
956 addReadAccumulator(convertTo: property);
957 addReadRegister(index: base, convertTo: callBase);
958}
959
960void QQmlJSTypePropagator::generate_SetLookup(int index, int base)
961{
962 generate_StoreProperty(nameIndex: m_jsUnitGenerator->lookupNameIndex(index), base);
963}
964
965void QQmlJSTypePropagator::generate_LoadSuperProperty(int property)
966{
967 Q_UNUSED(property)
968 INSTR_PROLOGUE_NOT_IMPLEMENTED();
969}
970
971void QQmlJSTypePropagator::generate_StoreSuperProperty(int property)
972{
973 Q_UNUSED(property)
974 INSTR_PROLOGUE_NOT_IMPLEMENTED();
975}
976
977void QQmlJSTypePropagator::generate_Yield()
978{
979 INSTR_PROLOGUE_NOT_IMPLEMENTED();
980}
981
982void QQmlJSTypePropagator::generate_YieldStar()
983{
984 INSTR_PROLOGUE_NOT_IMPLEMENTED();
985}
986
987void QQmlJSTypePropagator::generate_Resume(int)
988{
989 INSTR_PROLOGUE_NOT_IMPLEMENTED();
990}
991
992void QQmlJSTypePropagator::generate_CallValue(int name, int argc, int argv)
993{
994 m_state.setHasSideEffects(true);
995 Q_UNUSED(name)
996 Q_UNUSED(argc)
997 Q_UNUSED(argv)
998 INSTR_PROLOGUE_NOT_IMPLEMENTED();
999}
1000
1001void QQmlJSTypePropagator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv)
1002{
1003 m_state.setHasSideEffects(true);
1004 Q_UNUSED(name)
1005 Q_UNUSED(thisObject)
1006 Q_UNUSED(argc)
1007 Q_UNUSED(argv)
1008 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1009}
1010
1011static bool isLoggingMethod(const QString &consoleMethod)
1012{
1013 return consoleMethod == u"log" || consoleMethod == u"debug" || consoleMethod == u"info"
1014 || consoleMethod == u"warn" || consoleMethod == u"error";
1015}
1016
1017void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
1018{
1019 Q_ASSERT(m_state.registers.contains(base));
1020 const auto callBase = m_state.registers[base].content;
1021 const QString propertyName = m_jsUnitGenerator->stringForIndex(index: nameIndex);
1022
1023 if (m_typeResolver->registerContains(
1024 reg: callBase, type: m_typeResolver->jsGlobalObject()->property(name: u"Math"_s).type())) {
1025
1026 // If we call a method on the Math object we don't need the actual Math object. We do need
1027 // to transfer the type information to the code generator so that it knows that this is the
1028 // Math object. Read the base register as void. void isn't stored, and the place where it's
1029 // created will be optimized out if there are no other readers. The code generator can
1030 // retrieve the original type and determine that it was the Math object.
1031 addReadRegister(index: base, convertTo: m_typeResolver->globalType(type: m_typeResolver->voidType()));
1032
1033 QQmlJSRegisterContent realType = m_typeResolver->globalType(type: m_typeResolver->realType());
1034 for (int i = 0; i < argc; ++i)
1035 addReadRegister(index: argv + i, convertTo: realType);
1036 setAccumulator(realType);
1037 return;
1038 }
1039
1040 if (m_typeResolver->registerContains(
1041 reg: callBase, type: m_typeResolver->jsGlobalObject()->property(name: u"console"_s).type())
1042 && isLoggingMethod(consoleMethod: propertyName)) {
1043
1044 const QQmlJSRegisterContent voidType
1045 = m_typeResolver->globalType(type: m_typeResolver->voidType());
1046
1047 // If we call a method on the console object we don't need the console object.
1048 addReadRegister(index: base, convertTo: voidType);
1049
1050 const QQmlJSRegisterContent stringType
1051 = m_typeResolver->globalType(type: m_typeResolver->stringType());
1052
1053 if (argc > 0) {
1054 const QQmlJSScope::ConstPtr firstArg
1055 = m_typeResolver->containedType(container: m_state.registers[argv].content);
1056 if (firstArg->isReferenceType()) {
1057 // We cannot know whether this will be a logging category at run time.
1058 // Therefore we always pass any object types as special last argument.
1059 addReadRegister(index: argv, convertTo: m_typeResolver->globalType(
1060 type: m_typeResolver->genericType(type: firstArg)));
1061 } else {
1062 addReadRegister(index: argv, convertTo: stringType);
1063 }
1064 }
1065
1066 for (int i = 1; i < argc; ++i)
1067 addReadRegister(index: argv + i, convertTo: stringType);
1068
1069 m_state.setHasSideEffects(true);
1070 setAccumulator(voidType);
1071 return;
1072 }
1073
1074 if (m_typeResolver->registerContains(reg: callBase, type: m_typeResolver->jsValueType())
1075 || m_typeResolver->registerContains(reg: callBase, type: m_typeResolver->varType())) {
1076 const auto jsValueType = m_typeResolver->globalType(type: m_typeResolver->jsValueType());
1077 addReadRegister(index: base, convertTo: jsValueType);
1078 for (int i = 0; i < argc; ++i)
1079 addReadRegister(index: argv + i, convertTo: jsValueType);
1080 setAccumulator(jsValueType);
1081 m_state.setHasSideEffects(true);
1082 return;
1083 }
1084
1085 const auto baseType = m_typeResolver->containedType(container: callBase);
1086 const auto member = m_typeResolver->memberType(type: callBase, name: propertyName);
1087 if (!member.isMethod()) {
1088 setError(u"Type %1 does not have a property %2 for calling"_s
1089 .arg(args: callBase.descriptiveName(), args: propertyName));
1090
1091 if (callBase.isType() && isCallingProperty(scope: callBase.type(), name: propertyName))
1092 return;
1093
1094 if (checkForEnumProblems(base: callBase, propertyName))
1095 return;
1096
1097 std::optional<QQmlJSFixSuggestion> fixSuggestion;
1098
1099 if (auto suggestion = QQmlJSUtils::didYouMean(userInput: propertyName, candidates: baseType->methods().keys(),
1100 location: getCurrentSourceLocation());
1101 suggestion.has_value()) {
1102 fixSuggestion = suggestion;
1103 }
1104
1105 m_logger->log(message: u"Member \"%1\" not found on type \"%2\""_s.arg(
1106 args: propertyName, args: m_typeResolver->containedTypeName(container: callBase, useFancyName: true)),
1107 id: qmlMissingProperty, srcLocation: getCurrentSourceLocation(), showContext: true, showFileName: true, suggestion: fixSuggestion);
1108 return;
1109 }
1110
1111 checkDeprecated(scope: baseType, name: propertyName, isMethod: true);
1112
1113 if (m_passManager != nullptr) {
1114 // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass)
1115 m_passManager->d_func()->analyzeRead(
1116 element: QQmlJSScope::createQQmlSAElement(baseType), propertyName,
1117 readScope: QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
1118 location: QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
1119 jsLocation: getCurrentBindingSourceLocation()));
1120 }
1121
1122 addReadRegister(index: base, convertTo: callBase);
1123
1124 if (m_typeResolver->registerContains(reg: callBase, type: m_typeResolver->stringType())) {
1125 if (propertyName == u"arg"_s && argc == 1) {
1126 propagateStringArgCall(argv);
1127 return;
1128 }
1129 }
1130
1131 if (baseType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
1132 && m_typeResolver->equals(
1133 a: member.scopeType(),
1134 b: m_typeResolver->arrayType()->baseType())
1135 && propagateArrayMethod(name: propertyName, argc, argv, valueType: callBase)) {
1136 return;
1137 }
1138
1139 propagateCall(methods: member.method(), argc, argv, scope: member.scopeType());
1140}
1141
1142QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMethod> &methods,
1143 int argc, int argv, QStringList *errors)
1144{
1145 QQmlJSMetaMethod javascriptFunction;
1146 QQmlJSMetaMethod candidate;
1147 for (const auto &method : methods) {
1148
1149 // If we encounter a JavaScript function, use this as a fallback if no other method matches
1150 if (method.isJavaScriptFunction() && !javascriptFunction.isValid())
1151 javascriptFunction = method;
1152
1153 if (method.returnType().isNull() && !method.returnTypeName().isEmpty()) {
1154 errors->append(t: u"return type %1 cannot be resolved"_s
1155 .arg(a: method.returnTypeName()));
1156 continue;
1157 }
1158
1159 const auto arguments = method.parameters();
1160 if (argc != arguments.size()) {
1161 errors->append(
1162 t: u"Function expects %1 arguments, but %2 were provided"_s.arg(a: arguments.size())
1163 .arg(a: argc));
1164 continue;
1165 }
1166
1167 bool fuzzyMatch = true;
1168 bool exactMatch = true;
1169 for (int i = 0; i < argc; ++i) {
1170 const auto argumentType = arguments[i].type();
1171 if (argumentType.isNull()) {
1172 errors->append(
1173 t: u"type %1 for argument %2 cannot be resolved"_s.arg(a: arguments[i].typeName())
1174 .arg(a: i));
1175 exactMatch = false;
1176 fuzzyMatch = false;
1177 break;
1178 }
1179
1180 const auto content = m_state.registers[argv + i].content;
1181 if (m_typeResolver->registerContains(reg: content, type: argumentType))
1182 continue;
1183
1184 exactMatch = false;
1185 if (canConvertFromTo(from: content, to: m_typeResolver->globalType(type: argumentType)))
1186 continue;
1187
1188 errors->append(
1189 t: u"argument %1 contains %2 but is expected to contain the type %3"_s.arg(a: i).arg(
1190 args: m_state.registers[argv + i].content.descriptiveName(),
1191 args: arguments[i].typeName()));
1192 fuzzyMatch = false;
1193 break;
1194 }
1195
1196 if (exactMatch)
1197 return method;
1198 else if (fuzzyMatch && !candidate.isValid())
1199 candidate = method;
1200 }
1201
1202 return candidate.isValid() ? candidate : javascriptFunction;
1203}
1204
1205void QQmlJSTypePropagator::setAccumulator(const QQmlJSRegisterContent &content)
1206{
1207 setRegister(index: Accumulator, content);
1208}
1209
1210void QQmlJSTypePropagator::setRegister(int index, const QQmlJSRegisterContent &content)
1211{
1212 // If we've come to the same conclusion before, let's not track the type again.
1213 auto it = m_prevStateAnnotations.find(key: currentInstructionOffset());
1214 if (it != m_prevStateAnnotations.end()) {
1215 const QQmlJSRegisterContent &lastTry = it->second.changedRegister;
1216 if (m_typeResolver->registerContains(reg: lastTry, type: m_typeResolver->containedType(container: content))) {
1217 m_state.setRegister(registerIndex: index, content: lastTry);
1218 return;
1219 }
1220 }
1221
1222 m_state.setRegister(registerIndex: index, content: m_typeResolver->tracked(type: content));
1223}
1224
1225void QQmlJSTypePropagator::mergeRegister(
1226 int index, const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b)
1227{
1228 auto merged = m_typeResolver->merge(a, b);
1229
1230 Q_ASSERT(merged.isValid());
1231 Q_ASSERT(merged.isConversion());
1232
1233 auto tryPrevStateConversion = [this](int index, const QQmlJSRegisterContent &merged) -> bool {
1234 auto it = m_prevStateAnnotations.find(key: currentInstructionOffset());
1235 if (it == m_prevStateAnnotations.end())
1236 return false;
1237
1238 auto conversion = it->second.typeConversions.find(key: index);
1239 if (conversion == it->second.typeConversions.end())
1240 return false;
1241
1242 const VirtualRegister &lastTry = conversion.value();
1243
1244 Q_ASSERT(lastTry.content.isValid());
1245 Q_ASSERT(lastTry.content.isConversion());
1246
1247 if (!m_typeResolver->equals(a: lastTry.content.conversionResult(), b: merged.conversionResult())
1248 || lastTry.content.conversionOrigins() != merged.conversionOrigins()) {
1249 return false;
1250 }
1251
1252 // We don't need to track it again if we've come to the same conclusion before.
1253 m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry;
1254 m_state.registers[index] = lastTry;
1255 return true;
1256 };
1257
1258 if (!tryPrevStateConversion(index, merged)) {
1259 merged = m_typeResolver->tracked(type: merged);
1260 Q_ASSERT(merged.isValid());
1261 m_state.annotations[currentInstructionOffset()].typeConversions[index].content = merged;
1262 m_state.registers[index].content = merged;
1263 }
1264}
1265
1266void QQmlJSTypePropagator::addReadRegister(int index, const QQmlJSRegisterContent &convertTo)
1267{
1268 m_state.addReadRegister(registerIndex: index, reg: m_typeResolver->convert
1269 (from: m_state.registers[index].content, to: convertTo));
1270}
1271
1272void QQmlJSTypePropagator::propagateCall(
1273 const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
1274 const QQmlJSScope::ConstPtr &scope)
1275{
1276 QStringList errors;
1277 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, errors: &errors);
1278
1279 if (!match.isValid()) {
1280 Q_ASSERT(errors.size() == methods.size());
1281 if (methods.size() == 1)
1282 setError(errors.first());
1283 else
1284 setError(u"No matching override found. Candidates:\n"_s + errors.join(sep: u'\n'));
1285 return;
1286 }
1287
1288 const auto returnType = match.isJavaScriptFunction()
1289 ? m_typeResolver->jsValueType()
1290 : QQmlJSScope::ConstPtr(match.returnType());
1291 setAccumulator(m_typeResolver->returnType(
1292 type: returnType ? QQmlJSScope::ConstPtr(returnType) : m_typeResolver->voidType(),
1293 variant: match.isJavaScriptFunction()
1294 ? QQmlJSRegisterContent::JavaScriptReturnValue
1295 : QQmlJSRegisterContent::MethodReturnValue,
1296 scope));
1297 if (!m_state.accumulatorOut().isValid())
1298 setError(u"Cannot store return type of method %1()."_s.arg(a: match.methodName()));
1299
1300 m_state.setHasSideEffects(true);
1301 const auto types = match.parameters();
1302 for (int i = 0; i < argc; ++i) {
1303 if (i < types.size()) {
1304 const QQmlJSScope::ConstPtr type = match.isJavaScriptFunction()
1305 ? m_typeResolver->jsValueType()
1306 : QQmlJSScope::ConstPtr(types.at(i).type());
1307 if (!type.isNull()) {
1308 addReadRegister(index: argv + i, convertTo: m_typeResolver->globalType(type));
1309 continue;
1310 }
1311 }
1312 addReadRegister(index: argv + i, convertTo: m_typeResolver->globalType(type: m_typeResolver->jsValueType()));
1313 }
1314}
1315
1316bool QQmlJSTypePropagator::propagateTranslationMethod(
1317 const QList<QQmlJSMetaMethod> &methods, int argc, int argv)
1318{
1319 if (methods.size() != 1)
1320 return false;
1321
1322 const QQmlJSMetaMethod method = methods.front();
1323 const QQmlJSRegisterContent intType
1324 = m_typeResolver->globalType(type: m_typeResolver->int32Type());
1325 const QQmlJSRegisterContent stringType
1326 = m_typeResolver->globalType(type: m_typeResolver->stringType());
1327 const QQmlJSRegisterContent returnType
1328 = m_typeResolver->returnType(
1329 type: m_typeResolver->stringType(), variant: QQmlJSRegisterContent::MethodReturnValue,
1330 scope: m_typeResolver->jsGlobalObject());
1331
1332 if (method.methodName() == u"qsTranslate"_s) {
1333 switch (argc) {
1334 case 4:
1335 addReadRegister(index: argv + 3, convertTo: intType); // n
1336 Q_FALLTHROUGH();
1337 case 3:
1338 addReadRegister(index: argv + 2, convertTo: stringType); // disambiguation
1339 Q_FALLTHROUGH();
1340 case 2:
1341 addReadRegister(index: argv + 1, convertTo: stringType); // sourceText
1342 addReadRegister(index: argv, convertTo: stringType); // context
1343 setAccumulator(returnType);
1344 return true;
1345 default:
1346 return false;
1347 }
1348 }
1349
1350 if (method.methodName() == u"QT_TRANSLATE_NOOP"_s) {
1351 switch (argc) {
1352 case 3:
1353 addReadRegister(index: argv + 2, convertTo: stringType); // disambiguation
1354 Q_FALLTHROUGH();
1355 case 2:
1356 addReadRegister(index: argv + 1, convertTo: stringType); // sourceText
1357 addReadRegister(index: argv, convertTo: stringType); // context
1358 setAccumulator(returnType);
1359 return true;
1360 default:
1361 return false;
1362 }
1363 }
1364
1365 if (method.methodName() == u"qsTr"_s) {
1366 switch (argc) {
1367 case 3:
1368 addReadRegister(index: argv + 2, convertTo: intType); // n
1369 Q_FALLTHROUGH();
1370 case 2:
1371 addReadRegister(index: argv + 1, convertTo: stringType); // disambiguation
1372 Q_FALLTHROUGH();
1373 case 1:
1374 addReadRegister(index: argv, convertTo: stringType); // sourceText
1375 setAccumulator(returnType);
1376 return true;
1377 default:
1378 return false;
1379 }
1380 }
1381
1382 if (method.methodName() == u"QT_TR_NOOP"_s) {
1383 switch (argc) {
1384 case 2:
1385 addReadRegister(index: argv + 1, convertTo: stringType); // disambiguation
1386 Q_FALLTHROUGH();
1387 case 1:
1388 addReadRegister(index: argv, convertTo: stringType); // sourceText
1389 setAccumulator(returnType);
1390 return true;
1391 default:
1392 return false;
1393 }
1394 }
1395
1396 if (method.methodName() == u"qsTrId"_s) {
1397 switch (argc) {
1398 case 2:
1399 addReadRegister(index: argv + 1, convertTo: intType); // n
1400 Q_FALLTHROUGH();
1401 case 1:
1402 addReadRegister(index: argv, convertTo: stringType); // id
1403 setAccumulator(returnType);
1404 return true;
1405 default:
1406 return false;
1407 }
1408 }
1409
1410 if (method.methodName() == u"QT_TRID_NOOP"_s) {
1411 switch (argc) {
1412 case 1:
1413 addReadRegister(index: argv, convertTo: stringType); // id
1414 setAccumulator(returnType);
1415 return true;
1416 default:
1417 return false;
1418 }
1419 }
1420
1421 return false;
1422}
1423
1424void QQmlJSTypePropagator::propagateStringArgCall(int argv)
1425{
1426 setAccumulator(m_typeResolver->returnType(
1427 type: m_typeResolver->stringType(), variant: QQmlJSRegisterContent::MethodReturnValue,
1428 scope: m_typeResolver->stringType()));
1429 Q_ASSERT(m_state.accumulatorOut().isValid());
1430
1431 const QQmlJSScope::ConstPtr input = m_typeResolver->containedType(
1432 container: m_state.registers[argv].content);
1433
1434 if (m_typeResolver->equals(a: input, b: m_typeResolver->uint32Type())) {
1435 addReadRegister(index: argv, convertTo: m_typeResolver->globalType(type: m_typeResolver->realType()));
1436 return;
1437 }
1438
1439 if (m_typeResolver->isIntegral(type: input)) {
1440 addReadRegister(index: argv, convertTo: m_typeResolver->globalType(type: m_typeResolver->int32Type()));
1441 return;
1442 }
1443
1444 if (m_typeResolver->isNumeric(type: input)) {
1445 addReadRegister(index: argv, convertTo: m_typeResolver->globalType(type: m_typeResolver->realType()));
1446 return;
1447 }
1448
1449 if (m_typeResolver->equals(a: input, b: m_typeResolver->boolType())) {
1450 addReadRegister(index: argv, convertTo: m_typeResolver->globalType(type: m_typeResolver->boolType()));
1451 return;
1452 }
1453
1454 addReadRegister(index: argv, convertTo: m_typeResolver->globalType(type: m_typeResolver->stringType()));
1455}
1456
1457bool QQmlJSTypePropagator::propagateArrayMethod(
1458 const QString &name, int argc, int argv, const QQmlJSRegisterContent &baseType)
1459{
1460 // TODO:
1461 // * For concat() we need to decide what kind of array to return and what kinds of arguments to
1462 // accept.
1463 // * For entries(), keys(), and values() we need iterators.
1464 // * For find(), findIndex(), sort(), every(), some(), forEach(), map(), filter(), reduce(),
1465 // and reduceRight() we need typed function pointers.
1466
1467 const auto intType = m_typeResolver->globalType(type: m_typeResolver->int32Type());
1468 const auto boolType = m_typeResolver->globalType(type: m_typeResolver->boolType());
1469 const auto stringType = m_typeResolver->globalType(type: m_typeResolver->stringType());
1470 const auto valueType = m_typeResolver->globalType(
1471 type: m_typeResolver->containedType(container: baseType)->valueType());
1472
1473 // TODO: We should remember whether a register content can be written back when
1474 // converting and merging. Also, we need a way to detect the "only in same statement"
1475 // write back case. To do this, we should store the statementNumber(s) in
1476 // Property and Conversion RegisterContents.
1477 const bool canHaveSideEffects = (baseType.isProperty() && baseType.isWritable())
1478 || baseType.isConversion();
1479
1480 if (name == u"copyWithin" && argc > 0 && argc < 4) {
1481 for (int i = 0; i < argc; ++i) {
1482 if (!canConvertFromTo(from: m_state.registers[argv + i].content, to: intType))
1483 return false;
1484 }
1485
1486 for (int i = 0; i < argc; ++i)
1487 addReadRegister(index: argv + i, convertTo: intType);
1488
1489 setAccumulator(baseType);
1490 m_state.setHasSideEffects(canHaveSideEffects);
1491 return true;
1492 }
1493
1494 if (name == u"fill" && argc > 0 && argc < 4) {
1495 if (!canConvertFromTo(from: m_state.registers[argv].content, to: valueType))
1496 return false;
1497
1498 for (int i = 1; i < argc; ++i) {
1499 if (!canConvertFromTo(from: m_state.registers[argv + i].content, to: intType))
1500 return false;
1501 }
1502
1503 addReadRegister(index: argv, convertTo: valueType);
1504
1505 for (int i = 1; i < argc; ++i)
1506 addReadRegister(index: argv + i, convertTo: intType);
1507
1508 setAccumulator(baseType);
1509 m_state.setHasSideEffects(canHaveSideEffects);
1510 return true;
1511 }
1512
1513 if (name == u"includes" && argc > 0 && argc < 3) {
1514 if (!canConvertFromTo(from: m_state.registers[argv].content, to: valueType))
1515 return false;
1516
1517 if (argc == 2) {
1518 if (!canConvertFromTo(from: m_state.registers[argv + 1].content, to: intType))
1519 return false;
1520 addReadRegister(index: argv + 1, convertTo: intType);
1521 }
1522
1523 addReadRegister(index: argv, convertTo: valueType);
1524 setAccumulator(boolType);
1525 return true;
1526 }
1527
1528 if (name == u"toString" || (name == u"join" && argc < 2)) {
1529 if (argc == 1) {
1530 if (!canConvertFromTo(from: m_state.registers[argv].content, to: stringType))
1531 return false;
1532 addReadRegister(index: argv, convertTo: stringType);
1533 }
1534
1535 setAccumulator(stringType);
1536 return true;
1537 }
1538
1539 if ((name == u"pop" || name == u"shift") && argc == 0) {
1540 setAccumulator(valueType);
1541 m_state.setHasSideEffects(canHaveSideEffects);
1542 return true;
1543 }
1544
1545 if (name == u"push" || name == u"unshift") {
1546 for (int i = 0; i < argc; ++i) {
1547 if (!canConvertFromTo(from: m_state.registers[argv + i].content, to: valueType))
1548 return false;
1549 }
1550
1551 for (int i = 0; i < argc; ++i)
1552 addReadRegister(index: argv + i, convertTo: valueType);
1553
1554 setAccumulator(intType);
1555 m_state.setHasSideEffects(canHaveSideEffects);
1556 return true;
1557 }
1558
1559 if (name == u"reverse" && argc == 0) {
1560 setAccumulator(baseType);
1561 m_state.setHasSideEffects(canHaveSideEffects);
1562 return true;
1563 }
1564
1565 if (name == u"slice" && argc < 3) {
1566 for (int i = 0; i < argc; ++i) {
1567 if (!canConvertFromTo(from: m_state.registers[argv + i].content, to: intType))
1568 return false;
1569 }
1570
1571 for (int i = 0; i < argc; ++i)
1572 addReadRegister(index: argv + i, convertTo: intType);
1573
1574 setAccumulator(baseType.storedType()->isListProperty()
1575 ? m_typeResolver->globalType(type: m_typeResolver->qObjectListType())
1576 : baseType);
1577 return true;
1578 }
1579
1580 if (name == u"splice" && argc > 0) {
1581 for (int i = 0; i < 2; ++i) {
1582 if (!canConvertFromTo(from: m_state.registers[argv + i].content, to: intType))
1583 return false;
1584 }
1585
1586 for (int i = 2; i < argc; ++i) {
1587 if (!canConvertFromTo(from: m_state.registers[argv + i].content, to: valueType))
1588 return false;
1589 }
1590
1591 for (int i = 0; i < 2; ++i)
1592 addReadRegister(index: argv + i, convertTo: intType);
1593
1594 for (int i = 2; i < argc; ++i)
1595 addReadRegister(index: argv + i, convertTo: valueType);
1596
1597 setAccumulator(baseType);
1598 m_state.setHasSideEffects(canHaveSideEffects);
1599 return true;
1600 }
1601
1602 if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
1603 if (!canConvertFromTo(from: m_state.registers[argv].content, to: valueType))
1604 return false;
1605
1606 if (argc == 2) {
1607 if (!canConvertFromTo(from: m_state.registers[argv + 1].content, to: intType))
1608 return false;
1609 addReadRegister(index: argv + 1, convertTo: intType);
1610 }
1611
1612 addReadRegister(index: argv, convertTo: valueType);
1613 setAccumulator(intType);
1614 return true;
1615 }
1616
1617 return false;
1618}
1619
1620void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc,
1621 int argv)
1622{
1623 generate_CallProperty(nameIndex: m_jsUnitGenerator->lookupNameIndex(index: lookupIndex), base, argc, argv);
1624}
1625
1626void QQmlJSTypePropagator::generate_CallName(int name, int argc, int argv)
1627{
1628 propagateScopeLookupCall(functionName: m_jsUnitGenerator->stringForIndex(index: name), argc, argv);
1629}
1630
1631void QQmlJSTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv)
1632{
1633 m_state.setHasSideEffects(true);
1634 Q_UNUSED(argc)
1635 Q_UNUSED(argv)
1636 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1637}
1638
1639void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName, int argc, int argv)
1640{
1641 const QQmlJSRegisterContent resolvedContent
1642 = m_typeResolver->scopedType(scope: m_function->qmlScope, name: functionName);
1643 if (resolvedContent.isMethod()) {
1644 const auto methods = resolvedContent.method();
1645 if (resolvedContent.variant() == QQmlJSRegisterContent::JavaScriptGlobal) {
1646 if (propagateTranslationMethod(methods, argc, argv))
1647 return;
1648 }
1649
1650 if (!methods.isEmpty()) {
1651 propagateCall(methods, argc, argv, scope: resolvedContent.scopeType());
1652 return;
1653 }
1654 }
1655
1656 setError(u"method %1 cannot be resolved."_s.arg(a: functionName));
1657 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->jsValueType()));
1658
1659 setError(u"Cannot find function '%1'"_s.arg(a: functionName));
1660
1661 handleUnqualifiedAccess(name: functionName, isMethod: true);
1662}
1663
1664void QQmlJSTypePropagator::generate_CallGlobalLookup(int index, int argc, int argv)
1665{
1666 propagateScopeLookupCall(functionName: m_jsUnitGenerator->lookupName(index), argc, argv);
1667}
1668
1669void QQmlJSTypePropagator::generate_CallQmlContextPropertyLookup(int index, int argc, int argv)
1670{
1671 const QString name = m_jsUnitGenerator->lookupName(index);
1672 propagateScopeLookupCall(functionName: name, argc, argv);
1673 checkDeprecated(scope: m_function->qmlScope, name, isMethod: true);
1674}
1675
1676void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int argc, int argv)
1677{
1678 m_state.setHasSideEffects(true);
1679 Q_UNUSED(func)
1680 Q_UNUSED(thisObject)
1681 Q_UNUSED(argc)
1682 Q_UNUSED(argv)
1683 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1684}
1685
1686void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, int argv)
1687{
1688 m_state.setHasSideEffects(true);
1689 Q_UNUSED(func)
1690 Q_UNUSED(thisObject)
1691 Q_UNUSED(argc)
1692 Q_UNUSED(argv)
1693 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1694}
1695
1696void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv)
1697{
1698 m_state.setHasSideEffects(true);
1699 Q_UNUSED(func)
1700 Q_UNUSED(argv)
1701
1702 Q_UNUSED(argc)
1703
1704 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->jsValueType()));
1705}
1706
1707void QQmlJSTypePropagator::generate_ConstructWithSpread(int func, int argc, int argv)
1708{
1709 m_state.setHasSideEffects(true);
1710 Q_UNUSED(func)
1711 Q_UNUSED(argc)
1712 Q_UNUSED(argv)
1713 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1714}
1715
1716void QQmlJSTypePropagator::generate_SetUnwindHandler(int offset)
1717{
1718 m_state.setHasSideEffects(true);
1719 Q_UNUSED(offset)
1720 INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE();
1721}
1722
1723void QQmlJSTypePropagator::generate_UnwindDispatch()
1724{
1725 m_state.setHasSideEffects(true);
1726 INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE();
1727}
1728
1729void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset)
1730{
1731 m_state.setHasSideEffects(true);
1732 Q_UNUSED(level)
1733 Q_UNUSED(offset)
1734 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1735}
1736
1737void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name)
1738{
1739 const auto fail = [this, name]() {
1740 setError(u"Cannot statically assert the dead temporal zone check for %1"_s.arg(
1741 a: name ? m_jsUnitGenerator->stringForIndex(index: name) : u"the anonymous accumulator"_s));
1742 };
1743
1744 const QQmlJSRegisterContent in = m_state.accumulatorIn();
1745 if (in.isConversion()) {
1746 for (const QQmlJSScope::ConstPtr &origin : in.conversionOrigins()) {
1747 if (!m_typeResolver->equals(a: origin, b: m_typeResolver->emptyType()))
1748 continue;
1749 fail();
1750 break;
1751 }
1752 } else if (m_typeResolver->registerContains(reg: in, type: m_typeResolver->emptyType())) {
1753 fail();
1754 }
1755}
1756
1757void QQmlJSTypePropagator::generate_ThrowException()
1758{
1759 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->jsValueType()));
1760 m_state.setHasSideEffects(true);
1761 m_state.skipInstructionsUntilNextJumpTarget = true;
1762}
1763
1764void QQmlJSTypePropagator::generate_GetException()
1765{
1766 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1767}
1768
1769void QQmlJSTypePropagator::generate_SetException()
1770{
1771 m_state.setHasSideEffects(true);
1772 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1773}
1774
1775void QQmlJSTypePropagator::generate_CreateCallContext()
1776{
1777 m_state.setHasSideEffects(true);
1778}
1779
1780void QQmlJSTypePropagator::generate_PushCatchContext(int index, int name)
1781{
1782 m_state.setHasSideEffects(true);
1783 Q_UNUSED(index)
1784 Q_UNUSED(name)
1785 INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE();
1786}
1787
1788void QQmlJSTypePropagator::generate_PushWithContext()
1789{
1790 m_state.setHasSideEffects(true);
1791 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1792}
1793
1794void QQmlJSTypePropagator::generate_PushBlockContext(int index)
1795{
1796 m_state.setHasSideEffects(true);
1797 Q_UNUSED(index)
1798 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1799}
1800
1801void QQmlJSTypePropagator::generate_CloneBlockContext()
1802{
1803 m_state.setHasSideEffects(true);
1804 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1805}
1806
1807void QQmlJSTypePropagator::generate_PushScriptContext(int index)
1808{
1809 m_state.setHasSideEffects(true);
1810 Q_UNUSED(index)
1811 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1812}
1813
1814void QQmlJSTypePropagator::generate_PopScriptContext()
1815{
1816 m_state.setHasSideEffects(true);
1817 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1818}
1819
1820void QQmlJSTypePropagator::generate_PopContext()
1821{
1822 m_state.setHasSideEffects(true);
1823}
1824
1825void QQmlJSTypePropagator::generate_GetIterator(int iterator)
1826{
1827 Q_UNUSED(iterator)
1828 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1829}
1830
1831void QQmlJSTypePropagator::generate_IteratorNext(int value, int done)
1832{
1833 Q_UNUSED(value)
1834 Q_UNUSED(done)
1835 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1836}
1837
1838void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object)
1839{
1840 Q_UNUSED(iterator)
1841 Q_UNUSED(object)
1842 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1843}
1844
1845void QQmlJSTypePropagator::generate_IteratorClose(int done)
1846{
1847 Q_UNUSED(done)
1848 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1849}
1850
1851void QQmlJSTypePropagator::generate_DestructureRestElement()
1852{
1853 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1854}
1855
1856void QQmlJSTypePropagator::generate_DeleteProperty(int base, int index)
1857{
1858 Q_UNUSED(base)
1859 Q_UNUSED(index)
1860 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1861}
1862
1863void QQmlJSTypePropagator::generate_DeleteName(int name)
1864{
1865 Q_UNUSED(name)
1866 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1867}
1868
1869void QQmlJSTypePropagator::generate_TypeofName(int name)
1870{
1871 Q_UNUSED(name);
1872 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->stringType()));
1873}
1874
1875void QQmlJSTypePropagator::generate_TypeofValue()
1876{
1877 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->stringType()));
1878}
1879
1880void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable)
1881{
1882 Q_UNUSED(varName)
1883 Q_UNUSED(isDeletable)
1884 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1885}
1886
1887void QQmlJSTypePropagator::generate_DefineArray(int argc, int args)
1888{
1889 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->variantListType()));
1890
1891 // Track all arguments as the same type.
1892 const QQmlJSRegisterContent elementType
1893 = m_typeResolver->tracked(type: m_typeResolver->globalType(type: m_typeResolver->varType()));
1894 for (int i = 0; i < argc; ++i)
1895 addReadRegister(index: args + i, convertTo: elementType);
1896}
1897
1898void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
1899{
1900 // TODO: computed property names, getters, and setters are unsupported. How do we catch them?
1901
1902 Q_UNUSED(internalClassId)
1903 Q_UNUSED(argc)
1904 Q_UNUSED(args)
1905 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->jsValueType()));
1906}
1907
1908void QQmlJSTypePropagator::generate_CreateClass(int classIndex, int heritage, int computedNames)
1909{
1910 Q_UNUSED(classIndex)
1911 Q_UNUSED(heritage)
1912 Q_UNUSED(computedNames)
1913 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1914}
1915
1916void QQmlJSTypePropagator::generate_CreateMappedArgumentsObject()
1917{
1918 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1919}
1920
1921void QQmlJSTypePropagator::generate_CreateUnmappedArgumentsObject()
1922{
1923 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1924}
1925
1926void QQmlJSTypePropagator::generate_CreateRestParameter(int argIndex)
1927{
1928 Q_UNUSED(argIndex)
1929 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1930}
1931
1932void QQmlJSTypePropagator::generate_ConvertThisToObject()
1933{
1934 setRegister(index: This, content: m_typeResolver->globalType(type: m_typeResolver->qObjectType()));
1935}
1936
1937void QQmlJSTypePropagator::generate_LoadSuperConstructor()
1938{
1939 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1940}
1941
1942void QQmlJSTypePropagator::generate_ToObject()
1943{
1944 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1945}
1946
1947void QQmlJSTypePropagator::generate_Jump(int offset)
1948{
1949 saveRegisterStateForJump(offset);
1950 m_state.skipInstructionsUntilNextJumpTarget = true;
1951 m_state.setHasSideEffects(true);
1952}
1953
1954void QQmlJSTypePropagator::generate_JumpTrue(int offset)
1955{
1956 if (!canConvertFromTo(from: m_state.accumulatorIn(),
1957 to: m_typeResolver->globalType(type: m_typeResolver->boolType()))) {
1958 setError(u"cannot convert from %1 to boolean"_s
1959 .arg(a: m_state.accumulatorIn().descriptiveName()));
1960 return;
1961 }
1962 saveRegisterStateForJump(offset);
1963 m_state.setHasSideEffects(true);
1964 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->boolType()));
1965}
1966
1967void QQmlJSTypePropagator::generate_JumpFalse(int offset)
1968{
1969 if (!canConvertFromTo(from: m_state.accumulatorIn(),
1970 to: m_typeResolver->globalType(type: m_typeResolver->boolType()))) {
1971 setError(u"cannot convert from %1 to boolean"_s
1972 .arg(a: m_state.accumulatorIn().descriptiveName()));
1973 return;
1974 }
1975 saveRegisterStateForJump(offset);
1976 m_state.setHasSideEffects(true);
1977 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->boolType()));
1978}
1979
1980void QQmlJSTypePropagator::generate_JumpNoException(int offset)
1981{
1982 saveRegisterStateForJump(offset);
1983 m_state.setHasSideEffects(true);
1984}
1985
1986void QQmlJSTypePropagator::generate_JumpNotUndefined(int offset)
1987{
1988 Q_UNUSED(offset)
1989 INSTR_PROLOGUE_NOT_IMPLEMENTED();
1990}
1991
1992void QQmlJSTypePropagator::generate_CheckException()
1993{
1994 m_state.setHasSideEffects(true);
1995}
1996
1997void QQmlJSTypePropagator::recordEqualsNullType()
1998{
1999 // TODO: We can specialize this further, for QVariant, QJSValue, int, bool, whatever.
2000 if (m_typeResolver->registerContains(reg: m_state.accumulatorIn(), type: m_typeResolver->nullType())
2001 || m_typeResolver->containedType(container: m_state.accumulatorIn())->isReferenceType()) {
2002 addReadAccumulator(convertTo: m_state.accumulatorIn());
2003 } else {
2004 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->jsPrimitiveType()));
2005 }
2006}
2007void QQmlJSTypePropagator::recordEqualsIntType()
2008{
2009 // We have specializations for numeric types and bool.
2010 const QQmlJSScope::ConstPtr in = m_typeResolver->containedType(container: m_state.accumulatorIn());
2011 if (m_typeResolver->registerContains(reg: m_state.accumulatorIn(), type: m_typeResolver->boolType())
2012 || m_typeResolver->isNumeric(type: m_state.accumulatorIn())) {
2013 addReadAccumulator(convertTo: m_state.accumulatorIn());
2014 } else {
2015 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->jsPrimitiveType()));
2016 }
2017}
2018void QQmlJSTypePropagator::recordEqualsType(int lhs)
2019{
2020 const auto isNumericOrEnum = [this](const QQmlJSRegisterContent &content) {
2021 return content.isEnumeration() || m_typeResolver->isNumeric(type: content);
2022 };
2023
2024 const auto isIntCompatible = [this](const QQmlJSRegisterContent &content) {
2025 auto contained = m_typeResolver->containedType(container: content);
2026 if (contained->scopeType() == QQmlSA::ScopeType::EnumScope)
2027 contained = contained->baseType();
2028 return m_typeResolver->isIntegral(type: contained)
2029 && !m_typeResolver->equals(a: contained, b: m_typeResolver->uint32Type());
2030 };
2031
2032 const auto accumulatorIn = m_state.accumulatorIn();
2033 const auto lhsRegister = m_state.registers[lhs].content;
2034
2035 // If the types are primitive, we compare directly ...
2036 if (m_typeResolver->isPrimitive(type: accumulatorIn) || accumulatorIn.isEnumeration()) {
2037 if (m_typeResolver->registerContains(
2038 reg: accumulatorIn, type: m_typeResolver->containedType(container: lhsRegister))) {
2039 addReadRegister(index: lhs, convertTo: accumulatorIn);
2040 addReadAccumulator(convertTo: accumulatorIn);
2041 return;
2042 } else if (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister)) {
2043 const auto targetType = isIntCompatible(accumulatorIn) && isIntCompatible(lhsRegister)
2044 ? m_typeResolver->globalType(type: m_typeResolver->int32Type())
2045 : m_typeResolver->globalType(type: m_typeResolver->realType());
2046 addReadRegister(index: lhs, convertTo: targetType);
2047 addReadAccumulator(convertTo: targetType);
2048 return;
2049 } else if (m_typeResolver->isPrimitive(type: lhsRegister)) {
2050 const QQmlJSRegisterContent primitive = m_typeResolver->globalType(
2051 type: m_typeResolver->jsPrimitiveType());
2052 addReadRegister(index: lhs, convertTo: primitive);
2053 addReadAccumulator(convertTo: primitive);
2054 return;
2055 }
2056 }
2057
2058 // We don't modify types if the types are comparable with QObject, QUrl or var types
2059 if (canStrictlyCompareWithVar(typeResolver: m_typeResolver, lhsContent: lhsRegister, rhsContent: accumulatorIn)
2060 || canCompareWithQObject(typeResolver: m_typeResolver, lhsContent: lhsRegister, rhsContent: accumulatorIn)
2061 || canCompareWithQUrl(typeResolver: m_typeResolver, lhsContent: lhsRegister, rhsContent: accumulatorIn)) {
2062 addReadRegister(index: lhs, convertTo: lhsRegister);
2063 addReadAccumulator(convertTo: accumulatorIn);
2064 return;
2065 }
2066
2067 // Otherwise they're both casted to QJSValue.
2068 // TODO: We can add more specializations here: object/null etc
2069
2070 const QQmlJSRegisterContent jsval = m_typeResolver->globalType(type: m_typeResolver->jsValueType());
2071 addReadRegister(index: lhs, convertTo: jsval);
2072 addReadAccumulator(convertTo: jsval);
2073}
2074
2075void QQmlJSTypePropagator::recordCompareType(int lhs)
2076{
2077 // If they're both numeric, we can compare them directly.
2078 // They may be casted to double, though.
2079 const QQmlJSRegisterContent read
2080 = (m_typeResolver->isNumeric(type: m_state.accumulatorIn())
2081 && m_typeResolver->isNumeric(type: m_state.registers[lhs].content))
2082 ? m_typeResolver->merge(a: m_state.accumulatorIn(), b: m_state.registers[lhs].content)
2083 : m_typeResolver->globalType(type: m_typeResolver->jsPrimitiveType());
2084 addReadRegister(index: lhs, convertTo: read);
2085 addReadAccumulator(convertTo: read);
2086}
2087
2088void QQmlJSTypePropagator::generate_CmpEqNull()
2089{
2090 recordEqualsNullType();
2091 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->boolType()));
2092}
2093
2094void QQmlJSTypePropagator::generate_CmpNeNull()
2095{
2096 recordEqualsNullType();
2097 setAccumulator(m_typeResolver->globalType(type: m_typeResolver->boolType()));
2098}
2099
2100void QQmlJSTypePropagator::generate_CmpEqInt(int lhsConst)
2101{
2102 recordEqualsIntType();
2103 Q_UNUSED(lhsConst)
2104 setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation(
2105 oper: QSOperator::Op::Equal, left: m_typeResolver->globalType(type: m_typeResolver->int32Type()),
2106 right: m_state.accumulatorIn())));
2107}
2108
2109void QQmlJSTypePropagator::generate_CmpNeInt(int lhsConst)
2110{
2111 recordEqualsIntType();
2112 Q_UNUSED(lhsConst)
2113 setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation(
2114 oper: QSOperator::Op::NotEqual, left: m_typeResolver->globalType(type: m_typeResolver->int32Type()),
2115 right: m_state.accumulatorIn())));
2116}
2117
2118void QQmlJSTypePropagator::generate_CmpEq(int lhs)
2119{
2120 recordEqualsType(lhs);
2121 propagateBinaryOperation(op: QSOperator::Op::Equal, lhs);
2122}
2123
2124void QQmlJSTypePropagator::generate_CmpNe(int lhs)
2125{
2126 recordEqualsType(lhs);
2127 propagateBinaryOperation(op: QSOperator::Op::NotEqual, lhs);
2128}
2129
2130void QQmlJSTypePropagator::generate_CmpGt(int lhs)
2131{
2132 recordCompareType(lhs);
2133 propagateBinaryOperation(op: QSOperator::Op::Gt, lhs);
2134}
2135
2136void QQmlJSTypePropagator::generate_CmpGe(int lhs)
2137{
2138 recordCompareType(lhs);
2139 propagateBinaryOperation(op: QSOperator::Op::Ge, lhs);
2140}
2141
2142void QQmlJSTypePropagator::generate_CmpLt(int lhs)
2143{
2144 recordCompareType(lhs);
2145 propagateBinaryOperation(op: QSOperator::Op::Lt, lhs);
2146}
2147
2148void QQmlJSTypePropagator::generate_CmpLe(int lhs)
2149{
2150 recordCompareType(lhs);
2151 propagateBinaryOperation(op: QSOperator::Op::Le, lhs);
2152}
2153
2154void QQmlJSTypePropagator::generate_CmpStrictEqual(int lhs)
2155{
2156 recordEqualsType(lhs);
2157 propagateBinaryOperation(op: QSOperator::Op::StrictEqual, lhs);
2158}
2159
2160void QQmlJSTypePropagator::generate_CmpStrictNotEqual(int lhs)
2161{
2162 recordEqualsType(lhs);
2163 propagateBinaryOperation(op: QSOperator::Op::StrictNotEqual, lhs);
2164}
2165
2166void QQmlJSTypePropagator::generate_CmpIn(int lhs)
2167{
2168 // TODO: Most of the time we don't need the object at all, but only its metatype.
2169 // Fix this when we add support for the "in" instruction to the code generator.
2170 // Also, specialize on lhs to avoid conversion to QJSPrimitiveValue.
2171
2172 addReadRegister(index: lhs, convertTo: m_typeResolver->globalType(type: m_typeResolver->jsValueType()));
2173 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->jsValueType()));
2174
2175 propagateBinaryOperation(op: QSOperator::Op::In, lhs);
2176}
2177
2178void QQmlJSTypePropagator::generate_CmpInstanceOf(int lhs)
2179{
2180 Q_UNUSED(lhs)
2181 INSTR_PROLOGUE_NOT_IMPLEMENTED();
2182}
2183
2184void QQmlJSTypePropagator::generate_As(int lhs)
2185{
2186 const QQmlJSRegisterContent input = checkedInputRegister(reg: lhs);
2187 QQmlJSScope::ConstPtr contained;
2188
2189 switch (m_state.accumulatorIn().variant()) {
2190 case QQmlJSRegisterContent::ScopeAttached:
2191 contained = m_state.accumulatorIn().scopeType();
2192 break;
2193 case QQmlJSRegisterContent::MetaType:
2194 contained = m_state.accumulatorIn().scopeType();
2195 if (contained->isComposite()) // Otherwise we don't need it
2196 addReadAccumulator(convertTo: m_typeResolver->globalType(type: m_typeResolver->metaObjectType()));
2197 break;
2198 default:
2199 contained = m_typeResolver->containedType(container: m_state.accumulatorIn());
2200 break;
2201 }
2202
2203 QQmlJSRegisterContent output;
2204
2205 if (contained->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
2206 // A referece type cast can result in either the type or null.
2207 // Reference tpyes can hold null. We don't need to special case that.
2208 output = m_typeResolver->globalType(type: contained);
2209 } else if (!m_typeResolver->canAddressValueTypes()) {
2210 setError(u"invalid cast from %1 to %2. You can only cast object types."_s
2211 .arg(args: input.descriptiveName(), args: m_state.accumulatorIn().descriptiveName()));
2212 return;
2213 } else {
2214 // A value type cast can result in either the type or undefined.
2215 output = m_typeResolver->merge(
2216 a: m_typeResolver->globalType(type: contained),
2217 b: m_typeResolver->globalType(type: m_typeResolver->voidType()));
2218 }
2219
2220 addReadRegister(index: lhs, convertTo: input);
2221 setAccumulator(output);
2222}
2223
2224void QQmlJSTypePropagator::checkConversion(
2225 const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to)
2226{
2227 if (!canConvertFromTo(from, to)) {
2228 setError(u"cannot convert from %1 to %2"_s
2229 .arg(args: from.descriptiveName(), args: to.descriptiveName()));
2230 }
2231}
2232
2233void QQmlJSTypePropagator::generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator op)
2234{
2235 const QQmlJSRegisterContent type = m_typeResolver->typeForArithmeticUnaryOperation(
2236 op, operand: m_state.accumulatorIn());
2237 checkConversion(from: m_state.accumulatorIn(), to: type);
2238 addReadAccumulator(convertTo: type);
2239 setAccumulator(type);
2240}
2241
2242void QQmlJSTypePropagator::generate_UNot()
2243{
2244 generateUnaryArithmeticOperation(op: QQmlJSTypeResolver::UnaryOperator::Not);
2245}
2246
2247void QQmlJSTypePropagator::generate_UPlus()
2248{
2249 generateUnaryArithmeticOperation(op: QQmlJSTypeResolver::UnaryOperator::Plus);
2250}
2251
2252void QQmlJSTypePropagator::generate_UMinus()
2253{
2254 generateUnaryArithmeticOperation(op: QQmlJSTypeResolver::UnaryOperator::Minus);
2255}
2256
2257void QQmlJSTypePropagator::generate_UCompl()
2258{
2259 generateUnaryArithmeticOperation(op: QQmlJSTypeResolver::UnaryOperator::Complement);
2260}
2261
2262void QQmlJSTypePropagator::generate_Increment()
2263{
2264 generateUnaryArithmeticOperation(op: QQmlJSTypeResolver::UnaryOperator::Increment);
2265}
2266
2267void QQmlJSTypePropagator::generate_Decrement()
2268{
2269 generateUnaryArithmeticOperation(op: QQmlJSTypeResolver::UnaryOperator::Decrement);
2270}
2271
2272void QQmlJSTypePropagator::generateBinaryArithmeticOperation(QSOperator::Op op, int lhs)
2273{
2274 const QQmlJSRegisterContent type = propagateBinaryOperation(op, lhs);
2275
2276 checkConversion(from: checkedInputRegister(reg: lhs), to: type);
2277 addReadRegister(index: lhs, convertTo: type);
2278
2279 checkConversion(from: m_state.accumulatorIn(), to: type);
2280 addReadAccumulator(convertTo: type);
2281}
2282
2283void QQmlJSTypePropagator::generateBinaryConstArithmeticOperation(QSOperator::Op op)
2284{
2285 const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
2286 oper: op, left: m_state.accumulatorIn(),
2287 right: m_typeResolver->builtinType(type: m_typeResolver->int32Type()));
2288
2289 checkConversion(from: m_state.accumulatorIn(), to: type);
2290 addReadAccumulator(convertTo: type);
2291 setAccumulator(type);
2292}
2293
2294void QQmlJSTypePropagator::generate_Add(int lhs)
2295{
2296 generateBinaryArithmeticOperation(op: QSOperator::Op::Add, lhs);
2297}
2298
2299void QQmlJSTypePropagator::generate_BitAnd(int lhs)
2300{
2301 generateBinaryArithmeticOperation(op: QSOperator::Op::BitAnd, lhs);
2302}
2303
2304void QQmlJSTypePropagator::generate_BitOr(int lhs)
2305{
2306 generateBinaryArithmeticOperation(op: QSOperator::Op::BitOr, lhs);
2307}
2308
2309void QQmlJSTypePropagator::generate_BitXor(int lhs)
2310{
2311 generateBinaryArithmeticOperation(op: QSOperator::Op::BitXor, lhs);
2312}
2313
2314void QQmlJSTypePropagator::generate_UShr(int lhs)
2315{
2316 generateBinaryArithmeticOperation(op: QSOperator::Op::URShift, lhs);
2317}
2318
2319void QQmlJSTypePropagator::generate_Shr(int lhs)
2320{
2321 generateBinaryArithmeticOperation(op: QSOperator::Op::RShift, lhs);
2322}
2323
2324void QQmlJSTypePropagator::generate_Shl(int lhs)
2325{
2326 generateBinaryArithmeticOperation(op: QSOperator::Op::LShift, lhs);
2327}
2328
2329void QQmlJSTypePropagator::generate_BitAndConst(int rhsConst)
2330{
2331 Q_UNUSED(rhsConst)
2332 generateBinaryConstArithmeticOperation(op: QSOperator::Op::BitAnd);
2333}
2334
2335void QQmlJSTypePropagator::generate_BitOrConst(int rhsConst)
2336{
2337 Q_UNUSED(rhsConst)
2338 generateBinaryConstArithmeticOperation(op: QSOperator::Op::BitOr);
2339}
2340
2341void QQmlJSTypePropagator::generate_BitXorConst(int rhsConst)
2342{
2343 Q_UNUSED(rhsConst)
2344 generateBinaryConstArithmeticOperation(op: QSOperator::Op::BitXor);
2345}
2346
2347void QQmlJSTypePropagator::generate_UShrConst(int rhsConst)
2348{
2349 Q_UNUSED(rhsConst)
2350 generateBinaryConstArithmeticOperation(op: QSOperator::Op::URShift);
2351}
2352
2353void QQmlJSTypePropagator::generate_ShrConst(int rhsConst)
2354{
2355 Q_UNUSED(rhsConst)
2356 generateBinaryConstArithmeticOperation(op: QSOperator::Op::RShift);
2357}
2358
2359void QQmlJSTypePropagator::generate_ShlConst(int rhsConst)
2360{
2361 Q_UNUSED(rhsConst)
2362 generateBinaryConstArithmeticOperation(op: QSOperator::Op::LShift);
2363}
2364
2365void QQmlJSTypePropagator::generate_Exp(int lhs)
2366{
2367 generateBinaryArithmeticOperation(op: QSOperator::Op::Exp, lhs);
2368}
2369
2370void QQmlJSTypePropagator::generate_Mul(int lhs)
2371{
2372 generateBinaryArithmeticOperation(op: QSOperator::Op::Mul, lhs);
2373}
2374
2375void QQmlJSTypePropagator::generate_Div(int lhs)
2376{
2377 generateBinaryArithmeticOperation(op: QSOperator::Op::Div, lhs);
2378}
2379
2380void QQmlJSTypePropagator::generate_Mod(int lhs)
2381{
2382 generateBinaryArithmeticOperation(op: QSOperator::Op::Mod, lhs);
2383}
2384
2385void QQmlJSTypePropagator::generate_Sub(int lhs)
2386{
2387 generateBinaryArithmeticOperation(op: QSOperator::Op::Sub, lhs);
2388}
2389
2390void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
2391{
2392 for (int reg = firstReg, end = firstReg + count; reg < end; ++reg)
2393 setRegister(index: reg, content: m_typeResolver->globalType(type: m_typeResolver->emptyType()));
2394}
2395
2396void QQmlJSTypePropagator::generate_ThrowOnNullOrUndefined()
2397{
2398 INSTR_PROLOGUE_NOT_IMPLEMENTED();
2399}
2400
2401void QQmlJSTypePropagator::generate_GetTemplateObject(int index)
2402{
2403 Q_UNUSED(index)
2404 INSTR_PROLOGUE_NOT_IMPLEMENTED();
2405}
2406
2407QV4::Moth::ByteCodeHandler::Verdict
2408QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type)
2409{
2410 if (m_error->isValid())
2411 return SkipInstruction;
2412
2413 if (m_state.jumpTargets.contains(value: currentInstructionOffset())) {
2414 if (m_state.skipInstructionsUntilNextJumpTarget) {
2415 // When re-surfacing from dead code, all registers are invalid.
2416 m_state.registers.clear();
2417 m_state.skipInstructionsUntilNextJumpTarget = false;
2418 }
2419 } else if (m_state.skipInstructionsUntilNextJumpTarget
2420 && !instructionManipulatesContext(type)) {
2421 return SkipInstruction;
2422 }
2423
2424 const int currentOffset = currentInstructionOffset();
2425
2426 // If we reach an instruction that is a target of a jump earlier, then we must check that the
2427 // register state at the origin matches the current state. If not, then we may have to inject
2428 // conversion code (communicated to code gen via m_state.typeConversions). For
2429 // example:
2430 //
2431 // function blah(x: number) { return x > 10 ? 10 : x}
2432 //
2433 // translates to a situation where in the "true" case, we load an integer into the accumulator
2434 // and in the else case a number (x). When the control flow is joined, the types don't match and
2435 // we need to make sure that the int is converted to a double just before the jump.
2436 for (auto originRegisterStateIt =
2437 m_jumpOriginRegisterStateByTargetInstructionOffset.constFind(key: currentOffset);
2438 originRegisterStateIt != m_jumpOriginRegisterStateByTargetInstructionOffset.constEnd()
2439 && originRegisterStateIt.key() == currentOffset;
2440 ++originRegisterStateIt) {
2441 auto stateToMerge = *originRegisterStateIt;
2442 for (auto registerIt = stateToMerge.registers.constBegin(),
2443 end = stateToMerge.registers.constEnd();
2444 registerIt != end; ++registerIt) {
2445 const int registerIndex = registerIt.key();
2446
2447 auto newType = registerIt.value().content;
2448 if (!newType.isValid()) {
2449 setError(u"When reached from offset %1, %2 is undefined"_s
2450 .arg(a: stateToMerge.originatingOffset)
2451 .arg(a: registerName(registerIndex)));
2452 return SkipInstruction;
2453 }
2454
2455 auto currentRegister = m_state.registers.find(key: registerIndex);
2456 if (currentRegister != m_state.registers.end())
2457 mergeRegister(index: registerIndex, a: newType, b: currentRegister.value().content);
2458 else
2459 mergeRegister(index: registerIndex, a: newType, b: newType);
2460 }
2461 }
2462
2463 return ProcessInstruction;
2464}
2465
2466void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
2467{
2468 InstructionAnnotation &currentInstruction = m_state.annotations[currentInstructionOffset()];
2469 currentInstruction.changedRegister = m_state.changedRegister();
2470 currentInstruction.changedRegisterIndex = m_state.changedRegisterIndex();
2471 currentInstruction.readRegisters = m_state.takeReadRegisters();
2472 currentInstruction.hasSideEffects = m_state.hasSideEffects();
2473 currentInstruction.isRename = m_state.isRename();
2474
2475 switch (instr) {
2476 // the following instructions are not expected to produce output in the accumulator
2477 case QV4::Moth::Instr::Type::Ret:
2478 case QV4::Moth::Instr::Type::Jump:
2479 case QV4::Moth::Instr::Type::JumpFalse:
2480 case QV4::Moth::Instr::Type::JumpTrue:
2481 case QV4::Moth::Instr::Type::StoreReg:
2482 case QV4::Moth::Instr::Type::StoreElement:
2483 case QV4::Moth::Instr::Type::StoreNameSloppy:
2484 case QV4::Moth::Instr::Type::StoreProperty:
2485 case QV4::Moth::Instr::Type::SetLookup:
2486 case QV4::Moth::Instr::Type::MoveConst:
2487 case QV4::Moth::Instr::Type::MoveReg:
2488 case QV4::Moth::Instr::Type::CheckException:
2489 case QV4::Moth::Instr::Type::CreateCallContext:
2490 case QV4::Moth::Instr::Type::PopContext:
2491 case QV4::Moth::Instr::Type::JumpNoException:
2492 case QV4::Moth::Instr::Type::ThrowException:
2493 case QV4::Moth::Instr::Type::SetUnwindHandler:
2494 case QV4::Moth::Instr::Type::PushCatchContext:
2495 case QV4::Moth::Instr::Type::UnwindDispatch:
2496 case QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone:
2497 case QV4::Moth::Instr::Type::ConvertThisToObject:
2498 case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
2499 if (m_state.changedRegisterIndex() == Accumulator && !m_error->isValid()) {
2500 setError(u"Instruction is not expected to populate the accumulator"_s);
2501 return;
2502 }
2503 break;
2504 default:
2505 // If the instruction is expected to produce output, save it in the register set
2506 // for the next instruction.
2507 if ((!m_state.changedRegister().isValid() || m_state.changedRegisterIndex() != Accumulator)
2508 && !m_error->isValid()) {
2509 setError(u"Instruction is expected to populate the accumulator"_s);
2510 return;
2511 }
2512 }
2513
2514 if (m_state.changedRegisterIndex() != InvalidRegister) {
2515 Q_ASSERT(m_error->isValid() || m_state.changedRegister().isValid());
2516 VirtualRegister &r = m_state.registers[m_state.changedRegisterIndex()];
2517 r.content = m_state.changedRegister();
2518 r.canMove = false;
2519 r.affectedBySideEffects = m_state.isRename()
2520 && m_state.isRegisterAffectedBySideEffects(registerIndex: m_state.renameSourceRegisterIndex());
2521 m_state.clearChangedRegister();
2522 }
2523
2524 m_state.setHasSideEffects(false);
2525 m_state.setIsRename(false);
2526 m_state.setReadRegisters(VirtualRegisters());
2527}
2528
2529QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs)
2530{
2531 auto lhsRegister = checkedInputRegister(reg: lhs);
2532 if (!lhsRegister.isValid())
2533 return QQmlJSRegisterContent();
2534
2535 const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
2536 oper: op, left: lhsRegister, right: m_state.accumulatorIn());
2537
2538 setAccumulator(type);
2539
2540 // If we're dealing with QJSPrimitiveType, do not force premature conversion of the arguemnts
2541 // to the target type. Such an operation can lose information.
2542 if (type.storedType() == m_typeResolver->jsPrimitiveType())
2543 return m_typeResolver->globalType(type: m_typeResolver->jsPrimitiveType());
2544
2545 return type;
2546}
2547
2548void QQmlJSTypePropagator::saveRegisterStateForJump(int offset)
2549{
2550 auto jumpToOffset = offset + nextInstructionOffset();
2551 ExpectedRegisterState state;
2552 state.registers = m_state.registers;
2553 state.originatingOffset = currentInstructionOffset();
2554 m_state.jumpTargets.insert(value: jumpToOffset);
2555 if (offset < 0) {
2556 // We're jumping backwards. We won't get to merge the register states in this pass anymore.
2557
2558 const auto registerStates =
2559 m_jumpOriginRegisterStateByTargetInstructionOffset.equal_range(key: jumpToOffset);
2560 for (auto it = registerStates.first; it != registerStates.second; ++it) {
2561 if (it->registers.keys() == state.registers.keys()
2562 && it->registers.values() == state.registers.values()) {
2563 return; // We've seen the same register state before. No need for merging.
2564 }
2565 }
2566
2567 // The register state at the target offset needs to be resolved in a further pass.
2568 m_state.needsMorePasses = true;
2569 }
2570 m_jumpOriginRegisterStateByTargetInstructionOffset.insert(key: jumpToOffset, value: state);
2571}
2572
2573QString QQmlJSTypePropagator::registerName(int registerIndex) const
2574{
2575 if (registerIndex == Accumulator)
2576 return u"accumulator"_s;
2577 if (registerIndex >= FirstArgument
2578 && registerIndex < FirstArgument + m_function->argumentTypes.size()) {
2579 return u"argument %1"_s.arg(a: registerIndex - FirstArgument);
2580 }
2581
2582 return u"temporary register %1"_s.arg(
2583 a: registerIndex - FirstArgument - m_function->argumentTypes.size());
2584}
2585
2586QQmlJSRegisterContent QQmlJSTypePropagator::checkedInputRegister(int reg)
2587{
2588 const auto regIt = m_state.registers.find(key: reg);
2589 if (regIt == m_state.registers.end()) {
2590 if (isArgument(registerIndex: reg))
2591 return argumentType(registerIndex: reg);
2592
2593 setError(u"Type error: could not infer the type of an expression"_s);
2594 return {};
2595 }
2596 return regIt.value().content;
2597}
2598
2599bool QQmlJSTypePropagator::canConvertFromTo(const QQmlJSRegisterContent &from,
2600 const QQmlJSRegisterContent &to)
2601{
2602 return m_typeResolver->canConvertFromTo(from, to);
2603}
2604
2605QT_END_NAMESPACE
2606

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