aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/qml/common/qv4compileddata_p.h52
-rw-r--r--src/qml/compiler/qqmlirbuilder.cpp2
-rw-r--r--src/qml/compiler/qqmlirbuilder_p.h3
-rw-r--r--src/quicktestutils/qml/qmlutils.cpp35
-rw-r--r--src/quicktestutils/qml/qmlutils_p.h54
-rw-r--r--tests/auto/qml/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qmljsir/CMakeLists.txt18
-rw-r--r--tests/auto/qml/qmljsir/tst_qmljsir.cpp81
-rw-r--r--tests/auto/qml/qmljsir/tst_qmljsir.h12
-rw-r--r--tests/auto/qml/qqmlparser/tst_qqmlparser.cpp60
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;