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

Provided by KDAB

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

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