aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljstypepropagator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler/qqmljstypepropagator.cpp')
-rw-r--r--src/qmlcompiler/qqmljstypepropagator.cpp1262
1 files changed, 850 insertions, 412 deletions
diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp
index 380042e2fe..d7a7d68d9f 100644
--- a/src/qmlcompiler/qqmljstypepropagator.cpp
+++ b/src/qmlcompiler/qqmljstypepropagator.cpp
@@ -9,6 +9,8 @@
#include <private/qv4compilerscanfunctions_p.h>
+#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h>
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -26,20 +28,20 @@ using namespace Qt::StringLiterals;
QQmlJSTypePropagator::QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator,
const QQmlJSTypeResolver *typeResolver,
- QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo,
+ QQmlJSLogger *logger, BasicBlocks basicBlocks,
+ InstructionAnnotations annotations,
QQmlSA::PassManager *passManager)
- : QQmlJSCompilePass(unitGenerator, typeResolver, logger),
- m_typeInfo(typeInfo),
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger, basicBlocks, annotations),
m_passManager(passManager)
{
}
-QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run(
+QQmlJSCompilePass::BlocksAndAnnotations QQmlJSTypePropagator::run(
const Function *function, QQmlJS::DiagnosticMessage *error)
{
m_function = function;
m_error = error;
- m_returnType = m_typeResolver->globalType(m_function->returnType);
+ m_returnType = m_function->returnType;
do {
// Reset the error if we need to do another pass
@@ -48,6 +50,7 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run(
m_prevStateAnnotations = m_state.annotations;
m_state = PassState();
+ m_state.annotations = m_annotations;
m_state.State::operator=(initialState(m_function));
reset();
@@ -58,34 +61,49 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run(
// This means that we won't start over for the same reason again.
} while (m_state.needsMorePasses);
- return m_state.annotations;
+ return { std::move(m_basicBlocks), std::move(m_state.annotations) };
}
-#define INSTR_PROLOGUE_NOT_IMPLEMENTED() \
- setError(u"Instruction \"%1\" not implemented"_s \
- .arg(QString::fromUtf8(__func__))); \
- return;
+#define INSTR_PROLOGUE_NOT_IMPLEMENTED() \
+ setError(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__))); \
+ return;
#define INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE() \
m_logger->log(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__)), \
qmlCompiler, QQmlJS::SourceLocation()); \
return;
+void QQmlJSTypePropagator::generate_ret_SAcheck()
+{
+ if (!m_function->isProperty)
+ return;
+ QQmlSA::PassManagerPrivate::get(m_passManager)
+ ->analyzeBinding(QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
+ QQmlJSScope::createQQmlSAElement(
+ m_typeResolver->containedType(m_state.accumulatorIn())),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
void QQmlJSTypePropagator::generate_Ret()
{
- if (m_passManager != nullptr && m_function->isProperty) {
- m_passManager->analyzeBinding(m_function->qmlScope,
- m_typeResolver->containedType(m_state.accumulatorIn()),
- getCurrentBindingSourceLocation());
- }
+ if (m_passManager != nullptr)
+ generate_ret_SAcheck();
if (m_function->isSignalHandler) {
// Signal handlers cannot return anything.
- } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()
- && !m_typeResolver->registerContains(
- m_state.accumulatorIn(), m_typeResolver->voidType())) {
- setError(u"function without type annotation returns %1"_s
- .arg(m_state.accumulatorIn().descriptiveName()));
+ } else if (m_typeResolver->registerContains(
+ m_state.accumulatorIn(), m_typeResolver->voidType())) {
+ // You can always return undefined.
+ } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) {
+ setError(u"function without return type annotation returns %1. This may prevent proper "_s
+ u"compilation to Cpp."_s.arg(m_state.accumulatorIn().descriptiveName()));
+
+ if (m_function->isFullyTyped) {
+ // Do not complain if the function didn't have a valid annotation in the first place.
+ m_logger->log(u"Function without return type annotation returns %1"_s.arg(
+ m_typeResolver->containedTypeName(m_state.accumulatorIn(), true)),
+ qmlIncompatibleType, getCurrentBindingSourceLocation());
+ }
return;
} else if (!canConvertFromTo(m_state.accumulatorIn(), m_returnType)) {
setError(u"cannot convert from %1 to %2"_s
@@ -124,7 +142,7 @@ void QQmlJSTypePropagator::generate_LoadConst(int index)
void QQmlJSTypePropagator::generate_LoadZero()
{
- setAccumulator(m_typeResolver->globalType(m_typeResolver->intType()));
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->int32Type()));
}
void QQmlJSTypePropagator::generate_LoadTrue()
@@ -149,7 +167,7 @@ void QQmlJSTypePropagator::generate_LoadUndefined()
void QQmlJSTypePropagator::generate_LoadInt(int)
{
- setAccumulator(m_typeResolver->globalType(m_typeResolver->intType()));
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->int32Type()));
}
void QQmlJSTypePropagator::generate_MoveConst(int constIndex, int destTemp)
@@ -292,7 +310,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
return;
}
- std::optional<FixSuggestion> suggestion;
+ std::optional<QQmlJSFixSuggestion> suggestion;
auto childScopes = m_function->qmlScope->childScopes();
for (qsizetype i = 0; i < m_function->qmlScope->childScopes().size(); i++) {
@@ -304,12 +322,9 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
if (scope->childScopes().size() == 0)
continue;
- const auto jsId = scope->childScopes().first()->findJSIdentifier(name);
+ const auto jsId = scope->childScopes().first()->jsIdentifier(name);
if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
-
- suggestion = FixSuggestion {};
-
const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
QQmlJS::SourceLocation fixLocation = id.location;
@@ -328,15 +343,15 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
fixString += handler.isMultiline ? u") "_s : u") => "_s;
- suggestion->fixes << FixSuggestion::Fix {
- name
- + QString::fromLatin1(" is accessible in this scope because "
- "you are handling a signal at %1:%2. Use a "
- "function instead.\n")
- .arg(id.location.startLine)
- .arg(id.location.startColumn),
- fixLocation, fixString, QString(), false
+ suggestion = QQmlJSFixSuggestion {
+ name + u" is accessible in this scope because you are handling a signal"
+ " at %1:%2. Use a function instead.\n"_s
+ .arg(id.location.startLine)
+ .arg(id.location.startColumn),
+ fixLocation,
+ fixString
};
+ suggestion->setAutoApplicable();
}
break;
}
@@ -354,11 +369,10 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
if (!it->hasObject())
continue;
if (it->objectType() == m_function->qmlScope) {
- suggestion = FixSuggestion {};
-
- suggestion->fixes << FixSuggestion::Fix {
- name + u" is implicitly injected into this delegate. Add a required property instead."_s,
- m_function->qmlScope->sourceLocation(), QString(), QString(), true
+ suggestion = QQmlJSFixSuggestion {
+ name + " is implicitly injected into this delegate."
+ " Add a required property instead."_L1,
+ m_function->qmlScope->sourceLocation()
};
};
@@ -371,38 +385,36 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isM
for (QQmlJSScope::ConstPtr scope = m_function->qmlScope; !scope.isNull();
scope = scope->parentScope()) {
if (scope->hasProperty(name)) {
- const QString id = m_function->addressableScopes.id(scope);
-
- suggestion = FixSuggestion {};
+ const QString id = m_function->addressableScopes.id(scope, m_function->qmlScope);
QQmlJS::SourceLocation fixLocation = location;
fixLocation.length = 0;
- suggestion->fixes << FixSuggestion::Fix {
- name + QLatin1String(" is a member of a parent element\n")
- + QLatin1String(" You can qualify the access with its id "
- "to avoid this warning:\n"),
- fixLocation, (id.isEmpty() ? u"<id>."_s : (id + u'.')), QString(), id.isEmpty()
+ suggestion = QQmlJSFixSuggestion{
+ name
+ + " is a member of a parent element.\n You can qualify the access "
+ "with its id to avoid this warning.\n"_L1,
+ fixLocation, (id.isEmpty() ? u"<id>."_s : (id + u'.'))
};
- if (id.isEmpty()) {
- suggestion->fixes << FixSuggestion::Fix {
- u"You first have to give the element an id"_s, QQmlJS::SourceLocation {}, {}
- };
- }
+ if (id.isEmpty())
+ suggestion->setHint("You first have to give the element an id"_L1);
+ else
+ suggestion->setAutoApplicable();
}
}
}
if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
&& m_function->addressableScopes.existsAnywhereInDocument(name)) {
- FixSuggestion::Fix bindComponents;
const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
- bindComponents.replacementString = replacement + '\n'_L1;
- bindComponents.message = "Set \"%1\" in order to use IDs "
- "from outer components in nested components."_L1.arg(replacement);
- bindComponents.cutLocation = QQmlJS::SourceLocation(0, 0, 1, 1);
- bindComponents.isHint = false;
- suggestion = FixSuggestion {{ bindComponents }};
+ QQmlJSFixSuggestion bindComponents {
+ "Set \"%1\" in order to use IDs from outer components in nested components."_L1
+ .arg(replacement),
+ QQmlJS::SourceLocation(0, 0, 1, 1),
+ replacement + '\n'_L1
+ };
+ bindComponents.setAutoApplicable();
+ suggestion = bindComponents;
}
if (!suggestion.has_value()) {
@@ -468,52 +480,6 @@ void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QS
m_logger->log(message, qmlDeprecated, getCurrentSourceLocation());
}
-bool QQmlJSTypePropagator::isRestricted(const QString &propertyName) const
-{
- QString restrictedKind;
-
- const auto accumulatorIn = m_state.registers.find(Accumulator);
- if (accumulatorIn == m_state.registers.end())
- return false;
-
- if (accumulatorIn.value().content.isList() && propertyName != u"length") {
- restrictedKind = u"a list"_s;
- } else if (accumulatorIn.value().content.isEnumeration()) {
- const auto metaEn = accumulatorIn.value().content.enumeration();
- if (metaEn.isScoped()) {
- if (!metaEn.hasKey(propertyName))
- restrictedKind = u"an enum"_s;
- } else {
- restrictedKind = u"an unscoped enum"_s;
- }
- } else if (accumulatorIn.value().content.isMethod()) {
- auto overloadSet = accumulatorIn.value().content.method();
- auto potentiallyJSMethod = std::any_of(
- overloadSet.cbegin(), overloadSet.cend(),
- [](const QQmlJSMetaMethod &overload){
- return overload.isJavaScriptFunction();
- });
- if (potentiallyJSMethod) {
- /* JS global constructors like Number get detected as methods
- However, they still have properties that can be accessed
- e.g. Number.EPSILON. This also isn't restricted to constructor
- functions, so use isJavaScriptFunction as an overapproximation.
- That catches also QQmlV4Function, but we're purging uses of it
- anyway.
- */
- return false;
- }
- restrictedKind = u"a method"_s;
- }
-
- if (!restrictedKind.isEmpty())
- m_logger->log(u"Type is %1. You cannot access \"%2\" from here."_s.arg(restrictedKind,
- propertyName),
- qmlRestrictedType, getCurrentSourceLocation());
-
- return !restrictedKind.isEmpty();
-}
-
// Only to be called once a lookup has already failed
QQmlJSTypePropagator::PropertyResolution QQmlJSTypePropagator::propertyResolution(
QQmlJSScope::ConstPtr scope, const QString &propertyName) const
@@ -554,15 +520,17 @@ bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const
if (!methods.isEmpty()) {
errorType = u"shadowed by a property."_s;
switch (methods.first().methodType()) {
- case QQmlJSMetaMethod::Signal:
+ case QQmlJSMetaMethodType::Signal:
propertyType = u"Signal"_s;
break;
- case QQmlJSMetaMethod::Slot:
+ case QQmlJSMetaMethodType::Slot:
propertyType = u"Slot"_s;
break;
- case QQmlJSMetaMethod::Method:
+ case QQmlJSMetaMethodType::Method:
propertyType = u"Method"_s;
break;
+ default:
+ Q_UNREACHABLE();
}
} else if (m_typeResolver->equals(property.type(), m_typeResolver->varType())) {
errorType =
@@ -580,6 +548,17 @@ bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const
return true;
}
+
+void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup_SAcheck(const QString &name)
+{
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope), name,
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+
void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
{
// LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope.
@@ -588,7 +567,7 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
- setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name));
+ setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name, index));
if (!m_state.accumulatorOut().isValid() && m_typeResolver->isPrefix(name)) {
const QQmlJSRegisterContent inType = m_typeResolver->globalType(m_function->qmlScope);
@@ -603,36 +582,77 @@ void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
if (!m_state.accumulatorOut().isValid()) {
setError(u"Cannot access value for name "_s + name);
handleUnqualifiedAccess(name, false);
- } else if (m_typeResolver->genericType(m_state.accumulatorOut().storedType()).isNull()) {
+ return;
+ }
+
+ const QQmlJSScope::ConstPtr outStored
+ = m_typeResolver->genericType(m_state.accumulatorOut().storedType());
+
+ if (outStored.isNull()) {
// It should really be valid.
// We get the generic type from aotContext->loadQmlContextPropertyIdLookup().
setError(u"Cannot determine generic type for "_s + name);
- } else if (m_passManager != nullptr) {
- m_passManager->analyzeRead(m_function->qmlScope, name, m_function->qmlScope,
- getCurrentSourceLocation());
+ return;
+ }
+
+ if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectById
+ && !outStored->isReferenceType()) {
+ setError(u"Cannot retrieve a non-object type by ID: "_s + name);
+ return;
}
+ if (m_passManager != nullptr)
+ generate_LoadQmlContextPropertyLookup_SAcheck(name);
+
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ScopeAttached)
m_attachedContext = QQmlJSScope::ConstPtr();
}
-void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
+void QQmlJSTypePropagator::generate_StoreNameCommon_SAcheck(const QQmlJSRegisterContent &in, const QString &name)
+{
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope), name,
+ QQmlJSScope::createQQmlSAElement(m_typeResolver->containedType(in)),
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+/*!
+ \internal
+ As far as type propagation is involved, StoreNameSloppy and
+ StoreNameStrict are completely the same
+ StoreNameStrict is rejecting a few writes (where the variable was not
+ defined before) that would work in a sloppy context in JS, but the
+ compiler would always reject this. And for type propagation, this does
+ not matter at all.
+ \a nameIndex is the index in the string table corresponding to
+ the name which we are storing
+ */
+void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex)
{
const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name);
const QQmlJSRegisterContent in = m_state.accumulatorIn();
if (!type.isValid()) {
+ handleUnqualifiedAccess(name, false);
setError(u"Cannot find name "_s + name);
return;
}
if (!type.isProperty()) {
+ QString message = type.isMethod() ? u"Cannot assign to method %1"_s
+ : u"Cannot assign to non-property %1"_s;
+ // The interpreter treats methods as read-only properties in its error messages
+ // and we lack a better fitting category. We might want to revisit this later.
+ m_logger->log(message.arg(name), qmlReadOnlyProperty,
+ getCurrentSourceLocation());
setError(u"Cannot assign to non-property "_s + name);
return;
}
- if (!type.isWritable() && !m_function->qmlScope->hasOwnProperty(name)) {
+ if (!type.isWritable()) {
setError(u"Can't assign to read-only property %1"_s.arg(name));
m_logger->log(u"Cannot assign to read-only property %1"_s.arg(name), qmlReadOnlyProperty,
@@ -646,53 +666,95 @@ void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
.arg(in.descriptiveName(), type.descriptiveName()));
}
- if (m_passManager != nullptr) {
- m_passManager->analyzeWrite(m_function->qmlScope, name,
- m_typeResolver->containedType(in),
- m_function->qmlScope, getCurrentSourceLocation());
- }
+ if (m_passManager != nullptr)
+ generate_StoreNameCommon_SAcheck(in, name);
- m_state.setHasSideEffects(true);
if (m_typeResolver->canHoldUndefined(in) && !m_typeResolver->canHoldUndefined(type)) {
- if (type.property().reset().isEmpty())
- setError(u"Cannot assign potential undefined to %1"_s.arg(type.descriptiveName()));
- else if (m_typeResolver->registerIsStoredIn(in, m_typeResolver->voidType()))
+ if (m_typeResolver->registerIsStoredIn(in, m_typeResolver->voidType()))
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->varType()));
else
addReadAccumulator(in);
} else {
addReadAccumulator(type);
}
+
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
+{
+ return generate_StoreNameCommon(nameIndex);
}
void QQmlJSTypePropagator::generate_StoreNameStrict(int name)
{
- m_state.setHasSideEffects(true);
- Q_UNUSED(name)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ return generate_StoreNameCommon(name);
}
-void QQmlJSTypePropagator::generate_LoadElement(int base)
+bool QQmlJSTypePropagator::checkForEnumProblems(
+ const QQmlJSRegisterContent &base, const QString &propertyName)
{
- const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
+ if (base.isEnumeration()) {
+ const auto metaEn = base.enumeration();
+ if (!metaEn.hasKey(propertyName)) {
+ auto fixSuggestion = QQmlJSUtils::didYouMean(propertyName, metaEn.keys(),
+ getCurrentSourceLocation());
+ const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
+ .arg(propertyName, metaEn.name());
+ setError(error);
+ m_logger->log(
+ error, qmlMissingEnumEntry, getCurrentSourceLocation(), true, true,
+ fixSuggestion);
+ return true;
+ }
+ } else if (base.variant() == QQmlJSRegisterContent::MetaType) {
+ const QQmlJSMetaEnum metaEn = base.scopeType()->enumeration(propertyName);
+ if (metaEn.isValid() && !metaEn.isScoped() && !metaEn.isQml()) {
+ const QString error
+ = u"You cannot access unscoped enum \"%1\" from here."_s.arg(propertyName);
+ setError(error);
+ m_logger->log(error, qmlRestrictedType, getCurrentSourceLocation());
+ return true;
+ }
+ }
- if ((baseRegister.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence
- && !m_typeResolver->registerIsStoredIn(baseRegister, m_typeResolver->stringType()))
- || !m_typeResolver->isNumeric(m_state.accumulatorIn())) {
+ return false;
+}
+
+void QQmlJSTypePropagator::generate_LoadElement(int base)
+{
+ const auto fallback = [&]() {
const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType());
addReadAccumulator(jsValue);
addReadRegister(base, jsValue);
setAccumulator(jsValue);
+ };
+
+ const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
+ if (!baseRegister.isList()
+ && !m_typeResolver->registerContains(baseRegister, m_typeResolver->stringType())) {
+ fallback();
return;
}
+ addReadRegister(base, baseRegister);
- if (m_typeResolver->isIntegral(m_state.accumulatorIn()))
- addReadAccumulator(m_typeResolver->globalType(m_typeResolver->intType()));
- else
- addReadAccumulator(m_typeResolver->globalType(m_typeResolver->realType()));
+ if (m_typeResolver->isNumeric(m_state.accumulatorIn())) {
+ const auto contained = m_typeResolver->containedType(m_state.accumulatorIn());
+ if (m_typeResolver->isSignedInteger(contained))
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->sizeType()));
+ else if (m_typeResolver->isUnsignedInteger(contained))
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->uint32Type()));
+ else
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->realType()));
+ } else if (m_typeResolver->isNumeric(m_typeResolver->extractNonVoidFromOptionalType(
+ m_state.accumulatorIn()))) {
+ addReadAccumulator(m_state.accumulatorIn());
+ } else {
+ fallback();
+ return;
+ }
- addReadRegister(base, baseRegister);
// We can end up with undefined.
setAccumulator(m_typeResolver->merge(
m_typeResolver->valueType(baseRegister),
@@ -704,7 +766,7 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
const QQmlJSRegisterContent indexRegister = checkedInputRegister(index);
- if (baseRegister.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence
+ if (!baseRegister.isList()
|| !m_typeResolver->isNumeric(indexRegister)) {
const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType());
addReadAccumulator(jsValue);
@@ -717,8 +779,11 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
return;
}
- if (m_typeResolver->isIntegral(indexRegister))
- addReadRegister(index, m_typeResolver->globalType(m_typeResolver->intType()));
+ const auto contained = m_typeResolver->containedType(indexRegister);
+ if (m_typeResolver->isSignedInteger(contained))
+ addReadRegister(index, m_typeResolver->globalType(m_typeResolver->int32Type()));
+ else if (m_typeResolver->isUnsignedInteger(contained))
+ addReadRegister(index, m_typeResolver->globalType(m_typeResolver->uint32Type()));
else
addReadRegister(index, m_typeResolver->globalType(m_typeResolver->realType()));
@@ -733,7 +798,22 @@ void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
m_state.setHasSideEffects(true);
}
-void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
+void QQmlJSTypePropagator::propagatePropertyLookup_SAcheck(const QString &propertyName)
+{
+ const bool isAttached =
+ m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectAttached;
+
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
+ QQmlJSScope::createQQmlSAElement(
+ m_typeResolver->containedType(m_state.accumulatorIn())),
+ propertyName,
+ QQmlJSScope::createQQmlSAElement(isAttached ? m_attachedContext
+ : m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName, int lookupIndex)
{
setAccumulator(
m_typeResolver->memberType(
@@ -741,52 +821,7 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
m_state.accumulatorIn().isImportNamespace()
? m_jsUnitGenerator->stringForIndex(m_state.accumulatorIn().importNamespace())
+ u'.' + propertyName
- : propertyName));
-
- if (m_typeInfo != nullptr
- && m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ScopeAttached) {
- QQmlJSScope::ConstPtr attachedType = m_typeResolver->originalType(
- m_state.accumulatorIn().scopeType());
-
- for (QQmlJSScope::ConstPtr scope = m_function->qmlScope->parentScope(); !scope.isNull();
- scope = scope->parentScope()) {
- if (m_typeInfo->usedAttachedTypes.values(scope).contains(attachedType)) {
-
- // Ignore enum accesses, as these will not cause the attached object to be created
- if (m_state.accumulatorOut().isValid() && m_state.accumulatorOut().isEnumeration())
- continue;
-
- const QString id = m_function->addressableScopes.id(scope);
-
- FixSuggestion suggestion;
-
- QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation();
- fixLocation.length = 0;
-
- suggestion.fixes << FixSuggestion::Fix { u"Reference it by id instead:"_s,
- fixLocation,
- id.isEmpty() ? u"<id>."_s : (id + u'.'),
- QString(), id.isEmpty() };
-
- fixLocation = scope->sourceLocation();
- fixLocation.length = 0;
-
- if (id.isEmpty()) {
- suggestion.fixes
- << FixSuggestion::Fix { u"You first have to give the element an id"_s,
- QQmlJS::SourceLocation {},
- {} };
- }
-
- m_logger->log(
- u"Using attached type %1 already initialized in a parent scope."_s.arg(
- m_state.accumulatorIn().scopeType()->internalName()),
- qmlAttachedPropertyReuse, getCurrentSourceLocation(), true, true,
- suggestion);
- }
- }
- m_typeInfo->usedAttachedTypes.insert(m_function->qmlScope, attachedType);
- }
+ : propertyName, lookupIndex));
if (!m_state.accumulatorOut().isValid()) {
if (m_typeResolver->isPrefix(propertyName)) {
@@ -808,17 +843,29 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
u"Cannot access singleton as a property of an object. Did you want to access an attached object?"_s,
qmlAccessSingleton, getCurrentSourceLocation());
setAccumulator(QQmlJSRegisterContent());
+ } else if (m_state.accumulatorOut().isEnumeration()) {
+ switch (m_state.accumulatorIn().variant()) {
+ case QQmlJSRegisterContent::ExtensionObjectEnum:
+ case QQmlJSRegisterContent::MetaType:
+ case QQmlJSRegisterContent::ObjectAttached:
+ case QQmlJSRegisterContent::ObjectEnum:
+ case QQmlJSRegisterContent::ObjectModulePrefix:
+ case QQmlJSRegisterContent::ScopeAttached:
+ case QQmlJSRegisterContent::ScopeModulePrefix:
+ case QQmlJSRegisterContent::Singleton:
+ break; // OK, can look up enums on that thing
+ default:
+ setAccumulator(QQmlJSRegisterContent());
+ }
}
- const bool isRestrictedProperty = isRestricted(propertyName);
-
if (!m_state.accumulatorOut().isValid()) {
+ if (checkForEnumProblems(m_state.accumulatorIn(), propertyName))
+ return;
+
setError(u"Cannot load property %1 from %2."_s
.arg(propertyName, m_state.accumulatorIn().descriptiveName()));
- if (isRestrictedProperty)
- return;
-
const QString typeName = m_typeResolver->containedTypeName(m_state.accumulatorIn(), true);
if (typeName == u"QVariant")
@@ -832,7 +879,10 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
if (propertyResolution(baseType, propertyName) != PropertyMissing)
return;
- std::optional<FixSuggestion> fixSuggestion;
+ if (baseType->isScript())
+ return;
+
+ std::optional<QQmlJSFixSuggestion> fixSuggestion;
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
getCurrentSourceLocation());
@@ -854,7 +904,7 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
}
}
- m_logger->log(u"Property \"%1\" not found on type \"%2\""_s.arg(propertyName).arg(typeName),
+ m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName).arg(typeName),
qmlMissingProperty, getCurrentSourceLocation(), true, true, fixSuggestion);
return;
}
@@ -865,6 +915,22 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
}
if (m_state.accumulatorOut().isProperty()) {
+ const QQmlJSScope::ConstPtr mathObject
+ = m_typeResolver->jsGlobalObject()->property(u"Math"_s).type();
+ if (m_typeResolver->registerContains(m_state.accumulatorIn(), mathObject)) {
+ QQmlJSMetaProperty prop;
+ prop.setPropertyName(propertyName);
+ prop.setTypeName(u"double"_s);
+ prop.setType(m_typeResolver->realType());
+ setAccumulator(
+ QQmlJSRegisterContent::create(
+ m_typeResolver->realType(), prop, m_state.accumulatorIn().resultLookupIndex(), lookupIndex,
+ QQmlJSRegisterContent::GenericObjectProperty, mathObject)
+ );
+
+ return;
+ }
+
if (m_typeResolver->registerContains(
m_state.accumulatorOut(), m_typeResolver->voidType())) {
setError(u"Type %1 does not have a property %2 for reading"_s
@@ -879,14 +945,8 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName)
}
}
- if (m_passManager != nullptr) {
- const bool isAttached =
- m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectAttached;
-
- m_passManager->analyzeRead(
- m_typeResolver->containedType(m_state.accumulatorIn()), propertyName,
- isAttached ? m_attachedContext : m_function->qmlScope, getCurrentSourceLocation());
- }
+ if (m_passManager != nullptr)
+ propagatePropertyLookup_SAcheck(propertyName);
if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectAttached)
m_attachedContext = m_typeResolver->containedType(m_state.accumulatorIn());
@@ -920,14 +980,29 @@ void QQmlJSTypePropagator::generate_LoadOptionalProperty(int name, int offset)
void QQmlJSTypePropagator::generate_GetLookup(int index)
{
- propagatePropertyLookup(m_jsUnitGenerator->lookupName(index));
+ propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
}
void QQmlJSTypePropagator::generate_GetOptionalLookup(int index, int offset)
{
- Q_UNUSED(index);
Q_UNUSED(offset);
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ saveRegisterStateForJump(offset);
+ propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
+}
+
+void QQmlJSTypePropagator::generate_StoreProperty_SAcheck(const QString propertyName, const QQmlJSRegisterContent &callBase)
+{
+ const bool isAttached = callBase.variant() == QQmlJSRegisterContent::ObjectAttached;
+
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
+ QQmlJSScope::createQQmlSAElement(m_typeResolver->containedType(callBase)),
+ propertyName,
+ QQmlJSScope::createQQmlSAElement(
+ m_typeResolver->containedType(m_state.accumulatorIn())),
+ QQmlJSScope::createQQmlSAElement(isAttached ? m_attachedContext
+ : m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
}
void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
@@ -942,7 +1017,13 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
return;
}
- if (!property.isWritable()) {
+ if (property.storedType().isNull()) {
+ setError(u"Cannot determine type for property %1 of type %2"_s.arg(
+ propertyName, callBase.descriptiveName()));
+ return;
+ }
+
+ if (!property.isWritable() && !property.storedType()->isListProperty()) {
setError(u"Can't assign to read-only property %1"_s.arg(propertyName));
m_logger->log(u"Cannot assign to read-only property %1"_s.arg(propertyName),
@@ -957,18 +1038,25 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
return;
}
- if (m_passManager != nullptr) {
- const bool isAttached = callBase.variant() == QQmlJSRegisterContent::ObjectAttached;
+ if (m_passManager != nullptr)
+ generate_StoreProperty_SAcheck(propertyName, callBase);
- m_passManager->analyzeWrite(m_typeResolver->containedType(callBase), propertyName,
- m_typeResolver->containedType(m_state.accumulatorIn()),
- isAttached ? m_attachedContext : m_function->qmlScope,
- getCurrentSourceLocation());
- }
+ // If the input can hold undefined we must not coerce it to the property type
+ // as that might eliminate an undefined value. For example, undefined -> string
+ // becomes "undefined".
+ // We need the undefined value for either resetting the property if that is supported
+ // or generating an exception otherwise. Therefore we explicitly require the value to
+ // be given as QVariant. This triggers the QVariant fallback path that's also used for
+ // shadowable properties. QVariant can hold undefined and the lookup functions will
+ // handle that appropriately.
- m_state.setHasSideEffects(true);
- addReadAccumulator(property);
+ const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
+ const QQmlJSRegisterContent readType = m_typeResolver->canHoldUndefined(m_state.accumulatorIn())
+ ? property.storedIn(varType).castTo(varType)
+ : std::move(property);
+ addReadAccumulator(readType);
addReadRegister(base, callBase);
+ m_state.setHasSideEffects(true);
}
void QQmlJSTypePropagator::generate_SetLookup(int index, int base)
@@ -1028,88 +1116,123 @@ static bool isLoggingMethod(const QString &consoleMethod)
|| consoleMethod == u"warn" || consoleMethod == u"error";
}
-void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
+void QQmlJSTypePropagator::generate_CallProperty_SCMath(int base, int argc, int argv)
{
- Q_ASSERT(m_state.registers.contains(base));
- const auto callBase = m_state.registers[base].content;
- const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
+ // If we call a method on the Math object we don't need the actual Math object. We do need
+ // to transfer the type information to the code generator so that it knows that this is the
+ // Math object. Read the base register as void. void isn't stored, and the place where it's
+ // created will be optimized out if there are no other readers. The code generator can
+ // retrieve the original type and determine that it was the Math object.
- if (m_typeResolver->registerContains(
- callBase, m_typeResolver->jsGlobalObject()->property(u"Math"_s).type())) {
+ addReadRegister(base, m_typeResolver->globalType(m_typeResolver->voidType()));
- // If we call a method on the Math object we don't need the actual Math object. We do need
- // to transfer the type information to the code generator so that it knows that this is the
- // Math object. Read the base register as void. void isn't stored, and the place where it's
- // created will be optimized out if there are no other readers. The code generator can
- // retrieve the original type and determine that it was the Math object.
- addReadRegister(base, m_typeResolver->globalType(m_typeResolver->voidType()));
+ QQmlJSRegisterContent realType = m_typeResolver->returnType(
+ m_typeResolver->realType(), QQmlJSRegisterContent::MethodReturnValue,
+ m_typeResolver->mathObject());
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, realType);
+ setAccumulator(realType);
+}
- QQmlJSRegisterContent realType = m_typeResolver->globalType(m_typeResolver->realType());
- for (int i = 0; i < argc; ++i)
- addReadRegister(argv + i, realType);
- setAccumulator(realType);
- return;
- }
+void QQmlJSTypePropagator::generate_CallProperty_SCconsole(int base, int argc, int argv)
+{
+ const QQmlJSRegisterContent voidType
+ = m_typeResolver->globalType(m_typeResolver->voidType());
- if (m_typeResolver->registerContains(
- callBase, m_typeResolver->jsGlobalObject()->property(u"console"_s).type())
- && isLoggingMethod(propertyName)) {
+ // If we call a method on the console object we don't need the console object.
+ addReadRegister(base, voidType);
- const QQmlJSRegisterContent voidType
- = m_typeResolver->globalType(m_typeResolver->voidType());
+ const QQmlJSRegisterContent stringType
+ = m_typeResolver->globalType(m_typeResolver->stringType());
- // If we call a method on the console object we don't need the console object.
- addReadRegister(base, voidType);
+ if (argc > 0) {
+ const QQmlJSRegisterContent firstContent = m_state.registers[argv].content;
+ const QQmlJSScope::ConstPtr firstArg = m_typeResolver->containedType(firstContent);
+ switch (firstArg->accessSemantics()) {
+ case QQmlJSScope::AccessSemantics::Reference:
+ // We cannot know whether this will be a logging category at run time.
+ // Therefore we always pass any object types as special last argument.
+ addReadRegister(argv, m_typeResolver->globalType(
+ m_typeResolver->genericType(firstArg)));
+ break;
+ case QQmlJSScope::AccessSemantics::Sequence:
+ addReadRegister(argv, firstContent);
+ break;
+ default:
+ addReadRegister(argv, stringType);
+ break;
+ }
+ }
- const QQmlJSRegisterContent stringType
- = m_typeResolver->globalType(m_typeResolver->stringType());
+ for (int i = 1; i < argc; ++i) {
+ const QQmlJSRegisterContent argContent = m_state.registers[argv + i].content;
+ const QQmlJSScope::ConstPtr arg = m_typeResolver->containedType(argContent);
+ addReadRegister(
+ argv + i,
+ arg->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ ? argContent
+ : stringType);
+ }
- if (argc > 0) {
- const QQmlJSScope::ConstPtr firstArg
- = m_typeResolver->containedType(m_state.registers[argv].content);
- if (firstArg->isReferenceType()) {
- // We cannot know whether this will be a logging category at run time.
- // Therefore we always pass any object types as special last argument.
- addReadRegister(argv, m_typeResolver->globalType(
- m_typeResolver->genericType(firstArg)));
- } else {
- addReadRegister(argv, stringType);
- }
- }
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->returnType(
+ m_typeResolver->voidType(), QQmlJSRegisterContent::MethodReturnValue,
+ m_typeResolver->consoleObject()));
+}
- for (int i = 1; i < argc; ++i)
- addReadRegister(argv + i, stringType);
+void QQmlJSTypePropagator::generate_callProperty_SAcheck(const QString propertyName, const QQmlJSScope::ConstPtr &baseType)
+{
+ // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass)
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
+ QQmlJSScope::createQQmlSAElement(baseType), propertyName,
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
- m_state.setHasSideEffects(true);
- setAccumulator(voidType);
+void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
+{
+ Q_ASSERT(m_state.registers.contains(base));
+ const auto callBase = m_state.registers[base].content;
+ const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
+
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->mathObject())) {
+ generate_CallProperty_SCMath(base, argc, argv);
return;
}
- if (m_typeResolver->registerContains(callBase, m_typeResolver->jsValueType())
- || m_typeResolver->registerContains(callBase, m_typeResolver->varType())) {
- const auto jsValueType = m_typeResolver->globalType(m_typeResolver->jsValueType());
- addReadRegister(base, jsValueType);
- for (int i = 0; i < argc; ++i)
- addReadRegister(argv + i, jsValueType);
- setAccumulator(jsValueType);
- m_state.setHasSideEffects(true);
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->consoleObject()) && isLoggingMethod(propertyName)) {
+ generate_CallProperty_SCconsole(base, argc, argv);
return;
}
+ const auto baseType = m_typeResolver->containedType(callBase);
const auto member = m_typeResolver->memberType(callBase, propertyName);
+
if (!member.isMethod()) {
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->jsValueType())
+ || m_typeResolver->registerContains(callBase, m_typeResolver->varType())) {
+ const auto jsValueType = m_typeResolver->globalType(m_typeResolver->jsValueType());
+ addReadRegister(base, jsValueType);
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, jsValueType);
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->returnType(
+ m_typeResolver->jsValueType(), QQmlJSRegisterContent::JavaScriptReturnValue,
+ m_typeResolver->jsValueType()));
+ return;
+ }
+
setError(u"Type %1 does not have a property %2 for calling"_s
.arg(callBase.descriptiveName(), propertyName));
if (callBase.isType() && isCallingProperty(callBase.type(), propertyName))
return;
- if (isRestricted(propertyName))
+ if (checkForEnumProblems(callBase, propertyName))
return;
- std::optional<FixSuggestion> fixSuggestion;
-
- const auto baseType = m_typeResolver->containedType(callBase);
+ std::optional<QQmlJSFixSuggestion> fixSuggestion;
if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->methods().keys(),
getCurrentSourceLocation());
@@ -1117,20 +1240,16 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
fixSuggestion = suggestion;
}
- m_logger->log(u"Property \"%1\" not found on type \"%2\""_s.arg(
+ m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(
propertyName, m_typeResolver->containedTypeName(callBase, true)),
qmlMissingProperty, getCurrentSourceLocation(), true, true, fixSuggestion);
return;
}
- checkDeprecated(m_typeResolver->containedType(callBase), propertyName, true);
+ checkDeprecated(baseType, propertyName, true);
- if (m_passManager != nullptr) {
- // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass)
- m_passManager->analyzeRead(
- m_typeResolver->containedType(callBase),
- propertyName, m_function->qmlScope, getCurrentSourceLocation());
- }
+ if (m_passManager != nullptr)
+ generate_callProperty_SAcheck(propertyName, baseType);
addReadRegister(base, callBase);
@@ -1141,6 +1260,12 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar
}
}
+ if (baseType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ && m_typeResolver->equals(member.scopeType(), m_typeResolver->arrayPrototype())
+ && propagateArrayMethod(propertyName, argc, argv, callBase)) {
+ return;
+ }
+
propagateCall(member.method(), argc, argv, member.scopeType());
}
@@ -1148,10 +1273,13 @@ QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMe
int argc, int argv, QStringList *errors)
{
QQmlJSMetaMethod javascriptFunction;
+ QQmlJSMetaMethod candidate;
+ bool hasMultipleCandidates = false;
+
for (const auto &method : methods) {
// If we encounter a JavaScript function, use this as a fallback if no other method matches
- if (method.isJavaScriptFunction())
+ if (method.isJavaScriptFunction() && !javascriptFunction.isValid())
javascriptFunction = method;
if (method.returnType().isNull() && !method.returnTypeName().isEmpty()) {
@@ -1168,33 +1296,55 @@ QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMe
continue;
}
- bool matches = true;
+ bool fuzzyMatch = true;
+ bool exactMatch = true;
for (int i = 0; i < argc; ++i) {
const auto argumentType = arguments[i].type();
if (argumentType.isNull()) {
errors->append(
u"type %1 for argument %2 cannot be resolved"_s.arg(arguments[i].typeName())
.arg(i));
- matches = false;
+ exactMatch = false;
+ fuzzyMatch = false;
break;
}
- if (canConvertFromTo(m_state.registers[argv + i].content,
- m_typeResolver->globalType(argumentType))) {
+ const auto content = m_state.registers[argv + i].content;
+ if (m_typeResolver->registerContains(content, argumentType))
+ continue;
+
+ exactMatch = false;
+ if (canConvertFromTo(content, m_typeResolver->globalType(argumentType)))
+ continue;
+
+ // We can try to call a method that expects a derived type.
+ if (argumentType->isReferenceType()
+ && m_typeResolver->inherits(
+ argumentType->baseType(), m_typeResolver->containedType(content))) {
continue;
}
errors->append(
u"argument %1 contains %2 but is expected to contain the type %3"_s.arg(i).arg(
- m_state.registers[argv + i].content.descriptiveName(),
- arguments[i].typeName()));
- matches = false;
+ content.descriptiveName(), arguments[i].typeName()));
+ fuzzyMatch = false;
break;
}
- if (matches)
+
+ if (exactMatch) {
return method;
+ } else if (fuzzyMatch) {
+ if (!candidate.isValid())
+ candidate = method;
+ else
+ hasMultipleCandidates = true;
+ }
}
- return javascriptFunction;
+
+ if (hasMultipleCandidates)
+ return QQmlJSMetaMethod();
+
+ return candidate.isValid() ? candidate : javascriptFunction;
}
void QQmlJSTypePropagator::setAccumulator(const QQmlJSRegisterContent &content)
@@ -1221,9 +1371,14 @@ void QQmlJSTypePropagator::mergeRegister(
int index, const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b)
{
auto merged = m_typeResolver->merge(a, b);
-
Q_ASSERT(merged.isValid());
- Q_ASSERT(merged.isConversion());
+
+ if (!merged.isConversion()) {
+ // The registers were the same. We're already tracking them.
+ m_state.annotations[currentInstructionOffset()].typeConversions[index].content = merged;
+ m_state.registers[index].content = merged;
+ return;
+ }
auto tryPrevStateConversion = [this](int index, const QQmlJSRegisterContent &merged) -> bool {
auto it = m_prevStateAnnotations.find(currentInstructionOffset());
@@ -1234,19 +1389,20 @@ void QQmlJSTypePropagator::mergeRegister(
if (conversion == it->second.typeConversions.end())
return false;
- const QQmlJSRegisterContent &lastTry = conversion.value().content;
+ const VirtualRegister &lastTry = conversion.value();
- Q_ASSERT(lastTry.isValid());
- Q_ASSERT(lastTry.isConversion());
+ Q_ASSERT(lastTry.content.isValid());
+ if (!lastTry.content.isConversion())
+ return false;
- if (!m_typeResolver->equals(lastTry.conversionResult(), merged.conversionResult())
- || lastTry.conversionOrigins() != merged.conversionOrigins()) {
+ if (!m_typeResolver->equals(lastTry.content.conversionResult(), merged.conversionResult())
+ || lastTry.content.conversionOrigins() != merged.conversionOrigins()) {
return false;
}
// We don't need to track it again if we've come to the same conclusion before.
- m_state.annotations[currentInstructionOffset()].typeConversions[index].content = lastTry;
- m_state.registers[index].content = lastTry;
+ m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry;
+ m_state.registers[index] = lastTry;
return true;
};
@@ -1272,11 +1428,15 @@ void QQmlJSTypePropagator::propagateCall(
const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
if (!match.isValid()) {
- Q_ASSERT(errors.size() == methods.size());
- if (methods.size() == 1)
+ if (methods.size() == 1) {
+ // Cannot have multiple fuzzy matches if there is only one method
+ Q_ASSERT(errors.size() == 1);
setError(errors.first());
- else
+ } else if (errors.size() < methods.size()) {
+ setError(u"Multiple matching overrides found. Cannot determine the right one."_s);
+ } else {
setError(u"No matching override found. Candidates:\n"_s + errors.join(u'\n'));
+ }
return;
}
@@ -1292,7 +1452,6 @@ void QQmlJSTypePropagator::propagateCall(
if (!m_state.accumulatorOut().isValid())
setError(u"Cannot store return type of method %1()."_s.arg(match.methodName()));
- m_state.setHasSideEffects(true);
const auto types = match.parameters();
for (int i = 0; i < argc; ++i) {
if (i < types.size()) {
@@ -1306,6 +1465,7 @@ void QQmlJSTypePropagator::propagateCall(
}
addReadRegister(argv + i, m_typeResolver->globalType(m_typeResolver->jsValueType()));
}
+ m_state.setHasSideEffects(true);
}
bool QQmlJSTypePropagator::propagateTranslationMethod(
@@ -1316,7 +1476,7 @@ bool QQmlJSTypePropagator::propagateTranslationMethod(
const QQmlJSMetaMethod method = methods.front();
const QQmlJSRegisterContent intType
- = m_typeResolver->globalType(m_typeResolver->intType());
+ = m_typeResolver->globalType(m_typeResolver->int32Type());
const QQmlJSRegisterContent stringType
= m_typeResolver->globalType(m_typeResolver->stringType());
const QQmlJSRegisterContent returnType
@@ -1425,22 +1585,195 @@ void QQmlJSTypePropagator::propagateStringArgCall(int argv)
const QQmlJSScope::ConstPtr input = m_typeResolver->containedType(
m_state.registers[argv].content);
- for (QQmlJSScope::ConstPtr targetType : {
- m_typeResolver->intType(),
- m_typeResolver->uintType(),
- m_typeResolver->realType(),
- m_typeResolver->floatType(),
- m_typeResolver->boolType(),
- }) {
- if (m_typeResolver->equals(input, targetType)) {
- addReadRegister(argv, m_typeResolver->globalType(targetType));
- return;
- }
+
+ if (m_typeResolver->equals(input, m_typeResolver->uint32Type())) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ return;
+ }
+
+ if (m_typeResolver->isIntegral(input)) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->int32Type()));
+ return;
+ }
+
+ if (m_typeResolver->isNumeric(input)) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ return;
+ }
+
+ if (m_typeResolver->equals(input, m_typeResolver->boolType())) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->boolType()));
+ return;
}
addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->stringType()));
}
+bool QQmlJSTypePropagator::propagateArrayMethod(
+ const QString &name, int argc, int argv, const QQmlJSRegisterContent &baseType)
+{
+ // TODO:
+ // * For concat() we need to decide what kind of array to return and what kinds of arguments to
+ // accept.
+ // * For entries(), keys(), and values() we need iterators.
+ // * For find(), findIndex(), sort(), every(), some(), forEach(), map(), filter(), reduce(),
+ // and reduceRight() we need typed function pointers.
+
+ const auto intType = m_typeResolver->globalType(m_typeResolver->int32Type());
+ const auto stringType = m_typeResolver->globalType(m_typeResolver->stringType());
+ const auto baseContained = m_typeResolver->containedType(baseType);
+ const auto valueContained = baseContained->valueType();
+ const auto valueType = m_typeResolver->globalType(valueContained);
+
+ const bool canHaveSideEffects = (baseType.isProperty() && baseType.isWritable())
+ || baseContained->isListProperty()
+ || baseType.isConversion();
+
+ const auto setReturnType = [&](const QQmlJSScope::ConstPtr type) {
+ setAccumulator(m_typeResolver->returnType(
+ type, QQmlJSRegisterContent::MethodReturnValue, baseContained));
+ };
+
+ if (name == u"copyWithin" && argc > 0 && argc < 4) {
+ for (int i = 0; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, intType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if (name == u"fill" && argc > 0 && argc < 4) {
+ if (!canConvertFromTo(m_state.registers[argv].content, valueType))
+ return false;
+
+ for (int i = 1; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ addReadRegister(argv, valueType);
+
+ for (int i = 1; i < argc; ++i)
+ addReadRegister(argv + i, intType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if (name == u"includes" && argc > 0 && argc < 3) {
+ if (!canConvertFromTo(m_state.registers[argv].content, valueType))
+ return false;
+
+ if (argc == 2) {
+ if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
+ return false;
+ addReadRegister(argv + 1, intType);
+ }
+
+ addReadRegister(argv, valueType);
+ setReturnType(m_typeResolver->boolType());
+ return true;
+ }
+
+ if (name == u"toString" || (name == u"join" && argc < 2)) {
+ if (argc == 1) {
+ if (!canConvertFromTo(m_state.registers[argv].content, stringType))
+ return false;
+ addReadRegister(argv, stringType);
+ }
+
+ setReturnType(m_typeResolver->stringType());
+ return true;
+ }
+
+ if ((name == u"pop" || name == u"shift") && argc == 0) {
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(valueContained);
+ return true;
+ }
+
+ if (name == u"push" || name == u"unshift") {
+ for (int i = 0; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, valueType))
+ return false;
+ }
+
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, valueType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(m_typeResolver->int32Type());
+ return true;
+ }
+
+ if (name == u"reverse" && argc == 0) {
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if (name == u"slice" && argc < 3) {
+ for (int i = 0; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, intType);
+
+ setReturnType(baseType.storedType()->isListProperty()
+ ? m_typeResolver->qObjectListType()
+ : baseContained);
+ return true;
+ }
+
+ if (name == u"splice" && argc > 0) {
+ for (int i = 0; i < 2; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ for (int i = 2; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, valueType))
+ return false;
+ }
+
+ for (int i = 0; i < 2; ++i)
+ addReadRegister(argv + i, intType);
+
+ for (int i = 2; i < argc; ++i)
+ addReadRegister(argv + i, valueType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
+ if (!canConvertFromTo(m_state.registers[argv].content, valueType))
+ return false;
+
+ if (argc == 2) {
+ if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
+ return false;
+ addReadRegister(argv + 1, intType);
+ }
+
+ addReadRegister(argv, valueType);
+ setReturnType(m_typeResolver->int32Type());
+ return true;
+ }
+
+ return false;
+}
+
void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc,
int argv)
{
@@ -1517,14 +1850,70 @@ void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc,
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
+void QQmlJSTypePropagator::generate_Construct_SCDate(int argc, int argv)
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->dateTimeType()));
+
+ if (argc == 1) {
+ const QQmlJSRegisterContent argType = m_state.registers[argv].content;
+ if (m_typeResolver->isNumeric(argType)) {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ } else if (m_typeResolver->registerContains(argType, m_typeResolver->stringType())) {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->stringType()));
+ } else if (m_typeResolver->registerContains(argType, m_typeResolver->dateTimeType())
+ || m_typeResolver->registerContains(argType, m_typeResolver->dateType())
+ || m_typeResolver->registerContains(argType, m_typeResolver->timeType())) {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->dateTimeType()));
+ } else {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->jsPrimitiveType()));
+ }
+ } else {
+ constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds
+ for (int i = 0; i < std::min(argc, maxArgc); ++i) {
+ addReadRegister(
+ argv + i, m_typeResolver->globalType(m_typeResolver->realType()));
+ }
+ }
+}
+
+void QQmlJSTypePropagator::generate_Construct_SCArray(int argc, int argv)
+{
+ if (argc == 1) {
+ if (m_typeResolver->isNumeric(m_state.registers[argv].content)) {
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->variantListType()));
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ } else {
+ generate_DefineArray(argc, argv);
+ }
+ } else {
+ generate_DefineArray(argc, argv);
+ }
+}
void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv)
{
- m_state.setHasSideEffects(true);
- Q_UNUSED(func)
- Q_UNUSED(argv)
+ const QQmlJSRegisterContent type = m_state.registers[func].content;
+ if (!type.isMethod()) {
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+ return;
+ }
- Q_UNUSED(argc)
+ if (type.method() == m_typeResolver->jsGlobalObject()->methods(u"Date"_s)) {
+ generate_Construct_SCDate(argc, argv);
+ return;
+ }
+
+ if (type.method() == m_typeResolver->jsGlobalObject()->methods(u"Array"_s)) {
+ generate_Construct_SCArray(argc, argv);
+
+ return;
+ }
+ m_state.setHasSideEffects(true);
setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
}
@@ -1560,8 +1949,22 @@ void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset)
void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name)
{
- Q_UNUSED(name)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ const auto fail = [this, name]() {
+ setError(u"Cannot statically assert the dead temporal zone check for %1"_s.arg(
+ name ? m_jsUnitGenerator->stringForIndex(name) : u"the anonymous accumulator"_s));
+ };
+
+ const QQmlJSRegisterContent in = m_state.accumulatorIn();
+ if (in.isConversion()) {
+ for (const QQmlJSScope::ConstPtr &origin : in.conversionOrigins()) {
+ if (!m_typeResolver->equals(origin, m_typeResolver->emptyType()))
+ continue;
+ fail();
+ break;
+ }
+ } else if (m_typeResolver->registerContains(in, m_typeResolver->emptyType())) {
+ fail();
+ }
}
void QQmlJSTypePropagator::generate_ThrowException()
@@ -1634,28 +2037,41 @@ void QQmlJSTypePropagator::generate_PopContext()
void QQmlJSTypePropagator::generate_GetIterator(int iterator)
{
- Q_UNUSED(iterator)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ const QQmlJSRegisterContent listType = m_state.accumulatorIn();
+ if (!listType.isList()) {
+ const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType());
+ addReadAccumulator(jsValue);
+ setAccumulator(jsValue);
+ return;
+ }
+
+ addReadAccumulator(listType);
+ setAccumulator(m_typeResolver->iteratorPointer(
+ listType, QQmlJS::AST::ForEachType(iterator), currentInstructionOffset()));
}
-void QQmlJSTypePropagator::generate_IteratorNext(int value, int done)
+void QQmlJSTypePropagator::generate_IteratorNext(int value, int offset)
{
- Q_UNUSED(value)
- Q_UNUSED(done)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ const QQmlJSRegisterContent iteratorType = m_state.accumulatorIn();
+ addReadAccumulator(iteratorType);
+ setRegister(value, m_typeResolver->merge(
+ m_typeResolver->valueType(iteratorType),
+ m_typeResolver->globalType(m_typeResolver->voidType())));
+ saveRegisterStateForJump(offset);
+ m_state.setHasSideEffects(true);
}
-void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object)
+void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object, int offset)
{
Q_UNUSED(iterator)
Q_UNUSED(object)
+ Q_UNUSED(offset)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
-void QQmlJSTypePropagator::generate_IteratorClose(int done)
+void QQmlJSTypePropagator::generate_IteratorClose()
{
- Q_UNUSED(done)
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ // Noop
}
void QQmlJSTypePropagator::generate_DestructureRestElement()
@@ -1696,9 +2112,7 @@ void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable)
void QQmlJSTypePropagator::generate_DefineArray(int argc, int args)
{
- setAccumulator(m_typeResolver->globalType(argc == 0
- ? m_typeResolver->emptyListType()
- : m_typeResolver->variantListType()));
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->variantListType()));
// Track all arguments as the same type.
const QQmlJSRegisterContent elementType
@@ -1709,12 +2123,34 @@ void QQmlJSTypePropagator::generate_DefineArray(int argc, int args)
void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
{
- // TODO: computed property names, getters, and setters are unsupported. How do we catch them?
+ const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);
+ Q_ASSERT(argc >= classSize);
- Q_UNUSED(internalClassId)
- Q_UNUSED(argc)
- Q_UNUSED(args)
- setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+ // Track each element as separate type
+ for (int i = 0; i < classSize; ++i) {
+ addReadRegister(
+ args + i,
+ m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->varType())));
+ }
+
+ for (int i = classSize; i < argc; i += 3) {
+ // layout for remaining members is:
+ // 0: ObjectLiteralArgument - Value|Method|Getter|Setter
+ // We cannot do anything useful with this. Any code that would call a getter/setter/method
+ // could not be compiled to C++. Ignore it.
+
+ // 1: name of argument
+ addReadRegister(
+ args + i + 1,
+ m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->stringType())));
+
+ // 2: value of argument
+ addReadRegister(
+ args + i + 2,
+ m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->varType())));
+ }
+
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->variantMapType()));
}
void QQmlJSTypePropagator::generate_CreateClass(int classIndex, int heritage, int computedNames)
@@ -1743,7 +2179,7 @@ void QQmlJSTypePropagator::generate_CreateRestParameter(int argIndex)
void QQmlJSTypePropagator::generate_ConvertThisToObject()
{
- INSTR_PROLOGUE_NOT_IMPLEMENTED();
+ setRegister(This, m_typeResolver->globalType(m_typeResolver->qObjectType()));
}
void QQmlJSTypePropagator::generate_LoadSuperConstructor()
@@ -1772,8 +2208,8 @@ void QQmlJSTypePropagator::generate_JumpTrue(int offset)
return;
}
saveRegisterStateForJump(offset);
- m_state.setHasSideEffects(true);
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+ m_state.setHasSideEffects(true);
}
void QQmlJSTypePropagator::generate_JumpFalse(int offset)
@@ -1785,8 +2221,8 @@ void QQmlJSTypePropagator::generate_JumpFalse(int offset)
return;
}
saveRegisterStateForJump(offset);
- m_state.setHasSideEffects(true);
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+ m_state.setHasSideEffects(true);
}
void QQmlJSTypePropagator::generate_JumpNoException(int offset)
@@ -1833,42 +2269,33 @@ void QQmlJSTypePropagator::recordEqualsType(int lhs)
return content.isEnumeration() || m_typeResolver->isNumeric(content);
};
- const auto isIntCompatible = [this](const QQmlJSRegisterContent &content) {
- const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(content);
- return contained->scopeType() == QQmlJSScope::EnumScope
- || m_typeResolver->equals(contained, m_typeResolver->intType())
- || m_typeResolver->equals(contained, m_typeResolver->uintType());
- };
-
const auto accumulatorIn = m_state.accumulatorIn();
const auto lhsRegister = m_state.registers[lhs].content;
// If the types are primitive, we compare directly ...
if (m_typeResolver->isPrimitive(accumulatorIn) || accumulatorIn.isEnumeration()) {
if (m_typeResolver->registerContains(
- accumulatorIn, m_typeResolver->containedType(lhsRegister))) {
- addReadRegister(lhs, accumulatorIn);
+ accumulatorIn, m_typeResolver->containedType(lhsRegister))
+ || (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister))
+ || m_typeResolver->isPrimitive(lhsRegister)) {
+ addReadRegister(lhs, lhsRegister);
addReadAccumulator(accumulatorIn);
return;
- } else if (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister)) {
- const auto targetType = isIntCompatible(accumulatorIn) && isIntCompatible(lhsRegister)
- ? m_typeResolver->globalType(m_typeResolver->intType())
- : m_typeResolver->globalType(m_typeResolver->realType());
- addReadRegister(lhs, targetType);
- addReadAccumulator(targetType);
- return;
- } else if (m_typeResolver->isPrimitive(lhsRegister)) {
- const QQmlJSRegisterContent primitive = m_typeResolver->globalType(
- m_typeResolver->jsPrimitiveType());
- addReadRegister(lhs, primitive);
- addReadAccumulator(primitive);
- return;
}
}
- // We don't modify types if the types are comparable with QObject or var
- if (canStrictlyCompareWithVar(m_typeResolver, lhsRegister, accumulatorIn)
- || canCompareWithQObject(m_typeResolver, lhsRegister, accumulatorIn)) {
+ const auto containedAccumulatorIn = m_typeResolver->isOptionalType(accumulatorIn)
+ ? m_typeResolver->extractNonVoidFromOptionalType(accumulatorIn)
+ : m_typeResolver->containedType(accumulatorIn);
+
+ const auto containedLhs = m_typeResolver->isOptionalType(lhsRegister)
+ ? m_typeResolver->extractNonVoidFromOptionalType(lhsRegister)
+ : m_typeResolver->containedType(lhsRegister);
+
+ // We don't modify types if the types are comparable with QObject, QUrl or var types
+ if (canStrictlyCompareWithVar(m_typeResolver, containedLhs, containedAccumulatorIn)
+ || canCompareWithQObject(m_typeResolver, containedLhs, containedAccumulatorIn)
+ || canCompareWithQUrl(m_typeResolver, containedLhs, containedAccumulatorIn)) {
addReadRegister(lhs, lhsRegister);
addReadAccumulator(accumulatorIn);
return;
@@ -1912,7 +2339,7 @@ void QQmlJSTypePropagator::generate_CmpEqInt(int lhsConst)
recordEqualsIntType();
Q_UNUSED(lhsConst)
setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation(
- QSOperator::Op::Equal, m_typeResolver->globalType(m_typeResolver->intType()),
+ QSOperator::Op::Equal, m_typeResolver->globalType(m_typeResolver->int32Type()),
m_state.accumulatorIn())));
}
@@ -1921,7 +2348,7 @@ void QQmlJSTypePropagator::generate_CmpNeInt(int lhsConst)
recordEqualsIntType();
Q_UNUSED(lhsConst)
setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation(
- QSOperator::Op::NotEqual, m_typeResolver->globalType(m_typeResolver->intType()),
+ QSOperator::Op::NotEqual, m_typeResolver->globalType(m_typeResolver->int32Type()),
m_state.accumulatorIn())));
}
@@ -1994,32 +2421,53 @@ void QQmlJSTypePropagator::generate_CmpInstanceOf(int lhs)
void QQmlJSTypePropagator::generate_As(int lhs)
{
const QQmlJSRegisterContent input = checkedInputRegister(lhs);
- QQmlJSScope::ConstPtr contained;
+ const QQmlJSScope::ConstPtr inContained = m_typeResolver->containedType(input);
+
+ QQmlJSScope::ConstPtr outContained;
switch (m_state.accumulatorIn().variant()) {
case QQmlJSRegisterContent::ScopeAttached:
- contained = m_state.accumulatorIn().scopeType();
+ outContained = m_state.accumulatorIn().scopeType();
break;
case QQmlJSRegisterContent::MetaType:
- contained = m_state.accumulatorIn().scopeType();
- if (contained->isComposite()) // Otherwise we don't need it
+ outContained = m_state.accumulatorIn().scopeType();
+ if (outContained->isComposite()) // Otherwise we don't need it
addReadAccumulator(m_typeResolver->globalType(m_typeResolver->metaObjectType()));
break;
default:
- contained = m_typeResolver->containedType(m_state.accumulatorIn());
+ outContained = m_typeResolver->containedType(m_state.accumulatorIn());
break;
}
- addReadRegister(lhs, m_typeResolver->globalType(contained));
+ QQmlJSRegisterContent output;
+
+ if (outContained->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
+ // A referece type cast can result in either the type or null.
+ // Reference types can hold null. We don't need to special case that.
- if (m_typeResolver->containedType(input)->accessSemantics()
- != QQmlJSScope::AccessSemantics::Reference
- || contained->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
+ if (m_typeResolver->inherits(inContained, outContained))
+ output = input;
+ else
+ output = m_typeResolver->cast(input, outContained);
+ } else if (!m_typeResolver->canAddressValueTypes()) {
setError(u"invalid cast from %1 to %2. You can only cast object types."_s
- .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName()));
+ .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName()));
+ return;
} else {
- setAccumulator(m_typeResolver->globalType(contained));
+ if (m_typeResolver->inherits(inContained, outContained)) {
+ // A "slicing" cannot result in void
+ output = m_typeResolver->cast(input, outContained);
+ } else {
+ // A value type cast can result in either the type or undefined.
+ // Using convert() retains the variant of the input type.
+ output = m_typeResolver->merge(
+ m_typeResolver->cast(input, outContained),
+ m_typeResolver->cast(input, m_typeResolver->voidType()));
+ }
}
+
+ addReadRegister(lhs, input);
+ setAccumulator(output);
}
void QQmlJSTypePropagator::checkConversion(
@@ -2085,7 +2533,7 @@ void QQmlJSTypePropagator::generateBinaryConstArithmeticOperation(QSOperator::Op
{
const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
op, m_state.accumulatorIn(),
- m_typeResolver->builtinType(m_typeResolver->intType()));
+ m_typeResolver->builtinType(m_typeResolver->int32Type()));
checkConversion(m_state.accumulatorIn(), type);
addReadAccumulator(type);
@@ -2190,9 +2638,8 @@ void QQmlJSTypePropagator::generate_Sub(int lhs)
void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
{
- Q_UNUSED(firstReg)
- Q_UNUSED(count)
- // Ignore. We reject uninitialized values anyway.
+ for (int reg = firstReg, end = firstReg + count; reg < end; ++reg)
+ setRegister(reg, m_typeResolver->globalType(m_typeResolver->emptyType()));
}
void QQmlJSTypePropagator::generate_ThrowOnNullOrUndefined()
@@ -2206,31 +2653,6 @@ void QQmlJSTypePropagator::generate_GetTemplateObject(int index)
INSTR_PROLOGUE_NOT_IMPLEMENTED();
}
-static bool instructionManipulatesContext(QV4::Moth::Instr::Type type)
-{
- using Type = QV4::Moth::Instr::Type;
- switch (type) {
- case Type::PopContext:
- case Type::PopScriptContext:
- case Type::CreateCallContext:
- case Type::CreateCallContext_Wide:
- case Type::PushCatchContext:
- case Type::PushCatchContext_Wide:
- case Type::PushWithContext:
- case Type::PushWithContext_Wide:
- case Type::PushBlockContext:
- case Type::PushBlockContext_Wide:
- case Type::CloneBlockContext:
- case Type::CloneBlockContext_Wide:
- case Type::PushScriptContext:
- case Type::PushScriptContext_Wide:
- return true;
- default:
- break;
- }
- return false;
-}
-
QV4::Moth::ByteCodeHandler::Verdict
QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type)
{
@@ -2298,9 +2720,6 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
currentInstruction.readRegisters = m_state.takeReadRegisters();
currentInstruction.hasSideEffects = m_state.hasSideEffects();
currentInstruction.isRename = m_state.isRename();
- m_state.setHasSideEffects(false);
- m_state.setIsRename(false);
- m_state.setReadRegisters(VirtualRegisters());
switch (instr) {
// the following instructions are not expected to produce output in the accumulator
@@ -2324,6 +2743,10 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
case QV4::Moth::Instr::Type::PushCatchContext:
case QV4::Moth::Instr::Type::UnwindDispatch:
case QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone:
+ case QV4::Moth::Instr::Type::ConvertThisToObject:
+ case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
+ case QV4::Moth::Instr::Type::IteratorNext:
+ case QV4::Moth::Instr::Type::IteratorNextForYieldStar:
if (m_state.changedRegisterIndex() == Accumulator && !m_error->isValid()) {
setError(u"Instruction is not expected to populate the accumulator"_s);
return;
@@ -2339,11 +2762,26 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
}
}
+ if (!(m_error->isValid() && m_error->isError())
+ && instr != QV4::Moth::Instr::Type::DeadTemporalZoneCheck) {
+ // An instruction needs to have side effects or write to another register otherwise it's a
+ // noop. DeadTemporalZoneCheck is not needed by the compiler and is ignored.
+ Q_ASSERT(m_state.hasSideEffects() || m_state.changedRegisterIndex() != -1);
+ }
+
if (m_state.changedRegisterIndex() != InvalidRegister) {
Q_ASSERT(m_error->isValid() || m_state.changedRegister().isValid());
- m_state.registers[m_state.changedRegisterIndex()].content = m_state.changedRegister();
+ VirtualRegister &r = m_state.registers[m_state.changedRegisterIndex()];
+ r.content = m_state.changedRegister();
+ r.canMove = false;
+ r.affectedBySideEffects = m_state.isRename()
+ && m_state.isRegisterAffectedBySideEffects(m_state.renameSourceRegisterIndex());
m_state.clearChangedRegister();
}
+
+ m_state.setHasSideEffects(false);
+ m_state.setIsRename(false);
+ m_state.setReadRegisters(VirtualRegisters());
}
QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs)