diff options
Diffstat (limited to 'src/qmlcompiler/qqmljstypepropagator.cpp')
-rw-r--r-- | src/qmlcompiler/qqmljstypepropagator.cpp | 994 |
1 files changed, 685 insertions, 309 deletions
diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index 6072393e0f..2760f3401b 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -60,8 +60,13 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run( m_returnType = m_typeResolver->globalType(m_function->returnType); do { + // Reset the error if we need to do another pass + if (m_state.needsMorePasses) + *m_error = QQmlJS::DiagnosticMessage(); + + m_prevStateAnnotations = m_state.annotations; m_state = PassState(); - m_state.State::operator=(initialState(m_function, m_typeResolver)); + m_state.State::operator=(initialState(m_function)); reset(); decode(m_function->code.constData(), static_cast<uint>(m_function->code.length())); @@ -69,7 +74,7 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run( // If we have found unresolved backwards jumps, we need to start over with a fresh state. // Mind that m_jumpOriginRegisterStateByTargetInstructionOffset is retained in that case. // This means that we won't start over for the same reason again. - } while (!m_error->isValid() && m_state.needsMorePasses); + } while (m_state.needsMorePasses); return m_state.annotations; } @@ -80,34 +85,41 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSTypePropagator::run( return; #define INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE() \ - m_logger->logWarning( \ - u"Instruction \"%1\" not implemented"_qs.arg(QString::fromUtf8(__func__)), \ - Log_Compiler); \ + m_logger->log(u"Instruction \"%1\" not implemented"_qs.arg(QString::fromUtf8(__func__)), \ + Log_Compiler, QQmlJS::SourceLocation()); \ return; void QQmlJSTypePropagator::generate_Ret() { if (m_function->isSignalHandler) { // Signal handlers cannot return anything. - } else if (!m_returnType.isValid() && m_state.accumulatorIn.isValid() - && m_typeResolver->containedType(m_state.accumulatorIn) - != m_typeResolver->voidType()) { + } 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"_qs - .arg(m_state.accumulatorIn.descriptiveName())); + .arg(m_state.accumulatorIn().descriptiveName())); return; - } else if (m_state.accumulatorIn != m_returnType - && !canConvertFromTo(m_state.accumulatorIn, m_returnType)) { + } else if (!canConvertFromTo(m_state.accumulatorIn(), m_returnType)) { setError(u"cannot convert from %1 to %2"_qs - .arg(m_state.accumulatorIn.descriptiveName(), + .arg(m_state.accumulatorIn().descriptiveName(), m_returnType.descriptiveName())); - m_logger->logWarning(u"Cannot assign binding of type %1 to %2"_qs.arg( - m_typeResolver->containedTypeName(m_state.accumulatorIn), - m_typeResolver->containedTypeName(m_returnType)), - Log_Type, getCurrentBindingSourceLocation()); + m_logger->log(u"Cannot assign binding of type %1 to %2"_qs.arg( + m_typeResolver->containedTypeName(m_state.accumulatorIn()), + m_typeResolver->containedTypeName(m_returnType)), + Log_Type, getCurrentBindingSourceLocation()); return; } + if (m_returnType.isValid()) { + // We need to preserve any possible undefined value as that resets the property. + if (m_typeResolver->canHoldUndefined(m_state.accumulatorIn())) + addReadAccumulator(m_state.accumulatorIn()); + else + addReadAccumulator(m_returnType); + } + + m_state.setHasSideEffects(true); m_state.skipInstructionsUntilNextJumpTarget = true; } @@ -119,37 +131,37 @@ void QQmlJSTypePropagator::generate_Debug() void QQmlJSTypePropagator::generate_LoadConst(int index) { auto encodedConst = m_jsUnitGenerator->constant(index); - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->typeForConst(encodedConst)); + setAccumulator(m_typeResolver->globalType(m_typeResolver->typeForConst(encodedConst))); } void QQmlJSTypePropagator::generate_LoadZero() { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->intType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->intType())); } void QQmlJSTypePropagator::generate_LoadTrue() { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->boolType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); } void QQmlJSTypePropagator::generate_LoadFalse() { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->boolType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); } void QQmlJSTypePropagator::generate_LoadNull() { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->nullType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->nullType())); } void QQmlJSTypePropagator::generate_LoadUndefined() { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->voidType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->voidType())); } void QQmlJSTypePropagator::generate_LoadInt(int) { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->intType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->intType())); } void QQmlJSTypePropagator::generate_MoveConst(int constIndex, int destTemp) @@ -160,17 +172,24 @@ void QQmlJSTypePropagator::generate_MoveConst(int constIndex, int destTemp) void QQmlJSTypePropagator::generate_LoadReg(int reg) { - m_state.accumulatorOut = checkedInputRegister(reg); + // Do not re-track the register. We're not manipulating it. + m_state.setIsRename(true); + m_state.setRegister(Accumulator, checkedInputRegister(reg)); } void QQmlJSTypePropagator::generate_StoreReg(int reg) { - setRegister(reg, m_state.accumulatorIn); + // Do not re-track the register. We're not manipulating it. + m_state.setIsRename(true); + m_state.setRegister(reg, m_state.accumulatorIn()); } void QQmlJSTypePropagator::generate_MoveReg(int srcReg, int destReg) { - setRegister(destReg, m_state.registers[srcReg]); + Q_ASSERT(destReg != InvalidRegister); + // Do not re-track the register. We're not manipulating it. + m_state.setIsRename(true); + m_state.setRegister(destReg, m_state.registers[srcReg]); } void QQmlJSTypePropagator::generate_LoadImport(int index) @@ -182,7 +201,7 @@ void QQmlJSTypePropagator::generate_LoadImport(int index) void QQmlJSTypePropagator::generate_LoadLocal(int index) { Q_UNUSED(index); - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->jsValueType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType())); } void QQmlJSTypePropagator::generate_StoreLocal(int index) @@ -208,7 +227,7 @@ void QQmlJSTypePropagator::generate_StoreScopedLocal(int scope, int index) void QQmlJSTypePropagator::generate_LoadRuntimeString(int stringId) { Q_UNUSED(stringId) - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->stringType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->stringType())); // m_state.accumulatorOut.m_state.value = m_jsUnitGenerator->stringForIndex(stringId); } @@ -228,8 +247,8 @@ void QQmlJSTypePropagator::generate_LoadClosure(int value) void QQmlJSTypePropagator::generate_LoadName(int nameIndex) { const QString name = m_jsUnitGenerator->stringForIndex(nameIndex); - m_state.accumulatorOut = m_typeResolver->scopedType(m_function->qmlScope, name); - if (!m_state.accumulatorOut.isValid()) + setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name)); + if (!m_state.accumulatorOut().isValid()) setError(u"Cannot find name "_qs + name); } @@ -260,7 +279,7 @@ QQmlJS::SourceLocation QQmlJSTypePropagator::getCurrentBindingSourceLocation() c return combine(entries.constFirst().location, entries.constLast().location); } -void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const +void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const { auto location = getCurrentSourceLocation(); @@ -271,6 +290,13 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const return; } + if (isMethod) { + if (isCallingProperty(m_function->qmlScope, name)) + return; + } else if (isMissingPropertyType(m_function->qmlScope, name)) { + return; + } + std::optional<FixSuggestion> suggestion; auto childScopes = m_function->qmlScope->childScopes(); @@ -297,7 +323,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const const auto handler = m_typeResolver->signalHandlers()[id.location]; - QString fixString = handler.isMultiline ? u" function("_qs : u" ("_qs; + QString fixString = handler.isMultiline ? u"function("_qs : u"("_qs; const auto parameters = handler.signalParameters; for (int numParams = parameters.size(); numParams > 0; --numParams) { fixString += parameters.at(parameters.size() - numParams); @@ -314,7 +340,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const "function instead.\n") .arg(id.location.startLine) .arg(id.location.startColumn), - fixLocation, fixString + fixLocation, fixString, QString(), false }; } break; @@ -334,7 +360,7 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const 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>."_qs : (id + u'.')) + fixLocation, (id.isEmpty() ? u"<id>."_qs : (id + u'.')), QString(), id.isEmpty() }; if (id.isEmpty()) { @@ -358,8 +384,8 @@ void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name) const } } - m_logger->logWarning(QLatin1String("Unqualified access"), Log_UnqualifiedAccess, location, true, - true, suggestion); + m_logger->log(QLatin1String("Unqualified access"), Log_UnqualifiedAccess, location, true, true, + suggestion); } void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name, @@ -407,65 +433,122 @@ void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QS if (!deprecation.reason.isEmpty()) message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason)); - m_logger->logWarning(message, Log_Deprecation, getCurrentSourceLocation()); + m_logger->log(message, Log_Deprecation, getCurrentSourceLocation()); } -bool QQmlJSTypePropagator::checkRestricted(const QString &propertyName) const +bool QQmlJSTypePropagator::isRestricted(const QString &propertyName) const { QString restrictedKind; - if (!m_state.accumulatorIn.isValid()) + const auto accumulatorIn = m_state.registers.find(Accumulator); + if (accumulatorIn == m_state.registers.end()) return false; - if (m_state.accumulatorIn.isList() && propertyName != u"length") { + if (accumulatorIn.value().isList() && propertyName != u"length") { restrictedKind = u"a list"_qs; - } else if (m_state.accumulatorIn.isEnumeration() - && !m_state.accumulatorIn.enumeration().hasKey(propertyName)) { + } else if (accumulatorIn.value().isEnumeration() + && !accumulatorIn.value().enumeration().hasKey(propertyName)) { restrictedKind = u"an enum"_qs; - } else if (m_state.accumulatorIn.isMethod()) { + } else if (accumulatorIn.value().isMethod()) { restrictedKind = u"a method"_qs; } if (!restrictedKind.isEmpty()) - m_logger->logWarning(u"Type is %1. You cannot access \"%2\" from here."_qs.arg( - restrictedKind, propertyName), - Log_Type, getCurrentSourceLocation()); + m_logger->log(u"Type is %1. You cannot access \"%2\" from here."_qs.arg(restrictedKind, + propertyName), + Log_Type, getCurrentSourceLocation()); return !restrictedKind.isEmpty(); } +// Only to be called once a lookup has already failed +bool QQmlJSTypePropagator::isMissingPropertyType(QQmlJSScope::ConstPtr scope, + const QString &propertyName) const +{ + auto property = scope->property(propertyName); + if (!property.isValid()) + return false; + + QString errorType; + if (property.type().isNull()) + errorType = u"found"_qs; + else if (!property.type()->isFullyResolved()) + errorType = u"fully resolved"_qs; + + Q_ASSERT(!errorType.isEmpty()); + + m_logger->log( + u"Type \"%1\" of property \"%2\" not %3. This is likely due to a missing dependency entry or a type not being exposed declaratively."_qs + .arg(property.typeName(), propertyName, errorType), + Log_Type, getCurrentSourceLocation()); + + return true; +} + +bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const +{ + auto property = scope->property(name); + if (!property.isValid()) + return false; + + QString propertyType = u"Property"_qs; + + auto methods = scope->methods(name); + + QString errorType; + if (!methods.isEmpty()) { + errorType = u"shadowed by a property."_qs; + switch (methods.first().methodType()) { + case QQmlJSMetaMethod::Signal: + propertyType = u"Signal"_qs; + break; + case QQmlJSMetaMethod::Slot: + propertyType = u"Slot"_qs; + break; + case QQmlJSMetaMethod::Method: + propertyType = u"Method"_qs; + break; + } + } else if (m_typeResolver->equals(property.type(), m_typeResolver->varType())) { + errorType = + u"a variant property. It may or may not be a method. Use a regular function instead."_qs; + } else if (m_typeResolver->equals(property.type(), m_typeResolver->jsValueType())) { + errorType = + u"a QJSValue property. It may or may not be a method. Use a regular Q_INVOKABLE instead."_qs; + } else { + errorType = u"not a method"_qs; + } + + m_logger->log(u"%1 \"%2\" is %3"_qs.arg(propertyType, name, errorType), Log_Type, + getCurrentSourceLocation(), true, true, {}); + + return true; +} + void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index) { + // LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope. + // Any import namespaces etc. are handled via LoadProperty or GetLookup. + const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index); const QString name = m_jsUnitGenerator->stringForIndex(nameIndex); - m_state.accumulatorOut = m_typeResolver->scopedType( - m_function->qmlScope, - m_state.accumulatorIn.isImportNamespace() - ? m_jsUnitGenerator->stringForIndex(m_state.accumulatorIn.importNamespace()) - + u'.' + name - : name); - - if (!m_state.accumulatorOut.isValid() && m_typeResolver->isPrefix(name)) { - const QQmlJSRegisterContent inType = m_state.accumulatorIn.isValid() - ? m_state.accumulatorIn - : m_typeResolver->globalType(m_function->qmlScope); - m_state.accumulatorOut = QQmlJSRegisterContent::create( - inType.storedType(), nameIndex, QQmlJSRegisterContent::ScopeModulePrefix, - m_typeResolver->containedType(inType)); + setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name)); + + if (!m_state.accumulatorOut().isValid() && m_typeResolver->isPrefix(name)) { + const QQmlJSRegisterContent inType = m_typeResolver->globalType(m_function->qmlScope); + setAccumulator(QQmlJSRegisterContent::create( + m_typeResolver->voidType(), nameIndex, QQmlJSRegisterContent::ScopeModulePrefix, + m_typeResolver->containedType(inType))); return; } checkDeprecated(m_function->qmlScope, name, false); - bool isRestricted = checkRestricted(name); - - if (!m_state.accumulatorOut.isValid()) { + if (!m_state.accumulatorOut().isValid()) { setError(u"Cannot access value for name "_qs + name); - - if (!isRestricted) - handleUnqualifiedAccess(name); - } else if (m_typeResolver->genericType(m_state.accumulatorOut.storedType()).isNull()) { + handleUnqualifiedAccess(name, false); + } else if (m_typeResolver->genericType(m_state.accumulatorOut().storedType()).isNull()) { // It should really be valid. // We get the generic type from aotContext->loadQmlContextPropertyIdLookup(). setError(u"Cannot determine generic type for "_qs + name); @@ -490,20 +573,24 @@ void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex) if (!type.isWritable() && !m_function->qmlScope->hasOwnProperty(name)) { setError(u"Can't assign to read-only property %1"_qs.arg(name)); - m_logger->logWarning(u"Cannot assign to read-only property %1"_qs.arg(name), Log_Property, - getCurrentSourceLocation()); + m_logger->log(u"Cannot assign to read-only property %1"_qs.arg(name), Log_Property, + getCurrentSourceLocation()); return; } - if (!canConvertFromTo(m_state.accumulatorIn, type)) { + if (!canConvertFromTo(m_state.accumulatorIn(), type)) { setError(u"cannot convert from %1 to %2"_qs - .arg(m_state.accumulatorIn.descriptiveName(), type.descriptiveName())); + .arg(m_state.accumulatorIn().descriptiveName(), type.descriptiveName())); } + + m_state.setHasSideEffects(true); + addReadAccumulator(type); } void QQmlJSTypePropagator::generate_StoreNameStrict(int name) { + m_state.setHasSideEffects(true); Q_UNUSED(name) INSTR_PROLOGUE_NOT_IMPLEMENTED(); } @@ -511,41 +598,76 @@ void QQmlJSTypePropagator::generate_StoreNameStrict(int name) void QQmlJSTypePropagator::generate_LoadElement(int base) { const QQmlJSRegisterContent baseRegister = m_state.registers[base]; - if (!m_typeResolver->registerContains(m_state.accumulatorIn, m_typeResolver->intType()) - || baseRegister.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence) { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->jsValueType()); + + if (baseRegister.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence + || !m_typeResolver->isNumeric(m_state.accumulatorIn())) { + const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType()); + addReadAccumulator(jsValue); + addReadRegister(base, jsValue); + setAccumulator(jsValue); return; } - m_state.accumulatorOut = m_typeResolver->valueType(baseRegister); + if (m_typeResolver->registerContains(m_state.accumulatorIn(), m_typeResolver->intType())) + addReadAccumulator(m_state.accumulatorIn()); + else + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->realType())); + + addReadRegister(base, baseRegister); + setAccumulator(m_typeResolver->valueType(baseRegister)); } void QQmlJSTypePropagator::generate_StoreElement(int base, int index) { - Q_UNUSED(base) - Q_UNUSED(index) + const QQmlJSRegisterContent baseRegister = m_state.registers[base]; + const QQmlJSRegisterContent indexRegister = checkedInputRegister(index); + + if (baseRegister.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence + || !m_typeResolver->isNumeric(indexRegister)) { + const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType()); + addReadAccumulator(jsValue); + addReadRegister(base, jsValue); + addReadRegister(index, jsValue); + return; + } + + if (m_typeResolver->registerContains(indexRegister, m_typeResolver->intType())) + addReadRegister(index, indexRegister); + else + addReadRegister(index, m_typeResolver->globalType(m_typeResolver->realType())); + + addReadRegister(base, baseRegister); + addReadAccumulator(m_typeResolver->valueType(baseRegister)); + + // If we're writing a QQmlListProperty backed by a container somewhere else, + // that has side effects. + // If we're writing to a list retrieved from a property, that _should_ have side effects, + // but currently the QML engine doesn't implement them. + // TODO: Figure out the above and accurately set the flag. + m_state.setHasSideEffects(true); } void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) { - m_state.accumulatorOut = + setAccumulator( m_typeResolver->memberType( - m_state.accumulatorIn, - m_state.accumulatorIn.isImportNamespace() - ? m_jsUnitGenerator->stringForIndex(m_state.accumulatorIn.importNamespace()) + m_state.accumulatorIn(), + m_state.accumulatorIn().isImportNamespace() + ? m_jsUnitGenerator->stringForIndex(m_state.accumulatorIn().importNamespace()) + u'.' + propertyName - : propertyName); + : propertyName)); if (m_typeInfo != nullptr - && m_state.accumulatorIn.variant() == QQmlJSRegisterContent::ScopeAttached) { - QQmlJSScope::ConstPtr attachedType = m_state.accumulatorIn.scopeType(); + && 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()) + if (m_state.accumulatorOut().isValid() && m_state.accumulatorOut().isEnumeration()) continue; const QString id = m_function->addressableScopes.id(scope); @@ -555,11 +677,10 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) QQmlJS::SourceLocation fixLocation = getCurrentSourceLocation(); fixLocation.length = 0; - suggestion.fixes << FixSuggestion::Fix { - u"Reference it by id instead:"_qs, - fixLocation, - id.isEmpty() ? u"<id>."_qs : (id + u'.') - }; + suggestion.fixes << FixSuggestion::Fix { u"Reference it by id instead:"_qs, + fixLocation, + id.isEmpty() ? u"<id>."_qs : (id + u'.'), + QString(), id.isEmpty() }; fixLocation = scope->sourceLocation(); fixLocation.length = 0; @@ -571,9 +692,9 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) {} }; } - m_logger->logWarning( + m_logger->log( u"Using attached type %1 already initialized in a parent scope."_qs.arg( - m_state.accumulatorIn.scopeType()->internalName()), + m_state.accumulatorIn().scopeType()->internalName()), Log_AttachedPropertyReuse, getCurrentSourceLocation(), true, true, suggestion); } @@ -581,61 +702,47 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) m_typeInfo->usedAttachedTypes.insert(m_function->qmlScope, attachedType); } - if (!m_state.accumulatorOut.isValid()) { + if (!m_state.accumulatorOut().isValid()) { if (m_typeResolver->isPrefix(propertyName)) { - Q_ASSERT(m_state.accumulatorIn.isValid()); - m_state.accumulatorOut = QQmlJSRegisterContent::create( - m_state.accumulatorIn.storedType(), + Q_ASSERT(m_state.accumulatorIn().isValid()); + addReadAccumulator(m_state.accumulatorIn()); + setAccumulator(QQmlJSRegisterContent::create( + m_state.accumulatorIn().storedType(), m_jsUnitGenerator->getStringId(propertyName), QQmlJSRegisterContent::ObjectModulePrefix, - m_typeResolver->containedType(m_state.accumulatorIn)); + m_typeResolver->containedType(m_state.accumulatorIn()))); return; } - if (m_state.accumulatorIn.isImportNamespace()) - m_logger->logWarning(u"Type not found in namespace"_qs, Log_Type, - getCurrentSourceLocation()); - } else if (m_state.accumulatorOut.variant() == QQmlJSRegisterContent::Singleton - && m_state.accumulatorIn.variant() == QQmlJSRegisterContent::ObjectModulePrefix) { - m_logger->logWarning(u"Cannot load singleton as property of object"_qs, Log_Type, - getCurrentSourceLocation()); - m_state.accumulatorOut = QQmlJSRegisterContent(); + if (m_state.accumulatorIn().isImportNamespace()) + m_logger->log(u"Type not found in namespace"_qs, Log_Type, getCurrentSourceLocation()); + } else if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::Singleton + && m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectModulePrefix) { + m_logger->log(u"Cannot load singleton as property of object"_qs, Log_Type, + getCurrentSourceLocation()); + setAccumulator(QQmlJSRegisterContent()); } - bool isRestricted = checkRestricted(propertyName); + const bool isRestrictedProperty = isRestricted(propertyName); - if (!m_state.accumulatorOut.isValid()) { + if (!m_state.accumulatorOut().isValid()) { setError(u"Cannot load property %1 from %2."_qs - .arg(propertyName, m_state.accumulatorIn.descriptiveName())); + .arg(propertyName, m_state.accumulatorIn().descriptiveName())); - if (isRestricted) + if (isRestrictedProperty) return; - const QString typeName = m_typeResolver->containedTypeName(m_state.accumulatorIn); + const QString typeName = m_typeResolver->containedTypeName(m_state.accumulatorIn()); if (typeName == u"QVariant") return; - if (m_state.accumulatorIn.isList() && propertyName == u"length") + if (m_state.accumulatorIn().isList() && propertyName == u"length") return; - auto baseType = m_typeResolver->containedType(m_state.accumulatorIn); + auto baseType = m_typeResolver->containedType(m_state.accumulatorIn()); // Warn separately when a property is only not found because of a missing type - if (auto property = baseType->property(propertyName); property.isValid()) { - - QString errorType; - if (property.type().isNull()) - errorType = u"found"_qs; - else if (!property.type()->isFullyResolved()) - errorType = u"fully resolved"_qs; - - Q_ASSERT(!errorType.isEmpty()); - - m_logger->logWarning( - u"Type \"%1\" of property \"%2\" not %3. This is likely due to a missing dependency entry or a type not being exposed declaratively."_qs - .arg(property.typeName(), propertyName, errorType), - Log_Type, getCurrentSourceLocation()); + if (isMissingPropertyType(baseType, propertyName)) return; - } std::optional<FixSuggestion> fixSuggestion; @@ -650,30 +757,45 @@ void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName) } } - m_logger->logWarning( + m_logger->log( u"Property \"%1\" not found on type \"%2\""_qs.arg(propertyName).arg(typeName), Log_Type, getCurrentSourceLocation(), true, true, fixSuggestion); return; } - if (m_state.accumulatorOut.isMethod() && m_state.accumulatorOut.method().length() != 1) { + if (m_state.accumulatorOut().isMethod() && m_state.accumulatorOut().method().length() != 1) { setError(u"Cannot determine overloaded method on loadProperty"_qs); return; } - if (m_state.accumulatorOut.isProperty()) { - if (m_typeResolver->registerContains(m_state.accumulatorOut, m_typeResolver->voidType())) { + if (m_state.accumulatorOut().isProperty()) { + if (m_typeResolver->registerContains( + m_state.accumulatorOut(), m_typeResolver->voidType())) { setError(u"Type %1 does not have a property %2 for reading"_qs - .arg(m_state.accumulatorIn.descriptiveName(), propertyName)); + .arg(m_state.accumulatorIn().descriptiveName(), propertyName)); return; } - if (!m_state.accumulatorOut.property().type()) { - m_logger->logWarning( - QString::fromLatin1("Type of property \"%2\" not found").arg(propertyName), - Log_Type, getCurrentSourceLocation()); + if (!m_state.accumulatorOut().property().type()) { + m_logger->log( + QString::fromLatin1("Type of property \"%2\" not found").arg(propertyName), + Log_Type, getCurrentSourceLocation()); } } + + switch (m_state.accumulatorOut().variant()) { + case QQmlJSRegisterContent::ObjectEnum: + case QQmlJSRegisterContent::ExtensionObjectEnum: + case QQmlJSRegisterContent::Singleton: + // For reading enums or singletons, we don't need to access anything, unless it's an + // import namespace. Then we need the name. + if (m_state.accumulatorIn().isImportNamespace()) + addReadAccumulator(m_state.accumulatorIn()); + break; + default: + addReadAccumulator(m_state.accumulatorIn()); + break; + } } void QQmlJSTypePropagator::generate_LoadProperty(int nameIndex) @@ -715,17 +837,21 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base) if (!property.isWritable()) { setError(u"Can't assign to read-only property %1"_qs.arg(propertyName)); - m_logger->logWarning(u"Cannot assign to read-only property %1"_qs.arg(propertyName), - Log_Property, getCurrentSourceLocation()); + m_logger->log(u"Cannot assign to read-only property %1"_qs.arg(propertyName), Log_Property, + getCurrentSourceLocation()); return; } - if (!canConvertFromTo(m_state.accumulatorIn, property)) { + if (!canConvertFromTo(m_state.accumulatorIn(), property)) { setError(u"cannot convert from %1 to %2"_qs - .arg(m_state.accumulatorIn.descriptiveName(), property.descriptiveName())); + .arg(m_state.accumulatorIn().descriptiveName(), property.descriptiveName())); return; } + + m_state.setHasSideEffects(true); + addReadAccumulator(property); + addReadRegister(base, callBase); } void QQmlJSTypePropagator::generate_SetLookup(int index, int base) @@ -762,6 +888,7 @@ void QQmlJSTypePropagator::generate_Resume(int) void QQmlJSTypePropagator::generate_CallValue(int name, int argc, int argv) { + m_state.setHasSideEffects(true); Q_UNUSED(name) Q_UNUSED(argc) Q_UNUSED(argv) @@ -770,6 +897,7 @@ void QQmlJSTypePropagator::generate_CallValue(int name, int argc, int argv) void QQmlJSTypePropagator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv) { + m_state.setHasSideEffects(true); Q_UNUSED(name) Q_UNUSED(thisObject) Q_UNUSED(argc) @@ -779,22 +907,47 @@ void QQmlJSTypePropagator::generate_CallWithReceiver(int name, int thisObject, i void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv) { - auto callBase = m_state.registers[base]; + Q_ASSERT(m_state.registers.contains(base)); + const auto callBase = m_state.registers[base]; const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex); - const auto member = m_typeResolver->memberType(callBase, propertyName); - const auto containedType = m_typeResolver->containedType(callBase); - if (containedType == m_typeResolver->jsValueType() - || containedType == m_typeResolver->varType()) { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->jsValueType()); + if (m_typeResolver->registerContains( + callBase, m_typeResolver->jsGlobalObject()->property(u"Math"_qs).type())) { + + // 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->globalType(m_typeResolver->realType()); + for (int i = 0; i < argc; ++i) + addReadRegister(argv + i, realType); + setAccumulator(realType); + 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); return; } + const auto member = m_typeResolver->memberType(callBase, propertyName); if (!member.isMethod()) { setError(u"Type %1 does not have a property %2 for calling"_qs .arg(callBase.descriptiveName(), propertyName)); - if (checkRestricted(propertyName)) + if (callBase.isType() && isCallingProperty(callBase.type(), propertyName)) + return; + + if (isRestricted(propertyName)) return; std::optional<FixSuggestion> fixSuggestion; @@ -809,14 +962,15 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar } } - m_logger->logWarning(u"Property \"%1\" not found on type \"%2\""_qs.arg( - propertyName, m_typeResolver->containedTypeName(callBase)), - Log_Type, getCurrentSourceLocation(), true, true, fixSuggestion); + m_logger->log(u"Property \"%1\" not found on type \"%2\""_qs.arg( + propertyName, m_typeResolver->containedTypeName(callBase)), + Log_Type, getCurrentSourceLocation(), true, true, fixSuggestion); return; } - checkDeprecated(containedType, propertyName, true); + checkDeprecated(m_typeResolver->containedType(callBase), propertyName, true); + addReadRegister(base, callBase); propagateCall(member.method(), argc, argv); } @@ -873,6 +1027,72 @@ QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMe return javascriptFunction; } +void QQmlJSTypePropagator::setAccumulator(const QQmlJSRegisterContent &content) +{ + setRegister(Accumulator, content); +} + +void QQmlJSTypePropagator::setRegister(int index, const QQmlJSRegisterContent &content) +{ + // If we've come to the same conclusion before, let's not track the type again. + auto it = m_prevStateAnnotations.find(currentInstructionOffset()); + if (it != m_prevStateAnnotations.end()) { + const QQmlJSRegisterContent &lastTry = it->second.changedRegister; + if (m_typeResolver->registerContains(lastTry, m_typeResolver->containedType(content))) { + m_state.setRegister(index, lastTry); + return; + } + } + + m_state.setRegister(index, m_typeResolver->tracked(content)); +} + +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()); + + auto tryPrevStateConversion = [this](int index, const QQmlJSRegisterContent &merged) -> bool { + auto it = m_prevStateAnnotations.find(currentInstructionOffset()); + if (it == m_prevStateAnnotations.end()) + return false; + + auto conversion = it->second.typeConversions.find(index); + if (conversion == it->second.typeConversions.end()) + return false; + + const QQmlJSRegisterContent &lastTry = conversion.value(); + + Q_ASSERT(lastTry.isValid()); + Q_ASSERT(lastTry.isConversion()); + + if (!m_typeResolver->equals(lastTry.conversionResult(), merged.conversionResult()) + || lastTry.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] = lastTry; + m_state.registers[index] = lastTry; + return true; + }; + + if (!tryPrevStateConversion(index, merged)) { + merged = m_typeResolver->tracked(merged); + Q_ASSERT(merged.isValid()); + m_state.annotations[currentInstructionOffset()].typeConversions[index] = merged; + m_state.registers[index] = merged; + } +} + +void QQmlJSTypePropagator::addReadRegister(int index, const QQmlJSRegisterContent &convertTo) +{ + m_state.addReadRegister(index, m_typeResolver->convert(m_state.registers[index], convertTo)); +} + void QQmlJSTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv) { QStringList errors; @@ -887,11 +1107,30 @@ void QQmlJSTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, return; } - const auto returnType = match.returnType(); - m_state.accumulatorOut = m_typeResolver->globalType( - returnType ? QQmlJSScope::ConstPtr(returnType) : m_typeResolver->voidType()); - if (!m_state.accumulatorOut.isValid()) + const auto returnType = match.isJavaScriptFunction() + ? m_typeResolver->jsValueType() + : QQmlJSScope::ConstPtr(match.returnType()); + setAccumulator(m_typeResolver->returnType( + returnType ? QQmlJSScope::ConstPtr(returnType) : m_typeResolver->voidType(), + match.isJavaScriptFunction() ? QQmlJSRegisterContent::JavaScriptReturnValue + : QQmlJSRegisterContent::MethodReturnValue)); + if (!m_state.accumulatorOut().isValid()) setError(u"Cannot store return type of method %1()."_qs.arg(match.methodName())); + + m_state.setHasSideEffects(true); + const auto types = match.parameterTypes(); + for (int i = 0; i < argc; ++i) { + if (i < types.length()) { + const QQmlJSScope::ConstPtr type = match.isJavaScriptFunction() + ? m_typeResolver->jsValueType() + : QQmlJSScope::ConstPtr(types.at(i)); + if (!type.isNull()) { + addReadRegister(argv + i, m_typeResolver->globalType(type)); + continue; + } + } + addReadRegister(argv + i, m_typeResolver->globalType(m_typeResolver->jsValueType())); + } } void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc, @@ -902,6 +1141,7 @@ void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base void QQmlJSTypePropagator::generate_CallElement(int base, int index, int argc, int argv) { + m_state.setHasSideEffects(true); Q_UNUSED(base) Q_UNUSED(index) Q_UNUSED(argc) @@ -916,6 +1156,7 @@ void QQmlJSTypePropagator::generate_CallName(int name, int argc, int argv) void QQmlJSTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv) { + m_state.setHasSideEffects(true); Q_UNUSED(argc) Q_UNUSED(argv) INSTR_PROLOGUE_NOT_IMPLEMENTED(); @@ -934,10 +1175,11 @@ void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName, } setError(u"method %1 cannot be resolved."_qs.arg(functionName)); - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->jsValueType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType())); setError(u"Cannot find function '%1'"_qs.arg(functionName)); - handleUnqualifiedAccess(functionName); + + handleUnqualifiedAccess(functionName, true); } void QQmlJSTypePropagator::generate_CallGlobalLookup(int index, int argc, int argv) @@ -954,6 +1196,7 @@ void QQmlJSTypePropagator::generate_CallQmlContextPropertyLookup(int index, int void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int argc, int argv) { + m_state.setHasSideEffects(true); Q_UNUSED(func) Q_UNUSED(thisObject) Q_UNUSED(argc) @@ -963,6 +1206,7 @@ void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, int argv) { + m_state.setHasSideEffects(true); Q_UNUSED(func) Q_UNUSED(thisObject) Q_UNUSED(argc) @@ -972,16 +1216,18 @@ void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv) { + m_state.setHasSideEffects(true); Q_UNUSED(func) Q_UNUSED(argv) Q_UNUSED(argc) - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->jsValueType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType())); } void QQmlJSTypePropagator::generate_ConstructWithSpread(int func, int argc, int argv) { + m_state.setHasSideEffects(true); Q_UNUSED(func) Q_UNUSED(argc) Q_UNUSED(argv) @@ -990,17 +1236,20 @@ void QQmlJSTypePropagator::generate_ConstructWithSpread(int func, int argc, int void QQmlJSTypePropagator::generate_SetUnwindHandler(int offset) { + m_state.setHasSideEffects(true); Q_UNUSED(offset) INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE(); } void QQmlJSTypePropagator::generate_UnwindDispatch() { + m_state.setHasSideEffects(true); INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE(); } void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset) { + m_state.setHasSideEffects(true); Q_UNUSED(level) Q_UNUSED(offset) INSTR_PROLOGUE_NOT_IMPLEMENTED(); @@ -1014,7 +1263,9 @@ void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name) void QQmlJSTypePropagator::generate_ThrowException() { - m_state.accumulatorOut = QQmlJSRegisterContent(); + setAccumulator(QQmlJSRegisterContent()); + m_state.setHasSideEffects(true); + m_state.skipInstructionsUntilNextJumpTarget = true; } void QQmlJSTypePropagator::generate_GetException() @@ -1024,16 +1275,18 @@ void QQmlJSTypePropagator::generate_GetException() void QQmlJSTypePropagator::generate_SetException() { + m_state.setHasSideEffects(true); INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_CreateCallContext() { - m_state.accumulatorOut = m_state.accumulatorIn; + m_state.setHasSideEffects(true); } void QQmlJSTypePropagator::generate_PushCatchContext(int index, int name) { + m_state.setHasSideEffects(true); Q_UNUSED(index) Q_UNUSED(name) INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE(); @@ -1041,34 +1294,39 @@ void QQmlJSTypePropagator::generate_PushCatchContext(int index, int name) void QQmlJSTypePropagator::generate_PushWithContext() { + m_state.setHasSideEffects(true); INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_PushBlockContext(int index) { + m_state.setHasSideEffects(true); Q_UNUSED(index) INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_CloneBlockContext() { + m_state.setHasSideEffects(true); INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_PushScriptContext(int index) { + m_state.setHasSideEffects(true); Q_UNUSED(index) INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_PopScriptContext() { + m_state.setHasSideEffects(true); INSTR_PROLOGUE_NOT_IMPLEMENTED(); } void QQmlJSTypePropagator::generate_PopContext() { - m_state.accumulatorOut = m_state.accumulatorIn; + m_state.setHasSideEffects(true); } void QQmlJSTypePropagator::generate_GetIterator(int iterator) @@ -1118,12 +1376,12 @@ void QQmlJSTypePropagator::generate_DeleteName(int name) void QQmlJSTypePropagator::generate_TypeofName(int name) { Q_UNUSED(name); - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->stringType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->stringType())); } void QQmlJSTypePropagator::generate_TypeofValue() { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->stringType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->stringType())); } void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable) @@ -1135,9 +1393,10 @@ void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable) void QQmlJSTypePropagator::generate_DefineArray(int argc, int args) { - Q_UNUSED(argc); Q_UNUSED(args); - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->jsValueType()); + setAccumulator(m_typeResolver->globalType(argc == 0 + ? m_typeResolver->emptyListType() + : m_typeResolver->jsValueType())); } void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int argc, int args) @@ -1147,7 +1406,7 @@ void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int Q_UNUSED(internalClassId) Q_UNUSED(argc) Q_UNUSED(args) - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->jsValueType()); + setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType())); } void QQmlJSTypePropagator::generate_CreateClass(int classIndex, int heritage, int computedNames) @@ -1192,36 +1451,40 @@ void QQmlJSTypePropagator::generate_ToObject() void QQmlJSTypePropagator::generate_Jump(int offset) { saveRegisterStateForJump(offset); - m_state.accumulatorIn = QQmlJSRegisterContent(); - m_state.accumulatorOut = QQmlJSRegisterContent(); m_state.skipInstructionsUntilNextJumpTarget = true; + m_state.setHasSideEffects(true); } void QQmlJSTypePropagator::generate_JumpTrue(int offset) { - if (!canConvertFromTo(m_state.accumulatorIn, + if (!canConvertFromTo(m_state.accumulatorIn(), m_typeResolver->globalType(m_typeResolver->boolType()))) { setError(u"cannot convert from %1 to boolean"_qs - .arg(m_state.accumulatorIn.descriptiveName())); + .arg(m_state.accumulatorIn().descriptiveName())); return; } saveRegisterStateForJump(offset); + m_state.setHasSideEffects(true); + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); } void QQmlJSTypePropagator::generate_JumpFalse(int offset) { - if (!canConvertFromTo(m_state.accumulatorIn, + if (!canConvertFromTo(m_state.accumulatorIn(), m_typeResolver->globalType(m_typeResolver->boolType()))) { setError(u"cannot convert from %1 to boolean"_qs - .arg(m_state.accumulatorIn.descriptiveName())); + .arg(m_state.accumulatorIn().descriptiveName())); return; } saveRegisterStateForJump(offset); + m_state.setHasSideEffects(true); + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); } void QQmlJSTypePropagator::generate_JumpNoException(int offset) { saveRegisterStateForJump(offset); + m_state.setHasSideEffects(true); } void QQmlJSTypePropagator::generate_JumpNotUndefined(int offset) @@ -1232,77 +1495,174 @@ void QQmlJSTypePropagator::generate_JumpNotUndefined(int offset) void QQmlJSTypePropagator::generate_CheckException() { - m_state.accumulatorOut = m_state.accumulatorIn; + m_state.setHasSideEffects(true); +} + +void QQmlJSTypePropagator::recordEqualsNullType() +{ + // TODO: We can specialize this further, for QVariant, QJSValue, int, bool, whatever. + if (m_typeResolver->registerContains(m_state.accumulatorIn(), m_typeResolver->nullType()) + || m_typeResolver->containedType(m_state.accumulatorIn())->isReferenceType()) { + addReadAccumulator(m_state.accumulatorIn()); + } else { + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->jsPrimitiveType())); + } +} +void QQmlJSTypePropagator::recordEqualsIntType() +{ + // We have specializations for numeric types and bool. + const QQmlJSScope::ConstPtr in = m_typeResolver->containedType(m_state.accumulatorIn()); + if (m_typeResolver->registerContains(m_state.accumulatorIn(), m_typeResolver->boolType()) + || m_typeResolver->isNumeric(m_state.accumulatorIn())) { + addReadAccumulator(m_state.accumulatorIn()); + } else { + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->jsPrimitiveType())); + } +} +void QQmlJSTypePropagator::recordEqualsType(int lhs) +{ + const auto isNumericOrEnum = [this](const QQmlJSRegisterContent &content) { + return content.isEnumeration() || m_typeResolver->isNumeric(content); + }; + + const auto isIntCompatible = [this](const QQmlJSRegisterContent &content) { + return content.isEnumeration() + || m_typeResolver->registerContains(content, m_typeResolver->intType()); + }; + + const auto accumulatorIn = m_state.accumulatorIn(); + const auto lhsRegister = m_state.registers[lhs]; + + // If the types are primitive, we compare directly ... + if (m_typeResolver->isPrimitive(accumulatorIn)) { + if (m_typeResolver->registerContains( + accumulatorIn, m_typeResolver->containedType(lhsRegister))) { + addReadRegister(lhs, accumulatorIn); + 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); + } + } + + // Otherwise they're both casted to QJSValue. + // TODO: We can add more specializations here: void/void null/null object/null etc + + const QQmlJSRegisterContent jsval = m_typeResolver->globalType(m_typeResolver->jsValueType()); + addReadRegister(lhs, jsval); + addReadAccumulator(jsval); +} + +void QQmlJSTypePropagator::recordCompareType(int lhs) +{ + // If they're both numeric, we can compare them directly. + // They may be casted to double, though. + const QQmlJSRegisterContent read + = (m_typeResolver->isNumeric(m_state.accumulatorIn()) + && m_typeResolver->isNumeric(m_state.registers[lhs])) + ? m_typeResolver->merge(m_state.accumulatorIn(), m_state.registers[lhs]) + : m_typeResolver->globalType(m_typeResolver->jsPrimitiveType()); + addReadRegister(lhs, read); + addReadAccumulator(read); } void QQmlJSTypePropagator::generate_CmpEqNull() { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->boolType()); + recordEqualsNullType(); + setAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); } void QQmlJSTypePropagator::generate_CmpNeNull() { - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->boolType()); + recordEqualsNullType(); + setAccumulator(m_typeResolver->globalType(m_typeResolver->boolType())); } void QQmlJSTypePropagator::generate_CmpEqInt(int lhsConst) { + recordEqualsIntType(); Q_UNUSED(lhsConst) - m_state.accumulatorOut = QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation( + setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation( QSOperator::Op::Equal, m_typeResolver->globalType(m_typeResolver->intType()), - m_state.accumulatorIn)); + m_state.accumulatorIn()))); } void QQmlJSTypePropagator::generate_CmpNeInt(int lhsConst) { + recordEqualsIntType(); Q_UNUSED(lhsConst) - m_state.accumulatorOut = QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation( + setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation( QSOperator::Op::NotEqual, m_typeResolver->globalType(m_typeResolver->intType()), - m_state.accumulatorIn)); + m_state.accumulatorIn()))); } void QQmlJSTypePropagator::generate_CmpEq(int lhs) { + recordEqualsType(lhs); propagateBinaryOperation(QSOperator::Op::Equal, lhs); } void QQmlJSTypePropagator::generate_CmpNe(int lhs) { + recordEqualsType(lhs); propagateBinaryOperation(QSOperator::Op::NotEqual, lhs); } void QQmlJSTypePropagator::generate_CmpGt(int lhs) { + recordCompareType(lhs); propagateBinaryOperation(QSOperator::Op::Gt, lhs); } void QQmlJSTypePropagator::generate_CmpGe(int lhs) { + recordCompareType(lhs); propagateBinaryOperation(QSOperator::Op::Ge, lhs); } void QQmlJSTypePropagator::generate_CmpLt(int lhs) { + recordCompareType(lhs); propagateBinaryOperation(QSOperator::Op::Lt, lhs); } void QQmlJSTypePropagator::generate_CmpLe(int lhs) { + recordCompareType(lhs); propagateBinaryOperation(QSOperator::Op::Le, lhs); } void QQmlJSTypePropagator::generate_CmpStrictEqual(int lhs) { + recordEqualsType(lhs); propagateBinaryOperation(QSOperator::Op::StrictEqual, lhs); } void QQmlJSTypePropagator::generate_CmpStrictNotEqual(int lhs) { + recordEqualsType(lhs); propagateBinaryOperation(QSOperator::Op::StrictNotEqual, lhs); } void QQmlJSTypePropagator::generate_CmpIn(int lhs) { + // TODO: Most of the time we don't need the object at all, but only its metatype. + // Fix this when we add support for the "in" instruction to the code generator. + // Also, specialize on lhs to avoid conversion to QJSPrimitiveValue. + + addReadRegister(lhs, m_typeResolver->globalType(m_typeResolver->jsValueType())); + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType())); + propagateBinaryOperation(QSOperator::Op::In, lhs); } @@ -1317,47 +1677,59 @@ void QQmlJSTypePropagator::generate_As(int lhs) const QQmlJSRegisterContent input = checkedInputRegister(lhs); QQmlJSScope::ConstPtr contained; - switch (m_state.accumulatorIn.variant()) { + switch (m_state.accumulatorIn().variant()) { case QQmlJSRegisterContent::ScopeAttached: + contained = m_state.accumulatorIn().scopeType(); + break; case QQmlJSRegisterContent::MetaType: - contained = m_state.accumulatorIn.scopeType(); + contained = m_state.accumulatorIn().scopeType(); + if (contained->isComposite()) // Otherwise we don't need it + addReadAccumulator(m_typeResolver->globalType(m_typeResolver->metaObjectType())); break; default: - contained = m_typeResolver->containedType(m_state.accumulatorIn); + contained = m_typeResolver->containedType(m_state.accumulatorIn()); break; } + addReadRegister(lhs, m_typeResolver->globalType(contained)); + if (m_typeResolver->containedType(input)->accessSemantics() != QQmlJSScope::AccessSemantics::Reference || contained->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) { setError(u"invalid cast from %1 to %2. You can only cast object types."_qs - .arg(input.descriptiveName(), m_state.accumulatorIn.descriptiveName())); + .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName())); } else { - m_state.accumulatorOut = m_typeResolver->globalType(contained); + setAccumulator(m_typeResolver->globalType(contained)); } } void QQmlJSTypePropagator::generate_UNot() { - if (!canConvertFromTo(m_state.accumulatorIn, + if (!canConvertFromTo(m_state.accumulatorIn(), m_typeResolver->globalType(m_typeResolver->boolType()))) { setError(u"cannot convert from %1 to boolean"_qs - .arg(m_state.accumulatorIn.descriptiveName())); + .arg(m_state.accumulatorIn().descriptiveName())); return; } - m_state.accumulatorOut = m_typeResolver->globalType(m_typeResolver->boolType()); + const QQmlJSRegisterContent boolType = m_typeResolver->globalType(m_typeResolver->boolType()); + addReadAccumulator(boolType); + setAccumulator(boolType); } void QQmlJSTypePropagator::generate_UPlus() { - m_state.accumulatorOut = m_typeResolver->typeForUnaryOperation( - QQmlJSTypeResolver::UnaryOperator::Plus, m_state.accumulatorIn); + const QQmlJSRegisterContent type = m_typeResolver->typeForArithmeticUnaryOperation( + QQmlJSTypeResolver::UnaryOperator::Plus, m_state.accumulatorIn()); + addReadAccumulator(type); + setAccumulator(type); } void QQmlJSTypePropagator::generate_UMinus() { - m_state.accumulatorOut = m_typeResolver->typeForUnaryOperation( - QQmlJSTypeResolver::UnaryOperator::Minus, m_state.accumulatorIn); + const QQmlJSRegisterContent type = m_typeResolver->typeForArithmeticUnaryOperation( + QQmlJSTypeResolver::UnaryOperator::Minus, m_state.accumulatorIn()); + addReadAccumulator(type); + setAccumulator(type); } void QQmlJSTypePropagator::generate_UCompl() @@ -1367,19 +1739,25 @@ void QQmlJSTypePropagator::generate_UCompl() void QQmlJSTypePropagator::generate_Increment() { - m_state.accumulatorOut = m_typeResolver->typeForUnaryOperation( - QQmlJSTypeResolver::UnaryOperator::Increment, m_state.accumulatorIn); + const QQmlJSRegisterContent type = m_typeResolver->typeForArithmeticUnaryOperation( + QQmlJSTypeResolver::UnaryOperator::Increment, m_state.accumulatorIn()); + addReadAccumulator(type); + setAccumulator(type); } void QQmlJSTypePropagator::generate_Decrement() { - m_state.accumulatorOut = m_typeResolver->typeForUnaryOperation( - QQmlJSTypeResolver::UnaryOperator::Decrement, m_state.accumulatorIn); + const QQmlJSRegisterContent type = m_typeResolver->typeForArithmeticUnaryOperation( + QQmlJSTypeResolver::UnaryOperator::Decrement, m_state.accumulatorIn()); + addReadAccumulator(type); + setAccumulator(type); } void QQmlJSTypePropagator::generate_Add(int lhs) { - propagateBinaryOperation(QSOperator::Op::Add, lhs); + const auto type = propagateBinaryOperation(QSOperator::Op::Add, lhs); + addReadRegister(lhs, type); + addReadAccumulator(type); } void QQmlJSTypePropagator::generate_BitAnd(int lhs) @@ -1409,15 +1787,21 @@ void QQmlJSTypePropagator::generate_UShr(int lhs) void QQmlJSTypePropagator::generate_Shr(int lhs) { auto lhsRegister = checkedInputRegister(lhs); - m_state.accumulatorOut = m_typeResolver->typeForBinaryOperation( - QSOperator::Op::RShift, lhsRegister, m_state.accumulatorIn); + const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation( + QSOperator::Op::RShift, lhsRegister, m_state.accumulatorIn()); + addReadRegister(lhs, type); + addReadAccumulator(type); + setAccumulator(type); } void QQmlJSTypePropagator::generate_Shl(int lhs) { auto lhsRegister = checkedInputRegister(lhs); - m_state.accumulatorOut = m_typeResolver->typeForBinaryOperation( - QSOperator::Op::LShift, lhsRegister, m_state.accumulatorIn); + const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation( + QSOperator::Op::LShift, lhsRegister, m_state.accumulatorIn()); + addReadRegister(lhs, type); + addReadAccumulator(type); + setAccumulator(type); } void QQmlJSTypePropagator::generate_BitAndConst(int rhs) @@ -1448,18 +1832,22 @@ void QQmlJSTypePropagator::generate_ShrConst(int rhsConst) { Q_UNUSED(rhsConst) - m_state.accumulatorOut = m_typeResolver->typeForBinaryOperation( - QSOperator::Op::RShift, m_state.accumulatorIn, - m_typeResolver->globalType(m_typeResolver->intType())); + const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation( + QSOperator::Op::RShift, m_state.accumulatorIn(), + m_typeResolver->globalType(m_typeResolver->intType())); + addReadAccumulator(type); + setAccumulator(type); } void QQmlJSTypePropagator::generate_ShlConst(int rhsConst) { Q_UNUSED(rhsConst) - m_state.accumulatorOut = m_typeResolver->typeForBinaryOperation( - QSOperator::Op::LShift, m_state.accumulatorIn, - m_typeResolver->globalType(m_typeResolver->intType())); + const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation( + QSOperator::Op::LShift, m_state.accumulatorIn(), + m_typeResolver->globalType(m_typeResolver->intType())); + addReadAccumulator(type); + setAccumulator(type); } void QQmlJSTypePropagator::generate_Exp(int lhs) @@ -1470,22 +1858,30 @@ void QQmlJSTypePropagator::generate_Exp(int lhs) void QQmlJSTypePropagator::generate_Mul(int lhs) { - propagateBinaryOperation(QSOperator::Op::Mul, lhs); + const auto type = propagateBinaryOperation(QSOperator::Op::Mul, lhs); + addReadRegister(lhs, type); + addReadAccumulator(type); } void QQmlJSTypePropagator::generate_Div(int lhs) { - propagateBinaryOperation(QSOperator::Op::Div, lhs); + const auto type = propagateBinaryOperation(QSOperator::Op::Div, lhs); + addReadRegister(lhs, type); + addReadAccumulator(type); } void QQmlJSTypePropagator::generate_Mod(int lhs) { - propagateBinaryOperation(QSOperator::Op::Mod, lhs); + const auto type = propagateBinaryOperation(QSOperator::Op::Mod, lhs); + addReadRegister(lhs, type); + addReadAccumulator(type); } void QQmlJSTypePropagator::generate_Sub(int lhs) { - propagateBinaryOperation(QSOperator::Op::Sub, lhs); + const auto type = propagateBinaryOperation(QSOperator::Op::Sub, lhs); + addReadRegister(lhs, type); + addReadAccumulator(type); } void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count) @@ -1506,45 +1902,53 @@ 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 instr) +QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type) { if (m_error->isValid()) return SkipInstruction; - if (m_state.jumpTargets.contains(currentInstructionOffset())) - m_state.skipInstructionsUntilNextJumpTarget = false; - else if (m_state.skipInstructionsUntilNextJumpTarget) + if (m_state.jumpTargets.contains(currentInstructionOffset())) { + if (m_state.skipInstructionsUntilNextJumpTarget) { + // When re-surfacing from dead code, all registers are invalid. + m_state.registers.clear(); + m_state.skipInstructionsUntilNextJumpTarget = false; + } + } else if (m_state.skipInstructionsUntilNextJumpTarget + && !instructionManipulatesContext(type)) { return SkipInstruction; - - bool instructionWritesAccumulatorWithoutReading = false; - switch (instr) { - case QV4::Moth::Instr::Type::LoadReg: - case QV4::Moth::Instr::Type::LoadZero: - case QV4::Moth::Instr::Type::LoadTrue: - case QV4::Moth::Instr::Type::LoadFalse: - case QV4::Moth::Instr::Type::LoadConst: - case QV4::Moth::Instr::Type::LoadInt: - case QV4::Moth::Instr::Type::LoadUndefined: - case QV4::Moth::Instr::Type::LoadName: - case QV4::Moth::Instr::Type::LoadRuntimeString: - case QV4::Moth::Instr::Type::LoadLocal: - case QV4::Moth::Instr::Type::LoadQmlContextPropertyLookup: - case QV4::Moth::Instr::Type::LoadGlobalLookup: - case QV4::Moth::Instr::Type::CallQmlContextPropertyLookup: - case QV4::Moth::Instr::Type::CallGlobalLookup: - case QV4::Moth::Instr::Type::CallPropertyLookup: - instructionWritesAccumulatorWithoutReading = true; - break; - default: - break; } const int currentOffset = currentInstructionOffset(); // If we reach an instruction that is a target of a jump earlier, then we must check that the // register state at the origin matches the current state. If not, then we may have to inject - // conversion code (communicated to code gen via m_state.expectedTargetTypesBeforeJump). For + // conversion code (communicated to code gen via m_state.typeConversions). For // example: // // function blah(x: number) { return x > 10 ? 10 : x} @@ -1563,11 +1967,6 @@ QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type instr) registerIt != end; ++registerIt) { const int registerIndex = registerIt.key(); - // AccumulatorOut will be the "in" for this (upcoming) instruction, so if that one - // merely writes to it, we don't care about it's value at the origin of the jump. - if (registerIndex == Accumulator && instructionWritesAccumulatorWithoutReading) - continue; - auto newType = registerIt.value(); if (!newType.isValid()) { setError(u"When reached from offset %1, %2 is undefined"_qs @@ -1578,60 +1977,34 @@ QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type instr) auto currentRegister = m_state.registers.find(registerIndex); if (currentRegister != m_state.registers.end()) { - // Careful with accessing the hash iterator later inside this block, - // some operations may invalidate it. - auto currentRegisterType = currentRegister; - - if (*currentRegisterType != newType) { - auto merged = m_typeResolver->merge(newType, *currentRegisterType); - Q_ASSERT(merged.isValid()); - m_state.annotations[currentInstructionOffset()] - .expectedTargetTypesBeforeJump[registerIndex] = merged; - setRegister(registerIndex, merged); + if (currentRegister.value() != newType) { + mergeRegister(registerIndex, newType, currentRegister.value()); } else { // Clear the constant value as this from a jump that might be merging two // different value // currentRegister->m_state.value = {}; } + } else { + mergeRegister(registerIndex, newType, newType); } } } - // Most instructions require a valid accumulator as input - switch (instr) { - case QV4::Moth::Instr::Type::Jump: - case QV4::Moth::Instr::Type::LoadReg: - case QV4::Moth::Instr::Type::LoadName: - case QV4::Moth::Instr::Type::LoadRuntimeString: - case QV4::Moth::Instr::Type::LoadInt: - case QV4::Moth::Instr::Type::LoadNull: - case QV4::Moth::Instr::Type::TypeofName: - case QV4::Moth::Instr::Type::CallProperty: - case QV4::Moth::Instr::Type::CallName: - case QV4::Moth::Instr::Type::MoveReg: - case QV4::Moth::Instr::Type::MoveConst: - case QV4::Moth::Instr::Type::DefineArray: - case QV4::Moth::Instr::Type::DefineObjectLiteral: - case QV4::Moth::Instr::Type::CheckException: - case QV4::Moth::Instr::Type::CreateCallContext: - case QV4::Moth::Instr::Type::PopContext: - case QV4::Moth::Instr::Type::JumpNoException: - case QV4::Moth::Instr::Type::SetUnwindHandler: - case QV4::Moth::Instr::Type::PushCatchContext: - case QV4::Moth::Instr::Type::UnwindDispatch: - break; - default: - if (instructionWritesAccumulatorWithoutReading) - m_state.accumulatorIn = QQmlJSRegisterContent(); - else - m_state.accumulatorIn = checkedInputRegister(Accumulator); - } - return ProcessInstruction; } void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr) { + InstructionAnnotation ¤tInstruction = m_state.annotations[currentInstructionOffset()]; + currentInstruction.changedRegister = m_state.changedRegister(); + currentInstruction.changedRegisterIndex = m_state.changedRegisterIndex(); + 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 case QV4::Moth::Instr::Type::Ret: @@ -1653,30 +2026,45 @@ void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr) case QV4::Moth::Instr::Type::SetUnwindHandler: case QV4::Moth::Instr::Type::PushCatchContext: case QV4::Moth::Instr::Type::UnwindDispatch: + if (m_state.changedRegisterIndex() == Accumulator && !m_error->isValid()) { + setError(u"Instruction is not expected to populate the accumulator"_qs); + return; + } break; default: // If the instruction is expected to produce output, save it in the register set // for the next instruction. - if (m_state.accumulatorOut.isValid()) { - setRegister(Accumulator, m_state.accumulatorOut); - m_state.accumulatorOut = QQmlJSRegisterContent(); - } else if (!m_error->isValid()) { + if ((!m_state.changedRegister().isValid() || m_state.changedRegisterIndex() != Accumulator) + && !m_error->isValid()) { setError(u"Instruction is expected to populate the accumulator"_qs); return; } } - m_state.annotations[currentInstructionOffset()].registers = m_state.registers; + if (m_state.changedRegisterIndex() != InvalidRegister) { + Q_ASSERT(m_error->isValid() || m_state.changedRegister().isValid()); + m_state.registers[m_state.changedRegisterIndex()] = m_state.changedRegister(); + m_state.clearChangedRegister(); + } } -void QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs) +QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs) { auto lhsRegister = checkedInputRegister(lhs); if (!lhsRegister.isValid()) - return; + return QQmlJSRegisterContent(); - m_state.accumulatorOut = - m_typeResolver->typeForBinaryOperation(op, lhsRegister, m_state.accumulatorIn); + const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation( + op, lhsRegister, m_state.accumulatorIn()); + + setAccumulator(type); + + // If we're dealing with QJSPrimitiveType, do not force premature conversion of the arguemnts + // to the target type. Such an operation can lose information. + if (type.storedType() == m_typeResolver->jsPrimitiveType()) + return m_typeResolver->globalType(m_typeResolver->jsPrimitiveType()); + + return type; } void QQmlJSTypePropagator::saveRegisterStateForJump(int offset) @@ -1692,8 +2080,10 @@ void QQmlJSTypePropagator::saveRegisterStateForJump(int offset) const auto registerStates = m_jumpOriginRegisterStateByTargetInstructionOffset.equal_range(jumpToOffset); for (auto it = registerStates.first; it != registerStates.second; ++it) { - if (it->registers == state.registers) + if (it->registers.keys() == state.registers.keys() + && it->registers.values() == state.registers.values()) { return; // We've seen the same register state before. No need for merging. + } } // The register state at the target offset needs to be resolved in a further pass. @@ -1715,28 +2105,14 @@ QString QQmlJSTypePropagator::registerName(int registerIndex) const registerIndex - FirstArgument - m_function->argumentTypes.count()); } -// As the source register content may also be a register, we expect a copy here, -// rather than a reference. Otherwise you might pass a reference to another entry -// of m_state.registers, which then becomes invalid when making space for the new -// entry in the hash. -void QQmlJSTypePropagator::setRegister(int index, QQmlJSRegisterContent content) -{ - m_state.registers[index] = std::move(content); -} - -void QQmlJSTypePropagator::setRegister(int index, const QQmlJSScope::ConstPtr &content) -{ - m_state.registers[index] = m_typeResolver->globalType(content); -} - QQmlJSRegisterContent QQmlJSTypePropagator::checkedInputRegister(int reg) { - VirtualRegisters::ConstIterator regIt = m_state.registers.find(reg); - if (regIt == m_state.registers.constEnd()) { + const auto regIt = m_state.registers.find(reg); + if (regIt == m_state.registers.end()) { setError(u"Type error: could not infer the type of an expression"_qs); return {}; } - return *regIt; + return regIt.value(); } bool QQmlJSTypePropagator::canConvertFromTo(const QQmlJSRegisterContent &from, |