diff options
| author | Dmitrii Akshintsev <[email protected]> | 2025-10-28 15:34:45 +0100 |
|---|---|---|
| committer | Dmitrii Akshintsev <[email protected]> | 2025-12-04 21:01:19 +0100 |
| commit | d99c610674ada29c5280c07b45ebfab7bc4e3b11 (patch) | |
| tree | 398bdeb4776c53702e7d84e449536cbefcbfb675 | |
| parent | 87cac29f88b96241bfa5e3e0f65a57a0e9002450 (diff) | |
Qml IR: add support for virtual and override keywords
This patch adds two new fields, IsVirtual and IsOverride, to CompiledData::Property.
These fields will later be used to populate QQmlPropertyData and to handle
property override semantics.
At a high level, this change focuses on the data flow from the AST to the IR,
laying the groundwork for future semantic resolution.
Also moves test helper Syntax namespace to the quicktestutils
Task-number: QTBUG-98320
Change-Id: Ic2a2e28df08d53c8752c49304bd5f7ff46916d08
Reviewed-by: Fabian Kosmale <[email protected]>
| -rw-r--r-- | src/qml/common/qv4compileddata_p.h | 52 | ||||
| -rw-r--r-- | src/qml/compiler/qqmlirbuilder.cpp | 2 | ||||
| -rw-r--r-- | src/qml/compiler/qqmlirbuilder_p.h | 3 | ||||
| -rw-r--r-- | src/quicktestutils/qml/qmlutils.cpp | 35 | ||||
| -rw-r--r-- | src/quicktestutils/qml/qmlutils_p.h | 54 | ||||
| -rw-r--r-- | tests/auto/qml/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | tests/auto/qml/qmljsir/CMakeLists.txt | 18 | ||||
| -rw-r--r-- | tests/auto/qml/qmljsir/tst_qmljsir.cpp | 81 | ||||
| -rw-r--r-- | tests/auto/qml/qmljsir/tst_qmljsir.h | 12 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlparser/tst_qqmlparser.cpp | 60 |
10 files changed, 250 insertions, 68 deletions
diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index 01121bd008..e00e3aedd0 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -52,7 +52,7 @@ QT_BEGIN_NAMESPACE // Also change the comment behind the number to describe the latest change. This has the added // benefit that if another patch changes the version too, it will result in a merge conflict, and // not get removed silently. -#define QV4_DATA_STRUCTURE_VERSION 0x48 // Hotfix 6.10 - meta object change +#define QV4_DATA_STRUCTURE_VERSION 0x49 // Added isVirtual and isOverride fields to property class QIODevice; class QQmlTypeNameCache; @@ -788,19 +788,43 @@ struct Signal }; static_assert(sizeof(Signal) == 12, "Signal structure needs to have the expected size to be binary compatible on disk when generated by host compiler and loaded by target"); +/* + * We aim to minimize the size of a struct as much as possible, while preserving at least 28 bits + * for each index. + * + * Note that Name and Type indices are provided by StringTableGenerator, containing a hashmap + * (and a list) of unique strings within one Compilation Unit. It sounds rather unrealistic to have + * 2^28 (260+ million) unique strings within 1 CU (even if it's some form of global registry), not + * to mention the amount of memory needed to maintain such a StringTableGenerator. + * Therefore, after some preliminary analysis, it seems that even 20 bits + * should be a rather conservative cap. + * + * However it doesn't seem to easily provide many benefits atm other than better (logically) grouped + * unions like, let's say nameIndexAndAttributes. + * + * Atm 32-bit member nameIndexAndVirtSpecifiers looks smth like this: + * + * NameIndexField IsVirtualField IsOverrideField IsFinalField + * |10100000101000111000001110000| 0 | 1 | 0 | + * + */ struct Property { private: - using NameIndexField = quint32_le_bitfield_member<0, 31>; - using FinalField = quint32_le_bitfield_member<31, 1>; + using NameIndexField = quint32_le_bitfield_member<0, 29>; + using IsVirtualField = quint32_le_bitfield_member<29, 1>; + using IsOverrideField = quint32_le_bitfield_member<30, 1>; + using IsFinalField = quint32_le_bitfield_member<31, 1>; using CommonTypeOrTypeNameIndexField = quint32_le_bitfield_member<0, 28>; using IsRequiredField = quint32_le_bitfield_member<28, 1>; using IsCommonTypeField = quint32_le_bitfield_member<29, 1>; using IsListField = quint32_le_bitfield_member<30, 1>; using IsReadOnlyField = quint32_le_bitfield_member<31, 1>; + public: - quint32_le_bitfield_union<NameIndexField, FinalField> nameIndexAndFinal; + quint32_le_bitfield_union<NameIndexField, IsVirtualField, IsOverrideField, IsFinalField> + nameIndexAndVirtSpecifiers; quint32_le_bitfield_union< CommonTypeOrTypeNameIndexField, IsRequiredField, @@ -809,11 +833,23 @@ public: IsReadOnlyField> data; Location location; - quint32 nameIndex() const { return nameIndexAndFinal.get<NameIndexField>(); } - void setNameIndex(int nameIndex) { nameIndexAndFinal.set<NameIndexField>(nameIndex); } + quint32 nameIndex() const { return nameIndexAndVirtSpecifiers.get<NameIndexField>(); } + void setNameIndex(int nameIndex) { nameIndexAndVirtSpecifiers.set<NameIndexField>(nameIndex); } + + bool isVirtual() const { return nameIndexAndVirtSpecifiers.get<IsVirtualField>(); } + void setIsVirtual(bool isVirtual) + { + nameIndexAndVirtSpecifiers.set<IsVirtualField>(isVirtual ? 1 : 0); + } + + bool isOverride() const { return nameIndexAndVirtSpecifiers.get<IsOverrideField>(); } + void setIsOverride(bool isOverride) + { + nameIndexAndVirtSpecifiers.set<IsOverrideField>(isOverride ? 1 : 0); + } - bool isFinal() const { return nameIndexAndFinal.get<FinalField>(); } - void setIsFinal(bool final) { nameIndexAndFinal.set<FinalField>(final ? 1 : 0); } + bool isFinal() const { return nameIndexAndVirtSpecifiers.get<IsFinalField>(); } + void setIsFinal(bool isFinal) { nameIndexAndVirtSpecifiers.set<IsFinalField>(isFinal ? 1 : 0); } void setCommonType(CommonType t) { diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index bf124a2272..1605ab5f59 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -1045,6 +1045,8 @@ bool IRBuilder::visit(QQmlJS::AST::UiPublicMember *node) Property *property = New<Property>(); property->setIsReadOnly(node->isReadonly()); property->setIsRequired(node->isRequired()); + property->setIsVirtual(node->isVirtual()); + property->setIsOverride(node->isOverride()); property->setIsFinal(node->isFinal()); const QV4::CompiledData::CommonType builtinPropertyType diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index 68b2b462f6..441830e33b 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -464,6 +464,9 @@ struct Q_QML_COMPILER_EXPORT Pragma struct Q_QML_COMPILER_EXPORT Document { + // disable it explicitly, it's implicitly deleted because of the Engine::_pool + Q_DISABLE_COPY_MOVE(Document) + Document(const QString &fileName, const QString &finalUrl, bool debugMode); QString code; QQmlJS::Engine jsParserEngine; diff --git a/src/quicktestutils/qml/qmlutils.cpp b/src/quicktestutils/qml/qmlutils.cpp index 5f573e790b..528a843f17 100644 --- a/src/quicktestutils/qml/qmlutils.cpp +++ b/src/quicktestutils/qml/qmlutils.cpp @@ -137,6 +137,41 @@ void gc(QQmlEngine &engine, GCFlags flags) gc(*engine.handle(), flags); } +namespace Syntax { + +auto stringView(const Word &word) -> QLatin1StringView +{ + return std::holds_alternative<Token>(word) ? spellFor(std::get<Token>(word)) + : std::get<QLatin1StringView>(word); +} + +auto toString(const Phrase &phrase) -> QString +{ + QString result; + for (const auto &word : phrase) { + result += stringView(word) + QLatin1Char(' '); + } + return result; +} + +// comfort +auto operator+(const Word &word, const Phrase &phrase) -> Phrase +{ + return Phrase{ word } + phrase; +}; + +auto operator+(const Word &word1, const Word &word2) -> Phrase +{ + return Phrase{ word1, word2 }; +}; + +auto objectDeclaration(Phrase &&objectMember, QLatin1StringView objName) -> Phrase +{ + return Phrase{} << Word(objName) << Word(Token::T_LBRACE) << std::move(objectMember) + << Word(Token::T_RBRACE); +} + +} // namespace Syntax QT_END_NAMESPACE diff --git a/src/quicktestutils/qml/qmlutils_p.h b/src/quicktestutils/qml/qmlutils_p.h index d8d28256ac..d4fc83c270 100644 --- a/src/quicktestutils/qml/qmlutils_p.h +++ b/src/quicktestutils/qml/qmlutils_p.h @@ -22,6 +22,7 @@ #include <QtCore/QStringList> #include <QtTest/QTest> #include <QtCore/private/qglobal_p.h> +#include <private/qqmljsgrammar_p.h> QT_BEGIN_NAMESPACE @@ -122,6 +123,59 @@ void gc(QV4::ExecutionEngine &engine, GCFlags flags = GCFlags::None); bool gcDone(QQmlEngine *engine); void gc(QQmlEngine &engine, GCFlags flags = GCFlags::None); +namespace Syntax { +using Token = QQmlJSGrammar::VariousConstants; +// TODO(QTBUG-138020) +constexpr auto spellFor(Token token) -> QLatin1StringView +{ + switch (token) { + case Token::T_COLON: + return QLatin1StringView(":"); + case Token::T_LBRACE: + return QLatin1StringView("{"); + case Token::T_RBRACE: + return QLatin1StringView("}"); + case Token::T_VAR: + return QLatin1StringView("var"); + case Token::T_PROPERTY: + return QLatin1StringView("property"); + case Token::T_DEFAULT: + return QLatin1StringView("default"); + case Token::T_READONLY: + return QLatin1StringView("readonly"); + case Token::T_REQUIRED: + return QLatin1StringView("required"); + case Token::T_FINAL: + return QLatin1StringView("final"); + case Token::T_VIRTUAL: + return QLatin1StringView("virtual"); + case Token::T_OVERRIDE: + return QLatin1StringView("override"); + default: + break; + } + Q_UNREACHABLE_RETURN({}); +} + +using Word = std::variant<Token, QLatin1StringView>; +auto stringView(const Word &word) -> QLatin1StringView; + +using Phrase = QList<Word>; +auto toString(const Phrase &phrase) -> QString; + +// comfort +auto operator+(const Word &word, const Phrase &phrase) -> Phrase; +auto operator+(const Word &word1, const Word &word2) -> Phrase; + +// if such declaration generating functions start to grow it might be worth creating a dedicated +// more optimized generator + +// can probably be generalized later to accept QList<Phrase> if needed +auto objectDeclaration(Phrase &&objectMember = {}, + QLatin1StringView objName = QLatin1StringView("QtObject")) -> Phrase; + +} // namespace Syntax + QT_END_NAMESPACE #endif // QQMLTESTUTILS_P_H diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt index 7a856c778f..48fe29c2df 100644 --- a/tests/auto/qml/CMakeLists.txt +++ b/tests/auto/qml/CMakeLists.txt @@ -144,6 +144,7 @@ if(QT_FEATURE_private_tests) add_subdirectory(qqmlitemmodels) add_subdirectory(qqmltypeloader) add_subdirectory(qqmlparser) + add_subdirectory(qmljsir) if(QT_FEATURE_qml_worker_script) add_subdirectory(qquickworkerscript) endif() diff --git a/tests/auto/qml/qmljsir/CMakeLists.txt b/tests/auto/qml/qmljsir/CMakeLists.txt new file mode 100644 index 0000000000..a6df898e81 --- /dev/null +++ b/tests/auto/qml/qmljsir/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qmljsir LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qmljsir + SOURCES + tst_qmljsir.h + tst_qmljsir.cpp + LIBRARIES + Qt::Qml + Qt::QmlPrivate + Qt::QuickTestUtilsPrivate +) diff --git a/tests/auto/qml/qmljsir/tst_qmljsir.cpp b/tests/auto/qml/qmljsir/tst_qmljsir.cpp new file mode 100644 index 0000000000..54f57ef083 --- /dev/null +++ b/tests/auto/qml/qmljsir/tst_qmljsir.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "tst_qmljsir.h" + +#include <QtQml/private/qqmlirbuilder_p.h> +#include <QtQuickTestUtils/private/qmlutils_p.h> + +using namespace QmlIR; +using namespace Syntax; + +void tst_qmljsir::propertyVirtSpecifiers_data() +{ + QTest::addColumn<Phrase>("virtSpecifiers"); + QTest::addColumn<Property>("propertyWithVirtFlags"); + + const auto addTestRow = [](const Phrase &virtSpecifiers, + const Property &propertyWithVirtFlags) { + QTest::addRow("%s", qPrintable(toString(virtSpecifiers))) + << virtSpecifiers << propertyWithVirtFlags; + }; + addTestRow({}, {}); + { + Property p{}; + p.setIsVirtual(true); + addTestRow({ Token::T_VIRTUAL }, p); + } + { + Property p{}; + p.setIsOverride(true); + addTestRow({ Token::T_OVERRIDE }, p); + } + { + Property p{}; + p.setIsFinal(true); + addTestRow({ Token::T_FINAL }, p); + } +} + +// ideally, return type would be optional<Document>, or some IRConstructionResult, +// but Copy&Move are disabled on the Document because of the MemoryPool in JsEngine +static auto tryBuildIRDocumentFrom(const QString &source) -> std::unique_ptr<Document> +{ + auto documentPtr = std::make_unique<Document>(QString(), QString(), true); + return IRBuilder().generateFromQml(source, QString(), documentPtr.get()) + ? std::move(documentPtr) + : nullptr; +} + +/* +This test ensures that virtual specifiers are correctly propagated from the QML source code into the +intermediate representation (IR). + +Due to the current design, it is difficult (if not impossible) to test +IRBuilder::visit(UiPublicMember*) in complete isolation. + +Therefore, this test takes an end-to-end (E2E) approach: it constructs a valid QML object +declaration containing a property with the relevant specifier keywords, builds the IR for it, and +then verifies that the resulting property node in the IR has the expected flags.*/ +void tst_qmljsir::propertyVirtSpecifiers() +{ + QFETCH(Phrase, virtSpecifiers); + QFETCH(Property, propertyWithVirtFlags); + + Phrase propertyDeclaration = + virtSpecifiers + Phrase{ Token::T_PROPERTY, Token::T_VAR, QLatin1StringView("p") }; + const auto source = toString(objectDeclaration(std::move(propertyDeclaration))); + + const auto documentPtr = tryBuildIRDocumentFrom(source); + QVERIFY(documentPtr); + QCOMPARE(documentPtr->objectCount(), 1); + QCOMPARE(documentPtr->objectAt(0)->propertyCount(), 1); + + const auto property = documentPtr->objectAt(0)->firstProperty(); + QVERIFY(property); + QCOMPARE(property->isVirtual(), propertyWithVirtFlags.isVirtual()); + QCOMPARE(property->isOverride(), propertyWithVirtFlags.isOverride()); + QCOMPARE(property->isFinal(), propertyWithVirtFlags.isFinal()); +} + +QTEST_MAIN(tst_qmljsir) diff --git a/tests/auto/qml/qmljsir/tst_qmljsir.h b/tests/auto/qml/qmljsir/tst_qmljsir.h new file mode 100644 index 0000000000..6ae66b9047 --- /dev/null +++ b/tests/auto/qml/qmljsir/tst_qmljsir.h @@ -0,0 +1,12 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QTest> + +class tst_qmljsir : public QObject +{ + Q_OBJECT +private slots: + void propertyVirtSpecifiers_data(); + void propertyVirtSpecifiers(); +}; diff --git a/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp b/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp index 2277d70528..0e2b4af801 100644 --- a/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp +++ b/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp @@ -892,66 +892,6 @@ void tst_qqmlparser::invalidImportVersion() QVERIFY(regexp.match(parser.errorMessage()).hasMatch()); } -// TODO move somewhere to make more visible? (QTBUG-138020) -namespace Syntax { -using Token = QQmlJSGrammar::VariousConstants; -static constexpr auto spellFor(Token token) -> QLatin1StringView -{ - switch (token) { - case Token::T_COLON: - return QLatin1StringView(":"); - case Token::T_VAR: - return QLatin1StringView("var"); - case Token::T_PROPERTY: - return QLatin1StringView("property"); - case Token::T_DEFAULT: - return QLatin1StringView("default"); - case Token::T_READONLY: - return QLatin1StringView("readonly"); - case Token::T_REQUIRED: - return QLatin1StringView("required"); - case Token::T_FINAL: - return QLatin1StringView("final"); - case Token::T_VIRTUAL: - return QLatin1StringView("virtual"); - case Token::T_OVERRIDE: - return QLatin1StringView("override"); - default: - break; - } - Q_UNREACHABLE_RETURN({}); -} - -using Word = std::variant<Token, QLatin1StringView>; -static inline auto stringView(const Word &word) -> QLatin1StringView -{ - return std::holds_alternative<Token>(word) ? spellFor(std::get<Token>(word)) - : std::get<QLatin1StringView>(word); -} - -using Phrase = QList<Word>; -static inline auto toString(const Phrase &phrase) -> QString -{ - QString result; - for (const auto &word : phrase) { - result += stringView(word) + QChar(' '); - } - return result; -} - -// comfort -Phrase operator+(const Word &word, const Phrase &phrase) -{ - return Phrase{ word } + phrase; -}; - -Phrase operator+(const Word &word1, const Word &word2) -{ - return Phrase{ word1, word2 }; -}; - -} // namespace Syntax - void tst_qqmlparser::propertyDeclarations_data() { using namespace Syntax; |
