QmlCompiler: Implement resetting of properties
We piggy-back on the mechanism used to handle shadowable properties and pass the value as QVariant. QVariant can hold undefined and the lookup functions know how to handle it. Pick-to: 6.5 6.2 Fixes: QTBUG-120512 Change-Id: I9bca4940256c82bdcf5540b956600eb420be363e Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> (cherry picked from commitda6680cb2e
) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> (cherry picked from commit104c90e883
)
This commit is contained in:
parent
787e794af8
commit
49a62c4102
|
@ -178,6 +178,14 @@ public:
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QQmlJSRegisterContent castTo(const QQmlJSScope::ConstPtr &newContainedType) const
|
||||||
|
{
|
||||||
|
// This is not a conversion but a run time cast. It may result in null or undefined.
|
||||||
|
QQmlJSRegisterContent result = *this;
|
||||||
|
result.m_content = newContainedType;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum ContentKind { Type, Property, Enum, Method, ImportNamespace, Conversion };
|
enum ContentKind { Type, Property, Enum, Method, ImportNamespace, Conversion };
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,9 @@ void QQmlJSShadowCheck::run(
|
||||||
m_error = error;
|
m_error = error;
|
||||||
m_state = initialState(function);
|
m_state = initialState(function);
|
||||||
decode(m_function->code.constData(), static_cast<uint>(m_function->code.size()));
|
decode(m_function->code.constData(), static_cast<uint>(m_function->code.size()));
|
||||||
|
|
||||||
|
for (const auto &store : m_resettableStores)
|
||||||
|
checkResettable(store.accumulatorIn, store.instructionOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex)
|
void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex)
|
||||||
|
@ -70,15 +73,37 @@ void QQmlJSShadowCheck::generate_GetLookup(int index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QQmlJSShadowCheck::handleStore(int base, const QString &memberName)
|
||||||
|
{
|
||||||
|
const int instructionOffset = currentInstructionOffset();
|
||||||
|
const QQmlJSRegisterContent &readAccumulator
|
||||||
|
= (*m_annotations)[instructionOffset].readRegisters[Accumulator].content;
|
||||||
|
|
||||||
|
// If the accumulator is already read as var, we don't have to do anything.
|
||||||
|
if (m_typeResolver->registerContains(readAccumulator, m_typeResolver->varType()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto baseType = m_state.registers[base].content;
|
||||||
|
if (checkShadowing(baseType, memberName, base) == Shadowable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the property isn't shadowable but resettable, we have to turn the read register into
|
||||||
|
// var if the accumulator can hold undefined. This has to be done in a second pass
|
||||||
|
// because the accumulator may still turn into var due to its own shadowing.
|
||||||
|
const QQmlJSRegisterContent member = m_typeResolver->memberType(baseType, memberName);
|
||||||
|
if (member.isProperty() && !member.property().reset().isEmpty())
|
||||||
|
m_resettableStores.append({m_state.accumulatorIn(), instructionOffset});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base)
|
void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base)
|
||||||
{
|
{
|
||||||
checkShadowing(
|
handleStore(base, m_jsUnitGenerator->stringForIndex(nameIndex));
|
||||||
m_state.registers[base].content, m_jsUnitGenerator->stringForIndex(nameIndex), base);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QQmlJSShadowCheck::generate_SetLookup(int index, int base)
|
void QQmlJSShadowCheck::generate_SetLookup(int index, int base)
|
||||||
{
|
{
|
||||||
checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(index), base);
|
handleStore(base, m_jsUnitGenerator->lookupName(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv)
|
void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv)
|
||||||
|
@ -107,11 +132,11 @@ void QQmlJSShadowCheck::endInstruction(QV4::Moth::Instr::Type)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void QQmlJSShadowCheck::checkShadowing(
|
QQmlJSShadowCheck::Shadowability QQmlJSShadowCheck::checkShadowing(
|
||||||
const QQmlJSRegisterContent &baseType, const QString &memberName, int baseRegister)
|
const QQmlJSRegisterContent &baseType, const QString &memberName, int baseRegister)
|
||||||
{
|
{
|
||||||
if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference)
|
if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference)
|
||||||
return;
|
return NotShadowable;
|
||||||
|
|
||||||
switch (baseType.variant()) {
|
switch (baseType.variant()) {
|
||||||
case QQmlJSRegisterContent::ExtensionObjectProperty:
|
case QQmlJSRegisterContent::ExtensionObjectProperty:
|
||||||
|
@ -128,14 +153,14 @@ void QQmlJSShadowCheck::checkShadowing(
|
||||||
// those are not shadowable.
|
// those are not shadowable.
|
||||||
if (!member.isValid()) {
|
if (!member.isValid()) {
|
||||||
Q_ASSERT(m_typeResolver->isPrefix(memberName));
|
Q_ASSERT(m_typeResolver->isPrefix(memberName));
|
||||||
return;
|
return NotShadowable;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (member.isProperty()) {
|
if (member.isProperty()) {
|
||||||
if (member.property().isFinal())
|
if (member.property().isFinal())
|
||||||
return; // final properties can't be shadowed
|
return NotShadowable; // final properties can't be shadowed
|
||||||
} else if (!member.isMethod()) {
|
} else if (!member.isMethod()) {
|
||||||
return; // Only properties and methods can be shadowed
|
return NotShadowable; // Only properties and methods can be shadowed
|
||||||
}
|
}
|
||||||
|
|
||||||
m_logger->log(
|
m_logger->log(
|
||||||
|
@ -160,15 +185,33 @@ void QQmlJSShadowCheck::checkShadowing(
|
||||||
if (it.key() != baseRegister)
|
if (it.key() != baseRegister)
|
||||||
it->second.content = m_typeResolver->convert(it->second.content, varContent);
|
it->second.content = m_typeResolver->convert(it->second.content, varContent);
|
||||||
}
|
}
|
||||||
|
return Shadowable;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// In particular ObjectById is fine as that cannot change into something else
|
// In particular ObjectById is fine as that cannot change into something else
|
||||||
// Singleton should also be fine, unless the factory function creates an object
|
// Singleton should also be fine, unless the factory function creates an object
|
||||||
// with different property types than the declared class.
|
// with different property types than the declared class.
|
||||||
return;
|
return NotShadowable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QQmlJSShadowCheck::checkResettable(
|
||||||
|
const QQmlJSRegisterContent &accumulatorIn, int instructionOffset)
|
||||||
|
{
|
||||||
|
const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
|
||||||
|
|
||||||
|
// The stored type is not necessarily updated by the shadow check, but it
|
||||||
|
// will be in the basic blocks pass. For the purpose of adjusting newly
|
||||||
|
// shadowable types we can ignore it. We only want to know if any of the
|
||||||
|
// contents can hold undefined.
|
||||||
|
if (!m_typeResolver->canHoldUndefined(accumulatorIn.storedIn(varType)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QQmlJSRegisterContent varContent = m_typeResolver->globalType(varType);
|
||||||
|
|
||||||
|
QQmlJSRegisterContent &readAccumulator
|
||||||
|
= (*m_annotations)[instructionOffset].readRegisters[Accumulator].content;
|
||||||
|
readAccumulator = m_typeResolver->convert(readAccumulator, varContent);
|
||||||
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
|
@ -32,6 +32,13 @@ public:
|
||||||
QQmlJS::DiagnosticMessage *error);
|
QQmlJS::DiagnosticMessage *error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct ResettableStore {
|
||||||
|
QQmlJSRegisterContent accumulatorIn;
|
||||||
|
int instructionOffset = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
void handleStore(int base, const QString &memberName);
|
||||||
|
|
||||||
void generate_LoadProperty(int nameIndex) override;
|
void generate_LoadProperty(int nameIndex) override;
|
||||||
void generate_GetLookup(int index) override;
|
void generate_GetLookup(int index) override;
|
||||||
void generate_StoreProperty(int nameIndex, int base) override;
|
void generate_StoreProperty(int nameIndex, int base) override;
|
||||||
|
@ -42,9 +49,13 @@ private:
|
||||||
QV4::Moth::ByteCodeHandler::Verdict startInstruction(QV4::Moth::Instr::Type) override;
|
QV4::Moth::ByteCodeHandler::Verdict startInstruction(QV4::Moth::Instr::Type) override;
|
||||||
void endInstruction(QV4::Moth::Instr::Type) override;
|
void endInstruction(QV4::Moth::Instr::Type) override;
|
||||||
|
|
||||||
void checkShadowing(
|
enum Shadowability { NotShadowable, Shadowable };
|
||||||
|
Shadowability checkShadowing(
|
||||||
const QQmlJSRegisterContent &baseType, const QString &propertyName, int baseRegister);
|
const QQmlJSRegisterContent &baseType, const QString &propertyName, int baseRegister);
|
||||||
|
|
||||||
|
void checkResettable(const QQmlJSRegisterContent &accumulatorIn, int instructionOffset);
|
||||||
|
|
||||||
|
QList<ResettableStore> m_resettableStores;
|
||||||
InstructionAnnotations *m_annotations = nullptr;
|
InstructionAnnotations *m_annotations = nullptr;
|
||||||
State m_state;
|
State m_state;
|
||||||
};
|
};
|
||||||
|
|
|
@ -972,9 +972,22 @@ void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
|
||||||
getCurrentBindingSourceLocation()));
|
getCurrentBindingSourceLocation()));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_state.setHasSideEffects(true);
|
// If the property is resettable we must not coerce the input to the property type
|
||||||
addReadAccumulator(property);
|
// as that might eliminate an undefined value. For example, undefined -> string
|
||||||
|
// becomes "undefined".
|
||||||
|
// Therefore we explicitly require the value to be given as QVariant. This triggers
|
||||||
|
// the QVariant fallback path that also used for shadowable properties. QVariant can
|
||||||
|
// hold undefined and the lookup functions will handle that appropriately.
|
||||||
|
|
||||||
|
const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
|
||||||
|
const QQmlJSRegisterContent readType
|
||||||
|
= (property.property().reset().isEmpty()
|
||||||
|
|| !m_typeResolver->canHoldUndefined(m_state.accumulatorIn()))
|
||||||
|
? property
|
||||||
|
: property.storedIn(varType).castTo(varType);
|
||||||
|
addReadAccumulator(readType);
|
||||||
addReadRegister(base, callBase);
|
addReadRegister(base, callBase);
|
||||||
|
m_state.setHasSideEffects(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QQmlJSTypePropagator::generate_SetLookup(int index, int base)
|
void QQmlJSTypePropagator::generate_SetLookup(int index, int base)
|
||||||
|
|
|
@ -15,6 +15,7 @@ set(cpp_sources
|
||||||
multiforeign.h
|
multiforeign.h
|
||||||
objectwithmethod.h
|
objectwithmethod.h
|
||||||
person.cpp person.h
|
person.cpp person.h
|
||||||
|
resettable.h
|
||||||
sequenceToIterable.h
|
sequenceToIterable.h
|
||||||
sequencetypeexample.cpp sequencetypeexample.h
|
sequencetypeexample.cpp sequencetypeexample.h
|
||||||
state.h
|
state.h
|
||||||
|
@ -199,6 +200,7 @@ set(qml_files
|
||||||
registerPropagation.qml
|
registerPropagation.qml
|
||||||
registerelimination.qml
|
registerelimination.qml
|
||||||
renameAdjust.qml
|
renameAdjust.qml
|
||||||
|
resettable.qml
|
||||||
revisions.qml
|
revisions.qml
|
||||||
scopeIdLookup.qml
|
scopeIdLookup.qml
|
||||||
scopeVsObject.qml
|
scopeVsObject.qml
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (C) 2024 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#ifndef RESETTABLE_H
|
||||||
|
#define RESETTABLE_H
|
||||||
|
|
||||||
|
#include <QtCore/qobject.h>
|
||||||
|
#include <QtQml/qqml.h>
|
||||||
|
|
||||||
|
class ResettableProperty : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_NAMED_ELEMENT(Resettable)
|
||||||
|
Q_PROPERTY(qreal value READ value WRITE setValue RESET resetValue NOTIFY valueChanged FINAL)
|
||||||
|
Q_PROPERTY(qreal shadowable READ shadowable CONSTANT)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ResettableProperty(QObject *parent = nullptr) : QObject(parent) {}
|
||||||
|
qreal value() const { return m_value; }
|
||||||
|
qreal shadowable() const { return 25; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void resetValue() { setValue(0); }
|
||||||
|
void setValue(qreal value)
|
||||||
|
{
|
||||||
|
if (m_value == value)
|
||||||
|
return;
|
||||||
|
m_value = value;
|
||||||
|
emit valueChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void valueChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
qreal m_value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RESETTABLE_H
|
|
@ -0,0 +1,15 @@
|
||||||
|
pragma Strict
|
||||||
|
import QtQml
|
||||||
|
import TestTypes
|
||||||
|
|
||||||
|
Resettable {
|
||||||
|
id: self
|
||||||
|
value: 999
|
||||||
|
|
||||||
|
property Resettable shadowing: Resettable {
|
||||||
|
property var shadowable: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function doReset() { self.value = undefined }
|
||||||
|
function doReset2() { self.value = shadowing.shadowable }
|
||||||
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
#include <data/cppbaseclass.h>
|
#include <data/cppbaseclass.h>
|
||||||
#include <data/enumproblems.h>
|
#include <data/enumproblems.h>
|
||||||
#include <data/objectwithmethod.h>
|
#include <data/objectwithmethod.h>
|
||||||
|
#include <data/resettable.h>
|
||||||
|
|
||||||
#include <QtQml/private/qqmlengine_p.h>
|
#include <QtQml/private/qqmlengine_p.h>
|
||||||
#include <QtQml/private/qqmlpropertycachecreator_p.h>
|
#include <QtQml/private/qqmlpropertycachecreator_p.h>
|
||||||
|
@ -164,6 +165,7 @@ private slots:
|
||||||
void registerElimination();
|
void registerElimination();
|
||||||
void registerPropagation();
|
void registerPropagation();
|
||||||
void renameAdjust();
|
void renameAdjust();
|
||||||
|
void resettableProperty();
|
||||||
void revisions();
|
void revisions();
|
||||||
void scopeIdLookup();
|
void scopeIdLookup();
|
||||||
void scopeObjectDestruction();
|
void scopeObjectDestruction();
|
||||||
|
@ -3462,6 +3464,27 @@ void tst_QmlCppCodegen::renameAdjust()
|
||||||
QVERIFY(o);
|
QVERIFY(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tst_QmlCppCodegen::resettableProperty()
|
||||||
|
{
|
||||||
|
QQmlEngine engine;
|
||||||
|
QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/resettable.qml"_s));
|
||||||
|
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
|
||||||
|
QScopedPointer<QObject> o(c.create());
|
||||||
|
QVERIFY(o);
|
||||||
|
|
||||||
|
ResettableProperty *resettable = qobject_cast<ResettableProperty *>(o.data());
|
||||||
|
QVERIFY(resettable);
|
||||||
|
|
||||||
|
QCOMPARE(resettable->value(), 999);
|
||||||
|
QMetaObject::invokeMethod(resettable, "doReset");
|
||||||
|
QCOMPARE(resettable->value(), 0);
|
||||||
|
|
||||||
|
resettable->setValue(82);
|
||||||
|
QCOMPARE(resettable->value(), 82);
|
||||||
|
QMetaObject::invokeMethod(resettable, "doReset2");
|
||||||
|
QCOMPARE(resettable->value(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
void tst_QmlCppCodegen::revisions()
|
void tst_QmlCppCodegen::revisions()
|
||||||
{
|
{
|
||||||
QQmlEngine engine;
|
QQmlEngine engine;
|
||||||
|
|
Loading…
Reference in New Issue